refactor: strengthen architecture CI gates and runtime contracts

This commit is contained in:
jxxghp
2026-08-25 17:02:29 +08:00
parent 605bf8174a
commit b914e8b6e4
123 changed files with 4240 additions and 1342 deletions
+4 -1
View File
@@ -65,6 +65,9 @@ jobs:
- name: Check process runtime service locators
run: uv run --locked --no-sync python scripts/architecture/service_locator.py
- name: Check Ruff diagnostic ratchet
run: uv run --locked --no-sync python scripts/architecture/ruff_ratchet.py
- name: Check mypy error ratchet
run: uv run --locked --no-sync python scripts/architecture/mypy_ratchet.py
@@ -108,7 +111,6 @@ jobs:
run: uv run --locked --no-sync python tests/run.py --shard "${{ matrix.shard }}"
coverage:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
name: Coverage Report
timeout-minutes: 20
@@ -138,6 +140,7 @@ jobs:
uv run --locked --no-sync python -m coverage report
uv run --locked --no-sync python -m coverage json
uv run --locked --no-sync python -m coverage xml
uv run --locked --no-sync python scripts/architecture/coverage_ratchet.py
- name: Upload coverage report
uses: actions/upload-artifact@v7
+3 -3
View File
@@ -5,24 +5,24 @@ from typing import cast
from fastapi import Depends, Request
from app.application.messaging.chat import AsyncAgentChatRepository, AsyncUnitOfWork
from app.application.outbox import AsyncOutboxTransaction
from app.application.configuration import (
ApiRuntimeConfig,
get_api_runtime_config_snapshot,
)
from app.application.messaging.chat import AsyncAgentChatRepository, AsyncUnitOfWork
from app.application.outbox import AsyncOutboxTransaction
from app.application.subscription.delete import SubscribeDeletionRepository
from app.application.subscription.identity import SubscribeIdentityDeletionRepository
from app.application.subscription.mutation import (
SubscriptionHistoryMutationRepository,
SubscriptionMutationRepository,
)
from app.runtime.tasks import TaskRegistry, get_task_registry
from app.startup.composition.context import (
AgentChatRuntime,
HostRuntime,
SubscriptionRuntime,
)
from app.runtime.tasks import TaskRegistry, get_task_registry
def get_host_runtime(request: Request) -> HostRuntime:
-1
View File
@@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import AsyncGenerator, Callable, Generator
from typing import Any
SessionProvider = Callable[[], Generator[Any, None, None]]
AsyncSessionProvider = Callable[[], AsyncGenerator[Any, None]]
RepositoryFactory = Callable[[Any], Any]
+2 -2
View File
@@ -4,15 +4,15 @@ from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.context import (
get_agent_chat_runtime,
get_agent_chat_repository,
get_agent_chat_runtime,
get_agent_chat_transaction,
get_async_session,
get_host_runtime,
)
from app.application.messaging.chat import (
AgentChatService,
AgentChatPersistenceService,
AgentChatService,
AsyncAgentChatRepository,
AsyncUnitOfWork,
)
+11 -4
View File
@@ -9,25 +9,33 @@ from sqlalchemy.orm import Session
from app.adapters.external.server import MoviePilotServerHelper
from app.api.context import (
get_async_session,
get_background_task_registry,
get_host_runtime,
get_subscription_history_repository,
get_subscription_outbox,
get_subscription_repository,
get_subscription_transaction,
get_sync_session,
resolve_background_task_registry,
)
from app.application.outbox import AsyncOutboxTransaction
from app.application.scheduling import start_scheduler_job
from app.application.servarr import ServarrSubscriptionService
from app.application.subscription.delete import (
AsyncUnitOfWork as DeleteUnitOfWork,
)
from app.application.subscription.delete import (
DeleteSubscribeCommand,
SubscribeDeletionRepository,
)
from app.application.subscription.identity import DeleteSubscriptionsByIdentityCommand
from app.application.subscription.identity import SubscribeIdentityDeletionRepository
from app.application.subscription.identity import (
DeleteSubscriptionsByIdentityCommand,
SubscribeIdentityDeletionRepository,
)
from app.application.subscription.mutation import (
AsyncUnitOfWork as MutationUnitOfWork,
)
from app.application.subscription.mutation import (
SubscriptionHistoryMutationRepository,
SubscriptionMutationRepository,
SubscriptionMutationService,
@@ -36,10 +44,9 @@ from app.application.subscription.query import SubscriptionQueryService
from app.application.subscription.search import SearchSubscriptionsCommand
from app.runtime.events import eventmanager
from app.runtime.log import logger
from app.runtime.tasks import TaskRegistry
from app.schemas.types import EventType
from app.startup.composition.context import HostRuntime
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.runtime.tasks import TaskRegistry
async def _publish_subscribe_deleted(
+3 -3
View File
@@ -16,7 +16,7 @@ from app.application.workflow import (
WorkflowQueryService,
get_workflow_manager,
)
from app.runtime.config import global_vars
from app.runtime.stop import runtime_stop_state
from app.startup.composition.context import HostRuntime
@@ -36,7 +36,7 @@ def get_workflow_mutation_command(
load_event=workflow_manager.load_workflow_events,
remove_event=workflow_manager.remove_workflow_event,
refresh_event=workflow_manager.update_workflow_event,
stop_running=global_vars.stop_workflow,
stop_running=runtime_stop_state.stop_workflow,
delete_cache=lambda workflow_id: system_config.delete(
f"WorkflowCache-{workflow_id}"
),
@@ -52,7 +52,7 @@ def get_workflow_definition_command(
return WorkflowDefinitionCommand(
repository=runtime.workflow.repository(db),
unit_of_work=runtime.persistence.async_transaction(db),
stop_running=global_vars.stop_workflow,
stop_running=runtime_stop_state.stop_workflow,
async_delete_cache=lambda workflow_id: system_config.async_delete(
f"WorkflowCache-{workflow_id}"
),
+47 -49
View File
@@ -7,8 +7,8 @@ import shutil
import time
import uuid
from collections import deque
from queue import Empty, Queue
from pathlib import Path
from queue import Empty, Queue
from threading import Lock
from typing import Any, AsyncIterator, Awaitable, Callable, Optional, Union
@@ -16,15 +16,58 @@ import aiofiles
from fastapi import Depends, File, Form, HTTPException, Request, UploadFile, status
from fastapi.responses import FileResponse, StreamingResponse
from app.agent.contracts import ReplyMode, build_display_message
from app.agent.mcp import agent_mcp_manager
from app.agent.runtime_loader import get_moviepilot_agent_type
from app.api.dependencies.agent import (
get_agent_chat_persistence,
get_agent_chat_service,
)
from app.api.dependencies.auth import get_current_active_user
from app.api.presentation.sse import build_sse_error_response, build_sse_response
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.agent import (
get_running_agent_manager,
is_audio_input_available,
transcribe_audio,
)
from app.application.commands import get_command, get_commands
from app.application.configuration import get_api_runtime_config_snapshot
from app.application.messaging.agent import (
agent_interaction_manager,
attach_web_agent_edit_queue,
attach_web_agent_message_queue,
build_agent_choice_button_rows,
create_web_agent_background_task,
detach_web_agent_edit_queue,
detach_web_agent_message_queue,
is_web_agent_message_for_user,
normalize_web_agent_button_rows,
parse_agent_choice_callback,
)
from app.application.messaging.chat import (
AgentChatPersistenceService,
AgentChatRecord,
AgentChatService,
get_configured_agent_chat_persistence,
get_configured_agent_chat_service,
)
from app.application.messaging.router import has_pending_interaction
from app.application.security.user import get_configured_user_id_lookup
from app.chain.message import MessageChain
from app.runtime.execution import run_in_threadpool
from app.runtime.localization import LocaleHelper
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.agent import AgentChatDisplaySaveRequest as _SchemaAgentChatDisplaySaveRequest
from app.schemas.agent import AgentChatSessionDetail as _SchemaAgentChatSessionDetail
from app.schemas.agent import AgentChatSessionSummary as _SchemaAgentChatSessionSummary
from app.schemas.agent import AgentChatUploadAttachment as _SchemaAgentChatUploadAttachment
from app.schemas.agent import AgentMcpServerListData as _SchemaAgentMcpServerListData
from app.schemas.agent import AgentMcpServersSaveRequest as _SchemaAgentMcpServersSaveRequest
from app.schemas.agent import AgentMcpServerTestRequest as _SchemaAgentMcpServerTestRequest
from app.schemas.agent import AgentMcpServerTestResult as _SchemaAgentMcpServerTestResult
from app.schemas.agent import AgentMcpServersSaveRequest as _SchemaAgentMcpServersSaveRequest
from app.schemas.agent import AgentSessionStopData as _SchemaAgentSessionStopData
from app.schemas.agent import AgentWebCallbackData as _SchemaAgentWebCallbackData
from app.schemas.agent import AgentWebCommandInfo as _SchemaAgentWebCommandInfo
@@ -32,52 +75,7 @@ from app.schemas.message import AgentWebChatRequest as _SchemaAgentWebChatReques
from app.schemas.message import AgentWebChoiceRequest as _SchemaAgentWebChoiceRequest
from app.schemas.message import Message as _SchemaMessage
from app.schemas.response import Response as _SchemaResponse
from app.api.response import ResponseAPIRouter
from app.api.presentation.sse import build_sse_error_response, build_sse_response
from app.agent.contracts import ReplyMode, build_display_message
from app.agent.mcp import agent_mcp_manager
from app.agent.runtime_loader import get_moviepilot_agent_type
from app.application.agent import (
get_running_agent_manager,
is_audio_input_available,
transcribe_audio,
)
from app.chain.message import MessageChain
from app.application.commands import get_command, get_commands
from app.runtime.config import global_vars
from app.api.principal import ApiPrincipal
from app.api.dependencies.agent import (
get_agent_chat_persistence,
get_agent_chat_service,
)
from app.api.dependencies.auth import get_current_active_user
from app.application.messaging.chat import (
AgentChatRecord,
AgentChatPersistenceService,
AgentChatService,
get_configured_agent_chat_service,
get_configured_agent_chat_persistence,
)
from app.application.security.user import get_configured_user_id_lookup
from app.application.configuration import get_api_runtime_config_snapshot
from app.application.messaging.agent import (
attach_web_agent_message_queue,
attach_web_agent_edit_queue,
create_web_agent_background_task,
detach_web_agent_message_queue,
detach_web_agent_edit_queue,
is_web_agent_message_for_user,
)
from app.application.messaging.agent import agent_interaction_manager
from app.application.messaging.agent import (
build_agent_choice_button_rows,
normalize_web_agent_button_rows,
parse_agent_choice_callback,
)
from app.application.messaging.router import has_pending_interaction
from app.runtime.localization import LocaleHelper
from app.runtime.log import logger
from app.schemas.types import EventType, NotificationChannel
from app.schemas.types import NotificationChannel
router = ResponseAPIRouter()
@@ -2274,7 +2272,7 @@ async def _web_agent_stream_impl(
{"session_id": session_id},
locale=locale,
)
while not global_vars.is_system_stopped:
while not runtime_stop_state.is_system_stopped:
if await request.is_disconnected():
disconnected = True
break
+4 -4
View File
@@ -2,13 +2,13 @@ from typing import Annotated, Optional
from fastapi import Depends, Query
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.adapters.web.security.access import verify_token
from app.api.response import ResponseAPIRouter
from app.chain.anilist import AniListChain
from app.domain.context import MediaInfo
from app.adapters.web.security.access import verify_token
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+11 -11
View File
@@ -5,15 +5,15 @@ from typing import AsyncIterator, List, Optional
from fastapi import APIRouter, Depends, Header, Security
from fastapi.responses import JSONResponse
from app.schemas.openai import AnthropicErrorDetail as _SchemaAnthropicErrorDetail
from app.schemas.openai import AnthropicErrorResponse as _SchemaAnthropicErrorResponse
from app.schemas.openai import AnthropicMessagesRequest as _SchemaAnthropicMessagesRequest
from app.schemas.openai import AnthropicMessagesResponse as _SchemaAnthropicMessagesResponse
from app.schemas.openai import AnthropicTextBlock as _SchemaAnthropicTextBlock
from app.adapters.web.security.access import anthropic_api_key_header
from app.api.context import (
get_background_task_registry_compat,
resolve_background_task_registry,
)
from app.api.endpoints.openai import (
MODEL_ID,
_is_manager_unavailable,
_is_manager_queue_full,
_is_manager_unavailable,
_run_managed_agent,
)
from app.api.openai_utils import (
@@ -24,12 +24,12 @@ from app.api.openai_utils import (
from app.api.presentation.sse import build_sse_response, encode_named_event
from app.application.agent import get_running_agent_manager
from app.application.configuration import get_api_runtime_config_snapshot
from app.adapters.web.security.access import anthropic_api_key_header
from app.api.context import (
get_background_task_registry_compat,
resolve_background_task_registry,
)
from app.runtime.tasks import TaskRegistry
from app.schemas.openai import AnthropicErrorDetail as _SchemaAnthropicErrorDetail
from app.schemas.openai import AnthropicErrorResponse as _SchemaAnthropicErrorResponse
from app.schemas.openai import AnthropicMessagesRequest as _SchemaAnthropicMessagesRequest
from app.schemas.openai import AnthropicMessagesResponse as _SchemaAnthropicMessagesResponse
from app.schemas.openai import AnthropicTextBlock as _SchemaAnthropicTextBlock
ANTHROPIC_ERROR_RESPONSES = {
400: {"model": _SchemaAnthropicErrorResponse, "description": "请求格式错误"},
+4 -4
View File
@@ -3,12 +3,12 @@ from typing import Any
from fastapi import Depends, HTTPException
from pydantic import BaseModel
from app.api.dependencies.auth import get_auth_service
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.application.plugin.runtime import get_plugin_manager
from app.application.security.auth import AuthService, consume_plugin_auth_ticket
from app.schemas.token import Token as _SchemaToken
from app.schemas.user import AuthProviderInfo as _SchemaAuthProviderInfo
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.application.security.auth import AuthService, consume_plugin_auth_ticket
from app.application.plugin.runtime import get_plugin_manager
from app.api.dependencies.auth import get_auth_service
router = ResponseAPIRouter()
+5 -5
View File
@@ -1,14 +1,14 @@
from typing import List, Any, Optional
from typing import Any, List, Optional
from fastapi import Depends
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.adapters.web.security.access import verify_token
from app.api.response import ResponseAPIRouter
from app.chain.bangumi import BangumiChain
from app.domain.context import MediaInfo
from app.adapters.web.security.access import verify_token
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+13 -13
View File
@@ -1,8 +1,20 @@
from pathlib import Path
from typing import Any, List, Optional, Annotated
from typing import Annotated, Any, List, Optional
from fastapi import Depends
from app.adapters.system.host import SystemUtils
from app.adapters.web.security.access import verify_apitoken
from app.api.context import get_api_runtime_config, resolve_api_runtime_config
from app.api.dependencies.auth import get_current_active_superuser
from app.api.dependencies.history import get_dashboard_query_service
from app.api.response import ResponseAPIRouter
from app.application.configuration import ApiRuntimeConfig
from app.application.dashboard import DashboardQueryService
from app.application.directory import DirectoryHelper
from app.application.scheduling import get_scheduler
from app.chain.dashboard import DashboardChain
from app.chain.storage import StorageChain
from app.runtime.execution import run_in_threadpool
from app.schemas.dashboard import DashboardMemoryInfo as _SchemaDashboardMemoryInfo
from app.schemas.dashboard import DashboardSystemInfo as _SchemaDashboardSystemInfo
@@ -13,19 +25,7 @@ from app.schemas.dashboard import ScheduleProgress as _SchemaScheduleProgress
from app.schemas.dashboard import Statistic as _SchemaStatistic
from app.schemas.dashboard import Storage as _SchemaStorage
from app.schemas.response import Response as _SchemaResponse
from app.api.response import ResponseAPIRouter
from app.chain.dashboard import DashboardChain
from app.chain.storage import StorageChain
from app.api.context import get_api_runtime_config, resolve_api_runtime_config
from app.application.configuration import ApiRuntimeConfig
from app.adapters.web.security.access import verify_apitoken
from app.api.dependencies.auth import get_current_active_superuser
from app.api.dependencies.history import get_dashboard_query_service
from app.application.dashboard import DashboardQueryService
from app.schemas.types import StorageAction
from app.application.directory import DirectoryHelper
from app.application.scheduling import get_scheduler
from app.adapters.system.host import SystemUtils
router = ResponseAPIRouter()
+4 -4
View File
@@ -2,17 +2,17 @@ from typing import Any, List, Optional
from fastapi import Depends
from app.schemas.event import DiscoverMediaSource as _SchemaDiscoverMediaSource
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.adapters.web.security.access import verify_token
from app.api.response import ResponseAPIRouter
from app.chain.bangumi import BangumiChain
from app.chain.douban import DoubanChain
from app.chain.tmdb import TmdbChain
from app.runtime.events import eventmanager
from app.adapters.web.security.access import verify_token
from app.schemas.event import DiscoverMediaSource as _SchemaDiscoverMediaSource
from app.schemas.event import DiscoverSourceEventData
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.types import ChainEventType, MediaType
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+4 -4
View File
@@ -2,14 +2,14 @@ from typing import Any, List, Optional
from fastapi import Depends
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.adapters.web.security.access import verify_token
from app.api.response import ResponseAPIRouter
from app.chain.douban import DoubanChain
from app.domain.context import MediaInfo
from app.adapters.web.security.access import verify_token
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.types import MediaType
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+22 -22
View File
@@ -1,7 +1,26 @@
from typing import Any, List, Annotated, Optional, Union
from typing import Annotated, Any, List, Optional, Union
from fastapi import Depends, Body
from fastapi import Body, Depends
from app.adapters.web.security.access import verify_token
from app.api.dependencies.auth import get_current_active_user
from app.api.dependencies.site import get_site_sync_query_service
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_configured_system_config
from app.application.directory import DirectoryHelper
from app.application.security.url import SecurityUtils
from app.application.site.query import (
SiteQueryService,
get_configured_site_query_service,
)
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.domain.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo
from app.domain.media import is_music_media_source, normalize_music_type
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo
from app.schemas.common import ServiceClientInfo as _SchemaServiceClientInfo
from app.schemas.download import DownloadAddedData as _SchemaDownloadAddedData
from app.schemas.download import DownloadDirectory as _SchemaDownloadDirectory
@@ -13,24 +32,6 @@ from app.schemas.system import TorrentInfo as _SchemaTorrentInfo
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.transfer import DownloaderTorrent as _SchemaDownloaderTorrent
from app.schemas.transfer import MusicInfo as _SchemaMusicInfo
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.api.response import ResponseAPIRouter
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.domain.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo
from app.adapters.web.security.access import verify_token
from app.api.principal import ApiPrincipal
from app.application.configuration import get_configured_system_config
from app.application.site.query import (
SiteQueryService,
get_configured_site_query_service,
)
from app.api.dependencies.auth import get_current_active_user
from app.api.dependencies.site import get_site_sync_query_service
from app.application.directory import DirectoryHelper
from app.schemas.types import (
MUSIC_ENTITY_RECORDING,
MediaSource,
@@ -38,8 +39,7 @@ from app.schemas.types import (
MusicTargetEntityType,
SystemConfigKey,
)
from app.domain.media import is_music_media_source, normalize_music_type
from app.application.security.url import SecurityUtils
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+15 -15
View File
@@ -1,33 +1,21 @@
import time
from collections.abc import Coroutine
from typing import List, Any, Callable, Optional
from typing import Any, Callable, List, Optional
from fastapi import Depends
from app.schemas.common import BatchProgressKeyData as _SchemaBatchProgressKeyData
from app.schemas.common import ProgressKeyData as _SchemaProgressKeyData
from app.schemas.history import BatchTransferHistoryRedoRequest as _SchemaBatchTransferHistoryRedoRequest
from app.schemas.history import TransferHistory as _SchemaTransferHistory
from app.schemas.history import TransferHistoryPage as _SchemaTransferHistoryPage
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.history import DownloadHistory as _SchemaDownloadHistory
from app.api.response import ResponseAPIRouter
from app.adapters.web.security.access import verify_token
from app.agent.contracts import ReplyMode
from app.application.agent import get_running_agent_manager
from app.agent.prompt.transfer_redo import (
build_batch_manual_redo_prompt,
build_manual_redo_prompt,
)
from app.runtime.config import global_vars
from app.api.context import (
get_api_runtime_config,
get_background_task_registry,
resolve_api_runtime_config,
resolve_background_task_registry,
)
from app.application.configuration import ApiRuntimeConfig
from app.adapters.web.security.access import verify_token
from app.api.dependencies.auth import (
get_current_active_manage_user,
get_current_active_superuser,
@@ -37,14 +25,26 @@ from app.api.dependencies.history import (
get_history_query_service,
get_transfer_history_mutation_command,
)
from app.runtime.progress import AsyncProgressHelper
from app.api.response import ResponseAPIRouter
from app.application.agent import get_running_agent_manager
from app.application.configuration import ApiRuntimeConfig
from app.application.history import (
DownloadHistoryMutationCommand,
HistoryQueryService,
TransferHistoryMutationCommand,
)
from app.runtime.config import global_vars
from app.runtime.log import logger
from app.runtime.progress import AsyncProgressHelper
from app.runtime.tasks import TaskRegistry
from app.schemas.common import BatchProgressKeyData as _SchemaBatchProgressKeyData
from app.schemas.common import ProgressKeyData as _SchemaProgressKeyData
from app.schemas.history import BatchTransferHistoryRedoRequest as _SchemaBatchTransferHistoryRedoRequest
from app.schemas.history import DownloadHistory as _SchemaDownloadHistory
from app.schemas.history import TransferHistory as _SchemaTransferHistory
from app.schemas.history import TransferHistoryPage as _SchemaTransferHistoryPage
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
router = ResponseAPIRouter()
+3 -3
View File
@@ -3,11 +3,11 @@ from typing import Any, Dict, List, Optional, Union
from fastapi import Depends, Request, Response
from fastapi.responses import HTMLResponse
from app.agent.llm.gateway import resolve_llm_provider_runtime
from app.api.dependencies.auth import get_current_active_superuser_async
from app.api.response import ResponseAPIRouter
from app.schemas.common import ManageRequest as _SchemaManageRequest
from app.schemas.response import Response as _SchemaResponse
from app.api.response import ResponseAPIRouter
from app.api.dependencies.auth import get_current_active_superuser_async
from app.agent.llm.gateway import resolve_llm_provider_runtime
router = ResponseAPIRouter()
+10 -10
View File
@@ -1,22 +1,22 @@
from datetime import timedelta
from typing import Any, List, Annotated
from typing import Annotated, Any, List
from fastapi import Depends, Form, HTTPException, Request, Response
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordRequestForm
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
from app.api.context import get_api_runtime_config, resolve_api_runtime_config
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.application.configuration import ApiRuntimeConfig, get_configured_system_config
from app.application.image import WallpaperHelper
from app.application.security.token import create_access_token
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.chain.user import MfaRequired, UserChain
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import MfaChallenge as _SchemaMfaChallenge
from app.schemas.token import Token as _SchemaToken
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.chain.user import MfaRequired, UserChain
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
from app.application.security.token import create_access_token
from app.api.context import get_api_runtime_config, resolve_api_runtime_config
from app.application.configuration import ApiRuntimeConfig, get_configured_system_config
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.application.image import WallpaperHelper
from app.schemas.types import SystemConfigKey
router = ResponseAPIRouter()
+6 -6
View File
@@ -1,8 +1,13 @@
from typing import List, Any, Dict, Annotated, Union
from typing import Annotated, Any, Dict, List, Union
from fastapi import Depends, HTTPException, Request
from fastapi.responses import JSONResponse, Response
from app.adapters.web.security.access import verify_apikey
from app.agent.tools.manager import moviepilot_tool_manager
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.runtime.log import logger
from app.runtime.version import get_app_version
from app.schemas.mcp import MCP_JSONRPC_REQUEST_SCHEMA as _SchemaMCP_JSONRPC_REQUEST_SCHEMA
from app.schemas.mcp import McpJsonRpcError as _SchemaMcpJsonRpcError
from app.schemas.mcp import McpJsonRpcResponse as _SchemaMcpJsonRpcResponse
@@ -11,11 +16,6 @@ from app.schemas.mcp import McpToolInfo as _SchemaMcpToolInfo
from app.schemas.mcp import ToolCallData as _SchemaToolCallData
from app.schemas.mcp import ToolCallRequest as _SchemaToolCallRequest
from app.schemas.response import Response as _SchemaResponse
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.agent.tools.manager import moviepilot_tool_manager
from app.adapters.web.security.access import verify_apikey
from app.runtime.version import get_app_version
from app.runtime.log import logger
router = ResponseAPIRouter()
+19 -19
View File
@@ -5,36 +5,36 @@ from uuid import UUID
from fastapi import Depends, Query
from pydantic import BeforeValidator
from app.adapters.web.security.access import verify_apitoken, verify_token
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_user,
)
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_api_runtime_config_snapshot
from app.chain.media import MediaChain
from app.chain.scraping import ScrapingChain
from app.chain.tmdb import TmdbChain
from app.domain.context import Context, MusicInfo
from app.domain.media import is_music_media_source, normalize_music_type, parse_media_source_selection
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo, MetaInfoPath
from app.schemas.category import CategoryConfig
from app.schemas.category import CategoryConfig as _SchemaCategoryConfig
from app.schemas.category import MediaCategoryMap as _SchemaMediaCategoryMap
from app.schemas.context import MediaEpisodeGroup as _SchemaMediaEpisodeGroup
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.context import MediaSearchResults as _SchemaMediaSearchResults
from app.schemas.context import MediaSeason as _SchemaMediaSeason
from app.schemas.event import MediaSourceInfo as _SchemaMediaSourceInfo
from app.schemas.media import normalize_media_source, resolve_media_identity
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource, MediaType
from app.schemas.workflow import Context as _SchemaContext
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.api.response import ResponseAPIRouter
from app.chain.media import MediaChain
from app.chain.scraping import ScrapingChain
from app.chain.tmdb import TmdbChain
from app.application.configuration import get_api_runtime_config_snapshot
from app.domain.context import Context, MusicInfo
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo, MetaInfoPath
from app.adapters.web.security.access import verify_token, verify_apitoken
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_user,
)
from app.schemas.category import CategoryConfig
from app.schemas.event import MediaSourceInfo as _SchemaMediaSourceInfo
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource, MediaType
from app.domain.media import is_music_media_source, normalize_music_type, parse_media_source_selection
from app.schemas.media import normalize_media_source, resolve_media_identity
router = ResponseAPIRouter()
+15 -15
View File
@@ -2,32 +2,32 @@ from typing import Any, List, Optional
from fastapi import Depends, HTTPException, status
from app.adapters.web.security.access import verify_token
from app.api.dependencies.history import get_mediaserver_query_service
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_configured_system_config
from app.application.mediaserver import (
MediaServerQueryService,
get_mediaserver_configs,
)
from app.chain.download import DownloadChain
from app.chain.mediaserver import MediaServerChain
from app.domain.context import MediaInfo
from app.domain.metainfo import MetaInfo
from app.schemas.common import ServiceClientInfo as _SchemaServiceClientInfo
from app.schemas.media import build_media_key, resolve_media_identity
from app.schemas.mediaserver import ExistMediaInfo as _SchemaExistMediaInfo
from app.schemas.mediaserver import MediaServerExistingEpisodes as _SchemaMediaServerExistingEpisodes
from app.schemas.mediaserver import MediaServerExistsData as _SchemaMediaServerExistsData
from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary
from app.schemas.mediaserver import MediaServerPlayData as _SchemaMediaServerPlayData
from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem
from app.schemas.mediaserver import NotExistMediaInfo
from app.schemas.mediaserver import NotExistMediaInfo as _SchemaNotExistMediaInfo
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.api.response import ResponseAPIRouter
from app.chain.download import DownloadChain
from app.chain.mediaserver import MediaServerChain
from app.domain.context import MediaInfo
from app.domain.metainfo import MetaInfo
from app.adapters.web.security.access import verify_token
from app.application.configuration import get_configured_system_config
from app.application.mediaserver import (
MediaServerQueryService,
get_mediaserver_configs,
)
from app.api.dependencies.history import get_mediaserver_query_service
from app.schemas.mediaserver import NotExistMediaInfo
from app.schemas.types import MediaSource, MediaType, SystemConfigKey
from app.schemas.media import build_media_key, resolve_media_identity
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+17 -17
View File
@@ -7,6 +7,23 @@ from typing import Annotated, Any, List, Optional, Protocol, Union
from fastapi import Depends, Request
from starlette.responses import PlainTextResponse
from app.adapters.external.wechat_crypt import WXBizMsgCrypt
from app.adapters.web.security.access import verify_apitoken, verify_token
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.api.dependencies.agent import get_message_query_service
from app.api.dependencies.auth import get_current_active_superuser
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.configuration import (
get_api_runtime_config_snapshot,
get_configured_system_config,
)
from app.application.messaging.message import MessageQueryService
from app.application.notification import get_notification_configs
from app.chain.message import MessageChain
from app.runtime.config import global_vars
from app.runtime.log import logger
from app.runtime.tasks import TaskRegistry
from app.schemas.message import MessageClearBefore as _SchemaMessageClearBefore
from app.schemas.message import MessageClearData as _SchemaMessageClearData
from app.schemas.message import MessageClearScope as _SchemaMessageClearScope
@@ -16,24 +33,7 @@ from app.schemas.message import SubscriptionMessage as _SchemaSubscriptionMessag
from app.schemas.message import WebMessageItem as _SchemaWebMessageItem
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import ResponseAPIRouter
from app.chain.message import MessageChain
from app.runtime.config import global_vars
from app.adapters.web.security.access import verify_token, verify_apitoken
from app.api.principal import ApiPrincipal
from app.application.configuration import (
get_api_runtime_config_snapshot,
get_configured_system_config,
)
from app.api.dependencies.agent import get_message_query_service
from app.api.dependencies.auth import get_current_active_superuser
from app.application.messaging.message import MessageQueryService
from app.application.notification import get_notification_configs
from app.runtime.log import logger
from app.adapters.external.wechat_crypt import WXBizMsgCrypt
from app.schemas.types import NotificationChannel, SystemConfigKey
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.runtime.tasks import TaskRegistry
router = ResponseAPIRouter()
+29 -29
View File
@@ -4,10 +4,37 @@ MFA (Multi-Factor Authentication) API 端点
"""
import json
from typing import Any, Annotated, Optional
from typing import Annotated, Any, Optional
from fastapi import Depends, HTTPException, Body, Request, Response
from fastapi import Body, Depends, HTTPException, Request, Response
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
from app.api.dependencies.auth import (
get_current_active_user,
get_current_active_user_async,
get_passkey_service,
get_user_service,
)
from app.api.principal import ApiPrincipal
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.application.security.auth import get_configured_auth_service
from app.application.security.otp import OtpUtils
from app.application.security.passkey import (
PasskeyChallengeStore,
PassKeyHelper,
PassKeyRegistrationOriginMismatchError,
PassKeyRegistrationVerificationError,
)
from app.application.security.passkeys import (
PasskeyService,
)
from app.application.security.token import verify_password
from app.application.security.user import (
UserService,
get_configured_user_id_lookup,
get_configured_user_name_lookup,
)
from app.runtime.log import logger
from app.schemas.mcp import BaseModel as _SchemaBaseModel
from app.schemas.mcp import JsonData as _SchemaJsonData
from app.schemas.mfa import MfaStatusData as _SchemaMfaStatusData
@@ -17,33 +44,6 @@ from app.schemas.mfa import PasskeyStartData as _SchemaPasskeyStartData
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import Token as _SchemaToken
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
from app.application.security.token import verify_password
from app.application.security.auth import get_configured_auth_service
from app.application.security.user import UserService
from app.application.security.user import (
get_configured_user_id_lookup,
get_configured_user_name_lookup,
)
from app.application.security.passkeys import (
PasskeyService,
)
from app.api.principal import ApiPrincipal
from app.api.dependencies.auth import (
get_current_active_user,
get_current_active_user_async,
get_user_service,
get_passkey_service,
)
from app.application.security.passkey import (
PassKeyHelper,
PassKeyRegistrationOriginMismatchError,
PassKeyRegistrationVerificationError,
PasskeyChallengeStore,
)
from app.runtime.log import logger
from app.application.security.otp import OtpUtils
router = ResponseAPIRouter()
+12 -12
View File
@@ -2,6 +2,18 @@ from typing import Annotated, Optional
from fastapi import Depends, HTTPException, Query
from app.adapters.web.security.access import verify_token
from app.api.dependencies.auth import get_current_active_superuser_async
from app.api.response import ResponseAPIRouter
from app.chain.listenbrainz import (
LISTENBRAINZ_CHART_RANGES,
LISTENBRAINZ_FRESH_MAX_DAYS,
LISTENBRAINZ_FRESH_SORTS,
)
from app.chain.media import MediaChain
from app.chain.musicbrainz import MusicBrainzChain
from app.chain.recommend import RecommendChain
from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo
from app.schemas.music import MusicAlbumInfo as _SchemaMusicAlbumInfo
from app.schemas.music import MusicArtistInfo as _SchemaMusicArtistInfo
from app.schemas.music import MusicRecognitionCacheData as _SchemaMusicRecognitionCacheData
@@ -9,19 +21,7 @@ from app.schemas.music import MusicRecognizeRequest as _SchemaMusicRecognizeRequ
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.transfer import MusicInfo as _SchemaMusicInfo
from app.api.response import ResponseAPIRouter
from app.chain.media import MediaChain
from app.chain.recommend import RecommendChain
from app.schemas.types import MediaSource, MediaType
from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo
from app.adapters.web.security.access import verify_token
from app.api.dependencies.auth import get_current_active_superuser_async
from app.chain.listenbrainz import (
LISTENBRAINZ_CHART_RANGES,
LISTENBRAINZ_FRESH_MAX_DAYS,
LISTENBRAINZ_FRESH_SORTS,
)
from app.chain.musicbrainz import MusicBrainzChain
router = ResponseAPIRouter()
+3 -3
View File
@@ -2,11 +2,11 @@ from typing import Any, Dict
from fastapi import Depends
from app.schemas.common import ManageRequest as _SchemaManageRequest
from app.schemas.response import Response as _SchemaResponse
from app.api.dependencies.auth import get_current_active_superuser
from app.api.response import ResponseAPIRouter
from app.chain.notification import NotificationChain
from app.api.dependencies.auth import get_current_active_superuser
from app.schemas.common import ManageRequest as _SchemaManageRequest
from app.schemas.response import Response as _SchemaResponse
router = ResponseAPIRouter()
+17 -17
View File
@@ -8,6 +8,23 @@ from fastapi import APIRouter, Depends, Request, Security
from fastapi.responses import JSONResponse
from fastapi.security import HTTPAuthorizationCredentials
from app.adapters.web.security.access import openai_bearer_scheme
from app.agent.contracts import ReplyMode
from app.agent.runtime_loader import get_moviepilot_agent_type
from app.api.context import (
get_background_task_registry_compat,
resolve_background_task_registry,
)
from app.api.openai_utils import (
build_completion_payload,
build_prompt,
build_responses_input,
build_session_id,
)
from app.api.presentation.sse import build_sse_response, encode_data_event
from app.application.agent import get_running_agent_manager
from app.application.configuration import get_api_runtime_config_snapshot
from app.runtime.tasks import TaskRegistry
from app.schemas.openai import OpenAIChatCompletionResponse as _SchemaOpenAIChatCompletionResponse
from app.schemas.openai import OpenAIChatCompletionsRequest as _SchemaOpenAIChatCompletionsRequest
from app.schemas.openai import OpenAIErrorDetail as _SchemaOpenAIErrorDetail
@@ -19,24 +36,7 @@ from app.schemas.openai import OpenAIResponsesOutputText as _SchemaOpenAIRespons
from app.schemas.openai import OpenAIResponsesRequest as _SchemaOpenAIResponsesRequest
from app.schemas.openai import OpenAIResponsesResponse as _SchemaOpenAIResponsesResponse
from app.schemas.openai import OpenAIUsage as _SchemaOpenAIUsage
from app.api.openai_utils import (
build_completion_payload,
build_prompt,
build_responses_input,
build_session_id,
)
from app.api.presentation.sse import build_sse_response, encode_data_event
from app.agent.runtime_loader import get_moviepilot_agent_type
from app.application.agent import get_running_agent_manager
from app.agent.contracts import ReplyMode
from app.application.configuration import get_api_runtime_config_snapshot
from app.adapters.web.security.access import openai_bearer_scheme
from app.schemas.types import NotificationChannel
from app.api.context import (
get_background_task_registry_compat,
resolve_background_task_registry,
)
from app.runtime.tasks import TaskRegistry
OPENAI_ERROR_RESPONSES = {
400: {"model": _SchemaOpenAIErrorResponse, "description": "请求格式错误"},
+38 -39
View File
@@ -9,11 +9,48 @@ from fastapi import Depends, Header, HTTPException, Security
from starlette import status
from starlette.responses import StreamingResponse
from app.adapters.external.market import PluginHelper
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.system.plugin.package import PluginPackageManager
from app.adapters.web.security.access import (
resource_token_cookie,
verify_resource_token,
verify_token,
)
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_superuser_async,
)
from app.api.dependencies.plugin import (
get_plugin_config_command,
)
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.commands import init_commands
from app.application.configuration import get_api_runtime_config_snapshot, get_configured_system_config
from app.application.plugin.config import PluginConfigCommand
from app.application.plugin.folders import remove_plugin_from_folders
from app.application.plugin.install import PluginInstallCommand
from app.application.plugin.routes import register_plugin_api, remove_plugin_api
from app.application.plugin.runtime import PluginRuntime, get_plugin_manager
from app.application.scheduling import remove_plugin_job, update_plugin_job
from app.runtime.cache import async_fresh
from app.runtime.execution import run_in_threadpool
from app.runtime.extensions.plugin.contracts import (
PluginDashboardError,
PluginNotFoundError,
)
from app.runtime.log import logger
from app.runtime.tasks import TaskRegistry
from app.schemas.common import JsonObject as _SchemaJsonObject
from app.schemas.exception import (
PersistenceUnavailableError,
PluginMutationRejectedError,
)
from app.schemas.plugin import Plugin as _SchemaPlugin
from app.schemas.plugin import PluginDashboard as _SchemaPluginDashboard
from app.schemas.plugin import PluginCloneRequest as _SchemaPluginCloneRequest
from app.schemas.plugin import PluginDashboard as _SchemaPluginDashboard
from app.schemas.plugin import PluginDashboardMetaItem as _SchemaPluginDashboardMetaItem
from app.schemas.plugin import PluginFoldersData as _SchemaPluginFoldersData
from app.schemas.plugin import PluginRating as _SchemaPluginRating
@@ -26,45 +63,7 @@ from app.schemas.plugin import PluginRuntimeSummary as _SchemaPluginRuntimeSumma
from app.schemas.plugin import PluginSidebarNavItem as _SchemaPluginSidebarNavItem
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import ResponseAPIRouter
from app.application.plugin.folders import remove_plugin_from_folders
from app.application.plugin.routes import register_plugin_api, remove_plugin_api
from app.application.plugin.install import PluginInstallCommand
from app.application.plugin.config import PluginConfigCommand
from app.application.commands import init_commands
from app.application.scheduling import remove_plugin_job, update_plugin_job
from app.runtime.cache import async_fresh
from app.application.configuration import get_api_runtime_config_snapshot
from app.application.plugin.runtime import PluginRuntime, get_plugin_manager
from app.runtime.extensions.plugin.contracts import (
PluginDashboardError,
PluginNotFoundError,
)
from app.adapters.web.security.access import (
resource_token_cookie,
verify_resource_token,
verify_token,
)
from app.api.principal import ApiPrincipal
from app.application.configuration import get_configured_system_config
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_superuser_async,
)
from app.api.dependencies.plugin import (
get_plugin_config_command,
)
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.external.market import PluginHelper
from app.adapters.system.plugin.package import PluginPackageManager
from app.schemas.exception import (
PersistenceUnavailableError,
PluginMutationRejectedError,
)
from app.runtime.log import logger
from app.schemas.types import SystemConfigKey
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.runtime.tasks import TaskRegistry
router = ResponseAPIRouter()
_plugin_release_refresh_tasks: set[asyncio.Task] = set()
+6 -6
View File
@@ -2,17 +2,17 @@ from typing import Any, Awaitable, List, Optional
from fastapi import Depends, HTTPException, status
from app.schemas.event import RecommendMediaSource as _SchemaRecommendMediaSource
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.transfer import MusicInfo as _SchemaMusicInfo
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.adapters.web.security.access import verify_token
from app.api.response import ResponseAPIRouter
from app.chain.recommend import RecommendChain
from app.runtime.events import eventmanager
from app.adapters.web.security.access import verify_token
from app.schemas.exception import TMDbException
from app.schemas.event import RecommendMediaSource as _SchemaRecommendMediaSource
from app.schemas.event import RecommendSourceEventData
from app.schemas.exception import TMDbException
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.transfer import MusicInfo as _SchemaMusicInfo
from app.schemas.types import ChainEventType
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+10 -10
View File
@@ -4,25 +4,25 @@ import time
from typing import Any, AsyncIterator, Iterator, List, Optional
from uuid import uuid4
from fastapi import Depends, Body, Request
from fastapi import Body, Depends, Request
from fastapi.responses import StreamingResponse
from app.adapters.web.security.access import verify_resource_token, verify_token
from app.api.response import ResponseAPIRouter
from app.application.security.url import SecurityUtils
from app.chain.search import SearchChain
from app.domain.media import normalize_music_type
from app.runtime.localization import LocaleHelper
from app.runtime.log import logger
from app.schemas.media import resolve_media_identity
from app.schemas.response import Response as _SchemaResponse
from app.schemas.search import SearchLastContextData as _SchemaSearchLastContextData
from app.schemas.search import SearchRecommendStatusData as _SchemaSearchRecommendStatusData
from app.schemas.search import SubtitleInfo as _SchemaSubtitleInfo
from app.schemas.system import TorrentInfo as _SchemaTorrentInfo
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import Context as _SchemaContext
from app.api.response import ResponseAPIRouter
from app.chain.search import SearchChain
from app.adapters.web.security.access import verify_resource_token, verify_token
from app.runtime.localization import LocaleHelper
from app.runtime.log import logger
from app.schemas.types import MediaSource, MediaType
from app.domain.media import normalize_music_type
from app.schemas.media import resolve_media_identity
from app.application.security.url import SecurityUtils
from app.schemas.workflow import Context as _SchemaContext
router = ResponseAPIRouter()
+27 -28
View File
@@ -1,31 +1,9 @@
from typing import List, Any, Dict, Optional
from typing import Annotated, Any, Dict, List, Optional
from fastapi import Depends, HTTPException
from typing import Annotated
from app.schemas.common import JsonObject as _SchemaJsonObject
from app.schemas.response import Response as _SchemaResponse
from app.schemas.site import SiteAuth as _SchemaSiteAuth
from app.schemas.site import SiteCategory as _SchemaSiteCategory
from app.schemas.site import SiteCookieUpdate as _SchemaSiteCookieUpdate
from app.schemas.site import SiteIconData as _SchemaSiteIconData
from app.schemas.site import SiteMappingData as _SchemaSiteMappingData
from app.schemas.site import SiteStatistic as _SchemaSiteStatistic
from app.schemas.site import SiteUserData as _SchemaSiteUserData
from app.schemas.system import TorrentInfo as _SchemaTorrentInfo
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import Site as _SchemaSite
from app.api.response import ResponseAPIRouter
from app.application.site.mutation import SiteMutationCommand
from app.application.site.query import SiteQueryService
from app.api.endpoints.plugin import register_plugin_api
from app.chain.site import SiteChain
from app.chain.torrents import TorrentsChain
from app.application.commands import init_commands
from app.application.plugin.runtime import get_plugin_manager
from app.adapters.web.security.access import verify_token
from app.api.principal import ApiPrincipal
from app.application.configuration import get_configured_system_config
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.api.dependencies.auth import (
get_current_active_manage_user,
get_current_active_manage_user_async,
@@ -37,13 +15,34 @@ from app.api.dependencies.site import (
get_site_query_service,
get_site_sync_query_service,
)
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.runtime.log import logger
from app.api.endpoints.plugin import register_plugin_api
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.commands import init_commands
from app.application.configuration import get_configured_system_config
from app.application.plugin.runtime import get_plugin_manager
from app.application.scheduling import get_scheduler
from app.schemas.types import SystemConfigKey, MediaType
from app.application.site.mutation import SiteMutationCommand
from app.application.site.query import SiteQueryService
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.chain.site import SiteChain
from app.chain.torrents import TorrentsChain
from app.domain import site as site_rules
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.runtime.log import logger
from app.runtime.tasks import TaskRegistry
from app.schemas.common import JsonObject as _SchemaJsonObject
from app.schemas.response import Response as _SchemaResponse
from app.schemas.site import SiteAuth as _SchemaSiteAuth
from app.schemas.site import SiteCategory as _SchemaSiteCategory
from app.schemas.site import SiteCookieUpdate as _SchemaSiteCookieUpdate
from app.schemas.site import SiteIconData as _SchemaSiteIconData
from app.schemas.site import SiteMappingData as _SchemaSiteMappingData
from app.schemas.site import SiteStatistic as _SchemaSiteStatistic
from app.schemas.site import SiteUserData as _SchemaSiteUserData
from app.schemas.system import TorrentInfo as _SchemaTorrentInfo
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.types import MediaType, SystemConfigKey
from app.schemas.workflow import Site as _SchemaSite
router = ResponseAPIRouter()
+11 -11
View File
@@ -7,22 +7,22 @@ from typing import Any, Dict, List, Optional
from fastapi import Depends, HTTPException
from starlette.responses import FileResponse, Response
from app.schemas.common import ManageRequest as _SchemaManageRequest
from app.schemas.response import Response as _SchemaResponse
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.api.response import ResponseAPIRouter
from app.chain.media import MediaChain
from app.chain.storage import StorageChain
from app.chain.transfer import TransferChain
from app.application.configuration import get_api_runtime_config_snapshot
from app.api.principal import ApiPrincipal
from app.api.dependencies.auth import (
get_current_active_manage_user,
get_current_active_superuser,
)
from app.runtime.progress import ProgressHelper
from app.schemas.types import ProgressKey
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_api_runtime_config_snapshot
from app.chain.media import MediaChain
from app.chain.storage import StorageChain
from app.chain.transfer import TransferChain
from app.foundation import text as text_tools
from app.runtime.progress import ProgressHelper
from app.schemas.common import ManageRequest as _SchemaManageRequest
from app.schemas.response import Response as _SchemaResponse
from app.schemas.types import ProgressKey
from app.schemas.workflow import FileItem as _SchemaFileItem
router = ResponseAPIRouter()
+41 -41
View File
@@ -1,47 +1,14 @@
from typing import List, Any, Annotated, Optional
from typing import Annotated, Any, List, Optional
import cn2an
from fastapi import Request, Depends, HTTPException, Header
from fastapi import Depends, Header, HTTPException, Request
from app.schemas.common import IdData as _SchemaIdData
from app.schemas.response import Response as _SchemaResponse
from app.schemas.subscribe import SubscrbieInfo as _SchemaSubscrbieInfo
from app.schemas.subscribe import SubscribeShare as _SchemaSubscribeShare
from app.schemas.subscribe import SubscribeShareStatistics as _SchemaSubscribeShareStatistics
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.schemas.workflow import Subscribe as _SchemaSubscribe
from app.api.response import ResponseAPIRouter
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.web.security.access import verify_apitoken, verify_token
from app.api.context import (
get_background_task_registry,
resolve_background_task_registry,
)
from app.chain.subscribe import SubscribeChain
from app.runtime.events import eventmanager
from app.domain.context import MediaInfo
from app.domain.metainfo import MetaInfo
from app.adapters.web.security.access import verify_token, verify_apitoken
from app.application.subscription.delete import (
DeleteSubscribeCommand,
SubscribeDeletionActor,
)
from app.application.subscription.identity import (
DeleteSubscriptionsByIdentityCommand,
)
from app.application.subscription.search import (
SearchSubscriptionsCommand,
SubscribeSearchActor,
)
from app.api.principal import ApiPrincipal
from app.application.subscription.query import SubscriptionQueryService
from app.application.subscription.mutation import (
SubscriptionActor,
SubscriptionMutationService,
)
from app.application.configuration import (
get_api_runtime_config_snapshot,
get_configured_system_config,
)
from app.api.dependencies.auth import (
get_current_active_user,
get_current_active_user_async,
@@ -50,23 +17,56 @@ from app.api.dependencies.subscription import (
get_delete_subscribe_command,
get_delete_subscriptions_by_identity_command,
get_search_subscriptions_command,
get_subscription_query_service,
get_subscription_mutation_service,
get_subscription_query_service,
get_subscription_sync_mutation_service,
)
from app.adapters.external.server import MoviePilotServerHelper
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.configuration import (
get_api_runtime_config_snapshot,
get_configured_system_config,
)
from app.application.scheduling import get_scheduler
from app.application.subscription.delete import (
DeleteSubscribeCommand,
SubscribeDeletionActor,
)
from app.application.subscription.identity import (
DeleteSubscriptionsByIdentityCommand,
)
from app.application.subscription.mutation import (
SubscriptionActor,
SubscriptionMutationService,
)
from app.application.subscription.query import SubscriptionQueryService
from app.application.subscription.search import (
SearchSubscriptionsCommand,
SubscribeSearchActor,
)
from app.chain.subscribe import SubscribeChain
from app.domain.context import MediaInfo
from app.domain.metainfo import MetaInfo
from app.runtime.events import eventmanager
from app.runtime.tasks import TaskRegistry
from app.schemas.common import IdData as _SchemaIdData
from app.schemas.event import SubscribeModifiedEventData
from app.schemas.media import normalize_media_source, resolve_media_identity
from app.schemas.response import Response as _SchemaResponse
from app.schemas.subscribe import SubscrbieInfo as _SchemaSubscrbieInfo
from app.schemas.subscribe import SubscribeShare as _SchemaSubscribeShare
from app.schemas.subscribe import SubscribeShareStatistics as _SchemaSubscribeShareStatistics
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.types import (
MUSIC_ENTITY_ALBUM,
MUSIC_ENTITY_RECORDING,
EventType,
MediaSource,
MediaType,
EventType,
SystemConfigKey,
)
from app.schemas.media import normalize_media_source, resolve_media_identity
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.schemas.workflow import Subscribe as _SchemaSubscribe
router = ResponseAPIRouter()
+61 -61
View File
@@ -6,25 +6,76 @@ import zipfile
from collections import deque
from datetime import datetime
from pathlib import Path
from typing import Any, Optional, Union, Annotated
from urllib.parse import urljoin, urlparse
from typing import Annotated, Any, Optional, Union
from urllib.parse import urlparse
import aiofiles
import anyio
import pillow_avif # noqa: F401 # pylint: disable=unused-import # AVIF 注册副作用
from anyio import Path as AsyncPath
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from fastapi import Body, Depends, HTTPException, Header, Request, Response
from fastapi import Body, Depends, Header, HTTPException, Request, Response
from fastapi.responses import StreamingResponse
from app.adapters.external.market import (
PLUGIN_MARKET_WIKI_URL,
extract_plugin_market_repos_from_wiki,
merge_plugin_market_repos,
split_plugin_market_repo_urls,
)
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.network.http import AsyncRequestUtils, RequestUtils
from app.adapters.system import rust as rust_accel
from app.adapters.system.update import system_update_manager
from app.adapters.web.security.access import verify_apitoken, verify_resource_token, verify_token
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_superuser_async,
get_current_active_user_async,
)
from app.api.principal import ApiPrincipal
from app.api.response import ResponseAPIRouter
from app.application.backup import DatabaseBackupInProgressError
from app.application.configuration import (
get_configured_system_config,
get_runtime_settings,
)
from app.application.database import get_database_governance
from app.application.image import ImageHelper
from app.application.messaging.message import MessageHelper
from app.application.module import get_module_manager
from app.application.network import NetworkTestService
from app.application.plugin.runtime import plugin_system_config_mutation
from app.application.rules import RuleHelper
from app.application.scheduling import get_scheduler
from app.application.security.url import SecurityUtils
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.chain.media import MediaChain
from app.chain.mediaserver import MediaServerChain
from app.chain.search import SearchChain
from app.domain.metainfo import MetaInfo
from app.foundation.crypto import HashUtils
from app.foundation.environment import is_free_threaded_runtime, is_gil_enabled
from app.foundation.url import UrlUtils
from app.runtime.events import eventmanager
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.localization import LocaleHelper
from app.runtime.log import logger
from app.runtime.progress import AsyncProgressHelper
from app.runtime.scheduling import TimerUtils
from app.runtime.state import SystemHelper
from app.runtime.stop import runtime_stop_state
from app.runtime.config import global_vars
from app.runtime.version import get_app_version, get_frontend_version
from app.schemas.common import JsonObject as _SchemaJsonObject
from app.schemas.common import JsonObjectList as _SchemaJsonObjectList
from app.schemas.common import TimeData as _SchemaTimeData
from app.schemas.common import ValueData as _SchemaValueData
from app.schemas.event import ConfigChangeEventData
from app.schemas.exception import PluginMutationRejectedError
from app.schemas.response import Response as _SchemaResponse
from app.schemas.system import NetTestTarget as _SchemaNetTestTarget
from app.schemas.system import DatabaseBackupArtifactData as _SchemaDatabaseBackupArtifactData
from app.schemas.system import DatabaseBackupVerificationData as _SchemaDatabaseBackupVerificationData
from app.schemas.system import NetTestTarget as _SchemaNetTestTarget
from app.schemas.system import PluginMarketSyncData as _SchemaPluginMarketSyncData
from app.schemas.system import PluginMarketSyncRequest as _SchemaPluginMarketSyncRequest
from app.schemas.system import RuleTestData as _SchemaRuleTestData
@@ -33,58 +84,7 @@ from app.schemas.system import SystemModuleListData as _SchemaSystemModuleListDa
from app.schemas.system import SystemUpdateStatus as _SchemaSystemUpdateStatus
from app.schemas.system import TorrentInfo as _SchemaTorrentInfo
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import ResponseAPIRouter
from app.chain.media import MediaChain
from app.chain.mediaserver import MediaServerChain
from app.chain.search import SearchChain
from app.chain.system import SystemChain
from app.runtime.config import global_vars
from app.runtime.events import eventmanager
from app.domain.metainfo import MetaInfo
from app.application.module import get_module_manager
from app.adapters.web.security.access import verify_apitoken, verify_resource_token, verify_token
from app.api.principal import ApiPrincipal
from app.application.configuration import (
get_configured_system_config,
get_runtime_settings,
)
from app.application.backup import DatabaseBackupInProgressError
from app.application.database import get_database_governance
from app.application.plugin.runtime import plugin_system_config_mutation
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_superuser_async,
get_current_active_user_async,
)
from app.application.image import ImageHelper
from app.runtime.localization import LocaleHelper
from app.adapters.external.market import (
PLUGIN_MARKET_WIKI_URL,
extract_plugin_market_repos_from_wiki,
merge_plugin_market_repos,
split_plugin_market_repo_urls,
)
from app.application.messaging.message import MessageHelper
from app.runtime.progress import AsyncProgressHelper
from app.runtime.scheduling import TimerUtils
from app.application.rules import RuleHelper
from app.adapters.external.server import MoviePilotServerHelper
from app.runtime.state import SystemHelper
from app.runtime.log import logger
from app.runtime.execution import run_in_threadpool_to_completion
from app.application.scheduling import get_scheduler
from app.schemas.event import ConfigChangeEventData
from app.schemas.exception import PluginMutationRejectedError
from app.schemas.types import SystemConfigKey, EventType
from app.foundation.crypto import HashUtils
from app.foundation.environment import is_free_threaded_runtime, is_gil_enabled
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
from app.adapters.system import rust as rust_accel
from app.adapters.system.update import system_update_manager
from app.application.security.url import SecurityUtils
from app.application.network import NetworkTestService
from app.foundation.url import UrlUtils
from app.runtime.version import get_app_version, get_frontend_version
from app.schemas.types import EventType, SystemConfigKey
router = ResponseAPIRouter()
@@ -1042,7 +1042,7 @@ async def get_progress(
async def event_generator():
try:
while not global_vars.is_system_stopped:
while not runtime_stop_state.is_system_stopped:
if await request.is_disconnected():
break
detail = await progress.get(locale=locale)
@@ -1233,7 +1233,7 @@ async def get_message(
async def event_generator():
try:
while not global_vars.is_system_stopped:
while not runtime_stop_state.is_system_stopped:
if await request.is_disconnected():
break
detail = message.get(role)
@@ -1314,7 +1314,7 @@ async def _get_logging_impl(
initial_stat = await log_path.stat()
initial_size = initial_stat.st_size
# 实时监听新日志,使用更短的轮询间隔
while not global_vars.is_system_stopped:
while not runtime_stop_state.is_system_stopped:
if await request.is_disconnected():
break
# 检查文件是否有新内容
@@ -1413,7 +1413,7 @@ async def latest_version(_: _SchemaTokenPayload = Depends(verify_token)):
version_res = await AsyncRequestUtils(
proxies=get_runtime_settings().get("PROXY"),
headers=get_runtime_settings().get("GITHUB_HEADERS"),
).get_res(f"https://api.github.com/repos/jxxghp/MoviePilot/releases")
).get_res("https://api.github.com/repos/jxxghp/MoviePilot/releases")
if version_res is not None and version_res.status_code == 200:
ver_json = version_res.json()
if ver_json:
+8 -9
View File
@@ -1,21 +1,20 @@
from typing import List, Any, Optional
from typing import Any, List, Optional
from fastapi import Depends
from app.adapters.web.security.access import verify_token
from app.api.dependencies.auth import get_current_active_superuser_async
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_api_runtime_config_snapshot, get_configured_system_config
from app.chain.tmdb import TmdbChain
from app.schemas.context import MediaPerson as _SchemaMediaPerson
from app.schemas.response import Response as _SchemaResponse
from app.schemas.tmdb import TmdbEpisode as _SchemaTmdbEpisode
from app.schemas.tmdb import TmdbRecognitionCacheData as _SchemaTmdbRecognitionCacheData
from app.schemas.tmdb import TmdbSeason as _SchemaTmdbSeason
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.tmdb import TmdbEpisode as _SchemaTmdbEpisode
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
from app.api.response import ResponseAPIRouter
from app.chain.tmdb import TmdbChain
from app.application.configuration import get_api_runtime_config_snapshot
from app.adapters.web.security.access import verify_token
from app.application.configuration import get_configured_system_config
from app.api.dependencies.auth import get_current_active_superuser_async
from app.schemas.types import MediaType, SystemConfigKey
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
router = ResponseAPIRouter()
+10 -10
View File
@@ -2,24 +2,24 @@ from typing import Optional
from fastapi import Depends
from app.schemas.cache import TorrentCacheData as _SchemaTorrentCacheData
from app.schemas.cache import TorrentReidentifyData as _SchemaTorrentReidentifyData
from app.schemas.response import Response as _SchemaResponse
from app.api.response import ResponseAPIRouter
from app.chain.media import MediaChain
from app.chain.torrents import TorrentsChain
from app.application.configuration import get_api_runtime_config_snapshot
from app.api.dependencies.auth import (
get_current_active_superuser,
get_current_active_superuser_async,
)
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_api_runtime_config_snapshot
from app.application.torrent_cache import TorrentCacheRecognitionService
from app.chain.media import MediaChain
from app.chain.torrents import TorrentsChain
from app.foundation.crypto import HashUtils
from app.schemas.cache import TorrentCacheData as _SchemaTorrentCacheData
from app.schemas.cache import TorrentReidentifyData as _SchemaTorrentReidentifyData
from app.schemas.media import resolve_media_identity
from app.schemas.response import Response as _SchemaResponse
from app.schemas.types import (
MediaSource,
MusicTargetEntityType,
)
from app.foundation.crypto import HashUtils
from app.schemas.media import resolve_media_identity
from app.application.torrent_cache import TorrentCacheRecognitionService
router = ResponseAPIRouter()
+17 -18
View File
@@ -1,34 +1,33 @@
from pathlib import Path
from typing import Any, List, Annotated, Optional
from typing import Annotated, Any, List, Optional
from fastapi import Depends
from app.adapters.web.security.access import verify_apitoken, verify_token
from app.api.dependencies.auth import get_current_active_manage_user
from app.api.dependencies.history import get_transfer_history_lookup_service
from app.api.response import ResponseAPIRouter
from app.application.configuration import get_api_runtime_config_snapshot
from app.application.directory import DirectoryHelper
from app.application.history import TransferHistoryLookupService
from app.chain.media import MediaChain
from app.chain.transfer import TransferChain
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.common import NameData as _SchemaNameData
from app.schemas.response import Response as _SchemaResponse
from app.schemas.system import TransferDirectoryConf as _SchemaTransferDirectoryConf
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.transfer import EpisodeFormat as _SchemaEpisodeFormat
from app.schemas.transfer import EpisodeFormatRecommendData as _SchemaEpisodeFormatRecommendData
from app.schemas.transfer import EpisodeFormatRecommendItem, ManualTransferItem
from app.schemas.transfer import ManualTransferHistoryInfo as _SchemaManualTransferHistoryInfo
from app.schemas.transfer import ManualTransferResultData as _SchemaManualTransferResultData
from app.schemas.transfer import ManualTransferTargetPath as _SchemaManualTransferTargetPath
from app.schemas.system import TransferDirectoryConf as _SchemaTransferDirectoryConf
from app.schemas.transfer import TransferJob as _SchemaTransferJob
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.api.response import ResponseAPIRouter
from app.chain.media import MediaChain
from app.chain.transfer import TransferChain
from app.runtime.config import global_vars
from app.application.configuration import get_api_runtime_config_snapshot
from app.adapters.web.security.access import verify_token, verify_apitoken
from app.api.dependencies.auth import get_current_active_manage_user
from app.api.dependencies.history import get_transfer_history_lookup_service
from app.application.directory import DirectoryHelper
from app.application.history import TransferHistoryLookupService
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.schemas.workflow import FileItem
from app.schemas.transfer import ManualTransferItem
from app.schemas.transfer import EpisodeFormatRecommendItem
from app.schemas.workflow import FileItem as _SchemaFileItem
router = ResponseAPIRouter()
@@ -102,7 +101,7 @@ async def remove_queue(
"""
TransferChain().remove_from_queue(fileitem)
# 取消整理
global_vars.stop_transfer(fileitem.path)
runtime_stop_state.stop_transfer(fileitem.path)
return _SchemaResponse(success=True)
@@ -398,7 +397,7 @@ def _execute_manual_transfer(
elif transer_item.fileitem:
src_fileitems = [transer_item.fileitem]
else:
return _SchemaResponse(success=False, message=f"缺少参数")
return _SchemaResponse(success=False, message="缺少参数")
dedup_fileitems: List[FileItem] = []
seen_paths = set()
+10 -10
View File
@@ -2,23 +2,23 @@ import base64
import re
from typing import Annotated, Any, List, Union
from fastapi import Body, Depends, HTTPException, UploadFile, File
from fastapi import Body, Depends, File, HTTPException, UploadFile
from app.api.dependencies.auth import (
get_current_active_superuser_async,
get_current_active_user_async,
get_user_service,
)
from app.api.response import ResponseAPIRouter
from app.application.security.token import PasswordTooLongError, get_password_hash
from app.application.security.user import UserService
from app.application.security.userconfig import get_configured_user_configuration
from app.schemas.common import FileNameData as _SchemaFileNameData
from app.schemas.common import ValueData as _SchemaValueData
from app.schemas.response import Response as _SchemaResponse
from app.schemas.user import User as _SchemaUser
from app.schemas.user import UserCreate as _SchemaUserCreate
from app.schemas.user import UserUpdate as _SchemaUserUpdate
from app.api.response import ResponseAPIRouter
from app.application.security.token import PasswordTooLongError, get_password_hash
from app.application.security.user import UserService
from app.api.dependencies.auth import (
get_current_active_superuser_async,
get_current_active_user_async,
get_user_service,
)
from app.application.security.userconfig import get_configured_user_configuration
router = ResponseAPIRouter()
+4 -4
View File
@@ -1,13 +1,13 @@
from typing import Any, Annotated
from typing import Annotated, Any
from fastapi import Depends, Request
from app.schemas.response import Response as _SchemaResponse
from app.api.response import ResponseAPIRouter
from app.chain.webhook import WebhookChain
from app.adapters.web.security.access import verify_apitoken
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.api.response import ResponseAPIRouter
from app.chain.webhook import WebhookChain
from app.runtime.tasks import TaskRegistry
from app.schemas.response import Response as _SchemaResponse
router = ResponseAPIRouter()
+18 -18
View File
@@ -1,22 +1,8 @@
from typing import List, Any, Optional
from typing import Any, List, Optional
from fastapi import Depends
from app.schemas.response import Response as _SchemaResponse
from app.schemas.workflow import NameValueOption as _SchemaNameValueOption
from app.schemas.workflow import PluginWorkflowActionGroup as _SchemaPluginWorkflowActionGroup
from app.schemas.workflow import Workflow as _SchemaWorkflow
from app.schemas.workflow import WorkflowActionDefinition as _SchemaWorkflowActionDefinition
from app.schemas.workflow import WorkflowShare as _SchemaWorkflowShare
from app.api.response import ResponseAPIRouter
from app.application.workflow import (
WorkflowDefinitionCommand,
WorkflowMutationCommand,
WorkflowQueryService,
get_workflow_manager,
)
from app.chain.workflow import WorkflowChain
from app.application.plugin.runtime import get_plugin_manager
from app.adapters.external.server import MoviePilotServerHelper
from app.api.dependencies.auth import (
get_current_active_manage_user,
get_current_active_manage_user_async,
@@ -26,8 +12,22 @@ from app.api.dependencies.workflow import (
get_workflow_mutation_command,
get_workflow_query_service,
)
from app.adapters.external.server import MoviePilotServerHelper
from app.schemas.types import EventType, EVENT_TYPE_NAMES
from app.api.response import ResponseAPIRouter
from app.application.plugin.runtime import get_plugin_manager
from app.application.workflow import (
WorkflowDefinitionCommand,
WorkflowMutationCommand,
WorkflowQueryService,
get_workflow_manager,
)
from app.chain.workflow import WorkflowChain
from app.schemas.response import Response as _SchemaResponse
from app.schemas.types import EVENT_TYPE_NAMES, EventType
from app.schemas.workflow import NameValueOption as _SchemaNameValueOption
from app.schemas.workflow import PluginWorkflowActionGroup as _SchemaPluginWorkflowActionGroup
from app.schemas.workflow import Workflow as _SchemaWorkflow
from app.schemas.workflow import WorkflowActionDefinition as _SchemaWorkflowActionDefinition
from app.schemas.workflow import WorkflowShare as _SchemaWorkflowShare
router = ResponseAPIRouter()
-1
View File
@@ -6,7 +6,6 @@ from typing import Any
from fastapi.responses import StreamingResponse
SSE_HEADERS = {
"Cache-Control": "no-cache, no-transform",
"Connection": "keep-alive",
-1
View File
@@ -11,7 +11,6 @@ from starlette.responses import Response as StarletteResponse
from app.schemas.common import JsonData
from app.schemas.response import Response, ValidationIssue
ERROR_RESPONSES: dict[int, dict[str, Any]] = {
400: {"model": Response[None], "description": "请求错误"},
401: {"model": Response[None], "description": "未认证"},
+13 -14
View File
@@ -1,8 +1,19 @@
from typing import List, Optional, Annotated
from typing import Annotated, List, Optional
from fastapi import APIRouter, HTTPException, Depends
from fastapi import APIRouter, Depends, HTTPException
from app.adapters.web.security.access import verify_apikey
from app.api.dependencies.subscription import get_servarr_subscription_service
from app.api.response import ERROR_RESPONSES
from app.application.servarr import ServarrSubscription, ServarrSubscriptionService
from app.chain.media import MediaChain
from app.chain.subscribe import SubscribeChain
from app.chain.tvdb import TvdbChain
from app.domain.context import MediaInfo
from app.domain.metainfo import MetaInfo
from app.runtime.version import get_app_version
from app.schemas.response import Response as _SchemaResponse
from app.schemas.servarr import RadarrMovie, SonarrSeries
from app.schemas.servarr import RadarrMovie as _SchemaRadarrMovie
from app.schemas.servarr import ServarrIdResponse as _SchemaServarrIdResponse
from app.schemas.servarr import ServarrLanguageProfile as _SchemaServarrLanguageProfile
@@ -11,19 +22,7 @@ from app.schemas.servarr import ServarrRootFolder as _SchemaServarrRootFolder
from app.schemas.servarr import ServarrSystemStatus as _SchemaServarrSystemStatus
from app.schemas.servarr import ServarrTag as _SchemaServarrTag
from app.schemas.servarr import SonarrSeries as _SchemaSonarrSeries
from app.api.response import ERROR_RESPONSES
from app.chain.media import MediaChain
from app.chain.subscribe import SubscribeChain
from app.chain.tvdb import TvdbChain
from app.domain.context import MediaInfo
from app.domain.metainfo import MetaInfo
from app.application.servarr import ServarrSubscription, ServarrSubscriptionService
from app.adapters.web.security.access import verify_apikey
from app.api.dependencies.subscription import get_servarr_subscription_service
from app.schemas.servarr import RadarrMovie
from app.schemas.servarr import SonarrSeries
from app.schemas.types import MediaSource, MediaType
from app.runtime.version import get_app_version
arr_router = APIRouter(tags=["servarr"], responses=ERROR_RESPONSES)
+4 -4
View File
@@ -9,15 +9,15 @@ from fastapi import APIRouter, Body, Depends, Header, HTTPException, Path, Reque
from fastapi.responses import PlainTextResponse
from fastapi.routing import APIRoute
from app.api.response import ERROR_RESPONSES
from app.application.configuration import get_api_runtime_config_snapshot
from app.foundation.crypto import CryptoJsUtils, HashUtils
from app.runtime.log import logger
from app.schemas.servcookie import CookieActionResponse as _SchemaCookieActionResponse
from app.schemas.servcookie import CookieData as _SchemaCookieData
from app.schemas.servcookie import CookieDecryptedPayload as _SchemaCookieDecryptedPayload
from app.schemas.servcookie import CookieEncryptedPayload as _SchemaCookieEncryptedPayload
from app.schemas.servcookie import CookiePassword as _SchemaCookiePassword
from app.api.response import ERROR_RESPONSES
from app.application.configuration import get_api_runtime_config_snapshot
from app.runtime.log import logger
from app.foundation.crypto import CryptoJsUtils, HashUtils
class GzipRequest(Request):
+2
View File
@@ -9,6 +9,7 @@ from typing import Any, Optional
from app.application.chain.data import ChainDataPorts
from app.application.chain.durable_events import ChainDurableEventWriter
from app.application.configuration import ChainRuntimeConfig
from app.runtime.stop import StopState, runtime_stop_state
MessageQueueFactory = Callable[[Callable[..., Any]], Any]
@@ -34,6 +35,7 @@ class ChainRuntimeContext:
configuration: ChainRuntimeConfig = field(
default_factory=lambda: ChainRuntimeConfig(media_extensions=())
)
stop_state: StopState = field(default_factory=lambda: runtime_stop_state)
def _unconfigured_chain_runtime_context() -> ChainRuntimeContext:
+8 -10
View File
@@ -10,26 +10,24 @@ import time
from contextvars import Context, copy_context
from datetime import datetime
from functools import partial
from typing import Any, Literal, Optional, List, Dict, Protocol, Union
from typing import Callable
from typing import Any, Callable, Dict, List, Literal, Optional, Protocol, Union
from jinja2 import Template
from app.runtime.cache import TTLCache
from app.runtime.config import global_vars
from app.application.configuration import get_configured_system_config
from app.domain.context import MediaInfo, MusicInfo, TorrentInfo
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
from app.application.configuration import get_configured_system_config
from app.foundation import size as size_tools
from app.foundation.crypto import HashUtils
from app.foundation.singleton import Singleton, SingletonClass
from app.runtime.cache import TTLCache
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.message import Message
from app.schemas.tmdb import TmdbEpisode
from app.schemas.transfer import TransferInfo
from app.schemas.types import MUSIC_ENTITY_ALBUM, SystemConfigKey
from app.foundation.singleton import Singleton, SingletonClass
from app.foundation import size as size_tools
from app.foundation.crypto import HashUtils
# 专辑名尾部的括号年份标记;重命名模板会独立追加 `({{year}})`
# 标签或目录名中自带的尾部年份若不剥离,会生成重复年份的目录名(issue #6355)
@@ -998,7 +996,7 @@ class MessageQueueManager(metaclass=SingletonClass):
current_time = datetime.now()
if self._is_in_scheduled_time(current_time):
while self._running and not self.queue.empty():
if global_vars.is_system_stopped:
if runtime_stop_state.is_system_stopped:
break
if not self._is_in_scheduled_time(datetime.now()):
break
+14 -15
View File
@@ -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(
+96
View File
@@ -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: ...
+2
View File
@@ -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
+7 -5
View File
@@ -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
View File
@@ -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单曲/专辑目标识别实体快照同步候选筛选
择优下载与完成推进
+4 -2
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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):
+2 -2
View File
@@ -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):
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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}")
+7 -7
View File
@@ -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
View File
@@ -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
+8 -11
View File
@@ -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
View File
@@ -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):
+8 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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":
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+7 -6
View File
@@ -26,18 +26,18 @@ def _prepare_direct_execution_import_path() -> None:
_prepare_direct_execution_import_path()
import setproctitle
import signal
import threading
from pathlib import Path
from typing import Optional
import setproctitle
import uvicorn as uvicorn
from PIL import Image
from uvicorn import Config
from app.adapters.system.stdio import configure_rotating_stdio
from app.adapters.system.host import SystemUtils
from app.adapters.system.stdio import configure_rotating_stdio
stdio_log_file = os.getenv("MOVIEPILOT_STDIO_LOG_FILE")
if stdio_log_file:
@@ -55,8 +55,9 @@ elif SystemUtils.is_frozen():
sys.stderr = open(os.devnull, 'w')
from app.factory import app
from app.runtime.config import global_vars
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.config import global_vars
from app.runtime.stop import runtime_stop_state
settings = RuntimeSettingsCompat()
from app.runtime.topology import (
@@ -71,7 +72,7 @@ class MoviePilotServer(uvicorn.Server):
"""在 Uvicorn 开始优雅退出前发布应用协作停止标志"""
def handle_exit(self, sig, frame) -> None:
global_vars.stop_system()
getattr(global_vars, "stop_system")()
super().handle_exit(sig, frame)
@@ -92,7 +93,7 @@ def create_server() -> MoviePilotServer:
)
)
# 数据库准备阶段收到的信号早于 Server 物化,创建后必须继承既有停止意图。
if global_vars.is_system_stopped:
if runtime_stop_state.is_system_stopped:
server.should_exit = True
return server
@@ -124,7 +125,7 @@ def run_api_server() -> None:
def request_shutdown() -> None:
"""发布协作停止标志并请求 Uvicorn 退出"""
global_vars.stop_system()
getattr(global_vars, "stop_system")()
if Server is not None:
Server.should_exit = True
+6 -6
View File
@@ -1,17 +1,17 @@
from abc import ABCMeta, abstractmethod
from pathlib import Path, PurePosixPath
from typing import Optional, List, Dict, Tuple, Callable, Union
from typing import Callable, Dict, List, Optional, Tuple, Union
from tqdm import tqdm
from app.application.storage import StorageHelper
from app.foundation.crypto import HashUtils
from app.runtime.log import logger
from app.runtime.progress import ProgressHelper
from app.schemas.exception import StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.system import StorageConf as _SchemaStorageConf
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.progress import ProgressHelper
from app.application.storage import StorageHelper
from app.runtime.log import logger
from app.schemas.exception import StorageQueryError
from app.foundation.crypto import HashUtils
def transfer_process(path: str) -> Callable[[int | float], None]:
+8 -8
View File
@@ -8,19 +8,19 @@ from typing import List, Optional, Tuple, Union
import requests
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.config import global_vars
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils
from app.foundation import temporal as time_tools
from app.foundation.singleton import WeakSingleton
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.schemas.exception import StorageQueryError
from app.schemas.types import StorageSchema
from app.adapters.network.http import RequestUtils
from app.foundation.singleton import WeakSingleton
from app.foundation import temporal as time_tools
lock = threading.Lock()
@@ -645,7 +645,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
uploaded_size = 0
with open(local_path, "rb") as f:
for part_info in part_info_list:
if global_vars.is_transfer_stopped(local_path.as_posix()):
if runtime_stop_state.consume_transfer_stop(local_path.as_posix()):
logger.info(f"【阿里云盘】{target_name} 上传已取消!")
return None
@@ -780,7 +780,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
downloaded_size = 0
with open(local_path, "wb") as f:
for chunk in r.iter_content(chunk_size=self.chunk_size):
if global_vars.is_transfer_stopped(fileitem.path):
if runtime_stop_state.consume_transfer_stop(fileitem.path):
logger.info(f"【阿里云盘】{fileitem.path} 下载已取消!")
return None
if chunk:
+10 -11
View File
@@ -3,23 +3,22 @@ import json
import time
from datetime import datetime
from pathlib import Path
from typing import Optional, List
from typing import List, Optional
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.config import global_vars
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.schemas.exception import OperationInterrupted, StorageQueryError
from app.schemas.types import StorageSchema
from app.adapters.network.http import RequestUtils
from app.foundation.singleton import WeakSingleton
from app.foundation.url import UrlUtils
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.schemas.exception import OperationInterrupted, StorageQueryError
from app.schemas.types import StorageSchema
# OpenList/AList 在 per_page<=0 时会退回后端默认 200,显式指定最大页大小避免大目录被截断。
OPENLIST_MAX_LIST_PAGE_SIZE = 500
@@ -703,7 +702,7 @@ class Alist(StorageBase, metaclass=WeakSingleton):
r.raise_for_status()
with open(local_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
if global_vars.is_transfer_stopped(fileitem.path):
if runtime_stop_state.consume_transfer_stop(fileitem.path):
logger.info(f"【OpenList】{fileitem.path} 下载已取消!")
return None
f.write(chunk)
@@ -760,7 +759,7 @@ class Alist(StorageBase, metaclass=WeakSingleton):
return self.file_size
def read(self, size=-1):
if global_vars.is_transfer_stopped(path.as_posix()):
if runtime_stop_state.consume_transfer_stop(path.as_posix()):
logger.info(f"【OpenList】{path} 上传已取消!")
raise OperationInterrupted(f"Upload cancelled: {path}")
chunk = self.file.read(size)
+8 -8
View File
@@ -2,21 +2,21 @@ import os
import shutil
import time
from pathlib import Path
from typing import Optional, List
from typing import List, Optional
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.config import global_vars
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.application.directory import DirectoryHelper
from app.runtime.log import logger
from app.adapters.system.fsproxy import fsproxy
from app.adapters.system.host import SystemUtils
from app.application.directory import DirectoryHelper
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.schemas.exception import StorageQueryError
from app.schemas.types import StorageSchema
from app.adapters.system.host import SystemUtils
class LocalStorage(StorageBase):
@@ -301,7 +301,7 @@ class LocalStorage(StorageBase):
copied = fsproxy.copy(
src, partial,
progress_cb=progress_callback,
cancel_cb=lambda: global_vars.is_transfer_stopped(src.as_posix()),
cancel_cb=lambda: runtime_stop_state.consume_transfer_stop(src.as_posix()),
chunk_size=self.chunk_size,
)
if not copied:
@@ -352,7 +352,7 @@ class LocalStorage(StorageBase):
try:
with open(src, "rb") as fsrc, open(dest, "wb") as fdst:
while True:
if global_vars.is_transfer_stopped(src.as_posix()):
if runtime_stop_state.consume_transfer_stop(src.as_posix()):
logger.info(f"【本地】{src} 复制已取消!")
return False
buf = fsrc.read(self.chunk_size)
+5 -5
View File
@@ -4,19 +4,19 @@ import threading
import time
from collections import OrderedDict
from pathlib import Path
from typing import Optional, List, Union
from typing import List, Optional, Union
from app.runtime.settings import RuntimeSettingsCompat
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.system.host import SystemUtils
from app.foundation import temporal as time_tools
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.schemas.exception import StorageQueryError
from app.schemas.types import StorageSchema
from app.foundation import temporal as time_tools
from app.adapters.system.host import SystemUtils
_MAX_FOLDER_LOCKS = 4096
_folder_locks: OrderedDict[str, threading.Lock] = OrderedDict()
+7 -7
View File
@@ -7,22 +7,22 @@ from typing import List, Optional, Union
import smbclient
from smbclient import ClientConfig, register_session, reset_connection_cache
from smbprotocol.exceptions import (
SMBAuthenticationError,
SMBException,
SMBResponseException,
SMBAuthenticationError,
)
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.config import global_vars
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.foundation.singleton import WeakSingleton
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.schemas.exception import StorageQueryError
from app.schemas.types import StorageSchema
from app.foundation.singleton import WeakSingleton
lock = threading.Lock()
@@ -572,7 +572,7 @@ class SMB(StorageBase, metaclass=WeakSingleton):
with open(local_path, "wb") as dst_file:
downloaded_size = 0
while True:
if global_vars.is_transfer_stopped(fileitem.path):
if runtime_stop_state.consume_transfer_stop(fileitem.path):
logger.info(f"【SMB】{fileitem.path} 下载已取消!")
return None
chunk = src_file.read(self.chunk_size)
@@ -622,7 +622,7 @@ class SMB(StorageBase, metaclass=WeakSingleton):
with smbclient.open_file(smb_path, mode="wb") as dst_file:
uploaded_size = 0
while True:
if global_vars.is_transfer_stopped(path.as_posix()):
if runtime_stop_state.consume_transfer_stop(path.as_posix()):
logger.info(f"【SMB】{path} 上传已取消!")
return None
chunk = src_file.read(self.chunk_size)
+11 -12
View File
@@ -1,31 +1,30 @@
import base64
import secrets
import time
from hashlib import sha256
from pathlib import Path
from threading import Lock
from typing import List, Optional, Tuple, Union
from hashlib import sha256
import oss2
import httpx
import oss2
from cryptography.hazmat.primitives import hashes
from oss2 import SizedFileAdapter, determine_part_size
from oss2.models import PartInfo
from cryptography.hazmat.primitives import hashes
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.config import global_vars
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.foundation import size as size_tools
from app.foundation.singleton import WeakSingleton
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.runtime.rate import QpsRateLimiter, RateStats
from app.schemas.exception import StorageQueryError
from app.schemas.types import StorageSchema
from app.foundation.singleton import WeakSingleton
from app.foundation import size as size_tools
from app.runtime.rate import QpsRateLimiter, RateStats
lock = Lock()
@@ -778,7 +777,7 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
part_number = 1
offset = 0
while offset < file_size:
if global_vars.is_transfer_stopped(local_path.as_posix()):
if runtime_stop_state.consume_transfer_stop(local_path.as_posix()):
logger.info(f"【115】{local_path} 上传已取消!")
return None
num_to_upload = min(part_size, file_size - offset)
@@ -929,7 +928,7 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
with open(local_path, "wb") as f:
for chunk in r.iter_bytes(chunk_size=self.chunk_size):
if global_vars.is_transfer_stopped(fileitem.path):
if runtime_stop_state.consume_transfer_stop(fileitem.path):
logger.info(f"【115】{fileitem.path} 下载已取消!")
r.close()
return None
+30 -28
View File
@@ -9,31 +9,32 @@ import sys
import threading
from asyncio import AbstractEventLoop
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Type, Union, get_origin, get_args
from typing import Any, Dict, List, Optional, Tuple, Type, Union, get_args, get_origin
from urllib.parse import quote, urlencode, urlparse
from dotenv import set_key, unset_key
from pydantic import BaseModel, Field, ConfigDict, model_validator
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from app.foundation.environment import is_free_threaded_runtime
from app.runtime.log import (
LogConfigModel,
configure_log_settings,
configure_log_writer,
logger,
log_settings,
NonBlockingFileHandler,
)
from app.schemas.types import MediaType
from app.foundation.environment import (
cpu_arch,
get_env_path,
is_docker,
is_free_threaded_runtime,
is_frozen,
)
from app.foundation.url import UrlUtils
from app.runtime.log import (
LogConfigModel,
NonBlockingFileHandler,
configure_log_settings,
configure_log_writer,
log_settings,
logger,
)
from app.runtime.stop import runtime_stop_state
from app.runtime.version import get_app_version
from app.schemas.types import MediaType
class SystemConfModel(BaseModel):
@@ -1374,7 +1375,6 @@ class GlobalVar(object):
"""
# 系统停止事件
STOP_EVENT: threading.Event = threading.Event()
# webpush订阅
SUBSCRIPTIONS: List[dict] = []
# webpush订阅读写锁
@@ -1391,18 +1391,28 @@ class GlobalVar(object):
self._event_loop_owners: dict[object, AbstractEventLoop] = {}
self._event_loop_owner_lock = threading.Lock()
@property
def STOP_EVENT(self) -> threading.Event:
"""兼容旧代码读取进程停止事件。"""
return runtime_stop_state.system_event
@STOP_EVENT.setter
def STOP_EVENT(self, event: threading.Event) -> None:
"""兼容旧测试替换事件,同时保持新 StopState 为唯一状态源。"""
runtime_stop_state.replace_system_event(event)
def stop_system(self):
"""
停止系统
"""
self.STOP_EVENT.set()
runtime_stop_state.stop_system()
@property
def is_system_stopped(self):
"""
是否停止
"""
return self.STOP_EVENT.is_set()
return runtime_stop_state.is_system_stopped
def get_subscriptions(self):
"""
@@ -1444,39 +1454,31 @@ class GlobalVar(object):
"""
停止工作流
"""
if workflow_id not in self.EMERGENCY_STOP_WORKFLOWS:
self.EMERGENCY_STOP_WORKFLOWS.append(workflow_id)
runtime_stop_state.stop_workflow(workflow_id)
def workflow_resume(self, workflow_id: int):
"""
恢复工作流
"""
if workflow_id in self.EMERGENCY_STOP_WORKFLOWS:
self.EMERGENCY_STOP_WORKFLOWS.remove(workflow_id)
runtime_stop_state.resume_workflow(workflow_id)
def is_workflow_stopped(self, workflow_id: int) -> bool:
"""
是否停止工作流
"""
return self.is_system_stopped or workflow_id in self.EMERGENCY_STOP_WORKFLOWS
return runtime_stop_state.is_workflow_stopped(workflow_id)
def stop_transfer(self, path: str):
"""
停止文件整理
"""
if path not in self.EMERGENCY_STOP_TRANSFER:
self.EMERGENCY_STOP_TRANSFER.append(path)
runtime_stop_state.stop_transfer(path)
def is_transfer_stopped(self, path: str) -> bool:
"""
是否停止文件整理
"""
if self.is_system_stopped:
return True
if path in self.EMERGENCY_STOP_TRANSFER:
self.EMERGENCY_STOP_TRANSFER.remove(path)
return True
return False
return runtime_stop_state.consume_transfer_stop(path)
@property
def loop(self) -> AbstractEventLoop:
+112
View File
@@ -0,0 +1,112 @@
"""进程停止与细粒度取消信号契约。"""
from __future__ import annotations
import threading
from typing import Protocol
class StopState(Protocol):
"""向运行时消费者暴露系统、工作流和整理任务的停止状态。"""
@property
def is_system_stopped(self) -> bool:
"""返回系统是否已经进入停止阶段。"""
...
def stop_system(self) -> None:
"""发布不可逆的当前进程停止信号。"""
...
def stop_workflow(self, workflow_id: int) -> None:
"""请求停止指定工作流。"""
...
def resume_workflow(self, workflow_id: int) -> None:
"""清除指定工作流的停止请求。"""
...
def is_workflow_stopped(self, workflow_id: int) -> bool:
"""返回系统或指定工作流是否已经停止。"""
...
def stop_transfer(self, path: str) -> None:
"""登记指定源路径的一次性整理停止请求。"""
...
def consume_transfer_stop(self, path: str) -> bool:
"""消费指定路径的停止请求;系统停止时始终返回真。"""
...
class ProcessStopState:
"""线程安全地持有当前进程的停止与细粒度取消状态。"""
def __init__(self) -> None:
self._system_event = threading.Event()
self._workflow_ids: set[int] = set()
self._transfer_paths: set[str] = set()
self._lock = threading.Lock()
@property
def system_event(self) -> threading.Event:
"""返回兼容入口使用的系统停止事件。"""
return self._system_event
def replace_system_event(self, event: threading.Event) -> None:
"""替换系统事件,仅供旧 ABI 与隔离测试继续使用。"""
self._system_event = event
@property
def is_system_stopped(self) -> bool:
"""返回系统是否已经进入停止阶段。"""
return self._system_event.is_set()
def stop_system(self) -> None:
"""发布不可逆的当前进程停止信号。"""
self._system_event.set()
def stop_workflow(self, workflow_id: int) -> None:
"""请求停止指定工作流。"""
with self._lock:
self._workflow_ids.add(workflow_id)
def resume_workflow(self, workflow_id: int) -> None:
"""清除指定工作流的停止请求。"""
with self._lock:
self._workflow_ids.discard(workflow_id)
def is_workflow_stopped(self, workflow_id: int) -> bool:
"""返回系统或指定工作流是否已经停止。"""
if self.is_system_stopped:
return True
with self._lock:
return workflow_id in self._workflow_ids
def stop_transfer(self, path: str) -> None:
"""登记指定源路径的一次性整理停止请求。"""
with self._lock:
self._transfer_paths.add(path)
def consume_transfer_stop(self, path: str) -> bool:
"""消费指定路径的停止请求;系统停止时始终返回真。"""
if self.is_system_stopped:
return True
with self._lock:
if path not in self._transfer_paths:
return False
self._transfer_paths.remove(path)
return True
def workflow_stop_ids(self) -> list[int]:
"""返回旧诊断入口需要的工作流停止快照。"""
with self._lock:
return list(self._workflow_ids)
def transfer_stop_paths(self) -> list[str]:
"""返回旧诊断入口需要的整理停止快照。"""
with self._lock:
return list(self._transfer_paths)
runtime_stop_state = ProcessStopState()
+34 -33
View File
@@ -9,16 +9,30 @@ import time
import traceback
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Callable, Optional, Dict, Any, List
from typing import Any, Callable, Dict, List, Optional
import pytz
from apscheduler.executors.pool import ThreadPoolExecutor
from apscheduler.jobstores.base import JobLookupError
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.schemas.dashboard import ScheduleInfo as _SchemaScheduleInfo
from app.schemas.dashboard import ScheduleProgress as _SchemaScheduleProgress
from app.schemas.system import MediaServerConf as _SchemaMediaServerConf
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.system.update import system_update_manager
from app.application.agentdata import get_agent_task_port
from app.application.configuration import (
SchedulerRuntimeConfig,
get_configured_system_config,
get_scheduler_runtime_config,
)
from app.application.database import get_database_governance
from app.application.image import WallpaperHelper
from app.application.mediaserver import get_mediaserver_configs
from app.application.messaging.message import MessageHelper
from app.application.outbox import dispatch_pending_outbox
from app.application.plugin.routes import register_plugin_api
from app.application.plugin.runtime import get_plugin_manager
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.chain import ChainBase
from app.chain.mediaserver import MediaServerChain
from app.chain.recommend import RecommendChain
@@ -26,36 +40,23 @@ from app.chain.site import SiteChain
from app.chain.subscribe import SubscribeChain
from app.chain.transfer import TransferChain
from app.chain.workflow import WorkflowChain
from app.runtime.config import global_vars
from app.runtime.events import Event, eventmanager
from app.application.agentdata import get_agent_task_port
from app.application.database import get_database_governance
from app.application.outbox import dispatch_pending_outbox
from app.application.plugin.runtime import get_plugin_manager
from app.application.plugin.routes import register_plugin_api
from app.application.configuration import (
SchedulerRuntimeConfig,
get_configured_system_config,
get_scheduler_runtime_config,
)
from app.application.image import WallpaperHelper
from app.application.mediaserver import get_mediaserver_configs
from app.application.messaging.message import MessageHelper
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.system.update import system_update_manager
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.runtime.log import logger
from app.schemas.message import Message
from app.schemas.message import MessageType
from app.schemas.workflow import Workflow
from app.schemas.types import EventType, SystemConfigKey
from app.runtime.gc import get_memory_usage
from app.runtime.reload import ConfigReloadMixin
from app.foundation.singleton import SingletonClass
from app.runtime.scheduling import TimerUtils
from app.runtime.config import global_vars
from app.runtime.correlation import call_with_correlation, get_correlation_id
from app.runtime.events import Event, eventmanager
from app.runtime.gc import get_memory_usage
from app.runtime.log import logger
from app.runtime.observability import record_metric
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
from app.runtime.reload import ConfigReloadMixin
from app.runtime.scheduling import TimerUtils
from app.runtime.stop import runtime_stop_state
from app.schemas.dashboard import ScheduleInfo as _SchemaScheduleInfo
from app.schemas.dashboard import ScheduleProgress as _SchemaScheduleProgress
from app.schemas.message import Message, MessageType
from app.schemas.system import MediaServerConf as _SchemaMediaServerConf
from app.schemas.types import EventType, SystemConfigKey
from app.schemas.workflow import Workflow
lock = threading.Lock()
SCHEDULER_PROGRESS_PREFIX = "scheduler"
@@ -1966,7 +1967,7 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
"""停止旧计划的提交入口,保留已开始任务直到其自然完成。"""
with self._lock:
if (
global_vars.is_system_stopped
runtime_stop_state.is_system_stopped
or self._lifecycle_state in {"stopping", "reloading"}
):
return False, None
@@ -2066,7 +2067,7 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
if self._auth_count > __max_try__:
if not self._auth_message:
SchedulerChain().messagehelper.put(
title=f"用户认证失败",
title="用户认证失败",
message="用户认证失败次数过多,将不再尝试认证!",
role="system",
)
+3 -4
View File
@@ -4,15 +4,13 @@ from collections.abc import AsyncGenerator, Callable, Generator
from dataclasses import dataclass, field
from typing import Protocol
from app.runtime.tasks import TaskRegistry
from app.application.configuration import RuntimeConfiguration, RuntimeSettingsService
from app.application.messaging.chat import (
AsyncAgentChatRepository,
AgentChatPersistenceService,
AsyncAgentChatRepository,
AsyncUnitOfWork,
)
from app.application.outbox import AsyncOutboxTransaction
from app.application.configuration import RuntimeConfiguration, RuntimeSettingsService
from app.application.subscription.delete import SubscribeDeletionRepository
from app.application.subscription.identity import SubscribeIdentityDeletionRepository
from app.application.subscription.mutation import (
@@ -20,6 +18,7 @@ from app.application.subscription.mutation import (
SubscriptionMutationRepository,
)
from app.application.workflow import WorkflowCachePort
from app.runtime.tasks import TaskRegistry
class AgentChatRepositoryFactory(Protocol):
+9 -4
View File
@@ -1,15 +1,19 @@
from typing import Any
from app.agent.llm.gateway import register_llm_provider_runtime
from app.agent.runtime_loader import (
activate_agent_service,
begin_agent_shutdown,
close_materialized_terminal_sessions,
get_agent_manager as get_runtime_agent_manager,
get_running_agent_manager as get_runtime_running_agent_manager,
is_tool_factory_materialized,
reconcile_agent_service,
)
from app.agent.llm.gateway import register_llm_provider_runtime
from app.agent.runtime_loader import (
get_agent_manager as get_runtime_agent_manager,
)
from app.agent.runtime_loader import (
get_running_agent_manager as get_runtime_running_agent_manager,
)
from app.application.agent import register_agent_service_providers
from app.application.messaging.skill import register_skill_catalog_provider
from app.runtime.settings import RuntimeSettingsCompat
@@ -19,7 +23,6 @@ from app.runtime.events import Event, eventmanager
from app.runtime.log import logger
from app.schemas.types import EventType
AGENT_BLOCKING_EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS = 10.0
@@ -226,6 +229,8 @@ async def stop_agent() -> bool:
if is_tool_factory_materialized():
from app.agent.tools.base import (
begin_blocking_executor_shutdown,
)
from app.agent.tools.base import (
close_blocking_executors as close_executors,
)
+1 -1
View File
@@ -1,6 +1,6 @@
import traceback
from collections.abc import Callable
from configparser import ConfigParser as _ConfigParser
import traceback
from alembic.command import upgrade
from alembic.config import Config
+1 -1
View File
@@ -1,3 +1,4 @@
from app.adapters.system import rust as rust_accelerator
from app.domain.context import configure_tmdb_image_url_builder
from app.domain.media import configure_search_source_provider
from app.domain.meta.customization import configure_customization_provider
@@ -5,7 +6,6 @@ from app.domain.meta.releasegroup import configure_release_groups_provider
from app.domain.meta.runtime import configure_recognition_runtime
from app.domain.meta.words import configure_custom_words_provider
from app.domain.metainfo import clear_rust_parse_options_cache
from app.adapters.system import rust as rust_accelerator
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
@@ -17,7 +17,6 @@ from app.runtime.managed_resources import (
configure_managed_resource_runtime,
)
_runtime_lock = threading.RLock()
_managed_resource_runtime: Optional[CapabilityRuntime] = None
+104 -105
View File
@@ -3,7 +3,7 @@ import inspect
import sys
from typing import Callable
from app.adapters.cache.redis import RedisHelper, AsyncRedisHelper
from app.adapters.cache.redis import AsyncRedisHelper, RedisHelper
from app.chain.mediaserver import MediaServerChain
from app.chain.tmdb import TmdbChain
@@ -17,53 +17,57 @@ except ImportError as e:
sys.exit(1)
from app.adapters.system.host import SystemUtils
from app.runtime.config import settings as legacy_settings
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.config import settings as legacy_settings
from app.runtime.stop import runtime_stop_state
settings = RuntimeSettingsCompat()
from app.runtime.cache import AsyncFileCache, FileCache
from app.runtime.extensions.module_manager import ModuleManager
from app.runtime.extensions.module.dispatcher import ModuleInvocationDispatcher
from app.runtime.extensions.plugin_manager import PluginManager
from app.runtime.events import EventHandlerBinding, EventManager
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.observability import record_metric
from app.runtime.state import SystemHelper
from app.runtime.settings import configure_runtime_setting_provider
from app.runtime.thread import ThreadHelper
from app.adapters.external.server import (
MoviePilotServerHelper,
configure_server_application_services,
)
from app.adapters.network.doh import DohHelper
from app.adapters.system.resource import (
ResourceHelper,
configure_resource_version_provider,
)
from app.application.messaging.message import (
MessageHelper,
MessageQueueManager,
stop_message,
from app.adapters.web.security.access import set_superuser_token_payload_provider
from app.api.data import ApiDataPorts, configure_api_data_runtime
from app.application.agentdata import configure_agent_data_ports
from app.application.agenttask import (
AgentTaskExecutionService,
configure_agent_task_execution,
)
from app.application.chain.context import (
ChainRuntimeContext,
configure_chain_runtime_context_provider,
)
from app.application.chain.data import configure_chain_data_ports, get_chain_data_ports
from app.application.chain.durable_events import (
restore_download_added,
restore_transfer_result,
)
from app.application.configuration import (
RuntimeConfiguration,
RuntimeSettingsService,
SystemConfigService,
get_configured_system_config,
TransferRetryConfig,
configure_token_runtime_config,
configure_runtime_configuration,
configure_runtime_settings,
configure_system_config,
configure_token_runtime_config,
configure_transfer_retry_config,
)
from app.startup.composition.configuration import (
build_api_runtime_config,
build_chain_runtime_config,
build_scheduler_runtime_config,
build_token_runtime_config,
get_configured_system_config,
)
from app.application.database import configure_database_governance
from app.application.service import configure_service_directory
from app.application.plugin.runtime import configure_plugin_runtime
from app.application.module import configure_module_runtime
from app.application.history import configure_transfer_history_provider
from app.application.image import configure_wallpaper_providers
from app.application.messaging.agent import (
dispatch_web_agent_message_event,
shutdown_web_agent_background_tasks,
wait_web_agent_background_tasks,
)
from app.application.messaging.chat import (
AgentChatPersistenceService,
AgentChatService,
@@ -71,43 +75,58 @@ from app.application.messaging.chat import (
configure_agent_chat_service,
get_configured_agent_chat_persistence,
)
from app.application.messaging.agent import (
dispatch_web_agent_message_event,
shutdown_web_agent_background_tasks,
wait_web_agent_background_tasks,
from app.application.messaging.message import (
MessageHelper,
MessageQueueManager,
stop_message,
)
from app.application.security.user import configure_user_lookups
from app.application.security.auth import AuthService, configure_auth_service
from app.application.security.passkeys import PasskeyService, configure_passkey_service
from app.application.security.url import close_image_proxy_block_log_coalescer
from app.application.security.userconfig import (
UserConfigurationService,
configure_user_configuration,
)
from app.application.history import configure_transfer_history_provider
from app.application.module import configure_module_runtime
from app.application.outbox import (
OutboxDispatcher,
configure_outbox_dispatcher,
durable_event_topic,
validate_durable_event_handlers,
)
from app.db.adapters.outbox import SqlAlchemyAsyncOutboxStager, SqlAlchemyOutboxRepository
from app.application.site.query import SiteQueryService, configure_site_query_service
from app.application.site.health import SiteHealthService, configure_site_health_service
from app.application.workflow import WorkflowQueryService, configure_workflow_query
from app.application.agentdata import configure_agent_data_ports
from app.application.agenttask import (
AgentTaskExecutionService,
configure_agent_task_execution,
)
from app.api.data import ApiDataPorts, configure_api_data_runtime
from app.application.subscription.write import configure_subscribe_writer
from app.adapters.external.server import (
MoviePilotServerHelper,
configure_server_application_services,
from app.application.plugin.runtime import configure_plugin_runtime
from app.application.security.auth import AuthService, build_superuser_token_payload, configure_auth_service
from app.application.security.passkeys import PasskeyService, configure_passkey_service
from app.application.security.url import close_image_proxy_block_log_coalescer
from app.application.security.user import configure_user_lookups
from app.application.security.userconfig import (
UserConfigurationService,
configure_user_configuration,
)
from app.application.server.report import ServerReportService
from app.application.server.share import ServerSharingService
from app.application.service import configure_service_directory
from app.application.site.health import SiteHealthService, configure_site_health_service
from app.application.site.query import SiteQueryService, configure_site_query_service
from app.application.subscription.write import configure_subscribe_writer
from app.application.workflow import WorkflowQueryService, configure_workflow_query
from app.command import CommandChain
from app.db.adapters.chain import TransactionalChainDurableEventWriter
from app.db.adapters.download import TransactionalDownloadFailureRepository
from app.db.adapters.outbox import SqlAlchemyAsyncOutboxStager, SqlAlchemyOutboxRepository
from app.db.adapters.site import TransactionalSiteRepository
from app.db.adapters.subscription import TransactionalSubscribeWriter
from app.db.adapters.transaction import TransactionalWriteRunner
from app.db.adapters.workflow import TransactionalWorkflowExecutionService
from app.db.oper.agentchat import AgentChatOper
from app.db.oper.agenttask import AgentTaskOper
from app.db.oper.downloadhistory import DownloadHistoryOper
from app.db.oper.mediaserver import MediaServerOper
from app.db.oper.message import MessageOper
from app.db.oper.passkey import PassKeyOper
from app.db.oper.plugindata import PluginDataOper
from app.db.oper.site import SiteOper
from app.db.oper.subscribe import SubscribeOper
from app.db.oper.subscribehistory import SubscribeHistoryOper
from app.db.oper.systemconfig import SystemConfigOper
from app.db.oper.transferhistory import TransferHistoryOper
from app.db.oper.transferpending import TransferPendingOper
from app.db.oper.user import UserOper
from app.db.oper.userconfig import UserConfigOper
from app.db.oper.workflow import WorkflowOper, configure_workflow_legacy_writer
from app.db.session import (
SessionFactory,
async_session_scope,
@@ -115,47 +134,35 @@ from app.db.session import (
get_async_db,
get_db,
)
from app.db.worker import DatabaseWorker
from app.db.uow import (
SqlAlchemyAsyncUnitOfWork,
SqlAlchemyUnitOfWork,
configure_transaction_runners,
)
from app.db.oper.subscribe import SubscribeOper
from app.db.oper.agentchat import AgentChatOper
from app.db.oper.agenttask import AgentTaskOper
from app.db.oper.user import UserOper
from app.db.oper.passkey import PassKeyOper
from app.db.oper.userconfig import UserConfigOper
from app.db.oper.transferhistory import TransferHistoryOper
from app.db.oper.downloadhistory import DownloadHistoryOper
from app.db.oper.transferpending import TransferPendingOper
from app.db.oper.mediaserver import MediaServerOper
from app.db.oper.site import SiteOper
from app.db.oper.message import MessageOper
from app.db.oper.subscribehistory import SubscribeHistoryOper
from app.db.oper.plugindata import PluginDataOper
from app.db.oper.systemconfig import SystemConfigOper
from app.db.oper.workflow import WorkflowOper, configure_workflow_legacy_writer
from app.command import CommandChain
from app.schemas.message import Message
from app.schemas.message import MessageType
from app.db.worker import DatabaseWorker
from app.runtime.cache import AsyncFileCache, FileCache
from app.runtime.events import EventHandlerBinding, EventManager
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.extensions.module.dispatcher import ModuleInvocationDispatcher
from app.runtime.extensions.module_manager import ModuleManager
from app.runtime.extensions.plugin_manager import PluginManager
from app.runtime.extensions.service_config import (
ServiceConfigHelper,
configure_service_config_reader,
)
from app.runtime.observability import record_metric
from app.runtime.settings import configure_runtime_setting_provider
from app.runtime.state import SystemHelper
from app.runtime.tasks import get_task_registry
from app.runtime.thread import ThreadHelper
from app.schemas.message import Message, MessageType
from app.schemas.types import EventType, SystemConfigKey
from app.startup.initializers.agent import init_agent
from app.startup.composition.database import build_database_governance
from app.startup.initializers.managed_resources import (
init_managed_resources,
stop_managed_resources,
from app.startup.composition.configuration import (
build_api_runtime_config,
build_chain_runtime_config,
build_scheduler_runtime_config,
build_token_runtime_config,
)
from app.db.adapters.subscription import TransactionalSubscribeWriter
from app.startup.composition.subscription import (
configure_transactional_subscription_scopes,
)
from app.db.adapters.chain import TransactionalChainDurableEventWriter
from app.db.adapters.download import TransactionalDownloadFailureRepository
from app.db.adapters.site import TransactionalSiteRepository
from app.db.adapters.workflow import TransactionalWorkflowExecutionService
from app.db.adapters.transaction import TransactionalWriteRunner
from app.startup.composition.context import (
AgentChatRuntime,
AuthenticationRuntime,
@@ -167,24 +174,15 @@ from app.startup.composition.context import (
SubscriptionRuntime,
WorkflowRuntime,
)
from app.adapters.web.security.access import set_superuser_token_payload_provider
from app.application.security.auth import build_superuser_token_payload
from app.application.image import configure_wallpaper_providers
from app.application.chain.context import (
ChainRuntimeContext,
configure_chain_runtime_context_provider,
from app.startup.composition.database import build_database_governance
from app.startup.composition.subscription import (
configure_transactional_subscription_scopes,
)
from app.application.chain.durable_events import (
restore_download_added,
restore_transfer_result,
from app.startup.initializers.agent import init_agent
from app.startup.initializers.managed_resources import (
init_managed_resources,
stop_managed_resources,
)
from app.application.chain.data import configure_chain_data_ports, get_chain_data_ports
from app.runtime.extensions.service_config import (
ServiceConfigHelper,
configure_service_config_reader,
)
from app.runtime.tasks import get_task_registry
_database_worker: DatabaseWorker | None = None
@@ -252,6 +250,7 @@ def _build_chain_runtime_context() -> ChainRuntimeContext:
configuration=build_chain_runtime_config(settings),
data_ports=get_chain_data_ports(),
durable_event_writer=TransactionalChainDurableEventWriter(SessionFactory),
stop_state=runtime_stop_state,
)
@@ -503,7 +502,7 @@ def stop_frontend():
or not SystemUtils.is_windows():
return
import subprocess
subprocess.Popen(f"taskkill /f /im nginx.exe", shell=True)
subprocess.Popen("taskkill /f /im nginx.exe", shell=True)
def clear_temp():
+29 -29
View File
@@ -1,29 +1,36 @@
from pathlib import Path
from app.application.plugin.routes import register_plugin_api
from app.runtime.compat.diagnostics import (
configure_legacy_import_diagnostics,
scan_plugin_legacy_imports,
)
from app.runtime.compat.resource_imports import scan_plugin_resource_imports
from app.application.plugin.routes import register_plugin_api
from app.runtime.config import global_vars
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.extensions.plugin_manager import (
PluginManager,
configure_plugin_catalog_factory,
configure_plugin_install_reporter,
configure_plugin_legacy_import_services,
configure_plugin_route_refresher,
configure_plugin_resource_import_preparer,
configure_site_auth_level_provider,
from app.adapters.external.market import (
VERSION_BACKWARD_COMPATIBLE_FLAGS,
PluginHelper,
configure_installed_plugins_provider,
)
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.extensions.plugin.dependency import PluginDependencyInstallResult
from app.adapters.external.plugin.client import PluginMarketClient
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.system.host import SystemUtils
from app.adapters.system.plugin.dependency import PluginDependencyInstaller
from app.adapters.system.plugin.manifest import dependency_manifest_status
from app.adapters.system.plugin.package import PluginPackageManager
from app.application.configuration import get_configured_system_config
from app.application.plugin.catalog import PluginCatalogService
from app.application.plugin.data import DeletePluginDataCommand
from app.adapters.external.plugin.client import PluginMarketClient
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.db.oper.plugindata import PluginDataOper
from app.db.session import SessionFactory
from app.db.uow import SqlAlchemyUnitOfWork
from app.foundation.version import compare_version
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.extensions.plugin.dependency import PluginDependencyInstallResult
from app.runtime.extensions.plugin.storage import (
PluginStorage,
configure_plugin_storage,
@@ -32,26 +39,19 @@ from app.runtime.extensions.plugin.system import (
PluginSystemServices,
configure_plugin_system,
)
from app.runtime.managed_resources import acquire_managed_resource
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.external.market import (
PluginHelper,
VERSION_BACKWARD_COMPATIBLE_FLAGS,
configure_installed_plugins_provider,
from app.runtime.extensions.plugin_manager import (
PluginManager,
configure_plugin_catalog_factory,
configure_plugin_install_reporter,
configure_plugin_legacy_import_services,
configure_plugin_resource_import_preparer,
configure_plugin_route_refresher,
configure_site_auth_level_provider,
)
from app.adapters.system.plugin.dependency import PluginDependencyInstaller
from app.adapters.system.plugin.manifest import dependency_manifest_status
from app.adapters.system.plugin.package import PluginPackageManager
from app.adapters.system.host import SystemUtils
from app.db.oper.plugindata import PluginDataOper
from app.application.configuration import get_configured_system_config
from app.db.session import SessionFactory
from app.db.uow import SqlAlchemyUnitOfWork
from app.runtime.log import logger
from app.foundation.version import compare_version
from app.schemas.plugin import PluginRuntimeStatus
from app.runtime.managed_resources import acquire_managed_resource
from app.schemas.exception import PluginMutationRejectedError
from app.schemas.plugin import PluginRuntimeStatus
from app.schemas.types import SystemConfigKey
+1
View File
@@ -1,5 +1,6 @@
from fastapi import FastAPI
def init_routers(app: FastAPI, api_prefix: str = "/api/v1"):
"""
初始化路由
-1
View File
@@ -1,7 +1,6 @@
from app.application.workflow import configure_workflow_runtime
from app.workflow import WorkFlowManager
# 启动模块是 concrete WorkFlowManager 的唯一宿主装配边界。
configure_workflow_runtime(lambda: WorkFlowManager())
+20 -17
View File
@@ -9,6 +9,7 @@ from typing import Awaitable, Callable
from fastapi import FastAPI
from app.startup.initializers.cache import configure_cache_dependencies
# 缓存装饰器会在业务模块导入时创建后端,必须先完成适配器装配。
configure_cache_dependencies()
# urllib3-future 覆盖 urllib3 命名空间后删除了 format_header_param,导致 telebot 崩溃,需在加载模块前打补丁
@@ -24,23 +25,29 @@ try:
except Exception:
pass
from app.chain.system import SystemChain
from app.application.plugin.lifecycle import plugin_lifecycle
from app.application.plugin.runtime import get_plugin_manager
from app.chain.system import SystemChain
from app.foundation.environment import is_free_threaded_runtime, is_gil_enabled
from app.runtime.config import global_vars
from app.runtime.settings import RuntimeSettingsCompat
from app.foundation.environment import is_free_threaded_runtime, is_gil_enabled
from app.runtime.stop import runtime_stop_state
settings = RuntimeSettingsCompat()
from app.runtime.health import get_application_health
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.topology import validate_process_topology
from app.runtime.tasks import TaskRegistry, configure_task_registry
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.network.http import (
aclose_shared_async_transports,
configure_default_user_agent,
)
from app.db.engine import check_connection_budget, get_engine, get_global_async_engine
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.health import get_application_health
from app.runtime.log import LoggerManager, logger
from app.runtime.state import SystemHelper
from app.runtime.log import logger, LoggerManager
from app.startup.initializers.command import init_command, restart_command
from app.runtime.tasks import TaskRegistry, configure_task_registry
from app.runtime.topology import validate_process_topology
from app.startup.initializers.agent import stop_agent
from app.startup.initializers.command import init_command, restart_command
from app.startup.initializers.domain import configure_domain_dependencies
from app.startup.initializers.modules import (
drain_events,
@@ -48,7 +55,7 @@ from app.startup.initializers.modules import (
settle_events,
stop_modules,
)
from app.startup.initializers.monitor import stop_monitor, init_monitor
from app.startup.initializers.monitor import init_monitor, stop_monitor
from app.startup.initializers.plugins import (
configure_plugin_services,
execute_task,
@@ -61,11 +68,10 @@ from app.startup.initializers.plugins import (
)
from app.startup.initializers.routers import init_routers
from app.startup.initializers.scheduler import (
stop_scheduler,
init_scheduler,
init_plugin_scheduler,
init_scheduler,
stop_scheduler,
)
from app.db.engine import check_connection_budget, get_engine, get_global_async_engine
from app.startup.initializers.transfer import (
replay_pending_transfers,
stop_transfer_runtime,
@@ -77,10 +83,6 @@ from app.startup.lifecycle.components import (
LifecycleMode,
lifecycle_manifest,
)
from app.adapters.network.http import (
aclose_shared_async_transports,
configure_default_user_agent,
)
async def init_extra():
@@ -541,7 +543,8 @@ def build_lifecycle_components(app: FastAPI) -> tuple[LifecycleComponent, ...]:
# 停止信号必须先于一切资源释放发出,让工作流、整理等长任务尽早感知停机。
LifecycleComponent(
name="停止信号",
stop=global_vars.stop_system,
# 兼容旧测试与插件;GlobalVar 内部已委托到 StopState。
stop=getattr(global_vars, "stop_system"),
stop_order=4,
stop_timeout_seconds=10,
),
+7 -9
View File
@@ -4,18 +4,16 @@ from typing import Any, Dict, List, Optional, Tuple
from pydantic import BaseModel
from app.runtime.config import global_vars
from app.runtime.events import eventmanager, Event
from app.application.chain.data import get_chain_workflow_port
from app.application.workflow import WorkflowExecutionOwner
from app.foundation.reflection import ModuleHelper
from app.runtime.log import logger
from app.schemas.workflow import ActionContext
from app.schemas.workflow import Action
from app.schemas.workflow import ActionResult
from app.schemas.workflow import Workflow
from app.schemas.types import EventType
from app.foundation.singleton import Singleton
from app.runtime.config import global_vars
from app.runtime.events import Event, eventmanager
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.types import EventType
from app.schemas.workflow import Action, ActionContext, ActionResult, Workflow
_WORKFLOW_STOP_TIMEOUT_SECONDS = 10.0
@@ -283,7 +281,7 @@ class WorkFlowManager(metaclass=Singleton):
def _is_cancelled(workflow_id: int, cancel_token: Optional[Any]) -> bool:
if cancel_token and cancel_token.is_cancelled():
return True
return global_vars.is_workflow_stopped(workflow_id)
return runtime_stop_state.is_workflow_stopped(workflow_id)
def _sleep_with_cancel(self, workflow_id: int, seconds: float, cancel_token: Optional[Any]) -> None:
deadline = monotonic() + seconds
+2 -4
View File
@@ -1,11 +1,9 @@
from abc import ABC, abstractmethod
from typing import Any, ClassVar, Union
from app.chain import ChainBase
from app.application.configuration import get_configured_system_config
from app.schemas.workflow import ActionContext
from app.schemas.workflow import ActionParams
from app.schemas.workflow import ActionResult
from app.chain import ChainBase
from app.schemas.workflow import ActionContext, ActionParams, ActionResult
class ActionChain(ChainBase):
+4 -6
View File
@@ -2,16 +2,14 @@ from typing import Optional
from pydantic import Field
from app.workflow.actions import BaseAction
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.runtime.config import global_vars
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.schemas.workflow import ActionParams
from app.schemas.workflow import ActionContext
from app.schemas.workflow import DownloadTask
from app.runtime.stop import runtime_stop_state
from app.schemas.types import MediaType
from app.schemas.workflow import ActionContext, ActionParams, DownloadTask
from app.workflow.actions import BaseAction
class AddDownloadParams(ActionParams):
@@ -55,7 +53,7 @@ class AddDownloadAction(BaseAction):
params = AddDownloadParams(**params)
_started = False
for t in context.torrents:
if global_vars.is_workflow_stopped(workflow_id):
if runtime_stop_state.is_workflow_stopped(workflow_id):
break
# 检查缓存
cache_key = f"{t.torrent_info.site}-{t.torrent_info.title}"
+7 -8
View File
@@ -1,12 +1,11 @@
from app.workflow.actions import BaseAction
from app.chain.subscribe import SubscribeChain
from app.application.configuration import get_chain_runtime_config_snapshot
from app.runtime.config import global_vars
from app.domain.context import MediaInfo
from app.application.chain.data import get_chain_subscribe_port
from app.application.configuration import get_chain_runtime_config_snapshot
from app.chain.subscribe import SubscribeChain
from app.domain.context import MediaInfo
from app.runtime.log import logger
from app.schemas.workflow import ActionParams
from app.schemas.workflow import ActionContext
from app.runtime.stop import runtime_stop_state
from app.schemas.workflow import ActionContext, ActionParams
from app.workflow.actions import BaseAction
class AddSubscribeParams(ActionParams):
@@ -45,7 +44,7 @@ class AddSubscribeAction(BaseAction):
"""
_started = False
for media in context.medias:
if global_vars.is_workflow_stopped(workflow_id):
if runtime_stop_state.is_workflow_stopped(workflow_id):
break
# 检查缓存
cache_key = f"{media.type}-{media.title}-{media.year}-{media.season}"
+4 -5
View File
@@ -1,8 +1,7 @@
from app.workflow.actions import BaseAction, ActionChain
from app.runtime.config import global_vars
from app.schemas.workflow import ActionParams
from app.schemas.workflow import ActionContext
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.workflow import ActionContext, ActionParams
from app.workflow.actions import ActionChain, BaseAction
class FetchDownloadsParams(ActionParams):
@@ -45,7 +44,7 @@ class FetchDownloadsAction(BaseAction):
return context
for download in self._downloads:
if global_vars.is_workflow_stopped(workflow_id):
if runtime_stop_state.is_workflow_stopped(workflow_id):
break
logger.info(f"获取下载任务 {download.download_id} 状态 ...")
torrents = ActionChain().list_torrents(
+6 -8
View File
@@ -2,18 +2,16 @@ from typing import List, Optional
from pydantic import Field
from app.workflow.actions import BaseAction
from app.chain.recommend import RecommendChain
from app.adapters.network.http import RequestUtils
from app.application.configuration import get_chain_runtime_config_snapshot
from app.schemas.workflow import ActionParams
from app.schemas.workflow import ActionContext
from app.runtime.config import global_vars
from app.chain.recommend import RecommendChain
from app.runtime.events import eventmanager
from app.runtime.log import logger
from app.runtime.stop import runtime_stop_state
from app.schemas.event import RecommendSourceEventData
from app.schemas.workflow import MediaInfo
from app.schemas.types import ChainEventType
from app.adapters.network.http import RequestUtils
from app.schemas.workflow import ActionContext, ActionParams, MediaInfo
from app.workflow.actions import BaseAction
class FetchMediasParams(ActionParams):
@@ -141,7 +139,7 @@ class FetchMediasAction(BaseAction):
try:
if params.source_type == "ranking":
for api_path in params.sources:
if global_vars.is_workflow_stopped(workflow_id):
if runtime_stop_state.is_workflow_stopped(workflow_id):
break
source = self.__get_source(api_path)
if not source:

Some files were not shown because too many files have changed in this diff Show More