mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-02 21:34:32 +08:00
feat(widget): 显示下载上传速度和剩余磁盘空间 (#7177)
This commit is contained in:
86
app/api/dependencies/dashboard.py
Normal file
86
app/api/dependencies/dashboard.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
为仪表盘和受限小组件装配同一下载器与本地目录采样口径。
|
||||
"""
|
||||
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.chain.dashboard import DashboardChain
|
||||
from app.schemas.dashboard import DownloaderInfo
|
||||
|
||||
|
||||
def _download_free_space(*, btrfs_fsid_dedup: bool, require_source: bool) -> float | None:
|
||||
"""
|
||||
复用目录空间去重,严格读取不把未配置或不完整的容量误报为零。
|
||||
|
||||
:param btrfs_fsid_dedup: 原仪表盘使用的文件系统去重配置
|
||||
:param require_source: 小组件需要可确认的完整本地目录来源
|
||||
:return: 可确认的剩余字节数;严格模式的未知容量为 None
|
||||
:raises OSError: 普通仪表盘保留原有的空间读取异常
|
||||
"""
|
||||
try:
|
||||
directories = DirectoryHelper().get_local_download_dirs()
|
||||
paths = [Path(directory.download_path) for directory in directories if directory.download_path]
|
||||
if require_source and (not paths or not all(path.exists() for path in paths)):
|
||||
return None
|
||||
total, available = SystemUtils.space_usage(paths, btrfs_fsid_dedup=btrfs_fsid_dedup)
|
||||
if require_source and (
|
||||
not math.isfinite(total) or not math.isfinite(available) or not 0 <= available <= total or total <= 0
|
||||
):
|
||||
return None
|
||||
return available
|
||||
except OSError:
|
||||
if require_source:
|
||||
return None
|
||||
raise
|
||||
|
||||
|
||||
def _check_speeds(info: DownloaderInfo) -> None:
|
||||
"""
|
||||
拒绝缺失、负数和非有限的原始速度,真实零速率仍是有效采样。
|
||||
|
||||
:param info: 单个下载器或汇总后的原始字节统计
|
||||
:raises ValueError: 速率来源不能代表本次真实采样
|
||||
"""
|
||||
if any(
|
||||
value is None or not math.isfinite(value) or value < 0 for value in (info.download_speed, info.upload_speed)
|
||||
):
|
||||
raise ValueError("下载器速度来源暂不可用")
|
||||
|
||||
|
||||
def sample_downloader(
|
||||
name: str | None = None,
|
||||
*,
|
||||
btrfs_fsid_dedup: bool = False,
|
||||
require_source: bool = False,
|
||||
) -> DownloaderInfo:
|
||||
"""
|
||||
汇总原下载器的字节速率和累计量,包含下载列表之外的做种上传。
|
||||
|
||||
:param name: 原仪表盘可选择的下载器,None 表示全部
|
||||
:param btrfs_fsid_dedup: 本次请求的原目录空间去重配置
|
||||
:param require_source: 仅小组件严格拒绝缺失或非法速度;普通页面保留零值兼容
|
||||
:return: 共用口径的统计,严格模式的未知磁盘容量为 None
|
||||
:raises ValueError: 严格模式下缺少真实下载器采样或速度非法
|
||||
"""
|
||||
free_space = _download_free_space(btrfs_fsid_dedup=btrfs_fsid_dedup, require_source=require_source)
|
||||
transfer_infos = DashboardChain().downloader_info(name)
|
||||
if not transfer_infos:
|
||||
if require_source:
|
||||
raise ValueError("下载器统计来源暂不可用")
|
||||
return DownloaderInfo()
|
||||
if require_source:
|
||||
for info in transfer_infos:
|
||||
_check_speeds(info)
|
||||
result = DownloaderInfo(
|
||||
download_speed=sum(info.download_speed or 0 for info in transfer_infos),
|
||||
upload_speed=sum(info.upload_speed or 0 for info in transfer_infos),
|
||||
download_size=sum(info.download_size or 0 for info in transfer_infos),
|
||||
upload_size=sum(info.upload_size or 0 for info in transfer_infos),
|
||||
free_space=free_space,
|
||||
)
|
||||
if require_source:
|
||||
_check_speeds(result)
|
||||
return result
|
||||
@@ -8,8 +8,10 @@ from typing import NoReturn
|
||||
from fastapi import Depends, HTTPException, Response
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from app.api.context import get_host_runtime
|
||||
from app.api.context import get_api_runtime_config, get_host_runtime
|
||||
from app.api.dependencies.dashboard import sample_downloader
|
||||
from app.api.dependencies.history import get_dashboard_query_service, get_history_query_service
|
||||
from app.application.configuration import ApiRuntimeConfig
|
||||
from app.application.dashboard import DashboardQueryService
|
||||
from app.application.history import HistoryQueryService
|
||||
from app.application.query import get_configured_data_query_service
|
||||
@@ -74,12 +76,14 @@ def get_widget_grant(
|
||||
def get_widget_snapshot_service(
|
||||
dashboard: DashboardQueryService = Depends(get_dashboard_query_service),
|
||||
history: HistoryQueryService = Depends(get_history_query_service),
|
||||
runtime_config: ApiRuntimeConfig = Depends(get_api_runtime_config),
|
||||
) -> WidgetSnapshotService:
|
||||
"""
|
||||
只连接现有的查询与业务链,普通页面不会调用小组件专用接口。
|
||||
|
||||
:param dashboard: 原仪表板管理员查询服务
|
||||
:param history: 原历史列表查询服务
|
||||
:param runtime_config: 本次请求的空间去重配置,不从组件凭据或参数读取
|
||||
:return: 使用原有 owner、计数和成功筛选的快照投影服务
|
||||
"""
|
||||
return WidgetSnapshotService(
|
||||
@@ -87,5 +91,6 @@ def get_widget_snapshot_service(
|
||||
history=history,
|
||||
dashboard=dashboard,
|
||||
downloads=partial(DownloadChain().downloading, require_source=True),
|
||||
downloader=partial(sample_downloader, btrfs_fsid_dedup=runtime_config.btrfs_fsid_dedup, require_source=True),
|
||||
storage=partial(DashboardChain().storage, require_source=True),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
汇总仪表盘所需的服务统计、系统状态与活动数据。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any, List, Optional
|
||||
|
||||
from fastapi import Depends
|
||||
@@ -11,6 +10,7 @@ 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.dashboard import sample_downloader
|
||||
from app.api.dependencies.history import get_dashboard_query_service
|
||||
from app.api.response import (
|
||||
CompatibleCountParam,
|
||||
@@ -19,7 +19,6 @@ from app.api.response import (
|
||||
)
|
||||
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.runtime.execution import run_in_threadpool
|
||||
@@ -49,25 +48,13 @@ def _build_downloader(
|
||||
btrfs_fsid_dedup: bool = False,
|
||||
) -> _SchemaDownloaderInfo:
|
||||
"""
|
||||
构建下载器统计信息。
|
||||
使用共用采样口径构建下载器统计,保持普通页面的宽容来源语义。
|
||||
|
||||
:param name: 下载器名称,None 表示全部
|
||||
:param btrfs_fsid_dedup: 当前请求的 Btrfs 文件系统去重配置
|
||||
:return: 原仪表盘的全局字节统计与本地目录可用容量
|
||||
"""
|
||||
# 下载目录空间
|
||||
download_dirs = DirectoryHelper().get_local_download_dirs()
|
||||
_, free_space = SystemUtils.space_usage(
|
||||
[Path(d.download_path) for d in download_dirs if d.download_path],
|
||||
btrfs_fsid_dedup=btrfs_fsid_dedup,
|
||||
)
|
||||
# 下载器信息
|
||||
downloader_info = _SchemaDownloaderInfo()
|
||||
transfer_infos = DashboardChain().downloader_info(name)
|
||||
if transfer_infos:
|
||||
for transfer_info in transfer_infos:
|
||||
downloader_info.download_speed = (downloader_info.download_speed or 0) + (transfer_info.download_speed or 0)
|
||||
downloader_info.upload_speed = (downloader_info.upload_speed or 0) + (transfer_info.upload_speed or 0)
|
||||
downloader_info.download_size = (downloader_info.download_size or 0) + (transfer_info.download_size or 0)
|
||||
downloader_info.upload_size = (downloader_info.upload_size or 0) + (transfer_info.upload_size or 0)
|
||||
downloader_info.free_space = free_space
|
||||
return downloader_info
|
||||
return sample_downloader(name, btrfs_fsid_dedup=btrfs_fsid_dedup)
|
||||
|
||||
|
||||
@router.get("/statistic", summary="媒体数量统计", response_model=_SchemaStatistic)
|
||||
|
||||
Reference in New Issue
Block a user