Merge pull request #183 from thsrite/main

This commit is contained in:
jxxghp 2023-08-19 13:43:41 +08:00 committed by GitHub
commit fe58edd42e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 447 additions and 346 deletions

View File

@ -42,3 +42,16 @@ class DownloadHistoryOper(DbOper):
清空下载记录 清空下载记录
""" """
DownloadHistory.truncate(self._db) 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:
"""
按类型标题年份季集查询下载记录
"""
return DownloadHistory.get_last_by(db=self._db,
mtype=mtype,
title=title,
year=year,
season=season,
episode=episode,
tmdbid=tmdbid)

View File

@ -49,3 +49,43 @@ class DownloadHistory(Base):
@staticmethod @staticmethod
def get_by_path(db: Session, path: str): def get_by_path(db: Session, path: str):
return db.query(DownloadHistory).filter(DownloadHistory.path == path).first() return db.query(DownloadHistory).filter(DownloadHistory.path == path).first()
@staticmethod
def get_last_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None,
episode: str = None, tmdbid: str = None):
"""
据tmdbidseasonseason_episode查询转移记录
"""
if tmdbid and not season and not episode:
return db.query(DownloadHistory).filter(DownloadHistory.tmdbid == tmdbid).order_by(
DownloadHistory.id.desc()).first()
if tmdbid and season and not episode:
return db.query(DownloadHistory).filter(DownloadHistory.tmdbid == tmdbid,
DownloadHistory.seasons == season).order_by(
DownloadHistory.id.desc()).first()
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()
# 电视剧所有季集|电影
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()
# 电视剧某季
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()
# 电视剧某季某集
if season and episode:
return db.query(DownloadHistory).filter(DownloadHistory.type == mtype,
DownloadHistory.title == title,
DownloadHistory.year == year,
DownloadHistory.seasons == season,
DownloadHistory.episodes == episode).order_by(
DownloadHistory.id.desc()).first()

View File

@ -50,7 +50,7 @@ class TransferHistoryOper(DbOper):
""" """
return TransferHistory.statistic(self._db, days) return TransferHistory.statistic(self._db, days)
def get_by(self, mtype: str = None, title: str = None, year: int = None, def get_by(self, mtype: str = None, title: str = None, year: str = None,
season: str = None, episode: str = None, tmdbid: str = None) -> Any: season: str = None, episode: str = None, tmdbid: str = None) -> Any:
""" """
按类型标题年份季集查询转移记录 按类型标题年份季集查询转移记录

View File

