From 19c407f804a3f8c1c4ff5e12a649273476051ea0 Mon Sep 17 00:00:00 2001 From: David Trattnig <david.trattnig@o94.at> Date: Mon, 1 Jun 2020 09:43:01 +0200 Subject: [PATCH] Improved timer behaviour and schedule updates. --- modules/scheduling/scheduler.py | 125 ++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/modules/scheduling/scheduler.py b/modules/scheduling/scheduler.py index 98d075f2..879d2572 100644 --- a/modules/scheduling/scheduler.py +++ b/modules/scheduling/scheduler.py @@ -138,24 +138,27 @@ class AuraScheduler(threading.Thread): 3. Queues all playlists of the programm, if the soundssystem is ready to accept commands. """ while not self.exit_event.is_set(): - seconds_to_wait = int(self.config.get("fetching_frequency")) - next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait) - self.logger.info("Fetch new programmes every %ss. Next fetching in %ss." % (str(seconds_to_wait), str(next_time))) - self.fetch_new_programme() - - # The Scheduler is ready - if not self.is_initialized: - self.is_initialized = True - if self.func_on_initialized: - self.func_on_initialized() - - # The SoundSystem is ready - if self.soundsystem.is_ready(): - self.queue_programme() + try: + seconds_to_wait = int(self.config.get("fetching_frequency")) + next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait) + self.logger.info("Fetch new programmes every %ss. Next fetching in %ss." % (str(seconds_to_wait), str(next_time))) + self.fetch_new_programme() + + # The scheduler is ready + if not self.is_initialized: + self.is_initialized = True + if self.func_on_initialized: + self.func_on_initialized() + + # The soundsystem is ready + if self.soundsystem.is_ready(): + self.queue_programme() + except Exception as e: + self.logger.critical(SimpleUtil.red("Unhandled error while fetching & scheduling new programme! (%s)" % str(e)), e) self.print_message_queue() self.exit_event.wait(seconds_to_wait) - + self.logger.info(SimpleUtil.cyan("== start fetching new schedules ==")) @@ -242,7 +245,6 @@ class AuraScheduler(threading.Thread): # Load programme if necessary if not self.programme: - self.logger.info("Next track requested: Need to load programme from database first.") self.load_programme_from_db() # Check for scheduled playlist @@ -590,19 +592,23 @@ class AuraScheduler(threading.Thread): index += 1 entry_groups.append([]) entry_groups[index].append(entry) - previous_entry = entry self.logger.info("Built %s entry group(s)" % len(entry_groups)) # Schedule function calls - for entries in entry_groups: - - self.set_entry_timer(entries, fade_in, fade_out) + if len(clean_entries) > 0 and len(entry_groups) > 0: + for entries in entry_groups: + if not isinstance(entries, list): + raise ValueError("Invalid Entry Group: %s" % str(entries)) + + self.set_entry_timer(entries, fade_in, fade_out) - # Check if it's the last item, which needs special handling - if entries[-1] == clean_entries[-1]: - # The end of schedule is the actual end of the track - self.queue_end_of_schedule(entries[-1], fade_out) + # Check if it's the last item, which needs special handling + if entries[-1] == clean_entries[-1]: + # The end of schedule is the actual end of the track + self.queue_end_of_schedule(entries[-1], fade_out) + else: + self.logger.warn(SimpleUtil.red("Nothing to schedule...")) @@ -635,7 +641,7 @@ class AuraScheduler(threading.Thread): if play_timer: # Check if the Playlist IDs are different - if play_timer.entries[0].entry_id != entries[0].entry_id: + if play_timer.entries[0].playlist.playlist_id == entries[0].playlist.playlist_id: # If not, stop and remove the old timer, create a new one self.stop_timer(play_timer) else: @@ -644,7 +650,7 @@ class AuraScheduler(threading.Thread): return # If nothing is planned at given time, create a new timer - (entries[0].switchtimer, entries[0].loadtimer) = self.create_timer(diff, do_play, [entries], switcher=True) + (entries[0].switchtimer, entries[0].loadtimer) = self.create_timer(diff, do_play, entries, switcher=True) @@ -762,7 +768,7 @@ class AuraScheduler(threading.Thread): self.programme = Schedule.select_programme() if not self.programme: - self.logger.critical("Could not load programme from database. We are in big trouble my friend!") + self.logger.critical(SimpleUtil.red("Could not load programme from database. We are in big trouble my friend!")) return @@ -772,32 +778,34 @@ class AuraScheduler(threading.Thread): Checks for existing timers at the given time. """ for t in self.message_timer: - if t.entry[0].start_unix == given_time and (t.fade_in or t.switcher): + if t.entries[0].start_unix == given_time and (t.fadein or t.switcher): return t return False - def create_timer(self, diff, func, parameters, fadein=False, fadeout=False, switcher=False): + def create_timer(self, diff, func, entries, fadein=False, fadeout=False, switcher=False): """ Creates a new timer for timed execution of mixer commands. Args: diff (Integer): The difference in seconds from now, when the call should happen func (Function): The function to call - parameters ([]): The function parameters + entries ([]): The entries to be scheduled """ if not fadein and not fadeout and not switcher or fadein and fadeout or fadein and switcher or fadeout and switcher: - raise Exception("You have to call me with either fadein=true, fadeout=true or switcher=True") + raise ValueError("You have to call me with either fadein=true, fadeout=true or switcher=True") + if not isinstance(entries, list): + raise ValueError("No list of entries passed!") - t = CallFunctionTimer(diff, func, parameters, fadein, fadeout, switcher) + t = CallFunctionTimer(diff=diff, func=func, param=entries, fadein=fadein, fadeout=fadeout, switcher=switcher) self.message_timer.append(t) t.start() if switcher: - # Load function to be called by timer - def do_load(entries): + # Pre-roll function to be called by timer + def do_preroll(entries): try: if entries[0].type == ChannelType.FILESYSTEM: self.logger.info(SimpleUtil.cyan("=== preroll_group('%s') ===" % EngineUtil.get_entries_string(entries))) @@ -810,7 +818,7 @@ class AuraScheduler(threading.Thread): # TODO Fallback logic here loader_diff = diff - self.config.get("preload_offset") - loader = CallFunctionTimer(loader_diff, do_load, parameters, fadein, fadeout, switcher) + loader = CallFunctionTimer(diff=loader_diff, func=do_preroll, param=entries, fadein=fadein, fadeout=fadeout, switcher=False, loader=True) self.message_timer.append(loader) loader.start() return (t, loader) @@ -827,22 +835,26 @@ class AuraScheduler(threading.Thread): timer (Timer): The timer to stop. """ timer.cancel() + count = 1 - if timer.entry.loadtimer is not None: - timer.entry.loadtimer.cancel() - self.message_timer.remove(timer.entry.loadtimer) + if timer.entries[0].loadtimer is not None: + timer.entries[0].loadtimer.cancel() + self.message_timer.remove(timer.entries[0].loadtimer) + count += 1 - if timer.entry.fadeintimer is not None: - timer.entry.fadeintimer.cancel() - self.message_timer.remove(timer.entry.fadeintimer) + # if timer.entries[0].fadeintimer is not None: + # timer.entries[0].fadeintimer.cancel() + # self.message_timer.remove(timer.entries[0].fadeintimer) + # count += 1 - if timer.entry.fadeouttimer is not None: - timer.entry.fadeouttimer.cancel() - self.message_timer.remove(timer.entry.fadeouttimer) + if timer.entries[0].fadeouttimer is not None: + timer.entries[0].fadeouttimer.cancel() + self.message_timer.remove(timer.entries[0].fadeouttimer) + count += 1 # Remove it from message queue self.message_timer.remove(timer) - self.logger.info("Removed timer for " + str(timer.entry)) + self.logger.info("Removed %s timers for: %s" % (str(count), EngineUtil.get_entries_string(timer.entries))) @@ -909,29 +921,30 @@ class AuraScheduler(threading.Thread): class CallFunctionTimer(threading.Timer): logger = None param = None - entry = None + entries = None diff = None fadein = False fadeout = False switcher = False + loader = False - def __init__(self, diff, func, param, fadein=False, fadeout=False, switcher=False, loader=False): + def __init__(self, diff=None, func=None, param=None, fadein=False, fadeout=False, switcher=False, loader=False): self.logger = logging.getLogger("AuraEngine") self.logger.debug("Executing soundsystem command '%s' in %s seconds..." % (str(func.__name__), str(diff))) - threading.Timer.__init__(self, diff, func, args=param) + threading.Timer.__init__(self, diff, func, param) if not fadein and not fadeout and not switcher and not loader \ or fadein and fadeout \ or fadein and switcher \ or fadeout and switcher: - raise Exception("You have to create me with either fadein=true, fadeout=true or switcher=True") + raise Exception("You have to create me with either fadein=True, fadeout=True or switcher=True") self.diff = diff self.func = func self.param = param - self.entry = param[0] + self.entries = param self.fadein = fadein self.fadeout = fadeout self.switcher = switcher @@ -942,13 +955,17 @@ class CallFunctionTimer(threading.Timer): """ String represenation of the timer. """ + status = "Timer (Alive: %s)" % self.is_alive() + time_start = SimpleUtil.fmt_time(SimpleUtil.timestamp() + self.diff) + status += " starting in " + str(self.diff) + "s (" + time_start + ") " + if self.fadein: - return "CallFunctionTimer starting in " + str(self.diff) + "s fading in source '" + str(self.entry) + return status + " fading in entries '" + EngineUtil.get_entries_string(self.entries) elif self.fadeout: - return "CallFunctionTimer starting in " + str(self.diff) + "s fading out source '" + str(self.entry) + return status + " fading out schedule '" + str(self.entries[0].playlist.schedule) elif self.switcher: - return "CallFunctionTimer starting in " + str(self.diff) + "s switching to source '" + str(self.entry) + return status + " switching to entries '" + EngineUtil.get_entries_string(self.entries) elif self.loader: - return "CallFunctionTimer starting in " + str(self.diff) + "s loading source '" + str(self.entry) + return status + " pre-rolling entries '" + EngineUtil.get_entries_string(self.entries) else: return "CORRUPTED CallFunctionTimer around! How can that be?" -- GitLab