diff --git a/app/chain/__init__.py b/app/chain/__init__.py index 8d0266a8..58111cd9 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -319,7 +319,7 @@ class ChainBase(metaclass=ABCMeta): def torrent_files(self, tid: str) -> Optional[Union[TorrentFilesList, List[File]]]: """ - 根据种子文件,选择并添加下载任务 + 获取种子文件 :param tid: 种子Hash :return: 种子文件 """ diff --git a/app/chain/cookiecloud.py b/app/chain/cookiecloud.py index b5fddf98..6f276ffb 100644 --- a/app/chain/cookiecloud.py +++ b/app/chain/cookiecloud.py @@ -72,12 +72,13 @@ class CookieCloudChain(ChainBase): for domain, cookie in cookies.items(): # 获取站点信息 indexer = self.siteshelper.get_indexer(domain) - if self.siteoper.exists(domain): + site_info = self.siteoper.get_by_domain(domain) + if site_info: # 检查站点连通性 status, msg = self.sitechain.test(domain) # 更新站点Cookie if status: - logger.info(f"站点【{indexer.get('name')}】连通性正常,不同步CookieCloud数据") + logger.info(f"站点【{site_info.name}】连通性正常,不同步CookieCloud数据") continue # 更新站点Cookie self.siteoper.update_cookie(domain=domain, cookies=cookie) diff --git a/app/db/downloadhistory_oper.py b/app/db/downloadhistory_oper.py index 1e02ff31..c26766cc 100644 --- a/app/db/downloadhistory_oper.py +++ b/app/db/downloadhistory_oper.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Any +from typing import Any, List from app.db import DbOper from app.db.models.downloadhistory import DownloadHistory @@ -44,7 +44,7 @@ class DownloadHistoryOper(DbOper): DownloadHistory.truncate(self._db) def get_last_by(self, mtype=None, title: str = None, year: str = None, - season: str = None, episode: str = None, tmdbid=None) -> DownloadHistory: + season: str = None, episode: str = None, tmdbid=None): """ 按类型、标题、年份、季集查询下载记录 """ diff --git a/app/db/models/downloadhistory.py b/app/db/models/downloadhistory.py index 9d472350..0c39bc80 100644 --- a/app/db/models/downloadhistory.py +++ b/app/db/models/downloadhistory.py @@ -58,29 +58,29 @@ class DownloadHistory(Base): """ if tmdbid and not season and not episode: return db.query(DownloadHistory).filter(DownloadHistory.tmdbid == tmdbid).order_by( - DownloadHistory.id.desc()).first() + DownloadHistory.id.desc()).all() if tmdbid and season and not episode: return db.query(DownloadHistory).filter(DownloadHistory.tmdbid == tmdbid, DownloadHistory.seasons == season).order_by( - DownloadHistory.id.desc()).first() + DownloadHistory.id.desc()).all() if tmdbid and season and episode: return db.query(DownloadHistory).filter(DownloadHistory.tmdbid == tmdbid, DownloadHistory.seasons == season, DownloadHistory.episodes == episode).order_by( - DownloadHistory.id.desc()).first() + DownloadHistory.id.desc()).all() # 电视剧所有季集|电影 if not season and not episode: return db.query(DownloadHistory).filter(DownloadHistory.type == mtype, DownloadHistory.title == title, DownloadHistory.year == year).order_by( - DownloadHistory.id.desc()).first() + DownloadHistory.id.desc()).all() # 电视剧某季 if season and not episode: return db.query(DownloadHistory).filter(DownloadHistory.type == mtype, DownloadHistory.title == title, DownloadHistory.year == year, DownloadHistory.seasons == season).order_by( - DownloadHistory.id.desc()).first() + DownloadHistory.id.desc()).all() # 电视剧某季某集 if season and episode: return db.query(DownloadHistory).filter(DownloadHistory.type == mtype, @@ -88,4 +88,4 @@ class DownloadHistory(Base): DownloadHistory.year == year, DownloadHistory.seasons == season, DownloadHistory.episodes == episode).order_by( - DownloadHistory.id.desc()).first() + DownloadHistory.id.desc()).all() diff --git a/app/db/models/transferhistory.py b/app/db/models/transferhistory.py index c9334b8d..a92e59ad 100644 --- a/app/db/models/transferhistory.py +++ b/app/db/models/transferhistory.py @@ -85,7 +85,7 @@ class TransferHistory(Base): return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0] @staticmethod - def list_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None, + def list_by(db: Session, title: str = None, year: int = None, season: str = None, episode: str = None, tmdbid: str = None): """ 据tmdbid、season、season_episode查询转移记录 @@ -101,19 +101,16 @@ class TransferHistory(Base): TransferHistory.episodes == episode).all() # 电视剧所有季集|电影 if not season and not episode: - return db.query(TransferHistory).filter(TransferHistory.type == mtype, - TransferHistory.title == title, + return db.query(TransferHistory).filter(TransferHistory.title == title, TransferHistory.year == year).all() # 电视剧某季 if season and not episode: - return db.query(TransferHistory).filter(TransferHistory.type == mtype, - TransferHistory.title == title, + return db.query(TransferHistory).filter(TransferHistory.title == title, TransferHistory.year == year, TransferHistory.seasons == season).all() # 电视剧某季某集 if season and episode: - return db.query(TransferHistory).filter(TransferHistory.type == mtype, - TransferHistory.title == title, + return db.query(TransferHistory).filter(TransferHistory.title == title, TransferHistory.year == year, TransferHistory.seasons == season, TransferHistory.episodes == episode).all() diff --git a/app/db/transferhistory_oper.py b/app/db/transferhistory_oper.py index 8feef55f..529600e1 100644 --- a/app/db/transferhistory_oper.py +++ b/app/db/transferhistory_oper.py @@ -50,13 +50,12 @@ class TransferHistoryOper(DbOper): """ return TransferHistory.statistic(self._db, days) - def get_by(self, mtype: str = None, title: str = None, year: str = None, + def get_by(self, title: str = None, year: str = None, season: str = None, episode: str = None, tmdbid: str = None) -> Any: """ 按类型、标题、年份、季集查询转移记录 """ return TransferHistory.list_by(db=self._db, - mtype=mtype, title=title, year=year, season=season, @@ -79,4 +78,8 @@ class TransferHistoryOper(DbOper): """ 新增转移历史 """ - return TransferHistory(**kwargs).create(self._db) \ No newline at end of file + if kwargs.get("src"): + transferhistory = TransferHistory.get_by_src(self._db, kwargs.get("src")) + if transferhistory: + transferhistory.delete(self._db, transferhistory.id) + return TransferHistory(**kwargs).create(self._db) diff --git a/app/plugins/dirmonitor/__init__.py b/app/plugins/dirmonitor/__init__.py index 9ab4be31..ed97d96d 100644 --- a/app/plugins/dirmonitor/__init__.py +++ b/app/plugins/dirmonitor/__init__.py @@ -1,3 +1,4 @@ +import os import re import threading import time @@ -16,6 +17,8 @@ from app.core.metainfo import MetaInfo from app.db.downloadhistory_oper import DownloadHistoryOper from app.db.transferhistory_oper import TransferHistoryOper from app.log import logger +from app.modules.qbittorrent import Qbittorrent +from app.modules.transmission import Transmission from app.plugins import _PluginBase from app.schemas import Notification, NotificationType, TransferInfo from app.schemas.types import EventType @@ -82,6 +85,8 @@ class DirMonitor(_PluginBase): _exclude_keywords = "" # 存储源目录与目的目录关系 _dirconf: Dict[str, Path] = {} + qb = None + tr = None def init_plugin(self, config: dict = None): self.transferhis = TransferHistoryOper() @@ -104,6 +109,9 @@ class DirMonitor(_PluginBase): self.stop_service() if self._enabled: + self.qb = Qbittorrent() + self.tr = Transmission() + # 启动任务 monitor_dirs = self._monitor_dirs.split("\n") if not monitor_dirs: @@ -255,12 +263,9 @@ class DirMonitor(_PluginBase): return # 获取downloadhash - downloadHis = self.downloadhis.get_last_by(mtype=mediainfo.type.value, - title=mediainfo.title, - year=mediainfo.year, - season=file_meta.season, - episode=file_meta.episode, - tmdbid=mediainfo.tmdb_id) + download_hash = self.get_download_hash(file_name=os.path.basename(event_path), + tmdb_id=mediainfo.tmdb_id,) + # 新增转移成功历史记录 self.transferhis.add_force( src=event_path, @@ -277,7 +282,7 @@ class DirMonitor(_PluginBase): seasons=file_meta.season, episodes=file_meta.episode, image=mediainfo.get_poster_image(), - download_hash=downloadHis.download_hash if downloadHis else None, + download_hash=download_hash, status=1, date=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) ) @@ -300,6 +305,32 @@ class DirMonitor(_PluginBase): except Exception as e: logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc())) + def get_download_hash(self, file_name, tmdb_id): + """ + 获取download_hash + """ + downloadHis = self.downloadhis.get_last_by(tmdbid=tmdb_id) + if downloadHis: + for his in downloadHis: + # qb + if settings.DOWNLOADER == "qbittorrent": + files = self.qb.get_files(tid=his.download_hash) + if files: + for file in files: + torrent_file_name = file.get("name") + if str(file_name) == str(os.path.basename(torrent_file_name)): + return his.download_hash + # tr + if settings.DOWNLOADER == "transmission": + files = self.tr.get_files(tid=his.download_hash) + if files: + for file in files: + torrent_file_name = file.name + if str(file_name) == str(os.path.basename(torrent_file_name)): + return his.download_hash + + return None + def get_state(self) -> bool: return self._enabled diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index 33ac34ea..339721cc 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -17,7 +17,9 @@ from app.db.transferhistory_oper import TransferHistoryOper from app.log import logger from app.modules.emby import Emby from app.modules.jellyfin import Jellyfin +from app.modules.qbittorrent import Qbittorrent from app.modules.themoviedb.tmdbv3api import Episode +from app.modules.transmission import Transmission from app.plugins import _PluginBase from app.schemas.types import NotificationType, EventType from app.utils.path_utils import PathUtils @@ -55,10 +57,14 @@ class MediaSyncDel(_PluginBase): _del_source = False _exclude_path = None _transferhis = None + qb = None + tr = None def init_plugin(self, config: dict = None): self._transferhis = TransferHistoryOper() self.episode = Episode() + self.qb = Qbittorrent() + self.tr = Transmission() # 停止现有任务 self.stop_service() @@ -487,14 +493,17 @@ class MediaSyncDel(_PluginBase): logger.error(f"{media_name} 季同步删除失败,未获取到具体季") return msg = f'剧集 {media_name} S{season_num} {tmdb_id}' - transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id) + transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id, + season=f'S{season_num}') # 删除剧集S02E02 elif media_type == "Episode": if not season_num or not str(season_num).isdigit() or not episode_num or not str(episode_num).isdigit(): logger.error(f"{media_name} 集同步删除失败,未获取到具体集") return msg = f'剧集 {media_name} S{season_num}E{episode_num} {tmdb_id}' - transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id) + transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id, + season=f'S{season_num}', + episode=f'E{episode_num}') else: return @@ -507,25 +516,32 @@ class MediaSyncDel(_PluginBase): # 开始删除 image = 'https://emby.media/notificationicon.png' year = None + del_cnt = 0 + stop_cnt = 0 for transferhis in transfer_history: image = transferhis.image year = transferhis.year # 删除种子任务 if self._del_source: + # 0、删除转移记录 + self._transferhis.delete(transferhis.id) # 1、直接删除源文件 if transferhis.src and Path(transferhis.src).suffix in settings.RMT_MEDIAEXT: source_name = os.path.basename(transferhis.src) source_path = str(transferhis.src).replace(source_name, "") self.delete_media_file(filedir=source_path, filename=source_name) - if transferhis.download_hash: - try: - # 2、判断种子是否被删除完 - self.handle_torrent(history_id=transferhis.id, - src=transferhis.src, - torrent_hash=transferhis.download_hash) - except Exception as e: - logger.error("删除种子失败,尝试删除源文件:%s" % str(e)) + if transferhis.download_hash: + try: + # 2、判断种子是否被删除完 + delete_flag, stop_flag = self.handle_torrent(src=source_path, + torrent_hash=transferhis.download_hash) + if delete_flag: + del_cnt += 1 + if stop_flag: + stop_cnt += 1 + except Exception as e: + logger.error("删除种子失败,尝试删除源文件:%s" % str(e)) logger.info(f"同步删除 {msg} 完成!") @@ -544,6 +560,7 @@ class MediaSyncDel(_PluginBase): title="媒体库同步删除任务完成", image=image, text=f"{msg}\n" + f"数量 删除{del_cnt}个 暂停{stop_cnt}个\n" f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}" ) @@ -613,30 +630,29 @@ class MediaSyncDel(_PluginBase): if media_type == "Movie": msg = f'电影 {media_name}' transfer_history: List[TransferHistory] = self._transferhis.get_by( - mtype="电影", title=media_name, year=media_year) # 删除电视剧 elif media_type == "Series": msg = f'剧集 {media_name}' transfer_history: List[TransferHistory] = self._transferhis.get_by( - mtype="电视剧", title=media_name, year=media_year) # 删除季 S02 elif media_type == "Season": msg = f'剧集 {media_name} {media_season}' transfer_history: List[TransferHistory] = self._transferhis.get_by( - mtype="电视剧", title=media_name, - year=media_year) + year=media_year, + season=media_season) # 删除剧集S02E02 elif media_type == "Episode": msg = f'剧集 {media_name} {media_season}{media_episode}' transfer_history: List[TransferHistory] = self._transferhis.get_by( - mtype="电视剧", title=media_name, - year=media_year) + year=media_year, + season=media_season, + episode=media_episode) else: continue @@ -650,25 +666,32 @@ class MediaSyncDel(_PluginBase): # 开始删除 image = 'https://emby.media/notificationicon.png' + del_cnt = 0 + stop_cnt = 0 for transferhis in transfer_history: image = transferhis.image self._transferhis.delete(transferhis.id) # 删除种子任务 if self._del_source: + # 0、删除转移记录 + self._transferhis.delete(transferhis.id) # 1、直接删除源文件 if transferhis.src and Path(transferhis.src).suffix in settings.RMT_MEDIAEXT: source_name = os.path.basename(transferhis.src) source_path = str(transferhis.src).replace(source_name, "") self.delete_media_file(filedir=source_path, filename=source_name) - if transferhis.download_hash: - try: - # 2、判断种子是否被删除完 - self.handle_torrent(history_id=transferhis.id, - src=transferhis.src, - torrent_hash=transferhis.download_hash) - except Exception as e: - logger.error("删除种子失败,尝试删除源文件:%s" % str(e)) + if transferhis.download_hash: + try: + # 2、判断种子是否被删除完 + delete_flag, stop_flag = self.handle_torrent(src=source_path, + torrent_hash=transferhis.download_hash) + if delete_flag: + del_cnt += 1 + if stop_flag: + stop_cnt += 1 + except Exception as e: + logger.error("删除种子失败,尝试删除源文件:%s" % str(e)) logger.info(f"同步删除 {msg} 完成!") @@ -678,7 +701,7 @@ class MediaSyncDel(_PluginBase): mtype=NotificationType.MediaServer, title="媒体库同步删除任务完成", text=f"{msg}\n" - f"数量 {len(transfer_history)}\n" + f"数量 删除{del_cnt}个 暂停{stop_cnt}个\n" f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}", image=image) @@ -698,7 +721,7 @@ class MediaSyncDel(_PluginBase): self.save_data("last_time", datetime.datetime.now()) - def handle_torrent(self, history_id: int, src: str, torrent_hash: str): + def handle_torrent(self, src: str, torrent_hash: str): """ 判断种子是否局部删除 局部删除则暂停种子 @@ -716,9 +739,8 @@ class MediaSyncDel(_PluginBase): del_history = False # 删除种子标志 delete_flag = True - # 是否需要暂停源下载器种子 - stop_from = False + stop_flag = False # 如果有转种记录,则删除转种后的下载任务 if transfer_history and isinstance(transfer_history, dict): @@ -742,7 +764,7 @@ class MediaSyncDel(_PluginBase): if Path(torrent_file).exists(): logger.warn(f"种子有文件被删除,种子文件{torrent_file}暂未删除,暂停种子") delete_flag = False - stop_from = True + stop_flag = True break if delete_flag: logger.info(f"删除下载任务:{settings.DOWNLOADER} - {torrent_hash}") @@ -753,32 +775,60 @@ class MediaSyncDel(_PluginBase): # 如果是False则说明种子文件没有完全被删除,暂停种子,暂不处理 if delete_flag: try: - dl_files = self.chain.torrent_files(tid=download_id) - if not dl_files: - logger.info(f"未获取到 {download} - {download_id} 种子文件,种子已被删除") + # 转种download + if download == "transmission": + dl_files = self.tr.get_files(tid=download_id) + if not dl_files: + logger.info(f"未获取到 {download} - {download_id} 种子文件,种子已被删除") + else: + for dl_file in dl_files: + dl_file_name = dl_file.name + if not transfer_history or not stop_flag: + torrent_file = os.path.join(src, os.path.basename(dl_file_name)) + if Path(torrent_file).exists(): + logger.info(f"种子有文件被删除,种子文件{torrent_file}暂未删除,暂停种子") + delete_flag = False + stop_flag = True + break + if delete_flag: + # 删除源下载任务或转种后下载任务 + logger.info(f"删除下载任务:{download} - {download_id}") + self.tr.delete_torrents(delete_file=True, + ids=download_id) + + # 删除转种记录 + if del_history: + self.del_data(key=history_key, plugin_id=plugin_id) + + # 处理辅种 + self.__del_seed(download=download, download_id=download_id, action_flag="del") else: - for dl_file in dl_files: - dl_file_name = dl_file.get("name") - if not stop_from: - torrent_file = os.path.join(src, os.path.basename(dl_file_name)) - if Path(torrent_file).exists(): - logger.info(f"种子有文件被删除,种子文件{torrent_file}暂未删除,暂停种子") - delete_flag = False - break - if delete_flag: - # 删除源下载任务或转种后下载任务 - logger.info(f"删除下载任务:{download} - {download_id}") - self.chain.remove_torrents(download_id) + dl_files = self.qb.get_files(tid=download_id) + if not dl_files: + logger.info(f"未获取到 {download} - {download_id} 种子文件,种子已被删除") + else: + for dl_file in dl_files: + dl_file_name = dl_file.get("name") + if not transfer_history or not stop_flag: + torrent_file = os.path.join(src, os.path.basename(dl_file_name)) + if Path(torrent_file).exists(): + logger.info(f"种子有文件被删除,种子文件{torrent_file}暂未删除,暂停种子") + delete_flag = False + stop_flag = True + break - # 删除转移记录 - self._transferhis.delete(history_id) + if delete_flag: + # 删除源下载任务或转种后下载任务 + logger.info(f"删除下载任务:{download} - {download_id}") + self.qb.delete_torrents(delete_file=True, + ids=download_id) - # 删除转种记录 - if del_history: - self.del_data(key=history_key, plugin_id=plugin_id) + # 删除转种记录 + if del_history: + self.del_data(key=history_key, plugin_id=plugin_id) - # 处理辅种 - self.__del_seed(download=download, download_id=download_id, action_flag="del") + # 处理辅种 + self.__del_seed(download=download, download_id=download_id, action_flag="del") except Exception as e: logger.error(f"删除转种辅种下载任务失败: {str(e)}") @@ -786,17 +836,23 @@ class MediaSyncDel(_PluginBase): if not delete_flag: logger.error("开始暂停种子") # 暂停种子 - if stop_from: + if stop_flag: # 暂停源种 self.chain.stop_torrents(torrent_hash) logger.info(f"种子:{settings.DOWNLOADER} - {torrent_hash} 暂停") - # 转种 - self.chain.stop_torrents(download_id) - logger.info(f"转种:{download} - {download_id} 暂停") + # 暂停转种 + if del_history: + if download == "qbittorrent": + self.qb.stop_torrents(download_id) + logger.info(f"转种:{download} - {download_id} 暂停") + else: + self.tr.stop_torrents(download_id) + logger.info(f"转种:{download} - {download_id} 暂停") + # 暂停辅种 + self.__del_seed(download=download, download_id=download_id, action_flag="stop") - # 辅种 - self.__del_seed(download=download, download_id=download_id, action_flag="stop") + return delete_flag, stop_flag def __del_seed(self, download, download_id, action_flag): """ @@ -822,15 +878,26 @@ class MediaSyncDel(_PluginBase): # 删除辅种历史中与本下载器相同的辅种记录 if int(downloader) == download: for torrent in torrents: - # 删除辅种 - if action_flag == "del": - logger.info(f"删除辅种:{downloader} - {torrent}") - self.chain.remove_torrents(torrent) - # 暂停辅种 - if action_flag == "stop": - self.chain.stop_torrents(torrent) - logger.info(f"辅种:{downloader} - {torrent} 暂停") - + if download == "qbittorrent": + # 删除辅种 + if action_flag == "del": + logger.info(f"删除辅种:{downloader} - {torrent}") + self.qb.delete_torrents(delete_file=True, + ids=torrent) + # 暂停辅种 + if action_flag == "stop": + self.qb.stop_torrents(torrent) + logger.info(f"辅种:{downloader} - {torrent} 暂停") + else: + # 删除辅种 + if action_flag == "del": + logger.info(f"删除辅种:{downloader} - {torrent}") + self.tr.delete_torrents(delete_file=True, + ids=torrent) + # 暂停辅种 + if action_flag == "stop": + self.tr.stop_torrents(torrent) + logger.info(f"辅种:{downloader} - {torrent} 暂停") # 删除本下载器辅种历史 if action_flag == "del": del history