@ -12,6 +12,7 @@ from app.chain.transfer import TransferChain
from app.core.config import settings from app.core.config import settings
from app.core.context import MediaInfo from app.core.context import MediaInfo
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfo
from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper from app.db.transferhistory_oper import TransferHistoryOper
from app.log import logger from app.log import logger
from app.plugins import _PluginBase from app.plugins import _PluginBase
@ -65,6 +66,7 @@ class DirMonitor(_PluginBase):
# 私有属性 # 私有属性
transferhis = None transferhis = None
downloadhis = None
transferchian = None transferchian = None
_observer = [] _observer = []
_enabled = False _enabled = False
@ -78,6 +80,7 @@ class DirMonitor(_PluginBase):
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self.transferhis = TransferHistoryOper() self.transferhis = TransferHistoryOper()
self.downloadhis = DownloadHistoryOper()
self.transferchian = TransferChain() self.transferchian = TransferChain()
# 读取配置 # 读取配置
@ -121,8 +124,9 @@ class DirMonitor(_PluginBase):
except Exception as e: except Exception as e:
err_msg = str(e) err_msg = str(e)
if "inotify" in err_msg and "reached" in err_msg: if "inotify" in err_msg and "reached" in err_msg:
logger.warn(f"目录监控服务启动出现异常:{err_msg}请在宿主机上不是docker容器内执行以下命令并重启" logger.warn(
+ """ f"目录监控服务启动出现异常:{err_msg}请在宿主机上不是docker容器内执行以下命令并重启"
+ """
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf 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 echo fs.inotify.max_user_instances=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p sudo sysctl -p
@ -226,6 +230,13 @@ class DirMonitor(_PluginBase):
)) ))
return 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)
# 新增转移成功历史记录 # 新增转移成功历史记录
self.transferhis.add( self.transferhis.add(
src=event_path, src=event_path,
@ -242,6 +253,7 @@ class DirMonitor(_PluginBase):
seasons=file_meta.season, seasons=file_meta.season,
episodes=file_meta.episode, episodes=file_meta.episode,
image=mediainfo.get_poster_image(), image=mediainfo.get_poster_image(),
download_hash=downloadHis.download_hash if downloadHis else None,
status=1 status=1
) )
@ -251,7 +263,8 @@ class DirMonitor(_PluginBase):
self.chain.refresh_mediaserver(mediainfo=mediainfo, file_path=transferinfo.target_path) self.chain.refresh_mediaserver(mediainfo=mediainfo, file_path=transferinfo.target_path)
# 发送通知 # 发送通知
if self._notify: if self._notify:
self.transferchian.send_transfer_message(meta=file_meta, mediainfo=mediainfo, transferinfo=transferinfo) self.transferchian.send_transfer_message(meta=file_meta, mediainfo=mediainfo,
transferinfo=transferinfo)
# 广播事件 # 广播事件
self.eventmanager.send_event(EventType.TransferComplete, { self.eventmanager.send_event(EventType.TransferComplete, {
'meta': file_meta, 'meta': file_meta,
@ -274,147 +287,147 @@ class DirMonitor(_PluginBase):
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
return [ return [
{ {
'component': 'VForm', 'component': 'VForm',
'content': [ 'content': [
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 6 'md': 6
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'enabled', 'model': 'enabled',
'label': '启用插件', 'label': '启用插件',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 6 'md': 6
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'notify', 'model': 'notify',
'label': '发送通知', 'label': '发送通知',
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 6 'md': 6
}, },
'content': [ 'content': [
{ {
'component': 'VSelect', 'component': 'VSelect',
'props': { 'props': {
'model': 'mode', 'model': 'mode',
'label': '监控模式', 'label': '监控模式',
'items': [ 'items': [
{'title': '兼容模式', 'value': 'compatibility'}, {'title': '兼容模式', 'value': 'compatibility'},
{'title': '性能模式', 'value': 'fast'} {'title': '性能模式', 'value': 'fast'}
] ]
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 6 'md': 6
}, },
'content': [ 'content': [
{ {
'component': 'VSelect', 'component': 'VSelect',
'props': { 'props': {
'model': 'transfer_type', 'model': 'transfer_type',
'label': '转移方式', 'label': '转移方式',
'items': [ 'items': [
{'title': '移动', 'value': 'move'}, {'title': '移动', 'value': 'move'},
{'title': '复制', 'value': 'copy'}, {'title': '复制', 'value': 'copy'},
{'title': '硬链接', 'value': 'link'}, {'title': '硬链接', 'value': 'link'},
{'title': '软链接', 'value': 'softlink'} {'title': '软链接', 'value': 'softlink'}
] ]
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12 'cols': 12
}, },
'content': [ 'content': [
{ {
'component': 'VTextarea', 'component': 'VTextarea',
'props': { 'props': {
'model': 'monitor_dirs', 'model': 'monitor_dirs',
'label': '监控目录', 'label': '监控目录',
'rows': 5, 'rows': 5,
'placeholder': '每一行一个目录' 'placeholder': '每一行一个目录'
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12 'cols': 12
}, },
'content': [ 'content': [
{ {
'component': 'VTextarea', 'component': 'VTextarea',
'props': { 'props': {
'model': 'exclude_keywords', 'model': 'exclude_keywords',
'label': '排除关键词', 'label': '排除关键词',
'rows': 2, 'rows': 2,
'placeholder': '每一行一个关键词' 'placeholder': '每一行一个关键词'
} }
} }
] ]
} }
] ]
} }
] ]
} }
], { ], {
"enabled": False, "enabled": False,
"notify": False, "notify": False,
"mode": "fast", "mode": "fast",
"transfer_type": settings.TRANSFER_TYPE, "transfer_type": settings.TRANSFER_TYPE,
"monitor_dirs": "", "monitor_dirs": "",
"exclude_keywords": "" "exclude_keywords": ""
} }
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
pass pass

View File

@ -112,150 +112,150 @@ class MediaSyncDel(_PluginBase):
拼装插件配置页面需要返回两块数据1页面配置2数据结构 拼装插件配置页面需要返回两块数据1页面配置2数据结构
""" """
return [ return [
{ {
'component': 'VForm', 'component': 'VForm',
'content': [ 'content': [
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 4 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'enabled', 'model': 'enabled',
'label': '启用插件', 'label': '启用插件',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 4 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'notify', 'model': 'notify',
'label': '发送通知', 'label': '发送通知',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 4 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'del_source', 'model': 'del_source',
'label': '删除源文件', 'label': '删除源文件',
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 4 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VSelect', 'component': 'VSelect',
'props': { 'props': {
'model': 'sync_type', 'model': 'sync_type',
'label': '同步方式', 'label': '同步方式',
'items': [ 'items': [
{'title': '日志', 'value': 'log'}, {'title': '日志', 'value': 'log'},
{'title': 'Scripter X', 'value': 'plugin'} {'title': 'Scripter X', 'value': 'plugin'}
] ]
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 4 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VTextField', 'component': 'VTextField',
'props': { 'props': {
'model': 'cron', 'model': 'cron',
'label': '执行周期', 'label': '执行周期',
'placeholder': '5位cron表达式留空自动' 'placeholder': '5位cron表达式留空自动'
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 4 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VTextField', 'component': 'VTextField',
'props': { 'props': {
'model': 'exclude_path', 'model': 'exclude_path',
'label': '排除路径' 'label': '排除路径'
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
}, },
'content': [ 'content': [
{ {
'component': 'VAlert', 'component': 'VAlert',
'props': { 'props': {
'text': '同步方式分为日志同步和Scripter X。日志同步需要配置执行周期默认30分钟执行一次。' 'text': '同步方式分为日志同步和Scripter X。日志同步需要配置执行周期默认30分钟执行一次。'
'Scripter X方式需要emby安装并配置Scripter X插件无需配置执行周期。' 'Scripter X方式需要emby安装并配置Scripter X插件无需配置执行周期。'
} }
} }
] ]
} }
] ]
} }
] ]
} }
], { ], {
"enabled": False, "enabled": False,
"notify": True, "notify": True,
"del_source": False, "del_source": False,
"sync_type": "log", "sync_type": "log",
"cron": "*/30 * * * *", "cron": "*/30 * * * *",
"exclude_path": "", "exclude_path": "",
} }
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
""" """
@ -286,6 +286,83 @@ class MediaSyncDel(_PluginBase):
image = history.get("image") image = history.get("image")
del_time = history.get("del_time") del_time = history.get("del_time")
if season:
sub_contents = [
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'类型:{htype}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'标题:{title}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'年份:{year}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'季:{season}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'集:{episode}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'时间:{del_time}'
}
]
else:
sub_contents = [
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'类型:{htype}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'标题:{title}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'年份:{year}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'时间:{del_time}'
}
]
contents.append( contents.append(
{ {
'component': 'VCard', 'component': 'VCard',
@ -314,58 +391,7 @@ class MediaSyncDel(_PluginBase):
}, },
{ {
'component': 'div', 'component': 'div',
'content': [ 'content': sub_contents
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'类型:{htype}'
},
{
'component': 'VCardSubtitle',
'props': {
'class': 'pa-2 font-bold break-words whitespace-break-spaces'
},
'content': [
{
'component': 'a',
'props': {
'class': 'pa-0 px-2'
},
'text': title
}
]
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'年份:{year}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'季:{season}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'集:{episode}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'时间:{del_time}'
}
]
} }
] ]
} }
@ -510,9 +536,11 @@ class MediaSyncDel(_PluginBase):
if self._notify: if self._notify:
if media_type == "Episode": if media_type == "Episode":
# 根据tmdbid获取图片 # 根据tmdbid获取图片
image = self.episode.images(tv_id=tmdb_id, images = self.episode.images(tv_id=tmdb_id,
season_num=season_num, season_num=season_num,
episode_num=episode_num) episode_num=episode_num)
if images:
image = self.get_tmdbimage_url(images[-1].get("file_path"), prefix="original")
# 发送通知 # 发送通知
self.post_message( self.post_message(
mtype=NotificationType.MediaServer, mtype=NotificationType.MediaServer,
@ -530,7 +558,7 @@ class MediaSyncDel(_PluginBase):
"season": season_num, "season": season_num,
"episode": episode_num, "episode": episode_num,
"image": image, "image": image,
"del_time": str(datetime.datetime.now()) "del_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
}) })
# 保存历史 # 保存历史
@ -671,7 +699,7 @@ class MediaSyncDel(_PluginBase):
"season": media_season, "season": media_season,
"episode": media_episode, "episode": media_episode,
"image": image, "image": image,
"del_time": del_time "del_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
}) })
# 保存历史 # 保存历史
@ -1034,3 +1062,10 @@ class MediaSyncDel(_PluginBase):
if event: if event:
self.post_message(channel=event.event_data.get("channel"), self.post_message(channel=event.event_data.get("channel"),
title="媒体库同步删除完成!", userid=event.event_data.get("user")) title="媒体库同步删除完成!", userid=event.event_data.get("user"))
@staticmethod
def get_tmdbimage_url(path, prefix="w500"):
if not path:
return ""
tmdb_image_url = f"https://{settings.TMDB_IMAGE_DOMAIN}"
return tmdb_image_url + f"/t/p/{prefix}{path}"