mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-31 20:34:48 +08:00
* fix(download): share site limits and preserve frequency errors * test(startup): record shared site throttle module
249 lines
7.6 KiB
Python
249 lines
7.6 KiB
Python
"""
|
||
站点及站点运行数据的只读应用服务。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import builtins
|
||
from typing import Optional
|
||
|
||
from app.application.site.contract import SiteQueryPort
|
||
from app.schemas.site import Site, SiteIconData, SiteStatistic, SiteUserData
|
||
|
||
|
||
class SiteQueryService:
|
||
"""
|
||
把站点持久化快照投影为 API/Chain 可复用的 DTO。
|
||
"""
|
||
|
||
def __init__(self, repository: SiteQueryPort) -> None:
|
||
"""
|
||
保存站点查询仓储端口。
|
||
"""
|
||
self._repository = repository
|
||
|
||
async def list_ordered(
|
||
self,
|
||
*,
|
||
is_active: Optional[bool] = None,
|
||
name: Optional[str] = None,
|
||
site_ids: Optional[builtins.list[int]] = None,
|
||
domains: Optional[builtins.list[str]] = None,
|
||
page: Optional[int] = None,
|
||
count: Optional[int] = None,
|
||
) -> builtins.list[Site]:
|
||
"""
|
||
按筛选、优先级和可选分页窗口返回配置 DTO。
|
||
"""
|
||
if all(
|
||
value is None
|
||
for value in (is_active, name, site_ids, domains, page, count)
|
||
):
|
||
items = await self._repository.async_list_order_by_pri()
|
||
else:
|
||
items = await self._repository.async_list_order_by_pri(
|
||
is_active=is_active,
|
||
name=name,
|
||
site_ids=site_ids,
|
||
domains=domains,
|
||
page=page,
|
||
count=count,
|
||
)
|
||
return [Site.model_validate(item) for item in items]
|
||
|
||
async def count_ordered(
|
||
self,
|
||
*,
|
||
is_active: Optional[bool] = None,
|
||
name: Optional[str] = None,
|
||
site_ids: Optional[builtins.list[int]] = None,
|
||
domains: Optional[builtins.list[str]] = None,
|
||
) -> int:
|
||
"""
|
||
按与站点列表一致的筛选条件返回总数。
|
||
"""
|
||
return await self._repository.async_count_sites(
|
||
is_active=is_active,
|
||
name=name,
|
||
site_ids=site_ids,
|
||
domains=domains,
|
||
)
|
||
|
||
async def list(self) -> builtins.list[Site]:
|
||
"""
|
||
返回全部站点配置 DTO。
|
||
"""
|
||
return [Site.model_validate(item) for item in await self._repository.async_list()]
|
||
|
||
async def get(self, site_id: int) -> Optional[Site]:
|
||
"""
|
||
按 ID 返回站点配置 DTO。
|
||
"""
|
||
item = await self._repository.async_get(site_id)
|
||
return Site.model_validate(item) if item else None
|
||
|
||
def get_sync(self, site_id: int) -> Optional[Site]:
|
||
"""
|
||
同步按 ID 返回站点配置 DTO。
|
||
"""
|
||
item = self._repository.get(site_id)
|
||
return Site.model_validate(item) if item else None
|
||
|
||
async def get_by_domain(self, domain: str) -> Optional[Site]:
|
||
"""
|
||
按域名返回站点配置 DTO。
|
||
"""
|
||
item = await self._repository.async_get_by_domain(domain)
|
||
return Site.model_validate(item) if item else None
|
||
|
||
def get_by_domain_sync(self, domain: str) -> Optional[Site]:
|
||
"""
|
||
同步按规范域名读取站点配置,供独立种子下载匹配流控规则。
|
||
|
||
:param domain: 请求地址提取出的规范站点域名
|
||
:return: 站点配置;没有匹配站点时为 None
|
||
"""
|
||
item = self._repository.get_by_domain(domain)
|
||
return Site.model_validate(item) if item else None
|
||
|
||
async def userdata_latest(
|
||
self,
|
||
*,
|
||
page: Optional[int] = None,
|
||
count: Optional[int] = None,
|
||
) -> builtins.list[SiteUserData]:
|
||
"""
|
||
按可选分页窗口返回各站点最新用户数据 DTO。
|
||
"""
|
||
if page is None and count is None:
|
||
items = await self._repository.async_get_userdata_latest()
|
||
else:
|
||
items = await self._repository.async_get_userdata_latest(
|
||
page=page,
|
||
count=count,
|
||
)
|
||
return [SiteUserData.model_validate(item) for item in items]
|
||
|
||
async def count_userdata_latest(self) -> int:
|
||
"""
|
||
返回各站点最新用户数据查询的结果总数。
|
||
"""
|
||
return await self._repository.async_count_userdata_latest()
|
||
|
||
async def userdata(
|
||
self,
|
||
domain: str,
|
||
workdate: Optional[str] = None,
|
||
*,
|
||
page: Optional[int] = None,
|
||
count: Optional[int] = None,
|
||
) -> builtins.list[SiteUserData]:
|
||
"""
|
||
按可选分页窗口返回指定站点用户数据 DTO。
|
||
"""
|
||
if page is None and count is None:
|
||
items = await self._repository.async_get_userdata_by_domain(
|
||
domain,
|
||
workdate,
|
||
)
|
||
else:
|
||
items = await self._repository.async_get_userdata_by_domain(
|
||
domain,
|
||
workdate,
|
||
page=page,
|
||
count=count,
|
||
)
|
||
return [
|
||
SiteUserData.model_validate(item)
|
||
for item in items
|
||
]
|
||
|
||
async def count_userdata(
|
||
self,
|
||
domain: str,
|
||
workdate: Optional[str] = None,
|
||
) -> int:
|
||
"""
|
||
返回指定站点和日期的用户数据总数。
|
||
"""
|
||
return await self._repository.async_count_userdata_by_domain(
|
||
domain,
|
||
workdate,
|
||
)
|
||
|
||
async def icon(self, domain: str) -> Optional[SiteIconData]:
|
||
"""
|
||
返回站点图标 DTO。
|
||
"""
|
||
item = await self._repository.async_get_icon_by_domain(domain)
|
||
if not item:
|
||
return None
|
||
return SiteIconData(
|
||
icon=item.base64 if item.base64 else item.url,
|
||
)
|
||
|
||
async def statistic(self, domain: str) -> SiteStatistic:
|
||
"""
|
||
返回指定站点统计 DTO,未命中时返回空统计。
|
||
"""
|
||
item = await self._repository.async_get_statistic_by_domain(domain)
|
||
return SiteStatistic.model_validate(item) if item else SiteStatistic(domain=domain)
|
||
|
||
async def statistics(
|
||
self,
|
||
*,
|
||
page: Optional[int] = None,
|
||
count: Optional[int] = None,
|
||
) -> builtins.list[SiteStatistic]:
|
||
"""
|
||
按可选分页窗口返回站点统计 DTO。
|
||
"""
|
||
if page is None and count is None:
|
||
items = await self._repository.async_list_statistics()
|
||
else:
|
||
items = await self._repository.async_list_statistics(
|
||
page=page,
|
||
count=count,
|
||
)
|
||
return [SiteStatistic.model_validate(item) for item in items]
|
||
|
||
async def count_statistics(self) -> int:
|
||
"""
|
||
返回站点统计记录总数。
|
||
"""
|
||
return await self._repository.async_count_statistics()
|
||
|
||
def userdata_latest_sync(self) -> builtins.list[SiteUserData]:
|
||
"""
|
||
同步返回各站点最新用户数据 DTO。
|
||
"""
|
||
return [SiteUserData.model_validate(item) for item in self._repository.get_userdata_latest()]
|
||
|
||
|
||
_configured_site_query_service: SiteQueryService | None = None
|
||
|
||
|
||
def configure_site_query_service(service: SiteQueryService) -> None:
|
||
"""
|
||
由启动组合根登记站点查询服务。
|
||
"""
|
||
global _configured_site_query_service
|
||
_configured_site_query_service = service
|
||
|
||
|
||
def reset_site_query_service() -> None:
|
||
"""
|
||
清除当前 lifespan 的站点查询服务。
|
||
"""
|
||
global _configured_site_query_service
|
||
_configured_site_query_service = None
|
||
|
||
|
||
def get_configured_site_query_service() -> SiteQueryService:
|
||
"""
|
||
返回启动阶段登记的站点查询服务。
|
||
"""
|
||
if _configured_site_query_service is None:
|
||
raise RuntimeError("站点查询服务尚未配置")
|
||
return _configured_site_query_service
|