feat 下载器种子同步插件

This commit is contained in:
thsrite
2023-08-31 14:33:39 +08:00
parent cb41086fa3
commit 8739a67679
3 changed files with 373 additions and 0 deletions

View File

@ -114,3 +114,11 @@ class TransferHistory(Base):
TransferHistory.year == year,
TransferHistory.seasons == season,
TransferHistory.episodes == episode).all()
@staticmethod
def update_download_hash(db: Session, historyid: int = None, download_hash: str = None):
db.query(TransferHistory).filter(TransferHistory.id == historyid).update(
{
"download_hash": download_hash
}
)

View File

@ -79,3 +79,9 @@ class TransferHistoryOper(DbOper):
if transferhistory:
transferhistory.delete(self._db, transferhistory.id)
return TransferHistory(**kwargs).create(self._db)
def update_download_hash(self, historyid, download_hash):
"""
补充转移记录download_hash
"""
TransferHistory.update_download_hash(self._db, historyid, download_hash)

View File

@ -0,0 +1,359 @@
import datetime
import os
import time
from pathlib import Path
from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper
from app.modules.qbittorrent import Qbittorrent
from app.modules.transmission import Transmission
from app.plugins import _PluginBase
from typing import Any, List, Dict, Tuple
from app.log import logger
class SyncDownloadFiles(_PluginBase):
# 插件名称
plugin_name = "SyncDownloadFiles"
# 插件描述
plugin_desc = "同步下载器文件记录。"
# 插件图标
plugin_icon = "sync_file.png"
# 主题色
plugin_color = "bg-blue"
# 插件版本
plugin_version = "1.0"
# 插件作者
plugin_author = "thsrite"
# 作者主页
author_url = "https://github.com/thsrite"
# 插件配置项ID前缀
plugin_config_prefix = "syncdownloadfiles_"
# 加载顺序
plugin_order = 20
# 可使用的用户级别
auth_level = 2
# 私有属性
qb = None
tr = None
_onlyonce = False
_history = False
_downloaders = []
_dirs = None
downloadhis = None
transferhis = None
def init_plugin(self, config: dict = None):
if config:
self._history = config.get('history')
self._onlyonce = config.get("onlyonce")
self._downloaders = config.get('downloaders') or []
self._dirs = config.get("dirs") or ""
if self._onlyonce:
# 执行一次
self.qb = Qbittorrent()
self.tr = Transmission()
self.downloadhis = DownloadHistoryOper(self.db)
self.transferhis = TransferHistoryOper(self.db)
# 关闭onlyonce
self._onlyonce = False
self.update_config({
"history": self._history,
"onlyonce": self._onlyonce,
"downloaders": self._downloaders,
"dirs": self._dirs
})
self.sync()
def sync(self):
"""
同步所选下载器种子记录
"""
if not self._downloaders:
logger.error("未选择同步下载器,停止运行")
return
# 获取最后同步时间
last_sync_time = self.get_data("last_sync_time")
# 遍历下载器同步记录
for downloader in self._downloaders:
logger.info(f"开始扫描下载器 {downloader} ...")
downloader_obj = self.__get_downloader(downloader)
# 获取下载器中已完成的种子
torrents = downloader_obj.get_completed_torrents()
# 排序种子,根据种子添加时间倒序
if downloader == "qbittorrent":
torrents = sorted(torrents, key=lambda x: x.get("added_on"), reverse=True)
else:
torrents = sorted(torrents, key=lambda x: x.added_date, reverse=True)
if torrents:
logger.info(f"下载器 {downloader} 已完成种子数:{len(torrents)}")
else:
logger.info(f"下载器 {downloader} 没有已完成种子")
continue
for torrent in torrents:
# 返回false标识后续种子已被同步
sync_flag = self.__compare_time(torrent, downloader, last_sync_time)
if not sync_flag:
logger.info(f"最后同步时间{last_sync_time}, 之前种子已被同步,结束当前下载器 {downloader} 任务")
break
# 获取种子hash
hash_str = self.__get_hash(torrent, downloader)
# 获取种子download_dir
download_dir = self.__get_download_dir(torrent, downloader)
# 获取种子name
torrent_name = self.__get_torrent_name(torrent, downloader)
# 获取种子文件
torrent_files = self.__get_torrent_files(torrent, downloader, downloader_obj)
logger.info(f"开始同步种子 {hash_str}, 文件数 {len(torrent_files)}")
# 处理路径映射
if self._dirs:
paths = self._dirs.split("\n")
for path in paths:
sub_paths = path.split(":")
download_dir = download_dir.replace(sub_paths[0], sub_paths[1]).replace('\\', '/')
download_files = []
for file in torrent_files:
file_name = self.__get_file_name(file, downloader)
full_path = Path(download_dir).joinpath(torrent_name, file_name)
if self._history:
transferhis = self.transferhis.get_by_src(str(full_path))
if transferhis and not transferhis.download_hash:
logger.info(f"开始补充转移记录 {transferhis.id} download_hash {hash_str}")
self.transferhis.update_download_hash(historyid=transferhis.id,
download_hash=hash_str)
# 种子文件记录
download_files.append(
{
"download_hash": hash_str,
"downloader": downloader,
"fullpath": str(full_path),
"savepath": str(Path(download_dir).joinpath(torrent_name)),
"filepath": file_name,
"torrentname": torrent_name,
}
)
if download_files:
# 登记下载文件
self.downloadhis.add_files(download_files)
logger.info(f"种子 {hash_str} 同步完成")
logger.info(f"下载器种子文件同步完成!")
self.save_data("last_sync_time", datetime.datetime.now())
@staticmethod
def __compare_time(torrent: Any, dl_tpe: str, last_sync_time: str = None):
if last_sync_time:
# 获取种子时间
if dl_tpe == "qbittorrent":
torrent_date = time.gmtime(torrent.get("added_on")) # 将时间戳转换为时间元组
torrent_date = time.strftime("%Y-%m-%d %H:%M:%S", torrent_date) # 格式化时间
else:
torrent_date = torrent.added_date
# 之后的种子已经同步了
if last_sync_time > torrent_date:
return False
return True
@staticmethod
def __get_file_name(file: Any, dl_type: str):
"""
获取文件名
"""
try:
return os.path.basename(file.get("name")) if dl_type == "qbittorrent" else os.path.basename(file.name)
except Exception as e:
print(str(e))
return ""
@staticmethod
def __get_torrent_files(torrent: Any, dl_type: str, downloader_obj):
"""
获取种子文件
"""
try:
return torrent.files if dl_type == "qbittorrent" else downloader_obj.get_files(tid=torrent.id)
except Exception as e:
print(str(e))
return ""
@staticmethod
def __get_torrent_name(torrent: Any, dl_type: str):
"""
获取种子name
"""
try:
return torrent.get("name") if dl_type == "qbittorrent" else torrent.name
except Exception as e:
print(str(e))
return ""
@staticmethod
def __get_download_dir(torrent: Any, dl_type: str):
"""
获取种子download_dir
"""
try:
return torrent.get("save_path") if dl_type == "qbittorrent" else torrent.download_dir
except Exception as e:
print(str(e))
return ""
@staticmethod
def __get_hash(torrent: Any, dl_type: str):
"""
获取种子hash
"""
try:
return torrent.get("hash") if dl_type == "qbittorrent" else torrent.hashString
except Exception as e:
print(str(e))
return ""
def __get_downloader(self, dtype: str):
"""
根据类型返回下载器实例
"""
if dtype == "qbittorrent":
return self.qb
elif dtype == "transmission":
return self.tr
else:
return None
def get_state(self) -> bool:
return False
@staticmethod
def get_command() -> List[Dict[str, Any]]:
pass
def get_api(self) -> List[Dict[str, Any]]:
pass
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
"""
拼装插件配置页面需要返回两块数据1、页面配置2、数据结构
"""
return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '运行一次',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'history',
'label': '补充转移记录',
}
}
]
},
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VSelect',
'props': {
'chips': True,
'multiple': True,
'model': 'downloaders',
'label': '同步下载器',
'items': [
{'title': 'Qbittorrent', 'value': 'qbittorrent'},
{'title': 'Transmission', 'value': 'transmission'}
]
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'dirs',
'label': '目录映射',
'rows': 5,
'placeholder': '每一行一个目录,下载器地址:mp地址'
}
}
]
}
]
},
]
}
], {
"onlyonce": False,
"history": False,
"dirs": "",
"downloaders": []
}
def get_page(self) -> List[dict]:
pass
def stop_service(self):
"""
退出插件
"""
pass