Compare commits

...

13 Commits

Author SHA1 Message Date
jxxghp
ec309180da v1.3.4 2023-10-16 17:36:01 +08:00
jxxghp
ab3b674a6e fix 目录监控通知间隔 2023-10-16 17:30:25 +08:00
jxxghp
9231144518 fix 2023-10-16 17:11:45 +08:00
jxxghp
13c04de87c fix 2023-10-16 17:10:22 +08:00
jxxghp
70f533684f fix #607 目录监控全量同步 2023-10-16 17:04:51 +08:00
jxxghp
c94866631b 更新 bug_report.yml 2023-10-16 12:33:51 +08:00
jxxghp
40a77b438e feat 下载中信息增加用户 2023-10-16 08:28:54 +08:00
jxxghp
f5de48ca30 fix #867 2023-10-16 07:16:47 +08:00
jxxghp
89a2c00e64 fix #867 2023-10-16 07:16:02 +08:00
jxxghp
35afb50b26 fix #867 2023-10-16 07:14:36 +08:00
jxxghp
0e3e01bf9c fix #864 2023-10-16 07:06:10 +08:00
jxxghp
6e3ebd73c6 Merge remote-tracking branch 'origin/main' 2023-10-16 07:04:59 +08:00
jxxghp
add9b875aa fix #863 2023-10-16 07:04:31 +08:00
11 changed files with 371 additions and 262 deletions

View File

@@ -9,8 +9,9 @@ body:
请确认以下信息:
1. 请按此模板提交issues不按模板提交的问题将直接关闭。
2. 如果你的问题可以直接在以往 issue 或者 Telegram频道 中找到,那么你的 issue 将会被直接关闭。
3. 提交问题务必描述清楚、附上日志,描述不清导致无法理解和分析的问题会被直接关闭。
3. 提交问题务必描述清楚、附上日志,描述不清导致无法理解和分析的问题会被直接关闭。
4. 此仓库为后端仓库,如果是前端 WebUI 问题请在[前端仓库](https://github.com/jxxghp/MoviePilot-Frontend)提 issue。
5. 【不要通过issues来寻求解决你的环境问题、配置安装类问题、咨询类问题】否则直接关闭并加入用户黑名单实在没有精力陪一波又一波的伸手党玩。
- type: checkboxes
id: ensure
attributes:

View File

@@ -786,6 +786,7 @@ class DownloadChain(ChainBase):
for torrent in torrents:
history = self.downloadhis.get_by_hash(torrent.hash)
if history:
# 媒体信息
torrent.media = {
"tmdbid": history.tmdbid,
"type": history.type,
@@ -794,6 +795,8 @@ class DownloadChain(ChainBase):
"episode": history.episodes,
"image": history.image,
}
# 下载用户
torrent.userid = history.userid
ret_torrents.append(torrent)
return ret_torrents

View File

@@ -800,7 +800,7 @@ class Emby(metaclass=Singleton):
eventType = message.get('Event')
if not eventType:
return None
logger.info(f"接收到emby webhook{message}")
logger.debug(f"接收到emby webhook{message}")
eventItem = schemas.WebhookEventInfo(event=eventType, channel="emby")
if message.get('Item'):
if message.get('Item', {}).get('Type') == 'Episode':

View File

@@ -452,7 +452,7 @@ class Jellyfin(metaclass=Singleton):
return None
if not message:
return None
logger.info(f"接收到jellyfin webhook{message}")
logger.debug(f"接收到jellyfin webhook{message}")
eventType = message.get('NotificationType')
if not eventType:
return None

View File

@@ -492,7 +492,7 @@ class Plex(metaclass=Singleton):
eventType = message.get('event')
if not eventType:
return None
logger.info(f"接收到plex webhook{message}")
logger.debug(f"接收到plex webhook{message}")
eventItem = schemas.WebhookEventInfo(event=eventType, channel="plex")
if message.get('Metadata'):
if message.get('Metadata', {}).get('type') == 'episode':

View File

@@ -747,7 +747,7 @@ class BrushFlow(_PluginBase):
def get_page(self) -> List[dict]:
# 种子明细
data_list = self.get_data("torrents") or {}
torrents = self.get_data("torrents") or {}
# 统计数据
stattistic_data: Dict[str, dict] = self.get_data("statistic") or {
"count": 0,
@@ -755,7 +755,7 @@ class BrushFlow(_PluginBase):
"uploaded": 0,
"downloaded": 0,
}
if not data_list:
if not torrents:
return [
{
'component': 'div',
@@ -766,7 +766,9 @@ class BrushFlow(_PluginBase):
}
]
else:
data_list = data_list.values()
data_list = torrents.values()
# 按time倒序排序
data_list = sorted(data_list, key=lambda x: x.get("time") or 0, reverse=True)
# 总上传量格式化
total_upload = StringUtils.str_filesize(stattistic_data.get("uploaded") or 0)
# 总下载量格式化
@@ -1379,6 +1381,7 @@ class BrushFlow(_PluginBase):
"downloaded": 0,
"uploaded": 0,
"deleted": False,
"time": time.time()
}
# 统计数据
torrents_size += torrent.size

