fix 优化定时服务调度

This commit is contained in:
jxxghp 2023-09-24 12:41:59 +08:00
parent 41ff5363ea
commit 5cd48d5447
9 changed files with 268 additions and 207 deletions

View File

@ -11,9 +11,7 @@ from app.core.security import verify_token
from app.db import get_db from app.db import get_db
from app.db.models.transferhistory import TransferHistory from app.db.models.transferhistory import TransferHistory
from app.scheduler import Scheduler from app.scheduler import Scheduler
from app.utils.string import StringUtils
from app.utils.system import SystemUtils from app.utils.system import SystemUtils
from app.utils.timer import TimerUtils
router = APIRouter() router = APIRouter()
@ -83,37 +81,7 @@ def schedule(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
查询后台服务信息 查询后台服务信息
""" """
# 返回计时任务 return Scheduler().list()
schedulers = []
# 去重
added = []
jobs = Scheduler().list()
# 按照下次运行时间排序
jobs.sort(key=lambda x: x.next_run_time)
for job in jobs:
if job.name not in added:
added.append(job.name)
else:
continue
if not StringUtils.is_chinese(job.name):
continue
if not job.next_run_time:
status = "已停止"
next_run = ""
else:
next_run = TimerUtils.time_difference(job.next_run_time)
if not next_run:
status = "正在运行"
else:
status = "阻塞" if job.pending else "等待"
schedulers.append(schemas.ScheduleInfo(
id=job.id,
name=job.name,
status=status,
next_run=next_run
))
return schedulers
@router.get("/transfer", summary="文件整理统计", response_model=List[int]) @router.get("/transfer", summary="文件整理统计", response_model=List[int])

View File

@ -5,7 +5,6 @@ from sqlalchemy.orm import Session
from starlette.background import BackgroundTasks from starlette.background import BackgroundTasks
from app import schemas from app import schemas
from app.chain.cookiecloud import CookieCloudChain
from app.chain.site import SiteChain from app.chain.site import SiteChain
from app.chain.torrents import TorrentsChain from app.chain.torrents import TorrentsChain
from app.core.event import EventManager from app.core.event import EventManager
@ -15,19 +14,13 @@ from app.db.models.site import Site
from app.db.models.siteicon import SiteIcon from app.db.models.siteicon import SiteIcon
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.helper.sites import SitesHelper from app.helper.sites import SitesHelper
from app.scheduler import Scheduler
from app.schemas.types import SystemConfigKey, EventType from app.schemas.types import SystemConfigKey, EventType
from app.utils.string import StringUtils from app.utils.string import StringUtils
router = APIRouter() router = APIRouter()
def start_cookiecloud_sync(db: Session):
"""
后台启动CookieCloud站点同步
"""
CookieCloudChain(db).process(manual=True)
@router.get("/", summary="所有站点", response_model=List[schemas.Site]) @router.get("/", summary="所有站点", response_model=List[schemas.Site])
def read_sites(db: Session = Depends(get_db), def read_sites(db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> List[dict]: _: schemas.TokenPayload = Depends(verify_token)) -> List[dict]:
@ -101,12 +94,11 @@ def delete_site(
@router.get("/cookiecloud", summary="CookieCloud同步", response_model=schemas.Response) @router.get("/cookiecloud", summary="CookieCloud同步", response_model=schemas.Response)
def cookie_cloud_sync(background_tasks: BackgroundTasks, def cookie_cloud_sync(background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
运行CookieCloud同步站点信息 运行CookieCloud同步站点信息
""" """
background_tasks.add_task(start_cookiecloud_sync, db) background_tasks.add_task(Scheduler().start, job_id="cookiecloud")
return schemas.Response(success=True, message="CookieCloud同步任务已启动") return schemas.Response(success=True, message="CookieCloud同步任务已启动")
@ -119,7 +111,8 @@ def cookie_cloud_sync(db: Session = Depends(get_db),
Site.reset(db) Site.reset(db)
SystemConfigOper().set(SystemConfigKey.IndexerSites, []) SystemConfigOper().set(SystemConfigKey.IndexerSites, [])
SystemConfigOper().set(SystemConfigKey.RssSites, []) SystemConfigOper().set(SystemConfigKey.RssSites, [])
CookieCloudChain().process(manual=True) # 启动定时服务
Scheduler().start("cookiecloud", manual=True)
# 插件站点删除 # 插件站点删除
EventManager().send_event(EventType.SiteDeleted, EventManager().send_event(EventType.SiteDeleted,
{ {

View File

@ -1,5 +1,5 @@
import json import json
from typing import List, Any, Optional from typing import List, Any
from fastapi import APIRouter, Request, BackgroundTasks, Depends, HTTPException, Header from fastapi import APIRouter, Request, BackgroundTasks, Depends, HTTPException, Header
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -12,6 +12,7 @@ from app.db import get_db
from app.db.models.subscribe import Subscribe from app.db.models.subscribe import Subscribe
from app.db.models.user import User from app.db.models.user import User
from app.db.userauth import get_current_active_user from app.db.userauth import get_current_active_user
from app.scheduler import Scheduler
from app.schemas.types import MediaType from app.schemas.types import MediaType
router = APIRouter() router = APIRouter()
@ -26,13 +27,6 @@ def start_subscribe_add(db: Session, title: str, year: str,
mtype=mtype, tmdbid=tmdbid, season=season, username=username) mtype=mtype, tmdbid=tmdbid, season=season, username=username)
def start_subscribe_search(db: Session, sid: Optional[int], state: Optional[str]):
"""
启动订阅搜索任务
"""
SubscribeChain(db).search(sid=sid, state=state, manual=True)
@router.get("/", summary="所有订阅", response_model=List[schemas.Subscribe]) @router.get("/", summary="所有订阅", response_model=List[schemas.Subscribe])
def read_subscribes( def read_subscribes(
db: Session = Depends(get_db), db: Session = Depends(get_db),
@ -140,35 +134,36 @@ def subscribe_mediaid(
@router.get("/refresh", summary="刷新订阅", response_model=schemas.Response) @router.get("/refresh", summary="刷新订阅", response_model=schemas.Response)
def refresh_subscribes( def refresh_subscribes(
db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
刷新所有订阅 刷新所有订阅
""" """
SubscribeChain(db).refresh() Scheduler().start("subscribe_refresh")
return schemas.Response(success=True) return schemas.Response(success=True)
@router.get("/check", summary="刷新订阅 TMDB 信息", response_model=schemas.Response) @router.get("/check", summary="刷新订阅 TMDB 信息", response_model=schemas.Response)
def check_subscribes( def check_subscribes(
db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
刷新所有订阅 刷新订阅 TMDB 信息
""" """
SubscribeChain(db).check() Scheduler().start("subscribe_tmdb")
return schemas.Response(success=True) return schemas.Response(success=True)
@router.get("/search", summary="搜索所有订阅", response_model=schemas.Response) @router.get("/search", summary="搜索所有订阅", response_model=schemas.Response)
def search_subscribes( def search_subscribes(
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
搜索所有订阅 搜索所有订阅
""" """
background_tasks.add_task(start_subscribe_search, db=db, sid=None, state='R') background_tasks.add_task(
Scheduler().start,
job_id="subscribe_search",
sid=None, state='R'
)
return schemas.Response(success=True) return schemas.Response(success=True)
@ -176,12 +171,15 @@ def search_subscribes(
def search_subscribe( def search_subscribe(
subscribe_id: int, subscribe_id: int,
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
根据订阅编号搜索订阅 根据订阅编号搜索订阅
""" """
background_tasks.add_task(start_subscribe_search, db=db, sid=subscribe_id, state=None) background_tasks.add_task(
Scheduler().start,
job_id="subscribe_search",
sid=subscribe_id, state=None
)
return schemas.Response(success=True) return schemas.Response(success=True)

View File

@ -1,9 +1,9 @@
import json import json
import time import time
import tailer
from datetime import datetime from datetime import datetime
from typing import Union from typing import Union
import tailer
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -11,13 +11,13 @@ from sqlalchemy.orm import Session
from app import schemas from app import schemas
from app.chain.search import SearchChain from app.chain.search import SearchChain
from app.core.config import settings from app.core.config import settings
from app.core.event import eventmanager
from app.core.security import verify_token from app.core.security import verify_token
from app.db import get_db from app.db import get_db
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.helper.message import MessageHelper from app.helper.message import MessageHelper
from app.helper.progress import ProgressHelper from app.helper.progress import ProgressHelper
from app.schemas.types import SystemConfigKey, EventType from app.scheduler import Scheduler
from app.schemas.types import SystemConfigKey
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.system import SystemUtils from app.utils.system import SystemUtils
from version import APP_VERSION from version import APP_VERSION
@ -214,15 +214,13 @@ def restart_system(_: schemas.TokenPayload = Depends(verify_token)):
return schemas.Response(success=ret, message=msg) return schemas.Response(success=ret, message=msg)
@router.get("/command", summary="执行命令", response_model=schemas.Response) @router.get("/runscheduler", summary="运行服务", response_model=schemas.Response)
def execute_command(cmd: str, def execute_command(jobid: str,
_: schemas.TokenPayload = Depends(verify_token)): _: schemas.TokenPayload = Depends(verify_token)):
""" """
执行命令 执行命令
""" """
if not cmd: if not jobid:
return schemas.Response(success=False, message="命令不能为空!") return schemas.Response(success=False, message="命令不能为空!")
eventmanager.send_event(etype=EventType.CommandExcute, data={ Scheduler().start(jobid)
"cmd": cmd
})
return schemas.Response(success=True) return schemas.Response(success=True)

View File

@ -1,5 +1,5 @@
import base64 import base64
from typing import Tuple, Optional, Union from typing import Tuple, Optional
from urllib.parse import urljoin from urllib.parse import urljoin
from lxml import etree from lxml import etree
@ -16,7 +16,6 @@ from app.helper.message import MessageHelper
from app.helper.rss import RssHelper from app.helper.rss import RssHelper
from app.helper.sites import SitesHelper from app.helper.sites import SitesHelper
from app.log import logger from app.log import logger
from app.schemas import Notification, NotificationType, MessageChannel
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.site import SiteUtils from app.utils.site import SiteUtils
@ -40,21 +39,6 @@ class CookieCloudChain(ChainBase):
password=settings.COOKIECLOUD_PASSWORD password=settings.COOKIECLOUD_PASSWORD
) )
def remote_sync(self, channel: MessageChannel, userid: Union[int, str]):
"""
远程触发同步站点发送消息
"""
self.post_message(Notification(channel=channel, mtype=NotificationType.SiteMessage,
title="开始同步CookieCloud站点 ...", userid=userid))
# 开始同步
success, msg = self.process()
if success:
self.post_message(Notification(channel=channel, mtype=NotificationType.SiteMessage,
title=f"同步站点成功,{msg}", userid=userid))
else:
self.post_message(Notification(channel=channel, mtype=NotificationType.SiteMessage,
title=f"同步站点失败:{msg}", userid=userid))
def process(self, manual=False) -> Tuple[bool, str]: def process(self, manual=False) -> Tuple[bool, str]:
""" """
通过CookieCloud同步站点Cookie 通过CookieCloud同步站点Cookie

View File

@ -10,7 +10,6 @@ from app.core.config import settings
from app.db import SessionFactory from app.db import SessionFactory
from app.db.mediaserver_oper import MediaServerOper from app.db.mediaserver_oper import MediaServerOper
from app.log import logger from app.log import logger
from app.schemas import MessageChannel, Notification
lock = threading.Lock() lock = threading.Lock()
@ -41,16 +40,6 @@ class MediaServerChain(ChainBase):
""" """
return self.run_module("mediaserver_tv_episodes", server=server, item_id=item_id) return self.run_module("mediaserver_tv_episodes", server=server, item_id=item_id)
def remote_sync(self, channel: MessageChannel, userid: Union[int, str]):
"""
同步豆瓣想看数据发送消息
"""
self.post_message(Notification(channel=channel,
title="开始媒体服务器 ...", userid=userid))
self.sync()
self.post_message(Notification(channel=channel,
title="同步媒体服务器完成!", userid=userid))
def sync(self): def sync(self):
""" """
同步媒体库所有数据到本地数据库 同步媒体库所有数据到本地数据库

View File

@ -132,45 +132,6 @@ class SubscribeChain(ChainBase):
return True return True
return False return False
def remote_refresh(self, channel: MessageChannel, userid: Union[str, int] = None):
"""
远程刷新订阅发送消息
"""
self.post_message(Notification(channel=channel,
title=f"开始刷新订阅 ...", userid=userid))
self.refresh()
self.post_message(Notification(channel=channel,
title=f"订阅刷新完成!", userid=userid))
def remote_search(self, arg_str: str, channel: MessageChannel, userid: Union[str, int] = None):
"""
远程搜索订阅发送消息
"""
if arg_str and not str(arg_str).isdigit():
self.post_message(Notification(channel=channel,
title="请输入正确的命令格式:/subscribe_search [id]"
"[id]为订阅编号,不输入订阅编号时搜索所有订阅", userid=userid))
return
if arg_str:
sid = int(arg_str)
subscribe = self.subscribeoper.get(sid)
if not subscribe:
self.post_message(Notification(channel=channel,
title=f"订阅编号 {sid} 不存在!", userid=userid))
return
self.post_message(Notification(channel=channel,
title=f"开始搜索 {subscribe.name} ...", userid=userid))
# 搜索订阅
self.search(sid=int(arg_str))
self.post_message(Notification(channel=channel,
title=f"{subscribe.name} 搜索完成!", userid=userid))
else:
self.post_message(Notification(channel=channel,
title=f"开始搜索所有订阅 ...", userid=userid))
self.search(state='R')
self.post_message(Notification(channel=channel,
title=f"订阅搜索完成!", userid=userid))
def search(self, sid: int = None, state: str = 'N', manual: bool = False): def search(self, sid: int = None, state: str = 'N', manual: bool = False):
""" """
订阅搜索 订阅搜索

View File

@ -1,11 +1,9 @@
import traceback import traceback
from threading import Thread, Event from threading import Thread, Event
from typing import Any, Union from typing import Any, Union, Dict
from app.chain import ChainBase from app.chain import ChainBase
from app.chain.cookiecloud import CookieCloudChain
from app.chain.download import DownloadChain from app.chain.download import DownloadChain
from app.chain.mediaserver import MediaServerChain
from app.chain.site import SiteChain from app.chain.site import SiteChain
from app.chain.subscribe import SubscribeChain from app.chain.subscribe import SubscribeChain
from app.chain.system import SystemChain from app.chain.system import SystemChain
@ -15,6 +13,8 @@ from app.core.event import eventmanager, EventManager
from app.core.plugin import PluginManager from app.core.plugin import PluginManager
from app.db import SessionFactory from app.db import SessionFactory
from app.log import logger from app.log import logger
from app.scheduler import Scheduler
from app.schemas import Notification
from app.schemas.types import EventType, MessageChannel from app.schemas.types import EventType, MessageChannel
from app.utils.object import ObjectUtils from app.utils.object import ObjectUtils
from app.utils.singleton import Singleton from app.utils.singleton import Singleton
@ -49,13 +49,15 @@ class Command(metaclass=Singleton):
self.pluginmanager = PluginManager() self.pluginmanager = PluginManager()
# 处理链 # 处理链
self.chain = CommandChian(self._db) self.chain = CommandChian(self._db)
# 定时服务管理
self.scheduler = Scheduler()
# 内置命令 # 内置命令
self._commands = { self._commands = {
"/cookiecloud": { "/cookiecloud": {
"func": CookieCloudChain(self._db).remote_sync, "id": "cookiecloud",
"type": "scheduler",
"description": "同步站点", "description": "同步站点",
"category": "站点", "category": "站点"
"data": {}
}, },
"/sites": { "/sites": {
"func": SiteChain(self._db).remote_list, "func": SiteChain(self._db).remote_list,
@ -79,10 +81,10 @@ class Command(metaclass=Singleton):
"data": {} "data": {}
}, },
"/mediaserver_sync": { "/mediaserver_sync": {
"func": MediaServerChain(self._db).remote_sync, "id": "mediaserver_sync",
"type": "scheduler",
"description": "同步媒体服务器", "description": "同步媒体服务器",
"category": "管理", "category": "管理"
"data": {}
}, },
"/subscribes": { "/subscribes": {
"func": SubscribeChain(self._db).remote_list, "func": SubscribeChain(self._db).remote_list,
@ -91,16 +93,16 @@ class Command(metaclass=Singleton):
"data": {} "data": {}
}, },
"/subscribe_refresh": { "/subscribe_refresh": {
"func": SubscribeChain(self._db).remote_refresh, "id": "subscribe_refresh",
"type": "scheduler",
"description": "刷新订阅", "description": "刷新订阅",
"category": "订阅", "category": "订阅"
"data": {}
}, },
"/subscribe_search": { "/subscribe_search": {
"func": SubscribeChain(self._db).remote_search, "id": "subscribe_search",
"type": "scheduler",
"description": "搜索订阅", "description": "搜索订阅",
"category": "订阅", "category": "订阅"
"data": {}
}, },
"/subscribe_delete": { "/subscribe_delete": {
"func": SubscribeChain(self._db).remote_delete, "func": SubscribeChain(self._db).remote_delete,
@ -108,9 +110,9 @@ class Command(metaclass=Singleton):
"data": {} "data": {}
}, },
"/subscribe_tmdb": { "/subscribe_tmdb": {
"func": SubscribeChain(self._db).check, "id": "subscribe_tmdb",
"description": "订阅TMDB数据刷新", "type": "scheduler",
"data": {} "description": "订阅元数据更新"
}, },
"/downloading": { "/downloading": {
"func": DownloadChain(self._db).remote_downloading, "func": DownloadChain(self._db).remote_downloading,
@ -119,10 +121,10 @@ class Command(metaclass=Singleton):
"data": {} "data": {}
}, },
"/transfer": { "/transfer": {
"func": TransferChain(self._db).process, "id": "transfer",
"type": "scheduler",
"description": "下载文件整理", "description": "下载文件整理",
"category": "管理", "category": "管理"
"data": {}
}, },
"/redo": { "/redo": {
"func": TransferChain(self._db).remote_transfer, "func": TransferChain(self._db).remote_transfer,
@ -180,6 +182,56 @@ class Command(metaclass=Singleton):
except Exception as e: except Exception as e:
logger.error(f"事件处理出错:{str(e)} - {traceback.format_exc()}") logger.error(f"事件处理出错:{str(e)} - {traceback.format_exc()}")
def __run_command(self, command: Dict[str, any],
data_str: str = "",
channel: MessageChannel = None, userid: Union[str, int] = None):
"""
运行定时服务
"""
if command.get("type") == "scheduler":
# 定时服务
if userid:
self.chain.post_message(
Notification(
channel=channel,
title=f"开始执行 {command.get('description')} ...",
userid=userid
)
)
# 执行定时任务
self.scheduler.start(job_id=command.get("id"))
if userid:
self.chain.post_message(
Notification(
channel=channel,
title=f"{command.get('description')} 执行完成",
userid=userid
)
)
else:
# 命令
cmd_data = command['data'] if command.get('data') else {}
args_num = ObjectUtils.arguments(command['func'])
if args_num > 0:
if cmd_data:
# 有内置参数直接使用内置参数
data = cmd_data.get("data") or {}
data['channel'] = channel
data['user'] = userid
cmd_data['data'] = data
command['func'](**cmd_data)
elif args_num == 2:
# 没有输入参数只输入渠道和用户ID
command['func'](channel, userid)
elif args_num > 2:
# 多个输入参数用户输入、用户ID
command['func'](data_str, channel, userid)
else:
# 没有参数
command['func']()
def stop(self): def stop(self):
""" """
停止事件处理线程 停止事件处理线程
@ -225,25 +277,11 @@ class Command(metaclass=Singleton):
logger.info(f"用户 {userid} 开始执行:{command.get('description')} ...") logger.info(f"用户 {userid} 开始执行:{command.get('description')} ...")
else: else:
logger.info(f"开始执行:{command.get('description')} ...") logger.info(f"开始执行:{command.get('description')} ...")
cmd_data = command['data'] if command.get('data') else {}
args_num = ObjectUtils.arguments(command['func']) # 执行命令
if args_num > 0: self.__run_command(command, data_str=data_str,
if cmd_data: channel=channel, userid=userid)
# 有内置参数直接使用内置参数
data = cmd_data.get("data") or {}
data['channel'] = channel
data['user'] = userid
cmd_data['data'] = data
command['func'](**cmd_data)
elif args_num == 2:
# 没有输入参数只输入渠道和用户ID
command['func'](channel, userid)
elif args_num > 2:
# 多个输入参数用户输入、用户ID
command['func'](data_str, channel, userid)
else:
# 没有参数
command['func']()
if userid: if userid:
logger.info(f"用户 {userid} {command.get('description')} 执行完成") logger.info(f"用户 {userid} {command.get('description')} 执行完成")
else: else:

View File

@ -1,10 +1,12 @@
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List
import pytz import pytz
from apscheduler.executors.pool import ThreadPoolExecutor from apscheduler.executors.pool import ThreadPoolExecutor
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from app import schemas
from app.chain import ChainBase from app.chain import ChainBase
from app.chain.cookiecloud import CookieCloudChain from app.chain.cookiecloud import CookieCloudChain
from app.chain.mediaserver import MediaServerChain from app.chain.mediaserver import MediaServerChain
@ -40,65 +42,153 @@ class Scheduler(metaclass=Singleton):
def __init__(self): def __init__(self):
# 数据库连接 # 数据库连接
self._db = SessionFactory() self._db = SessionFactory()
# 各服务的运行状态
self._jobs = {
"cookiecloud": {
"func": CookieCloudChain(self._db).process,
"running": False,
},
"mediaserver_sync": {
"func": MediaServerChain(self._db).sync,
"running": False,
},
"subscribe_tmdb": {
"func": SubscribeChain(self._db).check,
"running": False,
},
"subscribe_search": {
"func": SubscribeChain(self._db).search,
"running": False,
},
"subscribe_refresh": {
"func": SubscribeChain(self._db).refresh,
"running": False,
},
"transfer": {
"func": TransferChain(self._db).process,
"running": False,
}
}
# 调试模式不启动定时服务 # 调试模式不启动定时服务
if settings.DEV: if settings.DEV:
return return
# CookieCloud定时同步 # CookieCloud定时同步
if settings.COOKIECLOUD_INTERVAL: if settings.COOKIECLOUD_INTERVAL:
self._scheduler.add_job(CookieCloudChain(self._db).process, self._scheduler.add_job(
"interval", self.start,
minutes=settings.COOKIECLOUD_INTERVAL, "interval",
next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=1), id="cookiecloud",
id="cookiecloud", name="同步CookieCloud站点",
name="同步CookieCloud站点") minutes=settings.COOKIECLOUD_INTERVAL,
next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=1),
kwargs={
'job_id': 'cookiecloud'
}
)
# 媒体服务器同步 # 媒体服务器同步
if settings.MEDIASERVER_SYNC_INTERVAL: if settings.MEDIASERVER_SYNC_INTERVAL:
self._scheduler.add_job(MediaServerChain(self._db).sync, "interval", self._scheduler.add_job(
hours=settings.MEDIASERVER_SYNC_INTERVAL, self.start,
next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=5), "interval",
id="mediaserver_sync", id="mediaserver_sync",
name="同步媒体服务器") name="同步媒体服务器",
hours=settings.MEDIASERVER_SYNC_INTERVAL,
next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=5),
kwargs={
'job_id': 'mediaserver_sync'
}
)
# 新增订阅时搜索5分钟检查一次 # 新增订阅时搜索5分钟检查一次
self._scheduler.add_job(SubscribeChain(self._db).search, "interval", self._scheduler.add_job(
minutes=5, kwargs={'state': 'N'}) self.start,
"interval",
minutes=5,
kwargs={
'job_id': 'subscribe_search',
'state': 'N'
}
)
# 检查更新订阅TMDB数据每隔6小时 # 检查更新订阅TMDB数据每隔6小时
self._scheduler.add_job(SubscribeChain(self._db).check, "interval", hours=6, self._scheduler.add_job(
id="subscribe_tmdb", name="订阅元数据更新") self.start,
"interval",
id="subscribe_tmdb",
name="订阅元数据更新",
hours=6,
kwargs={
'job_id': 'subscribe_tmdb'
}
)
# 订阅状态每隔24小时搜索一次 # 订阅状态每隔24小时搜索一次
if settings.SUBSCRIBE_SEARCH: if settings.SUBSCRIBE_SEARCH:
self._scheduler.add_job(SubscribeChain(self._db).search, "interval", self._scheduler.add_job(
hours=24, kwargs={'state': 'R'}, self.start,
id="subscribe_search", name="订阅搜索") "interval",
id="subscribe_search",
name="订阅搜索",
hours=24,
kwargs={
'job_id': 'subscribe_search',
'state': 'R'
}
)
if settings.SUBSCRIBE_MODE == "spider": if settings.SUBSCRIBE_MODE == "spider":
# 站点首页种子定时刷新模式 # 站点首页种子定时刷新模式
triggers = TimerUtils.random_scheduler(num_executions=30) triggers = TimerUtils.random_scheduler(num_executions=30)
for trigger in triggers: for trigger in triggers:
self._scheduler.add_job(SubscribeChain(self._db).refresh, "cron", self._scheduler.add_job(
hour=trigger.hour, minute=trigger.minute, self.start,
id=f"subscribe_refresh|{trigger.hour}:{trigger.minute}", "cron",
name="订阅刷新") id=f"subscribe_refresh|{trigger.hour}:{trigger.minute}",
name="订阅刷新",
hour=trigger.hour,
minute=trigger.minute,
kwargs={
'job_id': 'subscribe_refresh'
})
else: else:
# RSS订阅模式 # RSS订阅模式
if not settings.SUBSCRIBE_RSS_INTERVAL: if not settings.SUBSCRIBE_RSS_INTERVAL:
settings.SUBSCRIBE_RSS_INTERVAL = 30 settings.SUBSCRIBE_RSS_INTERVAL = 30
elif settings.SUBSCRIBE_RSS_INTERVAL < 5: elif settings.SUBSCRIBE_RSS_INTERVAL < 5:
settings.SUBSCRIBE_RSS_INTERVAL = 5 settings.SUBSCRIBE_RSS_INTERVAL = 5
self._scheduler.add_job(SubscribeChain(self._db).refresh, "interval", self._scheduler.add_job(
minutes=settings.SUBSCRIBE_RSS_INTERVAL, self.start,
id="subscribe_refresh", name="订阅刷新") "interval",
id="subscribe_refresh",
name="RSS订阅刷新",
minutes=settings.SUBSCRIBE_RSS_INTERVAL,
kwargs={
'job_id': 'subscribe_refresh'
}
)
# 下载器文件转移每5分钟 # 下载器文件转移每5分钟
if settings.DOWNLOADER_MONITOR: if settings.DOWNLOADER_MONITOR:
self._scheduler.add_job(TransferChain(self._db).process, "interval", minutes=5, self._scheduler.add_job(
id="transfer", name="下载文件整理") self.start,
"interval",
id="transfer",
name="下载文件整理",
minutes=5,
kwargs={
'job_id': 'transfer'
}
)
# 公共定时服务 # 公共定时服务
self._scheduler.add_job(SchedulerChain(self._db).scheduler_job, "interval", minutes=10) self._scheduler.add_job(
SchedulerChain(self._db).scheduler_job,
"interval",
minutes=10
)
# 打印服务 # 打印服务
logger.debug(self._scheduler.print_jobs()) logger.debug(self._scheduler.print_jobs())
@ -106,11 +196,53 @@ class Scheduler(metaclass=Singleton):
# 启动定时服务 # 启动定时服务
self._scheduler.start() self._scheduler.start()
def list(self): def start(self, job_id: str, *args, **kwargs):
"""
启动定时服务
"""
# 处理job_id格式
job = self._jobs.get(job_id)
if not job:
return
if job.get("running"):
logger.warning(f"定时任务 {job_id} 正在运行 ...")
return
self._jobs[job_id]["running"] = True
try:
job["func"](*args, **kwargs)
except Exception as e:
logger.error(f"定时任务 {job_id} 执行失败:{e}")
self._jobs[job_id]["running"] = False
def list(self) -> List[schemas.ScheduleInfo]:
""" """
当前所有任务 当前所有任务
""" """
return self._scheduler.get_jobs() # 返回计时任务
schedulers = []
# 去重
added = []
jobs = self._scheduler.get_jobs()
# 按照下次运行时间排序
jobs.sort(key=lambda x: x.next_run_time)
for job in jobs:
if job.name not in added:
added.append(job.name)
else:
continue
if not self._jobs.get(job.id):
continue
# 任务状态
status = "正在运行" if self._jobs[job.id].get("running") else "等待"
# 下次运行时间
next_run = TimerUtils.time_difference(job.next_run_time)
schedulers.append(schemas.ScheduleInfo(
id=job.id,
name=job.name,
status=status,
next_run=next_run
))
return schedulers
def stop(self): def stop(self):
""" """