mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-16 11:33:59 +08:00
375 lines
14 KiB
Python
375 lines
14 KiB
Python
import time
|
||
from typing import Any, List, Optional
|
||
|
||
from app.domain.context import MediaInfo
|
||
from app.domain.meta.metabase import MetaBase
|
||
from app.domain.meta.metamusic import MetaMusic
|
||
from app.db import DbOper
|
||
from app.db.models.transferhistory import TransferHistory
|
||
from app.schemas import TransferInfo, FileItem
|
||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource
|
||
from app.domain.media import normalize_media_identity_payload, resolve_media_identity
|
||
|
||
|
||
class TransferHistoryOper(DbOper):
|
||
"""
|
||
转移历史管理
|
||
"""
|
||
|
||
def get(self, historyid: int) -> TransferHistory:
|
||
"""
|
||
获取转移历史
|
||
:param historyid: 转移历史id
|
||
"""
|
||
return TransferHistory.get(self._db, historyid)
|
||
|
||
async def async_get(self, historyid: int) -> TransferHistory:
|
||
"""
|
||
异步获取转移历史。
|
||
"""
|
||
return await TransferHistory.async_get(self._db, historyid)
|
||
|
||
async def async_list_by_title(
|
||
self,
|
||
title: str,
|
||
page: Optional[int] = 1,
|
||
count: Optional[int] = 30,
|
||
status: Optional[bool] = None,
|
||
) -> List[TransferHistory]:
|
||
"""
|
||
异步按标题分页查询转移记录。
|
||
"""
|
||
return await TransferHistory.async_list_by_title(
|
||
self._db, title=title, page=page, count=count, status=status
|
||
)
|
||
|
||
async def async_list_by_page(
|
||
self,
|
||
page: Optional[int] = 1,
|
||
count: Optional[int] = 30,
|
||
status: Optional[bool] = None,
|
||
) -> List[TransferHistory]:
|
||
"""
|
||
异步分页查询转移记录。
|
||
"""
|
||
return await TransferHistory.async_list_by_page(
|
||
self._db, page=page, count=count, status=status
|
||
)
|
||
|
||
async def async_count(self, status: Optional[bool] = None) -> int:
|
||
"""
|
||
异步统计转移记录数量。
|
||
"""
|
||
return await TransferHistory.async_count(self._db, status=status)
|
||
|
||
async def async_count_by_title(
|
||
self,
|
||
title: str,
|
||
status: Optional[bool] = None,
|
||
) -> int:
|
||
"""
|
||
异步按标题统计转移记录数量。
|
||
"""
|
||
return await TransferHistory.async_count_by_title(
|
||
self._db, title=title, status=status
|
||
)
|
||
|
||
def get_by_title(self, title: str) -> List[TransferHistory]:
|
||
"""
|
||
按标题查询转移记录
|
||
:param title: 数据key
|
||
"""
|
||
return TransferHistory.list_by_title(self._db, title)
|
||
|
||
def get_by_src(
|
||
self, src: str, storage: Optional[str] = None
|
||
) -> Optional[TransferHistory]:
|
||
"""
|
||
按源查询转移记录
|
||
:param src: 数据key
|
||
:param storage: 存储类型
|
||
:return: 命中的整理记录,未命中时返回 None
|
||
"""
|
||
return TransferHistory.get_by_src(self._db, src, storage)
|
||
|
||
def get_success_by_src(
|
||
self, src: str, storage: Optional[str] = None
|
||
) -> Optional[TransferHistory]:
|
||
"""
|
||
按源查询成功的转移记录,源路径原样精确匹配
|
||
:param src: 数据key
|
||
:param storage: 存储类型
|
||
:return: 命中的成功整理记录,未命中时返回 None
|
||
"""
|
||
return TransferHistory.get_success_by_src(self._db, src, storage)
|
||
|
||
def get_by_dest(
|
||
self, dest: str, storage: Optional[str] = None
|
||
) -> Optional[TransferHistory]:
|
||
"""
|
||
按转移路径查询转移记录
|
||
:param dest: 数据key
|
||
:param storage: 存储类型
|
||
"""
|
||
return TransferHistory.get_by_dest(self._db, dest, storage)
|
||
|
||
def list_success_by_src(
|
||
self,
|
||
src: str,
|
||
storage: Optional[str] = None,
|
||
recursive: bool = False,
|
||
) -> List[TransferHistory]:
|
||
"""
|
||
按源路径查询成功整理记录。
|
||
|
||
:param src: 源路径
|
||
:param storage: 源存储类型
|
||
:param recursive: 是否递归匹配目录子项
|
||
:return: 命中的成功整理记录
|
||
"""
|
||
return TransferHistory.list_success_by_src(
|
||
self._db,
|
||
src=src,
|
||
storage=storage,
|
||
recursive=recursive,
|
||
)
|
||
|
||
def list_success_move_by_dest(
|
||
self,
|
||
dest: str,
|
||
storage: Optional[str] = None,
|
||
recursive: bool = False,
|
||
) -> List[TransferHistory]:
|
||
"""
|
||
按目标路径查询成功移动记录。
|
||
|
||
:param dest: 目标路径
|
||
:param storage: 目标存储类型
|
||
:param recursive: 是否递归匹配目录子项
|
||
:return: 命中的成功移动记录
|
||
"""
|
||
return TransferHistory.list_success_move_by_dest(
|
||
self._db,
|
||
dest=dest,
|
||
storage=storage,
|
||
recursive=recursive,
|
||
)
|
||
|
||
def list_by_hash(self, download_hash: str) -> List[TransferHistory]:
|
||
"""
|
||
按种子hash查询转移记录
|
||
:param download_hash: 种子hash
|
||
"""
|
||
return TransferHistory.list_by_hash(self._db, download_hash)
|
||
|
||
def add(self, **kwargs):
|
||
"""
|
||
新增转移历史
|
||
"""
|
||
kwargs = normalize_media_identity_payload(kwargs)
|
||
kwargs.update({
|
||
"date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||
})
|
||
TransferHistory(**kwargs).create(self._db)
|
||
|
||
def statistic(self, days: Optional[int] = 7) -> List[Any]:
|
||
"""
|
||
统计最近days天的下载历史数量
|
||
"""
|
||
return TransferHistory.statistic(self._db, days)
|
||
|
||
def get_by(self, title: Optional[str] = None, year: Optional[str] = None, mtype: Optional[str] = None,
|
||
season: Optional[str] = None, episode: Optional[str] = None,
|
||
media_source: Optional[MediaSource] = None, media_id: Optional[str] = None,
|
||
dest: Optional[str] = None) -> List[TransferHistory]:
|
||
"""
|
||
按类型、标题、年份、季集查询转移记录
|
||
"""
|
||
return TransferHistory.list_by(db=self._db,
|
||
mtype=mtype,
|
||
title=title,
|
||
dest=dest,
|
||
year=year,
|
||
season=season,
|
||
episode=episode,
|
||
media_source=media_source,
|
||
media_id=media_id)
|
||
|
||
def get_by_media_identity(
|
||
self, media_source: MediaSource, media_id: str,
|
||
mtype: Optional[str] = None,
|
||
) -> TransferHistory:
|
||
"""按规范媒体身份和类型查询整理记录。"""
|
||
return TransferHistory.get_by_media_identity(
|
||
db=self._db,
|
||
media_source=media_source,
|
||
media_id=media_id,
|
||
mtype=mtype,
|
||
)
|
||
|
||
def delete(self, historyid):
|
||
"""
|
||
删除转移记录
|
||
"""
|
||
TransferHistory.delete(self._db, historyid)
|
||
|
||
async def async_delete(self, historyid):
|
||
"""
|
||
异步删除转移记录。
|
||
"""
|
||
await TransferHistory.async_delete(self._db, historyid)
|
||
|
||
def truncate(self):
|
||
"""
|
||
清空转移记录
|
||
"""
|
||
TransferHistory.truncate(self._db)
|
||
|
||
def add_force(self, **kwargs) -> TransferHistory:
|
||
"""
|
||
新增转移历史,并以同源存储的记录为准替换旧记录。
|
||
"""
|
||
kwargs = normalize_media_identity_payload(kwargs)
|
||
# 文件项的默认存储是 local;归一化旧调用传入的 None,确保运行时语义与
|
||
# (src, src_storage) 唯一索引一致。
|
||
kwargs["src_storage"] = kwargs.get("src_storage") or "local"
|
||
# 旧记录的清理交给 replace_by_src 按 (src, src_storage) 处理:
|
||
# 仅按 src 删除会连带删掉其他存储下同路径的记录。
|
||
kwargs.update({
|
||
"date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||
})
|
||
TransferHistory.replace_by_src(self._db, **kwargs)
|
||
# 保持 add_force 的既有返回契约:返回可被调用方安全读取字段的查询结果,
|
||
# 而非事务提交后可能已脱离会话的新建实例。
|
||
return TransferHistory.get_by_src(
|
||
self._db,
|
||
kwargs.get("src"),
|
||
kwargs["src_storage"],
|
||
)
|
||
|
||
def update_download_hash(self, historyid, download_hash):
|
||
"""
|
||
补充转移记录download_hash
|
||
"""
|
||
TransferHistory.update_download_hash(self._db, historyid, download_hash)
|
||
|
||
@staticmethod
|
||
def _history_title(
|
||
meta: MetaBase, mediainfo: Optional[MediaInfo] = None
|
||
) -> Optional[str]:
|
||
"""音乐文件优先记录曲目标题,其它媒体保持识别标题。"""
|
||
if isinstance(meta, MetaMusic) and meta.title:
|
||
return meta.title
|
||
if mediainfo and mediainfo.title:
|
||
return mediainfo.title
|
||
return meta.name
|
||
|
||
def add_success(self, fileitem: FileItem, mode: str, meta: MetaBase,
|
||
mediainfo: MediaInfo, transferinfo: TransferInfo,
|
||
downloader: Optional[str] = None, download_hash: Optional[str] = None):
|
||
"""
|
||
新增转移成功历史记录
|
||
"""
|
||
media_source, media_id = resolve_media_identity(media=mediainfo)
|
||
return self.add_force(
|
||
src=fileitem.path,
|
||
src_storage=fileitem.storage,
|
||
src_fileitem=fileitem.model_dump(),
|
||
dest=transferinfo.target_item.path if transferinfo.target_item else None,
|
||
dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None,
|
||
dest_fileitem=transferinfo.target_item.model_dump() if transferinfo.target_item else None,
|
||
mode=mode,
|
||
type=mediainfo.type.value,
|
||
category=mediainfo.category,
|
||
title=self._history_title(meta, mediainfo),
|
||
year=mediainfo.year,
|
||
media_source=media_source,
|
||
media_id=media_id,
|
||
music_type=getattr(mediainfo, "music_type", None),
|
||
total_tracks=getattr(mediainfo, "total_tracks", None),
|
||
audio_format=getattr(meta, "audio_format", None),
|
||
audio_lossless=getattr(meta, "audio_lossless", None),
|
||
bit_depth=getattr(meta, "bit_depth", None),
|
||
sample_rate=getattr(meta, "sample_rate", None),
|
||
bitrate=getattr(meta, "bitrate", None),
|
||
seasons=meta.season,
|
||
episodes=meta.episode,
|
||
image=mediainfo.get_poster_image(),
|
||
downloader=downloader,
|
||
download_hash=download_hash,
|
||
status=1,
|
||
files=transferinfo.file_list
|
||
)
|
||
|
||
def add_fail(self, fileitem: FileItem, mode: str, meta: MetaBase, mediainfo: MediaInfo = None,
|
||
transferinfo: TransferInfo = None, downloader: Optional[str] = None, download_hash: Optional[str] = None):
|
||
"""
|
||
新增转移失败历史记录
|
||
"""
|
||
if mediainfo and transferinfo:
|
||
media_source, media_id = resolve_media_identity(media=mediainfo)
|
||
his = self.add_force(
|
||
src=fileitem.path,
|
||
src_storage=fileitem.storage,
|
||
src_fileitem=fileitem.model_dump(),
|
||
dest=transferinfo.target_item.path if transferinfo.target_item else None,
|
||
dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None,
|
||
dest_fileitem=transferinfo.target_item.model_dump() if transferinfo.target_item else None,
|
||
mode=mode,
|
||
type=mediainfo.type.value,
|
||
category=mediainfo.category,
|
||
title=self._history_title(meta, mediainfo),
|
||
year=mediainfo.year or meta.year,
|
||
media_source=media_source,
|
||
media_id=media_id,
|
||
music_type=getattr(mediainfo, "music_type", None),
|
||
total_tracks=getattr(mediainfo, "total_tracks", None),
|
||
audio_format=getattr(meta, "audio_format", None),
|
||
audio_lossless=getattr(meta, "audio_lossless", None),
|
||
bit_depth=getattr(meta, "bit_depth", None),
|
||
sample_rate=getattr(meta, "sample_rate", None),
|
||
bitrate=getattr(meta, "bitrate", None),
|
||
seasons=meta.season,
|
||
episodes=meta.episode,
|
||
image=mediainfo.get_poster_image(),
|
||
downloader=downloader,
|
||
download_hash=download_hash,
|
||
episode_group=mediainfo.episode_group,
|
||
status=0,
|
||
errmsg=transferinfo.message or '未知错误',
|
||
files=transferinfo.file_list
|
||
)
|
||
else:
|
||
media_source, media_id = resolve_media_identity(media=meta)
|
||
his = self.add_force(
|
||
type=meta.type.value if meta.type else None,
|
||
title=self._history_title(meta),
|
||
year=meta.year,
|
||
media_source=media_source,
|
||
media_id=media_id,
|
||
music_type=MUSIC_ENTITY_RECORDING if isinstance(meta, MetaMusic) else None,
|
||
audio_format=getattr(meta, "audio_format", None),
|
||
audio_lossless=getattr(meta, "audio_lossless", None),
|
||
bit_depth=getattr(meta, "bit_depth", None),
|
||
sample_rate=getattr(meta, "sample_rate", None),
|
||
bitrate=getattr(meta, "bitrate", None),
|
||
src=fileitem.path,
|
||
src_storage=fileitem.storage,
|
||
src_fileitem=fileitem.model_dump(),
|
||
mode=mode,
|
||
seasons=meta.season,
|
||
episodes=meta.episode,
|
||
downloader=downloader,
|
||
download_hash=download_hash,
|
||
status=0,
|
||
errmsg="未识别到媒体信息"
|
||
)
|
||
return his
|
||
|
||
def list_by_date(self, date: str) -> List[TransferHistory]:
|
||
"""
|
||
查询某时间之后的转移历史
|
||
:param date: 日期
|
||
"""
|
||
return TransferHistory.list_by_date(self._db, date)
|