fix TorrentTransfer

This commit is contained in:
jxxghp 2023-08-08 15:32:50 +08:00
parent 4ea48a4e11
commit 614e4f0138
2 changed files with 311 additions and 50 deletions

View File

@ -687,8 +687,8 @@ class IYUUAutoSeed(_PluginBase):
if not site_info: if not site_info:
logger.debug(f"没有维护种子对应的站点:{site_url}") logger.debug(f"没有维护种子对应的站点:{site_url}")
return False return False
if self._sites and str(site_info.get('id')) not in self._sites: if self._sites and site_info.get('id') not in self._sites:
logger.info("当前站点不在选择的辅站点范围,跳过 ...") logger.info("当前站点不在选择的辅站点范围,跳过 ...")
return False return False
self.realtotal += 1 self.realtotal += 1
# 查询hash值是否已经在下载器中 # 查询hash值是否已经在下载器中

View File

@ -2,15 +2,21 @@ import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from threading import Event from threading import Event
from typing import Any, List, Dict, Tuple from typing import Any, List, Dict, Tuple, Optional
import pytz import pytz
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
from torrentool.torrent import Torrent
from app.core.config import settings from app.core.config import settings
from app.helper.sites import SitesHelper
from app.helper.torrent import TorrentHelper
from app.log import logger from app.log import logger
from app.modules.qbittorrent import Qbittorrent
from app.modules.transmission import Transmission
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.utils.string import StringUtils
class TorrentTransfer(_PluginBase): class TorrentTransfer(_PluginBase):
@ -37,7 +43,10 @@ class TorrentTransfer(_PluginBase):
# 私有属性 # 私有属性
_scheduler = None _scheduler = None
qb = None
tr = None
sites = None sites = None
torrent = None
# 开关 # 开关
_enabled = False _enabled = False
_cron = None _cron = None
@ -61,6 +70,8 @@ class TorrentTransfer(_PluginBase):
_torrent_tags = ["已整理", "转移做种"] _torrent_tags = ["已整理", "转移做种"]
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self.sites = SitesHelper()
self.torrent = TorrentHelper()
# 读取配置 # 读取配置
if config: if config:
self._enabled = config.get("enabled") self._enabled = config.get("enabled")
@ -82,9 +93,12 @@ class TorrentTransfer(_PluginBase):
# 启动定时任务 & 立即运行一次 # 启动定时任务 & 立即运行一次
if self.get_state() or self._onlyonce: if self.get_state() or self._onlyonce:
self.qb = Qbittorrent()
self.tr = Transmission()
# 检查配置 # 检查配置
if self._fromtorrentpath and not Path(self._fromtorrentpath).exists(): if self._fromtorrentpath and not Path(self._fromtorrentpath).exists():
logger.error(f"源下载器种子文件保存路径不存在:{self._fromtorrentpath}") logger.error(f"源下载器种子文件保存路径不存在:{self._fromtorrentpath}")
self.systemmessage.put(f"源下载器种子文件保存路径不存在:{self._fromtorrentpath}")
return return
if self._fromdownloader == self._todownloader: if self._fromdownloader == self._todownloader:
logger.error(f"源下载器和目的下载器不能相同") logger.error(f"源下载器和目的下载器不能相同")
@ -188,6 +202,218 @@ class TorrentTransfer(_PluginBase):
] ]
} }
] ]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'cron',
'label': '执行周期',
'placeholder': '0 0 0 ? *'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'nolabels',
'label': '不转移种子标签',
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VSelect',
'props': {
'model': 'fromdownloader',
'label': '源下载器',
'items': [
{'title': 'Qbittorrent', 'value': 'qbittorrent'},
{'title': 'Transmission', 'value': 'transmission'}
]
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'fromtorrentpath',
'label': '种子文件路径',
'placeholder': 'BT_backup、torrents'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'frompath',
'label': '数据文件根路径',
'placeholder': '根路径,留空不进行路径转换'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VSelect',
'props': {
'model': 'todownloader',
'label': '目的下载器',
'items': [
{'title': 'Qbittorrent', 'value': 'qbittorrent'},
{'title': 'Transmission', 'value': 'transmission'}
]
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 12
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'topath',
'label': '数据文件根路径',
'placeholder': '根路径,留空不进行路径转换'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'nopaths',
'label': '不转移数据文件目录',
'rows': 3,
'placeholder': '每一行一个目录'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'autostart',
'label': '校验完成后自动开始',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'deletesource',
'label': '删除源种子',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
} }
] ]
} }
@ -210,6 +436,52 @@ class TorrentTransfer(_PluginBase):
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
pass pass
def __get_downloader(self, dtype: str):
"""
根据类型返回下载器实例
"""
if dtype == "qbittorrent":
return self.qb
elif dtype == "transmission":
return self.tr
else:
return None
def __download(self, downloader: str, content: bytes,
save_path: str) -> Optional[str]:
"""
添加下载任务
"""
if downloader == "qbittorrent":
# 生成随机Tag
tag = StringUtils.generate_random_str(10)
state = self.qb.add_torrent(content=content,
download_dir=save_path,
is_paused=True,
tag=["已整理", "转移做种", tag])
if not state:
return None
else:
# 获取种子Hash
torrent_hash = self.qb.get_torrent_id_by_tag(tags=tag)
if not torrent_hash:
logger.error(f"{downloader} 获取种子Hash失败")
return None
return torrent_hash
elif downloader == "transmission":
# 添加任务
torrent = self.tr.add_torrent(content=content,
download_dir=save_path,
is_paused=True,
labels=["已整理", "转移做种"])
if not torrent:
return None
else:
return torrent.hashString
logger.error(f"不支持的下载器:{downloader}")
return None
def transfer(self): def transfer(self):
""" """
开始移转做种 开始移转做种
@ -225,8 +497,9 @@ class TorrentTransfer(_PluginBase):
downloader = self._fromdownloader downloader = self._fromdownloader
# 目的下载器 # 目的下载器
todownloader = self._todownloader todownloader = self._todownloader
# TODO 获取下载器中已完成的种子 # 获取下载器中已完成的种子
torrents = [] downloader_obj = self.__get_downloader(downloader)
torrents = downloader_obj.get_completed_torrents()
if torrents: if torrents:
logger.info(f"下载器 {downloader} 已完成种子数:{len(torrents)}") logger.info(f"下载器 {downloader} 已完成种子数:{len(torrents)}")
else: else:
@ -276,14 +549,14 @@ class TorrentTransfer(_PluginBase):
fail = 0 fail = 0
for hash_item in hash_strs: for hash_item in hash_strs:
# 检查种子文件是否存在 # 检查种子文件是否存在
torrent_file = os.path.join(self._fromtorrentpath, torrent_file = Path(self._fromtorrentpath) / f"{hash_item.get('hash')}.torrent"
f"{hash_item.get('hash')}.torrent") if not torrent_file.exists():
if not os.path.exists(torrent_file):
logger.error(f"种子文件不存在:{torrent_file}") logger.error(f"种子文件不存在:{torrent_file}")
fail += 1 fail += 1
continue continue
# TODO 查询hash值是否已经在目的下载器中 # 查询hash值是否已经在目的下载器中
torrent_info = [] todownloader_obj = self.__get_downloader(todownloader)
torrent_info = todownloader_obj.get_torrents(ids=[hash_item.get('hash')])
if torrent_info: if torrent_info:
logger.debug(f"{hash_item.get('hash')} 已在目的下载器中,跳过 ...") logger.debug(f"{hash_item.get('hash')} 已在目的下载器中,跳过 ...")
continue continue
@ -298,16 +571,10 @@ class TorrentTransfer(_PluginBase):
# 如果是QB检查是否有Tracker没有的话补充解析 # 如果是QB检查是否有Tracker没有的话补充解析
if downloader == "qbittorrent": if downloader == "qbittorrent":
# TODO 读取种子内容、解析种子文件 # 读取trackers
content, retmsg = None, ""
if not content:
logger.error(f"读取种子文件失败:{retmsg}")
fail += 1
continue
# TODO 读取trackers
try: try:
torrent_main = {} torrent_main = Torrent.from_file(torrent_file)
main_announce = None main_announce = torrent_main.announce_urls
except Exception as err: except Exception as err:
logger.error(f"解析种子文件 {torrent_file} 失败:{err}") logger.error(f"解析种子文件 {torrent_file} 失败:{err}")
fail += 1 fail += 1
@ -316,42 +583,36 @@ class TorrentTransfer(_PluginBase):
if not main_announce: if not main_announce:
logger.info(f"{hash_item.get('hash')} 未发现tracker信息尝试补充tracker信息...") logger.info(f"{hash_item.get('hash')} 未发现tracker信息尝试补充tracker信息...")
# 读取fastresume文件 # 读取fastresume文件
fastresume_file = os.path.join(self._fromtorrentpath, fastresume_file = Path(self._fromtorrentpath) / f"{hash_item.get('hash')}.fastresume"
f"{hash_item.get('hash')}.fastresume") if not fastresume_file.exists():
if not os.path.exists(fastresume_file):
logger.error(f"fastresume文件不存在{fastresume_file}") logger.error(f"fastresume文件不存在{fastresume_file}")
fail += 1 fail += 1
continue continue
# 尝试补充trackers # 尝试补充trackers
try: try:
with open(fastresume_file, 'rb') as f: # 解析fastresume文件
fastresume = f.read() torrent_fastresume = Torrent.from_file(fastresume_file)
# TODO 解析fastresume文件 # 读取trackers
torrent_fastresume = None fastresume_trackers = torrent_fastresume.announce_urls
# TODO 读取trackers if fastresume_trackers:
fastresume_trackers = None
if isinstance(fastresume_trackers, list) \
and len(fastresume_trackers) > 0 \
and fastresume_trackers[0]:
# 重新赋值 # 重新赋值
torrent_main['announce'] = fastresume_trackers[0][0] torrent_main.announce_urls = fastresume_trackers
# 替换种子文件路径 # 替换种子文件路径
torrent_file = settings.TEMP_PATH / f"{hash_item.get('hash')}.torrent" torrent_file = settings.TEMP_PATH / f"{hash_item.get('hash')}.torrent"
# TODO 编码并保存到临时文件 # 编码并保存到临时文件
with open(torrent_file, 'wb') as f: Torrent.to_file(torrent_file)
pass
except Exception as err: except Exception as err:
logger.error(f"解析fastresume文件 {fastresume_file} 失败:{err}") logger.error(f"解析fastresume文件 {fastresume_file} 失败:{err}")
fail += 1 fail += 1
continue continue
# TODO 发送到另一个下载器中下载:默认暂停、传输下载路径、关闭自动管理模式 # 发送到另一个下载器中下载:默认暂停、传输下载路径、关闭自动管理模式
download_id, retmsg = None, "" logger.info(f"添加转移做种任务:{torrent_file} ...")
download_id = self.__download(downloader=downloader,
content=torrent_file.read_bytes(),
save_path=download_dir)
if not download_id: if not download_id:
# 下载失败 # 下载失败
logger.warn(f"添加转移任务出错,"
f"错误原因:{retmsg or '下载器添加任务失败'}"
f"种子文件:{torrent_file}")
fail += 1 fail += 1
continue continue
else: else:
@ -364,17 +625,16 @@ class TorrentTransfer(_PluginBase):
logger.info(f"成功添加转移做种任务,种子文件:{torrent_file}") logger.info(f"成功添加转移做种任务,种子文件:{torrent_file}")
# TR会自动校验 # TR会自动校验
if downloader == "qbittorrent": if downloader == "qbittorrent":
# TODO 开始校验种子 todownloader_obj.recheck_torrents(ids=[download_id])
pass # 删除源种子,不能删除文件!
# TODO 删除源种子,不能删除文件!
if self._deletesource: if self._deletesource:
pass downloader_obj.delete_torrents(delete_file=False, ids=[hash_item.get('hash')])
success += 1 success += 1
# 插入转种记录 # 插入转种记录
history_key = "%s-%s" % (int(self._fromdownloader[0]), hash_item.get('hash')) history_key = "%s-%s" % (self._fromdownloader, hash_item.get('hash'))
self.save_data(key=history_key, self.save_data(key=history_key,
value={ value={
"to_download": int(self._todownloader[0]), "to_download": self._todownloader,
"to_download_id": download_id, "to_download_id": download_id,
"delete_source": self._deletesource, "delete_source": self._deletesource,
}) })
@ -408,8 +668,8 @@ class TorrentTransfer(_PluginBase):
return return
logger.info(f"开始检查下载器 {downloader} 的校验任务 ...") logger.info(f"开始检查下载器 {downloader} 的校验任务 ...")
self._is_recheck_running = True self._is_recheck_running = True
# TODO 获取下载器中的种子 downloader_obj = self.__get_downloader(downloader)
torrents = [] torrents = downloader_obj.get_torrents(ids=recheck_torrents)
if torrents: if torrents:
can_seeding_torrents = [] can_seeding_torrents = []
for torrent in torrents: for torrent in torrents:
@ -418,8 +678,9 @@ class TorrentTransfer(_PluginBase):
if self.__can_seeding(torrent, downloader): if self.__can_seeding(torrent, downloader):
can_seeding_torrents.append(hash_str) can_seeding_torrents.append(hash_str)
if can_seeding_torrents: if can_seeding_torrents:
logger.info(f"{len(can_seeding_torrents)} 个任务校验完成,开始辅种 ...") logger.info(f"{len(can_seeding_torrents)} 个任务校验完成,开始做种 ...")
# TODO 开始辅种 # 开始做种
downloader_obj.start_torrents(ids=can_seeding_torrents)
# 去除已经处理过的种子 # 去除已经处理过的种子
self._recheck_torrents[downloader] = list( self._recheck_torrents[downloader] = list(
set(recheck_torrents).difference(set(can_seeding_torrents))) set(recheck_torrents).difference(set(can_seeding_torrents)))