2018-10-31 19:34:40 +00:00
|
|
|
# coding=utf-8
|
2018-08-16 02:01:49 +00:00
|
|
|
|
2022-02-23 03:55:07 +00:00
|
|
|
import os
|
|
|
|
import pretty
|
|
|
|
|
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
|
|
from apscheduler.triggers.interval import IntervalTrigger
|
|
|
|
from apscheduler.triggers.cron import CronTrigger
|
|
|
|
from apscheduler.triggers.date import DateTrigger
|
|
|
|
from apscheduler.events import EVENT_JOB_SUBMITTED, EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
|
|
|
|
from apscheduler.jobstores.base import JobLookupError
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from calendar import day_name
|
|
|
|
from random import randrange
|
2023-01-16 19:05:06 +00:00
|
|
|
from tzlocal import get_localzone
|
|
|
|
from tzlocal.utils import ZoneInfoNotFoundError
|
|
|
|
from dateutil import tz
|
2023-01-16 19:08:34 +00:00
|
|
|
import logging
|
2022-02-23 03:55:07 +00:00
|
|
|
|
2022-05-01 12:00:20 +00:00
|
|
|
from sonarr.sync.series import update_series
|
|
|
|
from sonarr.sync.episodes import sync_episodes, update_all_episodes
|
|
|
|
from radarr.sync.movies import update_movies, update_all_movies
|
|
|
|
from subtitles.wanted import wanted_search_missing_subtitles_series, wanted_search_missing_subtitles_movies
|
|
|
|
from subtitles.upgrade import upgrade_subtitles
|
|
|
|
from utilities.cache import cache_maintenance
|
|
|
|
from utilities.health import check_health
|
|
|
|
from utilities.backup import backup_to_zip
|
|
|
|
|
|
|
|
from .config import settings
|
|
|
|
from .get_args import args
|
|
|
|
from .event_handler import event_stream
|
|
|
|
|
2019-01-02 19:19:31 +00:00
|
|
|
if not args.no_update:
|
2022-05-01 12:00:20 +00:00
|
|
|
from .check_update import check_if_new_update, check_releases
|
2018-11-02 19:08:07 +00:00
|
|
|
else:
|
2022-05-01 12:00:20 +00:00
|
|
|
from .check_update import check_releases
|
2018-03-10 04:17:51 +00:00
|
|
|
|
2018-04-09 03:45:53 +00:00
|
|
|
|
2020-02-01 00:54:17 +00:00
|
|
|
class Scheduler:
|
2019-10-31 17:46:05 +00:00
|
|
|
|
2020-02-01 00:54:17 +00:00
|
|
|
def __init__(self):
|
|
|
|
self.__running_tasks = []
|
2019-10-31 17:46:05 +00:00
|
|
|
|
2023-01-16 19:05:06 +00:00
|
|
|
try:
|
|
|
|
self.timezone = get_localzone()
|
2023-01-16 19:08:34 +00:00
|
|
|
except ZoneInfoNotFoundError as e:
|
|
|
|
logging.error(f"BAZARR cannot use specified timezone: {e}")
|
2023-01-16 19:05:06 +00:00
|
|
|
self.timezone = tz.gettz("UTC")
|
|
|
|
|
|
|
|
self.aps_scheduler = BackgroundScheduler({'apscheduler.timezone': self.timezone})
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
# task listener
|
|
|
|
def task_listener_add(event):
|
|
|
|
if event.job_id not in self.__running_tasks:
|
|
|
|
self.__running_tasks.append(event.job_id)
|
2021-05-08 14:25:29 +00:00
|
|
|
event_stream(type='task')
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
def task_listener_remove(event):
|
|
|
|
if event.job_id in self.__running_tasks:
|
|
|
|
self.__running_tasks.remove(event.job_id)
|
2021-05-08 14:25:29 +00:00
|
|
|
event_stream(type='task')
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
self.aps_scheduler.add_listener(task_listener_add, EVENT_JOB_SUBMITTED)
|
|
|
|
self.aps_scheduler.add_listener(task_listener_remove, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
|
|
|
|
|
|
|
|
# configure all tasks
|
|
|
|
self.__cache_cleanup_task()
|
2021-05-08 14:25:29 +00:00
|
|
|
self.__check_health_task()
|
2020-02-01 00:54:17 +00:00
|
|
|
self.update_configurable_tasks()
|
|
|
|
|
|
|
|
self.aps_scheduler.start()
|
|
|
|
|
|
|
|
def update_configurable_tasks(self):
|
2020-05-08 04:22:14 +00:00
|
|
|
self.__sonarr_update_task()
|
|
|
|
self.__radarr_update_task()
|
2020-02-01 00:54:17 +00:00
|
|
|
self.__sonarr_full_update_task()
|
|
|
|
self.__radarr_full_update_task()
|
|
|
|
self.__update_bazarr_task()
|
|
|
|
self.__search_wanted_subtitles_task()
|
|
|
|
self.__upgrade_subtitles_task()
|
2020-02-20 11:41:05 +00:00
|
|
|
self.__randomize_interval_task()
|
2022-02-23 03:55:07 +00:00
|
|
|
self.__automatic_backup()
|
2020-04-01 03:11:13 +00:00
|
|
|
if args.no_tasks:
|
|
|
|
self.__no_task()
|
2020-02-01 00:54:17 +00:00
|
|
|
|
2021-01-19 04:49:51 +00:00
|
|
|
def add_job(self, job, name=None, max_instances=1, coalesce=True, args=None, kwargs=None):
|
2020-02-01 00:54:17 +00:00
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
job, DateTrigger(run_date=datetime.now()), name=name, id=name, max_instances=max_instances,
|
2021-01-19 04:49:51 +00:00
|
|
|
coalesce=coalesce, args=args, kwargs=kwargs)
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
def execute_job_now(self, taskid):
|
|
|
|
self.aps_scheduler.modify_job(taskid, next_run_time=datetime.now())
|
|
|
|
|
|
|
|
def get_running_tasks(self):
|
|
|
|
return self.__running_tasks
|
|
|
|
|
|
|
|
def get_task_list(self):
|
|
|
|
def get_time_from_interval(td_object):
|
|
|
|
seconds = int(td_object.total_seconds())
|
|
|
|
periods = [
|
|
|
|
('year', 60 * 60 * 24 * 365),
|
|
|
|
('month', 60 * 60 * 24 * 30),
|
|
|
|
('day', 60 * 60 * 24),
|
|
|
|
('hour', 60 * 60),
|
|
|
|
('minute', 60),
|
|
|
|
('second', 1)
|
|
|
|
]
|
|
|
|
|
|
|
|
strings = []
|
|
|
|
for period_name, period_seconds in periods:
|
|
|
|
if seconds > period_seconds:
|
|
|
|
period_value, seconds = divmod(seconds, period_seconds)
|
|
|
|
has_s = 's' if period_value > 1 else ''
|
|
|
|
strings.append("%s %s%s" % (period_value, period_name, has_s))
|
|
|
|
|
|
|
|
return ", ".join(strings)
|
|
|
|
|
|
|
|
def get_time_from_cron(cron):
|
|
|
|
year = str(cron[0])
|
|
|
|
if year == "2100":
|
|
|
|
return "Never"
|
|
|
|
|
|
|
|
day = str(cron[4])
|
|
|
|
hour = str(cron[5])
|
|
|
|
|
|
|
|
if day == "*":
|
|
|
|
text = "everyday"
|
|
|
|
else:
|
|
|
|
text = "every " + day_name[int(day)]
|
|
|
|
|
|
|
|
if hour != "*":
|
|
|
|
text += " at " + hour + ":00"
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
task_list = []
|
|
|
|
for job in self.aps_scheduler.get_jobs():
|
2020-04-01 03:11:13 +00:00
|
|
|
next_run = 'Never'
|
|
|
|
if job.next_run_time:
|
|
|
|
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
|
2020-02-01 00:54:17 +00:00
|
|
|
if isinstance(job.trigger, CronTrigger):
|
2020-04-01 03:11:13 +00:00
|
|
|
if job.next_run_time and str(job.trigger.__getstate__()['fields'][0]) != "2100":
|
2020-02-01 00:54:17 +00:00
|
|
|
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
|
|
|
|
|
2020-02-22 14:03:58 +00:00
|
|
|
if job.id in self.__running_tasks:
|
|
|
|
running = True
|
|
|
|
else:
|
|
|
|
running = False
|
|
|
|
|
2020-02-01 00:54:17 +00:00
|
|
|
if isinstance(job.trigger, IntervalTrigger):
|
|
|
|
interval = "every " + get_time_from_interval(job.trigger.__getstate__()['interval'])
|
2020-02-20 11:41:05 +00:00
|
|
|
task_list.append({'name': job.name, 'interval': interval, 'next_run_in': next_run,
|
2020-04-01 03:11:13 +00:00
|
|
|
'next_run_time': next_run, 'job_id': job.id, 'job_running': running})
|
2020-02-01 00:54:17 +00:00
|
|
|
elif isinstance(job.trigger, CronTrigger):
|
2020-02-20 11:41:05 +00:00
|
|
|
task_list.append({'name': job.name, 'interval': get_time_from_cron(job.trigger.fields),
|
2020-04-01 03:11:13 +00:00
|
|
|
'next_run_in': next_run, 'next_run_time': next_run, 'job_id': job.id,
|
|
|
|
'job_running': running})
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
return task_list
|
|
|
|
|
|
|
|
def __sonarr_update_task(self):
|
|
|
|
if settings.general.getboolean('use_sonarr'):
|
|
|
|
self.aps_scheduler.add_job(
|
2020-05-08 04:22:14 +00:00
|
|
|
update_series, IntervalTrigger(minutes=int(settings.sonarr.series_sync)), max_instances=1,
|
2021-04-05 14:00:43 +00:00
|
|
|
coalesce=True, misfire_grace_time=15, id='update_series', name='Update Series list from Sonarr',
|
|
|
|
replace_existing=True)
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
sync_episodes, IntervalTrigger(minutes=int(settings.sonarr.episodes_sync)), max_instances=1,
|
|
|
|
coalesce=True, misfire_grace_time=15, id='sync_episodes', name='Sync episodes with Sonarr',
|
2020-05-08 04:22:14 +00:00
|
|
|
replace_existing=True)
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
def __radarr_update_task(self):
|
|
|
|
if settings.general.getboolean('use_radarr'):
|
|
|
|
self.aps_scheduler.add_job(
|
2020-05-08 04:22:14 +00:00
|
|
|
update_movies, IntervalTrigger(minutes=int(settings.radarr.movies_sync)), max_instances=1,
|
2021-04-05 14:00:43 +00:00
|
|
|
coalesce=True, misfire_grace_time=15, id='update_movies', name='Update Movie list from Radarr',
|
2020-05-08 04:22:14 +00:00
|
|
|
replace_existing=True)
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
def __cache_cleanup_task(self):
|
|
|
|
self.aps_scheduler.add_job(cache_maintenance, IntervalTrigger(hours=24), max_instances=1, coalesce=True,
|
|
|
|
misfire_grace_time=15, id='cache_cleanup', name='Cache maintenance')
|
|
|
|
|
2021-05-08 14:25:29 +00:00
|
|
|
def __check_health_task(self):
|
|
|
|
self.aps_scheduler.add_job(check_health, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
|
|
|
|
misfire_grace_time=15, id='check_health', name='Check health')
|
|
|
|
|
2022-02-23 03:55:07 +00:00
|
|
|
def __automatic_backup(self):
|
|
|
|
backup = settings.backup.frequency
|
|
|
|
if backup == "Daily":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
backup_to_zip, CronTrigger(hour=settings.backup.hour), max_instances=1, coalesce=True,
|
|
|
|
misfire_grace_time=15, id='backup', name='Backup database and configuration file',
|
|
|
|
replace_existing=True)
|
|
|
|
elif backup == "Weekly":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
backup_to_zip, CronTrigger(day_of_week=settings.backup.day, hour=settings.backup.hour),
|
|
|
|
max_instances=1, coalesce=True, misfire_grace_time=15, id='backup',
|
|
|
|
name='Backup database and configuration file', replace_existing=True)
|
|
|
|
elif backup == "Manually":
|
|
|
|
try:
|
|
|
|
self.aps_scheduler.remove_job(job_id='backup')
|
|
|
|
except JobLookupError:
|
|
|
|
pass
|
|
|
|
|
2020-02-01 00:54:17 +00:00
|
|
|
def __sonarr_full_update_task(self):
|
|
|
|
if settings.general.getboolean('use_sonarr'):
|
|
|
|
full_update = settings.sonarr.full_update
|
|
|
|
if full_update == "Daily":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
update_all_episodes, CronTrigger(hour=settings.sonarr.full_update_hour), max_instances=1,
|
|
|
|
coalesce=True, misfire_grace_time=15, id='update_all_episodes',
|
|
|
|
name='Update all Episode Subtitles from disk', replace_existing=True)
|
|
|
|
elif full_update == "Weekly":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
update_all_episodes,
|
|
|
|
CronTrigger(day_of_week=settings.sonarr.full_update_day, hour=settings.sonarr.full_update_hour),
|
|
|
|
max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes',
|
|
|
|
name='Update all Episode Subtitles from disk', replace_existing=True)
|
|
|
|
elif full_update == "Manually":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
update_all_episodes, CronTrigger(year='2100'), max_instances=1, coalesce=True,
|
|
|
|
misfire_grace_time=15, id='update_all_episodes',
|
|
|
|
name='Update all Episode Subtitles from disk', replace_existing=True)
|
|
|
|
|
|
|
|
def __radarr_full_update_task(self):
|
|
|
|
if settings.general.getboolean('use_radarr'):
|
|
|
|
full_update = settings.radarr.full_update
|
|
|
|
if full_update == "Daily":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
update_all_movies, CronTrigger(hour=settings.radarr.full_update_hour), max_instances=1,
|
|
|
|
coalesce=True, misfire_grace_time=15,
|
|
|
|
id='update_all_movies', name='Update all Movie Subtitles from disk', replace_existing=True)
|
|
|
|
elif full_update == "Weekly":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
update_all_movies,
|
|
|
|
CronTrigger(day_of_week=settings.radarr.full_update_day, hour=settings.radarr.full_update_hour),
|
|
|
|
max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_movies',
|
|
|
|
name='Update all Movie Subtitles from disk', replace_existing=True)
|
|
|
|
elif full_update == "Manually":
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
update_all_movies, CronTrigger(year='2100'), max_instances=1, coalesce=True, misfire_grace_time=15,
|
|
|
|
id='update_all_movies', name='Update all Movie Subtitles from disk', replace_existing=True)
|
|
|
|
|
|
|
|
def __update_bazarr_task(self):
|
2021-02-26 15:57:47 +00:00
|
|
|
if not args.no_update and os.environ["BAZARR_VERSION"] != '':
|
|
|
|
task_name = 'Update Bazarr'
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
if settings.general.getboolean('auto_update'):
|
|
|
|
self.aps_scheduler.add_job(
|
2021-02-26 15:57:47 +00:00
|
|
|
check_if_new_update, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
|
2020-02-01 00:54:17 +00:00
|
|
|
misfire_grace_time=15, id='update_bazarr', name=task_name, replace_existing=True)
|
|
|
|
else:
|
|
|
|
self.aps_scheduler.add_job(
|
2021-02-26 15:57:47 +00:00
|
|
|
check_if_new_update, CronTrigger(year='2100'), hour=4, id='update_bazarr', name=task_name,
|
2020-02-01 00:54:17 +00:00
|
|
|
replace_existing=True)
|
|
|
|
self.aps_scheduler.add_job(
|
2021-01-27 16:29:08 +00:00
|
|
|
check_releases, IntervalTrigger(hours=3), max_instances=1, coalesce=True, misfire_grace_time=15,
|
2020-02-01 00:54:17 +00:00
|
|
|
id='update_release', name='Update Release Info', replace_existing=True)
|
2019-06-11 18:45:48 +00:00
|
|
|
|
2019-03-20 23:50:26 +00:00
|
|
|
else:
|
2020-02-01 00:54:17 +00:00
|
|
|
self.aps_scheduler.add_job(
|
2021-01-27 16:29:08 +00:00
|
|
|
check_releases, IntervalTrigger(hours=3), max_instances=1, coalesce=True, misfire_grace_time=15,
|
2020-02-01 00:54:17 +00:00
|
|
|
id='update_release', name='Update Release Info', replace_existing=True)
|
|
|
|
|
|
|
|
def __search_wanted_subtitles_task(self):
|
2020-02-13 04:16:22 +00:00
|
|
|
if settings.general.getboolean('use_sonarr'):
|
|
|
|
self.aps_scheduler.add_job(
|
2023-01-16 19:05:06 +00:00
|
|
|
wanted_search_missing_subtitles_series,
|
|
|
|
IntervalTrigger(hours=int(settings.general.wanted_search_frequency)), max_instances=1, coalesce=True,
|
|
|
|
misfire_grace_time=15, id='wanted_search_missing_subtitles_series', replace_existing=True,
|
|
|
|
name='Search for wanted Series Subtitles')
|
2020-02-13 04:16:22 +00:00
|
|
|
if settings.general.getboolean('use_radarr'):
|
2020-02-01 00:54:17 +00:00
|
|
|
self.aps_scheduler.add_job(
|
2023-01-16 19:05:06 +00:00
|
|
|
wanted_search_missing_subtitles_movies,
|
|
|
|
IntervalTrigger(hours=int(settings.general.wanted_search_frequency_movie)), max_instances=1,
|
|
|
|
coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles_movies',
|
2020-02-13 04:18:41 +00:00
|
|
|
name='Search for wanted Movies Subtitles', replace_existing=True)
|
2020-02-01 00:54:17 +00:00
|
|
|
|
|
|
|
def __upgrade_subtitles_task(self):
|
|
|
|
if settings.general.getboolean('upgrade_subs') and \
|
|
|
|
(settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr')):
|
|
|
|
self.aps_scheduler.add_job(
|
|
|
|
upgrade_subtitles, IntervalTrigger(hours=int(settings.general.upgrade_frequency)), max_instances=1,
|
|
|
|
coalesce=True, misfire_grace_time=15, id='upgrade_subtitles',
|
|
|
|
name='Upgrade previously downloaded Subtitles', replace_existing=True)
|
2020-02-20 11:41:05 +00:00
|
|
|
|
|
|
|
def __randomize_interval_task(self):
|
|
|
|
for job in self.aps_scheduler.get_jobs():
|
|
|
|
if isinstance(job.trigger, IntervalTrigger):
|
2023-01-16 19:05:06 +00:00
|
|
|
self.aps_scheduler.modify_job(job.id,
|
|
|
|
next_run_time=datetime.now(tz=self.timezone) +
|
|
|
|
timedelta(seconds=randrange(
|
|
|
|
job.trigger.interval.total_seconds() * 0.75,
|
|
|
|
job.trigger.interval.total_seconds())))
|
2020-04-01 03:11:13 +00:00
|
|
|
|
|
|
|
def __no_task(self):
|
|
|
|
for job in self.aps_scheduler.get_jobs():
|
|
|
|
self.aps_scheduler.modify_job(job.id, next_run_time=None)
|
2020-05-18 12:21:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
scheduler = Scheduler()
|
2021-01-19 04:49:51 +00:00
|
|
|
|
|
|
|
# Force the execution of the sync process with Sonarr and Radarr after migration to v0.9.1
|
|
|
|
if 'BAZARR_AUDIO_PROFILES_MIGRATION' in os.environ:
|
|
|
|
if settings.general.getboolean('use_sonarr'):
|
|
|
|
scheduler.aps_scheduler.modify_job('update_series', next_run_time=datetime.now())
|
2021-04-05 14:00:43 +00:00
|
|
|
scheduler.aps_scheduler.modify_job('sync_episodes', next_run_time=datetime.now())
|
2021-01-19 04:49:51 +00:00
|
|
|
if settings.general.getboolean('use_radarr'):
|
|
|
|
scheduler.aps_scheduler.modify_job('update_movies', next_run_time=datetime.now())
|
|
|
|
del os.environ['BAZARR_AUDIO_PROFILES_MIGRATION']
|