feat 文件复制v1.0

This commit is contained in:
thsrite
2024-07-17 17:08:17 +08:00
parent 7fa32adf7b
commit 0770e6fed2
4 changed files with 1028 additions and 659 deletions

View File

@@ -47,3 +47,4 @@ MoviePilot三方插件市场https://github.com/thsrite/MoviePilot-Plugins/
- 云盘同步删除 v1.4
- 媒体库重复媒体检测 v1.9
- 演员作品订阅 v1.0
- 文件复制 v1.0

BIN
icons/copy_files.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,356 @@
import datetime
import random
import threading
import time
from pathlib import Path
from typing import List, Tuple, Dict, Any
import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.core.config import settings
from app.log import logger
from app.plugins import _PluginBase
from app.utils.system import SystemUtils
lock = threading.Lock()
class FileCopy(_PluginBase):
# 插件名称
plugin_name = "文件复制"
# 插件描述
plugin_desc = "自定义文件类型从源目录复制到目的目录。"
# 插件图标
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/copy_files.png"
# 插件版本
plugin_version = "1.0"
# 插件作者
plugin_author = "thsrite"
# 作者主页
author_url = "https://github.com/thsrite"
# 插件配置项ID前缀
plugin_config_prefix = "filecopy_"
# 加载顺序
plugin_order = 30
# 可使用的用户级别
auth_level = 1
# 私有属性
_scheduler = None
_enabled = False
_onlyonce = False
_cron = None
_delay = None
_monitor_dirs = ""
# 存储源目录与目的目录关系
_dirconf: Dict[str, Path] = {}
_rmt_mediaext = None
# 退出事件
_event = threading.Event()
def init_plugin(self, config: dict = None):
# 清空配置
self._dirconf = {}
# 读取配置
if config:
self._enabled = config.get("enabled")
self._onlyonce = config.get("onlyonce")
self._monitor_dirs = config.get("monitor_dirs") or ""
self._cron = config.get("cron")
self._delay = config.get("delay")
self._rmt_mediaext = config.get("rmt_mediaext") or ".nfo, .jpg"
# 停止现有任务
self.stop_service()
if self._enabled or self._onlyonce:
# 定时服务管理器
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
# 读取目录配置
monitor_dirs = self._monitor_dirs.split("\n")
if not monitor_dirs:
return
for mon_path in monitor_dirs:
# 格式源目录:目的目录
if not mon_path:
continue
# 存储目的目录
if SystemUtils.is_windows():
if mon_path.count(":") > 1:
paths = [mon_path.split(":")[0] + ":" + mon_path.split(":")[1],
mon_path.split(":")[2] + ":" + mon_path.split(":")[3]]
else:
paths = [mon_path]
else:
paths = mon_path.split(":")
# 目的目录
if len(paths) > 1:
mon_path = paths[0]
target_path = Path(paths[1])
self._dirconf[mon_path] = target_path
else:
self._dirconf[mon_path] = None
# 启用目录监控
if self._enabled:
self._scheduler.add_job(func=self.copy_files, trigger='date',
run_date=datetime.datetime.now(
tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3),
name=f"文件复制 {mon_path}")
# 运行一次定时服务
if self._onlyonce:
logger.info("文件复制服务启动,立即运行一次")
self._scheduler.add_job(name="文件复制", func=self.copy_files, 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 copy_files(self):
"""
定时任务,复制文件
"""
logger.info("开始全量复制监控目录 ...")
# 遍历所有监控目录
for mon_path in self._dirconf.keys():
target_path = self._dirconf[mon_path]
# 遍历目录下所有文件
files = SystemUtils.list_files(Path(mon_path), [ext.strip() for ext in self._rmt_mediaext.split(",")])
cnt = 0
for file in files:
logger.info(f"开始处理本地文件:{file}")
cloud_file = str(file).replace(mon_path, str(target_path))
if Path(cloud_file).exists():
logger.info(f"{cloud_file} 文件已存在,跳过")
continue
state, _ = SystemUtils.copy(file, Path(cloud_file))
print(f"{file} -> {cloud_file} {'成功' if state == 0 else '失败'}")
# 随机延时
if self._delay:
cnt += 1
delays = self._delay.split(",")
if cnt >= int(delays[0]):
if str(delays[1]).count("-") == 1:
wait_time = random.randint(int(str(delays[1]).split("-")[0]),
int(str(delays[1]).split("-")[1]))
logger.info(f"随机延迟 {wait_time}")
time.sleep(wait_time)
else:
delay = int(delays[1])
logger.info(f"延迟 {delay}")
time.sleep(delay)
cnt = 0
logger.info("全量复制监控目录完成!")
def __update_config(self):
"""
更新配置
"""
self.update_config({
"enabled": self._enabled,
"onlyonce": self._onlyonce,
"monitor_dirs": self._monitor_dirs,
"cron": self._cron,
"delay": self._delay,
"rmt_mediaext": self._rmt_mediaext
})
def get_state(self) -> bool:
return self._enabled
@staticmethod
def get_command() -> List[Dict[str, Any]]:
pass
def get_api(self) -> List[Dict[str, Any]]:
pass
def get_service(self) -> List[Dict[str, Any]]:
"""
注册插件公共服务
[{
"id": "服务ID",
"name": "服务名称",
"trigger": "触发器cron/interval/date/CronTrigger.from_crontab()",
"func": self.xxx,
"kwargs": {} # 定时器参数
}]
"""
if self._enabled and self._cron:
return [{
"id": "FileCopy",
"name": "文件复制",
"trigger": CronTrigger.from_crontab(self._cron),
"func": self.copy_files,
"kwargs": {}
}]
return []
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'enabled',
'label': '启用插件',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
},
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'cron',
'label': '定时全量同步周期',
'placeholder': '5位cron表达式留空关闭'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'delay',
'label': '随机延时',
'placeholder': '20,1-10 处理10个文件后随机延迟1-10秒'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'monitor_dirs',
'label': '监控目录',
'rows': 5,
'placeholder': '监控目录:转移目的目录'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'rmt_mediaext',
'label': '文件格式',
'rows': 2,
'placeholder': ".nfo, .jpg"
}
}
]
}
]
},
]
}
], {
"enabled": False,
"onlyonce": False,
"monitor_dirs": "",
"cron": "",
"delay": "20,1-10",
"rmt_mediaext": ".nfo, .jpg"
}
def get_page(self) -> List[dict]:
pass
def stop_service(self):
"""
退出插件
"""
if self._scheduler:
self._scheduler.remove_all_jobs()
if self._scheduler.running:
self._event.set()
self._scheduler.shutdown()
self._event.clear()
self._scheduler = None