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