From 900ff081e3631c591b7f9fc6746372437db8cd9c Mon Sep 17 00:00:00 2001 From: David Trattnig <david.trattnig@o94.at> Date: Sat, 31 Oct 2020 21:32:46 +0100 Subject: [PATCH] Trigger new "on_fallback_active" event. #38 --- src/scheduling/fallback.py | 122 +++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 18 deletions(-) diff --git a/src/scheduling/fallback.py b/src/scheduling/fallback.py index a8375e78..cc6bde81 100644 --- a/src/scheduling/fallback.py +++ b/src/scheduling/fallback.py @@ -34,17 +34,26 @@ from src.core.control import EngineExecutor class FallbackType(Enum): """ - Types of playlists. + Types of fallbacks. + + NONE: No fallback active, default playout + SCHEDULE: The first played when some default playlist fails + SHOW: The second played when the timeslot fallback fails + STATION: The last played when everything else fails """ - NONE = { "id": 0, "name": "default", "lqs_sources": [ Channel.QUEUE_A, Channel.QUEUE_A] } # No fallback active, default playout - SCHEDULE = { "id": 1, "name": "schedule", "lqs_sources": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B]} # The first played when some default playlist fails - SHOW = { "id": 2, "name": "show", "lqs_sources": [ "station_folder", "station_playlist"]} # The second played when the timeslot fallback fails - STATION = { "id": 3, "name": "station", "lqs_sources": [ "station_folder", "station_playlist"] } # The last played when everything else fails + NONE = { "id": 0, "name": "default", "channels": [ Channel.QUEUE_A, Channel.QUEUE_B ] } + SCHEDULE = { "id": 1, "name": "schedule", "channels": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B ] } + SHOW = { "id": 2, "name": "show", "channels": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B ] } + STATION = { "id": 3, "name": "station", "channels": [ Channel.FALLBACK_STATION_FOLDER, Channel.FALLBACK_STATION_PLAYLIST ] } @property def id(self): return self.value["id"] + @property + def channels(self): + return self.value["channels"] + def __str__(self): return str(self.value["name"]) @@ -52,30 +61,80 @@ class FallbackType(Enum): class FallbackManager: """ - Handles all types of fallbacks in case there is an outage - for the regular radio programme. - - Attributes: - config (AuraConfig): The engine configuration - logger (AuraLogger): The logger - mail (AuraMailer): Mail service - scheduler (AuraScheduler): The scheduler + Handles all types of fallbacks in case there is an outage or missing schedules + for the radio programme. """ config = None logger = None - scheduler = None - + engine = None + state = None - def __init__(self, scheduler): + + def __init__(self, engine): """ Constructor Args: - + scheduler (Scheduler): The scheduler """ self.config = AuraConfig.config() self.logger = logging.getLogger("AuraEngine") - self.scheduler = scheduler + self.engine = engine + self.state = { + "fallback_type": FallbackType.NONE, + "previous_fallback_type": None, + "timeslot": None + } + + + # + # EVENTS + # + + + def on_timeslot_start(self, timeslot=None): + """ + Some new timeslot has just started. + """ + self.state["timeslot"] = timeslot + + + def on_timeslot_end(self, timeslot): + """ + The timeslot has ended and the state is updated. The method ensures that any intermediate state update doesn't get overwritten. + """ + if self.state["timeslot"] == timeslot: + self.state["timeslot"] = None + + + def on_play(self, entry): + """ + Event Handler which is called by the engine when some entry is actually playing. + + Args: + source (String): The `PlaylistEntry` object + """ + self.update_fallback_state(entry.channel) + + + def on_metadata(self, data): + """ + Event called by the soundsystem implementation (i.e. Liquidsoap) when some entry is actually playing. + This does not include live or stream sources, since they ain't have metadata and are triggered from + engine core (see `on_play(..)`). + + Args: + data (dict): A collection of metadata related to the current track + """ + channel = data.get("source") + fallback_type = self.update_fallback_state(channel) + + # If we turned into a fallback state we issue an event + if fallback_type is not FallbackType.NONE: + # Only trigger the event the upon first state change + if fallback_type != self.state.get("previous_fallback_type"): + self.engine.event_dispatcher.on_fallback_active(self.state["timeslot"], fallback_type) + # @@ -83,6 +142,32 @@ class FallbackManager: # + def update_fallback_state(self, channel): + """ + Update the current and previously active fallback state. + + Returns: + (FallbackType): The current fallback + """ + fallback_type = self.type_for_channel(channel) + self.state["previous_fallback_type"] = self.state["fallback_type"] + self.state["fallback_type"] = fallback_type + return fallback_type + + + def type_for_channel(self, source): + """ + Retrieves the matching fallback type for the given source. + """ + if source in [str(i) for i in FallbackType.SCHEDULE.channels]: + return FallbackType.SCHEDULE + if source in [str(i) for i in FallbackType.SHOW.channels]: + return FallbackType.SHOW + if source in [str(i) for i in FallbackType.STATION.channels]: + return FallbackType.STATION + return FallbackType.NONE + + def queue_fallback_playlist(self, timeslot): """ Evaluates the scheduled fallback and queues it using a timed thread. @@ -198,6 +283,7 @@ class FallbackCommand(EngineExecutor): are created. """ + def __init__(self, timeslot, entries): """ Constructor -- GitLab