View File

@@ -214,7 +214,7 @@ class CustomSites(_PluginBase):
'label': '站点列表',
'rows': 5,
'placeholder': '每一行一个站点,配置方式:\n'
'站点名称|站点 URL|是否仿真(Y|y|N|n)\n'
'站点名称|站点地址|是否仿真(Y/N)\n'
}
}
]

View File

@@ -1,12 +1,12 @@
import datetime
import re
import shutil
import threading
import traceback
from datetime import datetime
from pathlib import Path
from threading import Event
from typing import List, Tuple, Dict, Any
import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
@@ -16,6 +16,7 @@ from app.chain.tmdb import TmdbChain
from app.chain.transfer import TransferChain
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.event import eventmanager, Event
from app.core.metainfo import MetaInfoPath
from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper
@@ -79,6 +80,7 @@ class DirMonitor(_PluginBase):
_observer = []
_enabled = False
_notify = False
_onlyonce = False
# 模式 compatibility/fast
_mode = "fast"
# 转移方式
@@ -91,7 +93,7 @@ class DirMonitor(_PluginBase):
_transferconf: Dict[str, str] = {}
_medias = {}
# 退出事件
_event = Event()
_event = threading.Event()
def init_plugin(self, config: dict = None):
self.transferhis = TransferHistoryOper(self.db)
@@ -106,6 +108,7 @@ class DirMonitor(_PluginBase):
if config:
self._enabled = config.get("enabled")
self._notify = config.get("notify")
self._onlyonce = config.get("onlyonce")
self._mode = config.get("mode")
self._transfer_type = config.get("transfer_type")
self._monitor_dirs = config.get("monitor_dirs") or ""
@@ -114,10 +117,13 @@ class DirMonitor(_PluginBase):
# 停止现有任务
self.stop_service()
if self._enabled:
if self._enabled or self._onlyonce:
# 定时服务管理器
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
# 追加入库消息统一发送服务
self._scheduler.add_job(self.send_msg, trigger='interval', seconds=30)
# 启动任务
# 读取目录配置
monitor_dirs = self._monitor_dirs.split("\n")
if not monitor_dirs:
return
@@ -152,47 +158,100 @@ class DirMonitor(_PluginBase):
# 转移方式
self._transferconf[mon_path] = _transfer_type
# 检查媒体库目录是不是下载目录的子目录
try:
if target_path and target_path.is_relative_to(Path(mon_path)):
logger.warn(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控")
self.systemmessage.put(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控")
continue
except Exception as e:
logger.debug(str(e))
pass
# 启用目录监控
if self._enabled:
# 检查媒体库目录是不是下载目录的子目录
try:
if target_path and target_path.is_relative_to(Path(mon_path)):
logger.warn(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控")
self.systemmessage.put(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控")
continue
except Exception as e:
logger.debug(str(e))
pass
try:
if self._mode == "compatibility":
# 兼容模式目录同步性能降低且NAS不能休眠但可以兼容挂载的远程共享目录如SMB
observer = PollingObserver(timeout=10)
else:
# 内部处理系统操作类型选择最优解
observer = Observer(timeout=10)
self._observer.append(observer)
observer.schedule(FileMonitorHandler(mon_path, self), path=mon_path, recursive=True)
observer.daemon = True
observer.start()
logger.info(f"{mon_path} 的目录监控服务启动")
except Exception as e:
err_msg = str(e)
if "inotify" in err_msg and "reached" in err_msg:
logger.warn(
f"目录监控服务启动出现异常:{err_msg}请在宿主机上不是docker容器内执行以下命令并重启"
+ """
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
echo fs.inotify.max_user_instances=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
""")
else:
logger.error(f"{mon_path} 启动目录监控失败:{err_msg}")
self.systemmessage.put(f"{mon_path} 启动目录监控失败:{err_msg}")
try:
if self._mode == "compatibility":
# 兼容模式目录同步性能降低且NAS不能休眠但可以兼容挂载的远程共享目录如SMB
observer = PollingObserver(timeout=10)
else:
# 内部处理系统操作类型选择最优解
observer = Observer(timeout=10)
self._observer.append(observer)
observer.schedule(FileMonitorHandler(mon_path, self), path=mon_path, recursive=True)
observer.daemon = True
observer.start()
logger.info(f"{mon_path} 的目录监控服务启动")
except Exception as e:
err_msg = str(e)
if "inotify" in err_msg and "reached" in err_msg:
logger.warn(
f"目录监控服务启动出现异常:{err_msg}请在宿主机上不是docker容器内执行以下命令并重启"
+ """
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
echo fs.inotify.max_user_instances=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
""")
else:
logger.error(f"{mon_path} 启动目录监控失败:{err_msg}")
self.systemmessage.put(f"{mon_path} 启动目录监控失败:{err_msg}")
# 追加入库消息统一发送服务
self._scheduler.add_job(self.send_msg, trigger='interval', seconds=15)
# 启动服务
self._scheduler.print_jobs()
self._scheduler.start()
# 运行一次定时服务
if self._onlyonce:
logger.info("目录监控服务启动,立即运行一次")
self._scheduler.add_job(func=self.sync_all, trigger='date',
run_date=datetime.datetime.now(
tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3)
)
# 关闭一次性开关
self._onlyonce = False
# 保存配置
self.__update_config()
# 启动定时服务
if self._scheduler.get_jobs():
self._scheduler.print_jobs()
self._scheduler.start()
def __update_config(self):
"""
更新配置
"""
self.update_config({
"enabled": self._enabled,
"notify": self._notify,
"onlyonce": self._onlyonce,
"mode": self._mode,
"transfer_type": self._transfer_type,
"monitor_dirs": self._monitor_dirs,
"exclude_keywords": self._exclude_keywords
})
@eventmanager.register(EventType.DirectorySync)
def remote_sync(self, event: Event):
"""
远程全量同步
"""
if event:
self.post_message(channel=event.event_data.get("channel"),
title="开始同步监控目录 ...",
userid=event.event_data.get("user"))
self.sync_all()
if event:
self.post_message(channel=event.event_data.get("channel"),
title="监控目录同步完成!", userid=event.event_data.get("user"))
def sync_all(self):
"""
立即运行一次,全量同步目录中所有文件
"""
logger.info("开始全量同步监控目录 ...")
# 遍历所有监控目录
for mon_path in self._dirconf.keys():
# 遍历目录下所有文件
for file_path in SystemUtils.list_files(Path(mon_path), settings.RMT_MEDIAEXT):
self.__handle_file(event_path=str(file_path), mon_path=mon_path)
logger.info("全量同步监控目录完成!")
def event_handler(self, event, mon_path: str, text: str, event_path: str):
"""
@@ -204,141 +263,136 @@ class DirMonitor(_PluginBase):
"""
if not event.is_directory:
# 文件发生变化
file_path = Path(event_path)
try:
if not file_path.exists():
logger.debug("文件%s%s" % (text, event_path))
self.__handle_file(event_path=event_path, mon_path=mon_path)
def __handle_file(self, event_path: str, mon_path: str):
"""
同步一个文件
:param event_path: 事件文件路径
:param mon_path: 监控目录
"""
file_path = Path(event_path)
try:
if not file_path.exists():
return
# 全程加锁
with lock:
transfer_history = self.transferhis.get_by_src(event_path)
if transfer_history:
logger.debug("文件已处理过:%s" % event_path)
return
logger.debug("文件%s%s" % (text, event_path))
# 回收站及隐藏的文件不处理
if event_path.find('/@Recycle/') != -1 \
or event_path.find('/#recycle/') != -1 \
or event_path.find('/.') != -1 \
or event_path.find('/@eaDir') != -1:
logger.debug(f"{event_path} 是回收站或隐藏的文件")
return
# 全程加锁
with lock:
transfer_history = self.transferhis.get_by_src(event_path)
if transfer_history:
logger.debug("文件已处理过:%s" % event_path)
return
# 命中过滤关键字不处理
if self._exclude_keywords:
for keyword in self._exclude_keywords.split("\n"):
if keyword and re.findall(keyword, event_path):
logger.info(f"{event_path} 命中过滤关键字 {keyword},不处理")
return
# 回收站及隐藏的文件不处理
if event_path.find('/@Recycle/') != -1 \
or event_path.find('/#recycle/') != -1 \
or event_path.find('/.') != -1 \
or event_path.find('/@eaDir') != -1:
logger.debug(f"{event_path} 是回收站或隐藏的文件")
return
# 整理屏蔽词不处理
transfer_exclude_words = self.systemconfig.get(SystemConfigKey.TransferExcludeWords)
if transfer_exclude_words:
for keyword in transfer_exclude_words:
if not keyword:
continue
if keyword and re.search(r"%s" % keyword, event_path, re.IGNORECASE):
logger.info(f"{event_path} 命中整理屏蔽词 {keyword},不处理")
return
# 命中过滤关键字不处理
if self._exclude_keywords:
for keyword in self._exclude_keywords.split("\n"):
if keyword and re.findall(keyword, event_path):
logger.info(f"{event_path} 命中过滤关键字 {keyword},不处理")
return
# 不是媒体文件不处理
if file_path.suffix not in settings.RMT_MEDIAEXT:
logger.debug(f"{event_path} 不是媒体文件")
return
# 整理屏蔽词不处理
transfer_exclude_words = self.systemconfig.get(SystemConfigKey.TransferExcludeWords)
if transfer_exclude_words:
for keyword in transfer_exclude_words:
if not keyword:
continue
if keyword and re.search(r"%s" % keyword, event_path, re.IGNORECASE):
logger.info(f"{event_path} 命中整理屏蔽词 {keyword},不处理")
return
# 元数据
file_meta = MetaInfoPath(file_path)
if not file_meta.name:
logger.error(f"{file_path.name} 无法识别有效信息")
return
# 不是媒体文件不处理
if file_path.suffix not in settings.RMT_MEDIAEXT:
logger.debug(f"{event_path} 不是媒体文件")
return
# 判断是不是蓝光目录
if re.search(r"BDMV[/\\]STREAM", event_path, re.IGNORECASE):
# 截取BDMV前面的路径
event_path = event_path[:event_path.find("BDMV")]
file_path = Path(event_path)
# 查询历史记录,已转移的不处理
if self.transferhis.get_by_src(event_path):
logger.info(f"{event_path} 已整理过")
return
# 查询历史记录,已转移的不处理
if self.transferhis.get_by_src(event_path):
logger.info(f"{event_path} 已整理过")
return
# 元数据
file_meta = MetaInfoPath(file_path)
if not file_meta.name:
logger.error(f"{file_path.name} 无法识别有效信息")
return
# 查询转移目的目录
target: Path = self._dirconf.get(mon_path)
# 查询转移方式
transfer_type = self._transferconf.get(mon_path)
# 根据父路径获取下载历史
download_history = self.downloadhis.get_by_path(Path(event_path).parent)
# 识别媒体信息
mediainfo: MediaInfo = self.chain.recognize_media(meta=file_meta,
tmdbid=download_history.tmdbid if download_history else None)
if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{file_meta.name}')
# 新增转移成功历史记录
his = self.transferhis.add_fail(
src_path=file_path,
mode=transfer_type,
meta=file_meta
)
if self._notify:
self.chain.post_message(Notification(
mtype=NotificationType.Manual,
title=f"{file_path.name} 未识别到媒体信息,无法入库!\n"
f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。"
))
return
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
if not settings.SCRAP_FOLLOW_TMDB:
transfer_history = self.transferhis.get_by_type_tmdbid(tmdbid=mediainfo.tmdb_id,
mtype=mediainfo.type.value)
if transfer_history:
mediainfo.title = transfer_history.title
logger.info(f"{file_path.name} 识别为:{mediainfo.type.value} {mediainfo.title_year}")
# 更新媒体图片
self.chain.obtain_images(mediainfo=mediainfo)
# 获取集数据
if mediainfo.type == MediaType.TV:
episodes_info = self.tmdbchain.tmdb_episodes(tmdbid=mediainfo.tmdb_id,
season=file_meta.begin_season or 1)
else:
episodes_info = None
# 获取downloadhash
download_hash = self.get_download_hash(src=str(file_path))
# 转移
transferinfo: TransferInfo = self.chain.transfer(mediainfo=mediainfo,
path=file_path,
transfer_type=transfer_type,
target=target,
meta=file_meta,
episodes_info=episodes_info)
if not transferinfo:
logger.error("文件转移模块运行失败")
return
if not transferinfo.success:
# 转移失败
logger.warn(f"{file_path.name} 入库失败:{transferinfo.message}")
# 新增转移失败历史记录
self.transferhis.add_fail(
src_path=file_path,
mode=transfer_type,
download_hash=download_hash,
meta=file_meta,
mediainfo=mediainfo,
transferinfo=transferinfo
)
if self._notify:
self.chain.post_message(Notification(
title=f"{mediainfo.title_year}{file_meta.season_episode} 入库失败!",
text=f"原因:{transferinfo.message or '未知'}",
image=mediainfo.get_message_image()
))
return
# 查询转移目的目录
target: Path = self._dirconf.get(mon_path)
# 查询转移方式
transfer_type = self._transferconf.get(mon_path)
# 根据父路径获取下载历史
download_history = self.downloadhis.get_by_path(Path(event_path).parent)
# 识别媒体信息
mediainfo: MediaInfo = self.chain.recognize_media(meta=file_meta,
tmdbid=download_history.tmdbid if download_history else None)
if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{file_meta.name}')
# 新增转移成功历史记录
self.transferhis.add_success(
his = self.transferhis.add_fail(
src_path=file_path,
mode=transfer_type,
meta=file_meta
)
if self._notify:
self.chain.post_message(Notification(
mtype=NotificationType.Manual,
title=f"{file_path.name} 未识别到媒体信息,无法入库!\n"
f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。"
))
return
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
if not settings.SCRAP_FOLLOW_TMDB:
transfer_history = self.transferhis.get_by_type_tmdbid(tmdbid=mediainfo.tmdb_id,
mtype=mediainfo.type.value)
if transfer_history:
mediainfo.title = transfer_history.title
logger.info(f"{file_path.name} 识别为:{mediainfo.type.value} {mediainfo.title_year}")
# 更新媒体图片
self.chain.obtain_images(mediainfo=mediainfo)
# 获取集数据
if mediainfo.type == MediaType.TV:
episodes_info = self.tmdbchain.tmdb_episodes(tmdbid=mediainfo.tmdb_id,
season=file_meta.begin_season or 1)
else:
episodes_info = None
# 获取downloadhash
download_hash = self.get_download_hash(src=str(file_path))
# 转移
transferinfo: TransferInfo = self.chain.transfer(mediainfo=mediainfo,
path=file_path,
transfer_type=transfer_type,
target=target,
meta=file_meta,
episodes_info=episodes_info)
if not transferinfo:
logger.error("文件转移模块运行失败")
return
if not transferinfo.success:
# 转移失败
logger.warn(f"{file_path.name} 入库失败:{transferinfo.message}")
# 新增转移失败历史记录
self.transferhis.add_fail(
src_path=file_path,
mode=transfer_type,
download_hash=download_hash,
@@ -346,95 +400,112 @@ class DirMonitor(_PluginBase):
mediainfo=mediainfo,
transferinfo=transferinfo
)
if self._notify:
self.chain.post_message(Notification(
title=f"{mediainfo.title_year}{file_meta.season_episode} 入库失败!",
text=f"原因:{transferinfo.message or '未知'}",
image=mediainfo.get_message_image()
))
return
# 刮削单个文件
if settings.SCRAP_METADATA:
self.chain.scrape_metadata(path=transferinfo.target_path,
mediainfo=mediainfo,
transfer_type=transfer_type)
# 新增转移成功历史记录
self.transferhis.add_success(
src_path=file_path,
mode=transfer_type,
download_hash=download_hash,
meta=file_meta,
mediainfo=mediainfo,
transferinfo=transferinfo
)
"""
{
"title_year season": {
"files": [
{
"path":,
"mediainfo":,
"file_meta":,
"transferinfo":
}
],
"time": "2023-08-24 23:23:23.332"
}
# 刮削单个文件
if settings.SCRAP_METADATA:
self.chain.scrape_metadata(path=transferinfo.target_path,
mediainfo=mediainfo,
transfer_type=transfer_type)
"""
{
"title_year season": {
"files": [
{
"path":,
"mediainfo":,
"file_meta":,
"transferinfo":
}
],
"time": "2023-08-24 23:23:23.332"
}
"""
# 发送消息汇总
media_list = self._medias.get(mediainfo.title_year + " " + file_meta.season) or {}
if media_list:
media_files = media_list.get("files") or []
if media_files:
file_exists = False
for file in media_files:
if str(event_path) == file.get("path"):
file_exists = True
break
if not file_exists:
media_files.append({
"path": event_path,
"mediainfo": mediainfo,
"file_meta": file_meta,
"transferinfo": transferinfo
})
else:
media_files = [
{
"path": event_path,
"mediainfo": mediainfo,
"file_meta": file_meta,
"transferinfo": transferinfo
}
]
media_list = {
"files": media_files,
"time": datetime.now()
}
else:
media_list = {
"files": [
}
"""
# 发送消息汇总
media_list = self._medias.get(mediainfo.title_year + " " + file_meta.season) or {}
if media_list:
media_files = media_list.get("files") or []
if media_files:
file_exists = False
for file in media_files:
if str(event_path) == file.get("path"):
file_exists = True
break
if not file_exists:
media_files.append({
"path": event_path,
"mediainfo": mediainfo,
"file_meta": file_meta,
"transferinfo": transferinfo
})
else:
media_files = [
{
"path": event_path,
"mediainfo": mediainfo,
"file_meta": file_meta,
"transferinfo": transferinfo
}
],
"time": datetime.now()
}
self._medias[mediainfo.title_year + " " + file_meta.season] = media_list
]
media_list = {
"files": media_files,
"time": datetime.datetime.now()
}
else:
media_list = {
"files": [
{
"path": event_path,
"mediainfo": mediainfo,
"file_meta": file_meta,
"transferinfo": transferinfo
}
],
"time": datetime.datetime.now()
}
self._medias[mediainfo.title_year + " " + file_meta.season] = media_list
# 汇总刷新媒体库
if settings.REFRESH_MEDIASERVER:
self.chain.refresh_mediaserver(mediainfo=mediainfo, file_path=transferinfo.target_path)
# 广播事件
self.eventmanager.send_event(EventType.TransferComplete, {
'meta': file_meta,
'mediainfo': mediainfo,
'transferinfo': transferinfo
})
# 汇总刷新媒体库
if settings.REFRESH_MEDIASERVER:
self.chain.refresh_mediaserver(mediainfo=mediainfo, file_path=transferinfo.target_path)
# 广播事件
self.eventmanager.send_event(EventType.TransferComplete, {
'meta': file_meta,
'mediainfo': mediainfo,
'transferinfo': transferinfo
})
# 移动模式删除空目录
if transfer_type == "move":
for file_dir in file_path.parents:
if len(str(file_dir)) <= len(str(Path(mon_path))):
# 重要,删除到监控目录为止
break
files = SystemUtils.list_files(file_dir, settings.RMT_MEDIAEXT)
if not files:
logger.warn(f"移动模式,删除空目录:{file_dir}")
shutil.rmtree(file_dir, ignore_errors=True)
# 移动模式删除空目录
if transfer_type == "move":
for file_dir in file_path.parents:
if len(str(file_dir)) <= len(str(Path(mon_path))):
# 重要,删除到监控目录为止
break
files = SystemUtils.list_files(file_dir, settings.RMT_MEDIAEXT)
if not files:
logger.warn(f"移动模式,删除空目录:{file_dir}")
shutil.rmtree(file_dir, ignore_errors=True)
except Exception as e:
logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc()))
except Exception as e:
logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc()))
def send_msg(self):
"""
@@ -461,7 +532,7 @@ class DirMonitor(_PluginBase):
file_meta = media_files[0].get("file_meta")
mediainfo = media_files[0].get("mediainfo")
# 判断最后更新时间距现在是已超过5秒超过则发送消息
if (datetime.now() - last_update_time).total_seconds() > 5:
if (datetime.datetime.now() - last_update_time).total_seconds() > 5:
# 发送通知
if self._notify:
@@ -513,7 +584,17 @@ class DirMonitor(_PluginBase):
@staticmethod
def get_command() -> List[Dict[str, Any]]:
pass
"""
定义远程控制命令
:return: 命令关键字、事件、描述、附带数据
"""
return [{
"cmd": "/directory_sync",
"event": EventType.DirectorySync,
"desc": "目录监控同步",
"category": "管理",
"data": {}
}]
def get_api(self) -> List[Dict[str, Any]]:
pass
@@ -530,7 +611,7 @@ class DirMonitor(_PluginBase):
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
'md': 4
},
'content': [
{
@@ -546,7 +627,7 @@ class DirMonitor(_PluginBase):
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
'md': 4
},
'content': [
{
@@ -557,6 +638,22 @@ class DirMonitor(_PluginBase):
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
},
@@ -622,11 +719,11 @@ class DirMonitor(_PluginBase):
'model': 'monitor_dirs',
'label': '监控目录',
'rows': 5,
'placeholder': '每一行一个目录,支持种配置方式:\n'
'placeholder': '每一行一个目录,支持以下几种配置方式,转移方式支持 move、copy、link、softlink、rclone_copy、rclone_move\n'
'监控目录\n'
'监控目录#转移方式move|copy|link|softlink|rclone_copy|rclone_move\n'
'监控目录:转移目的目录(需同时在媒体库目录中配置该目的目录)\n'
'监控目录:转移目的目录#转移方式move|copy|link|softlink|rclone_copy|rclone_move'
'监控目录#转移方式\n'
'监控目录:转移目的目录\n'
'监控目录:转移目的目录#转移方式'
}
}
]
@@ -660,6 +757,7 @@ class DirMonitor(_PluginBase):
], {
"enabled": False,
"notify": False,
"onlyonce": False,
"mode": "fast",
"transfer_type": settings.TRANSFER_TYPE,
"monitor_dirs": "",

View File

@@ -12,6 +12,7 @@ class TransferTorrent(BaseModel):
path: Optional[Path] = None
hash: Optional[str] = None
tags: Optional[str] = None
userid: Optional[str] = None
class DownloadingTorrent(BaseModel):
@@ -29,6 +30,7 @@ class DownloadingTorrent(BaseModel):
upspeed: Optional[str] = None
dlspeed: Optional[str] = None
media: Optional[dict] = {}
userid: Optional[str] = None
class TransferInfo(BaseModel):

View File

@@ -44,6 +44,8 @@ class EventType(Enum):
NameRecognize = "name.recognize"
# 名称识别结果
NameRecognizeResult = "name.recognize.result"
# 目录监控同步
DirectorySync = "directory.sync"
# 系统配置Key字典

View File

@@ -1 +1 @@
APP_VERSION = 'v1.3.3'
APP_VERSION = 'v1.3.4'