diff --git a/bazarr/main.py b/bazarr/main.py index f83f3697f..6e6efc54f 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -28,7 +28,6 @@ import queueconfig import platform import apprise import operator -from calendar import day_name from get_args import args from logger import empty_log @@ -61,11 +60,14 @@ from get_subtitle import download_subtitle, series_download_subtitles, movies_do manual_search, manual_download_subtitle, manual_upload_subtitle from utils import history_log, history_log_movie, get_sonarr_version, get_radarr_version from helper import path_replace_reverse, path_replace_reverse_movie -from scheduler import * +from scheduler import Scheduler from notifier import send_notifications, send_notifications_movie from subliminal_patch.extensions import provider_registry as provider_manager from subliminal_patch.core import SUBTITLE_EXTENSIONS + +scheduler = Scheduler() + if six.PY2: reload(sys) sys.setdefaultencoding('utf8') @@ -920,7 +922,7 @@ def search_missing_subtitles(no): authorize() ref = request.environ['HTTP_REFERER'] - add_job(series_download_subtitles, args=[no], name=('search_missing_subtitles_' + str(no))) + scheduler.add_job(series_download_subtitles, args=[no], name=('search_missing_subtitles_' + str(no))) redirect(ref) @@ -931,7 +933,7 @@ def search_missing_subtitles_movie(no): authorize() ref = request.environ['HTTP_REFERER'] - add_job(movies_download_subtitles, args=[no], name=('movies_download_subtitles_' + str(no))) + scheduler.add_job(movies_download_subtitles, args=[no], name=('movies_download_subtitles_' + str(no))) redirect(ref) @@ -1177,7 +1179,7 @@ def wanted_search_missing_subtitles_list(): authorize() ref = request.environ['HTTP_REFERER'] - add_job(wanted_search_missing_subtitles, name='manual_wanted_search_missing_subtitles') + scheduler.add_job(wanted_search_missing_subtitles, name='manual_wanted_search_missing_subtitles') redirect(ref) @@ -1613,12 +1615,8 @@ def save_settings(): notifier_url = request.forms.get('settings_notifier_' + notifier['name'] + '_url') database.execute("UPDATE table_settings_notifier SET enabled=?, url=? WHERE name=?", (enabled,notifier_url,notifier['name'])) - - schedule_update_job() - sonarr_full_update() - radarr_full_update() - schedule_wanted_search() - schedule_upgrade_subs() + + scheduler.update_configurable_tasks() logging.info('BAZARR Settings saved succesfully.') @@ -1645,61 +1643,8 @@ def check_update(): def system(): authorize() - 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]) - text = "" - - if day == "*": - text = "everyday" - else: - text = "every " + day_name[int(day)] - - if hour != "*": - text += " at " + hour + ":00" - - return text + task_list = scheduler.get_task_list() - task_list = [] - for job in scheduler.get_jobs(): - if isinstance(job.trigger, CronTrigger): - if str(job.trigger.__getstate__()['fields'][0]) == "2100": - next_run = 'Never' - else: - next_run = pretty.date(job.next_run_time.replace(tzinfo=None)) - else: - next_run = pretty.date(job.next_run_time.replace(tzinfo=None)) - - if isinstance(job.trigger, IntervalTrigger): - interval = "every " + get_time_from_interval(job.trigger.__getstate__()['interval']) - task_list.append([job.name, interval, next_run, job.id]) - elif isinstance(job.trigger, CronTrigger): - task_list.append([job.name, get_time_from_cron(job.trigger.fields), next_run, job.id]) - throttled_providers = list_throttled_providers() try: @@ -1744,9 +1689,9 @@ def get_logs(): def execute_task(taskid): authorize() ref = request.environ['HTTP_REFERER'] - - execute_now(taskid) - + + scheduler.execute_job_now(taskid) + redirect(ref) @@ -2162,7 +2107,7 @@ def notifications(): @custom_auth_basic(check_credentials) def running_tasks_list(): authorize() - return dict(tasks=running_tasks) + return dict(tasks=scheduler.get_running_tasks()) @route(base_url + 'episode_history/') diff --git a/bazarr/scheduler.py b/bazarr/scheduler.py index eba04a828..2fab39767 100644 --- a/bazarr/scheduler.py +++ b/bazarr/scheduler.py @@ -20,134 +20,207 @@ from apscheduler.events import EVENT_JOB_SUBMITTED, EVENT_JOB_EXECUTED, EVENT_JO from datetime import datetime import pytz from tzlocal import get_localzone +from calendar import day_name +import pretty -def sonarr_full_update(): - if settings.general.getboolean('use_sonarr'): - full_update = settings.sonarr.full_update - if full_update == "Daily": - 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": - 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": - 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(): - if settings.general.getboolean('use_radarr'): - full_update = settings.radarr.full_update - if full_update == "Daily": - 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": - 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": - 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 execute_now(taskid): - scheduler.modify_job(taskid, next_run_time=datetime.now()) - - -if str(get_localzone()) == "local": - scheduler = BackgroundScheduler(timezone=pytz.timezone('UTC')) -else: - scheduler = BackgroundScheduler() - -global running_tasks -running_tasks = [] - - -def task_listener_add(event): - if event.job_id not in running_tasks: - running_tasks.append(event.job_id) +class Scheduler: + def __init__(self): + self.__running_tasks = [] -def task_listener_remove(event): - if event.job_id in running_tasks: - running_tasks.remove(event.job_id) - - -scheduler.add_listener(task_listener_add, EVENT_JOB_SUBMITTED) -scheduler.add_listener(task_listener_remove, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR) - + if str(get_localzone()) == "local": + self.aps_scheduler = BackgroundScheduler(timezone=pytz.timezone('UTC')) + else: + self.aps_scheduler = BackgroundScheduler() + + # task listener + def task_listener_add(event): + if event.job_id not in self.__running_tasks: + self.__running_tasks.append(event.job_id) + + def task_listener_remove(event): + if event.job_id in self.__running_tasks: + self.__running_tasks.remove(event.job_id) + + 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.__sonarr_update_task() + self.__radarr_update_task() + self.__cache_cleanup_task() + self.update_configurable_tasks() + + self.aps_scheduler.start() + + def update_configurable_tasks(self): + self.__sonarr_full_update_task() + self.__radarr_full_update_task() + self.__update_bazarr_task() + self.__search_wanted_subtitles_task() + self.__upgrade_subtitles_task() + + def add_job(self, job, name=None, max_instances=1, coalesce=True, args=None): + self.aps_scheduler.add_job( + job, DateTrigger(run_date=datetime.now()), name=name, id=name, max_instances=max_instances, + coalesce=coalesce, args=args) + + 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(): + if isinstance(job.trigger, CronTrigger): + if str(job.trigger.__getstate__()['fields'][0]) == "2100": + next_run = 'Never' + else: + next_run = pretty.date(job.next_run_time.replace(tzinfo=None)) + else: + next_run = pretty.date(job.next_run_time.replace(tzinfo=None)) + + if isinstance(job.trigger, IntervalTrigger): + interval = "every " + get_time_from_interval(job.trigger.__getstate__()['interval']) + task_list.append([job.name, interval, next_run, job.id]) + elif isinstance(job.trigger, CronTrigger): + task_list.append([job.name, get_time_from_cron(job.trigger.fields), next_run, job.id]) + + return task_list + + def __sonarr_update_task(self): + if settings.general.getboolean('use_sonarr'): + self.aps_scheduler.add_job( + update_series, IntervalTrigger(minutes=1), max_instances=1, coalesce=True, misfire_grace_time=15, + id='update_series', name='Update Series list from Sonarr') + self.aps_scheduler.add_job( + sync_episodes, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15, + id='sync_episodes', name='Sync episodes with Sonarr') + + def __radarr_update_task(self): + if settings.general.getboolean('use_radarr'): + self.aps_scheduler.add_job( + update_movies, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15, + id='update_movies', name='Update Movie list from Radarr') + + 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') + + 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): + if not args.no_update: + task_name = 'Update Bazarr from source on Github' + if args.release_update: + task_name = 'Update Bazarr from release on Github' + + if settings.general.getboolean('auto_update'): + self.aps_scheduler.add_job( + check_and_apply_update, IntervalTrigger(hours=6), max_instances=1, coalesce=True, + misfire_grace_time=15, id='update_bazarr', name=task_name, replace_existing=True) + else: + self.aps_scheduler.add_job( + check_and_apply_update, CronTrigger(year='2100'), hour=4, id='update_bazarr', name=task_name, + replace_existing=True) + self.aps_scheduler.add_job( + check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True, misfire_grace_time=15, + id='update_release', name='Update Release Info', replace_existing=True) -def schedule_update_job(): - if not args.no_update: - if settings.general.getboolean('auto_update'): - scheduler.add_job(check_and_apply_update, IntervalTrigger(hours=6), max_instances=1, coalesce=True, - misfire_grace_time=15, id='update_bazarr', - name='Update Bazarr from source on Github' if not args.release_update else 'Update Bazarr from release on Github', - replace_existing=True) else: - scheduler.add_job(check_and_apply_update, CronTrigger(year='2100'), hour=4, id='update_bazarr', - name='Update Bazarr from source on Github' if not args.release_update else 'Update Bazarr from release on Github', - replace_existing=True) - scheduler.add_job(check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True, - misfire_grace_time=15, id='update_release', name='Update Release Info', - replace_existing=True) - - else: - scheduler.add_job(check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True, - misfire_grace_time=15, - id='update_release', name='Update Release Info', replace_existing=True) - - -if settings.general.getboolean('use_sonarr'): - scheduler.add_job(update_series, IntervalTrigger(minutes=1), max_instances=1, coalesce=True, misfire_grace_time=15, - id='update_series', name='Update Series list from Sonarr') - scheduler.add_job(sync_episodes, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15, - id='sync_episodes', name='Sync episodes with Sonarr') - -if settings.general.getboolean('use_radarr'): - scheduler.add_job(update_movies, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15, - id='update_movies', name='Update Movie list from Radarr') - -def schedule_wanted_search(): - if settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr'): - scheduler.add_job(wanted_search_missing_subtitles, - IntervalTrigger(hours=int(settings.general.wanted_search_frequency)), max_instances=1, - coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles', - name='Search for wanted Subtitles', replace_existing=True) - - -def schedule_upgrade_subs(): - if settings.general.getboolean('upgrade_subs') and (settings.general.getboolean('use_sonarr') or - settings.general.getboolean('use_radarr')): - 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) - -scheduler.add_job(cache_maintenance, IntervalTrigger(hours=24), max_instances=1, coalesce=True, - misfire_grace_time=15, id='cache_cleanup', name='Cache maintenance') - -schedule_update_job() -sonarr_full_update() -radarr_full_update() -schedule_wanted_search() -schedule_upgrade_subs() -scheduler.start() - - -def add_job(job, name=None, max_instances=1, coalesce=True, args=None): - scheduler.add_job(job, DateTrigger(run_date=datetime.now()), name=name, id=name, max_instances=max_instances, - coalesce=coalesce, args=args) - - -def shutdown_scheduler(): - scheduler.shutdown(wait=True) + self.aps_scheduler.add_job( + check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True, misfire_grace_time=15, + id='update_release', name='Update Release Info', replace_existing=True) + + def __search_wanted_subtitles_task(self): + if settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr'): + self.aps_scheduler.add_job( + wanted_search_missing_subtitles, IntervalTrigger(hours=int(settings.general.wanted_search_frequency)), + max_instances=1, coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles', + name='Search for wanted Subtitles', replace_existing=True) + + 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)