diff --git a/src/models.py b/src/models.py index 7723a6ec7f4d5356ef0e9e36b26f04295c7a8cc3..fca72e60194b4754c6157adff23c41f032ab67cc 100644 --- a/src/models.py +++ b/src/models.py @@ -19,6 +19,7 @@ import datetime +import json from sqlalchemy import create_engine, Column, DateTime, String, Integer, Boolean from sqlalchemy.event import listen @@ -43,9 +44,9 @@ class PlayLog(db.Model): track_artist = Column(String(256)) track_album = Column(String(256)) track_title = Column(String(256)) - track_duration = Column(String(256)) + track_duration = Column(Integer) track_type = Column(Integer) - timeslot_id = Column(Integer) + schedule_id = Column(Integer) show_name = Column(String(256)) log_source = Column(Integer) # The play-out source which this log is coming from (e.g. engine1, engine2) is_synced = Column(Boolean) # Only relevant for main nodes, in a multi-node setup @@ -62,7 +63,7 @@ class PlayLog(db.Model): self.track_title = data.track_title self.track_duration = data.track_duration self.track_type = data.track_type - self.timeslot_id = data.timeslot_id + self.schedule_id = data.schedule_id self.show_name = data.show_name self.log_source = data.log_source self.is_synced = False @@ -73,6 +74,18 @@ class PlayLog(db.Model): db.session.commit() + @staticmethod + def get(start_time): + """ + Selects the playlog identified by start time. + """ + db.session.commit() + track = db.session.query(PlayLog).\ + filter(PlayLog.track_start <= str(start_time)).\ + order_by(PlayLog.track_start.desc()).first() + return track + + @staticmethod def select_current(): """ @@ -185,7 +198,11 @@ class TrackSchema(ma.SQLAlchemySchema): "track_start", "track_artist", "track_album", - "track_title" + "track_title", + "track_duration", + "track_type", + "schedule_id", + "show_name" ) @@ -288,4 +305,93 @@ class HealthHistorySchema(ma.SQLAlchemyAutoSchema): """ class Meta: model = HealthHistory - sqla_session = db.session \ No newline at end of file + sqla_session = db.session + + + +class ClockInfo(db.Model): + """ + Table holding information for the current and next show to be displayed by the studio clock. + + Important: This table doesn't hold the history of information for efficiency. It only stores one + record for each possible source. For example in a redundant deployment it holds two records, for + engine1 and engine2. + + The stringified objects allow easy, future extension of the properties, without the need to change + the database model. + """ + __tablename__ = 'clock_info' + + # Primary Key + log_source = Column(Integer, primary_key=True) # The source this entry was updated from ("1" for engine1, "2" for engine2) + + # Columns + log_time = Column(DateTime) + current_track = None # Populated live from within `get_info(..)` + current_playlist = Column(String(2048)) # Stringified "#/components/schemas/Playlist" OpenAPI JSON object + current_schedule = Column(String(2048)) # Stringified "#/components/schemas/Schedule" OpenAPI JSON object + next_schedule = Column(String(2048)) # Stringified "#/components/schemas/Schedule" OpenAPI JSON object + + def __init__(self, source_number, current_playlist, current_schedule, next_schedule): + """ + Initializes an health entry. + """ + self.log_time = datetime.datetime.now() + self.log_source = source_number + if current_playlist: + self.current_playlist = json.dumps(current_playlist.to_dict(), default=str) + if current_schedule: + self.current_schedule = json.dumps(current_schedule.to_dict(), default=str) + if next_schedule: + self.next_schedule = json.dumps(next_schedule.to_dict(), default=str) + + + @staticmethod + def get_info(source_number): + """ + Retrieves the clock info for the given source number. + """ + info = dict() + data = db.session.query(ClockInfo).filter(ClockInfo.log_source == source_number).first() + current_track = PlayLog.select_current() + track_schema = TrackSchema() + + info["log_source"] = data.log_source + info["log_time"] = data.log_time + + if current_track: + info["current_track"] = track_schema.dump(current_track) + if data.current_playlist: + info["current_playlist"] = json.loads(data.current_playlist) + if data.current_schedule: + info["current_schedule"] = json.loads(data.current_schedule) + if data.next_schedule: + info["next_schedule"] = json.loads(data.next_schedule) + + return info + + + def save(self): + db.session.add(self) + db.session.commit() + + def merge(self): + db.session.merge(self) + db.session.commit() + + +class ClockInfoSchema(ma.SQLAlchemySchema): + """ + Schema for trackservice entries. + """ + class Meta: + model = ClockInfo + sqla_session = db.session + fields = ( + "log_source", + "log_time", + "current_track", + "current_playlist", + "current_schedule", + "next_schedule" + ) \ No newline at end of file diff --git a/src/rest/controllers/internal_controller.py b/src/rest/controllers/internal_controller.py index d018861c66d1bdc7ebc7cbafef5ba979565800e9..bdd5d1e92c6ca9b7fd330cb3bd92faea481173fd 100644 --- a/src/rest/controllers/internal_controller.py +++ b/src/rest/controllers/internal_controller.py @@ -154,4 +154,4 @@ def set_clock_info(body): # noqa: E501 if connexion.request.is_json: body = ClockInfo.from_dict(connexion.request.get_json()) # noqa: E501 service = current_app.config['SERVICE'] - return service.set_clock_info(body) + return service.set_clock_info(body, connexion.request.get_json()) diff --git a/src/service.py b/src/service.py index eedf7a8f580a2f3e1351ded6e780db105b166809..3e1ea49489a1c23579901c6d4d93e4b53d856005 100644 --- a/src/service.py +++ b/src/service.py @@ -19,11 +19,15 @@ import datetime import requests +import json +import sqlalchemy from dateutil.parser import parse from base.node import NodeType -from models import PlayLog, PlayLogSchema, TrackSchema, ActivityLog, HealthHistory, HealthHistorySchema +from models import \ + PlayLog, PlayLogSchema, TrackSchema, ActivityLog, \ + ClockInfo, ClockInfoSchema, HealthHistory, HealthHistorySchema @@ -40,6 +44,7 @@ class ApiService(): active_source = None api_playlog = None api_healthlog = None + api_clockinfo = None def __init__(self, config, logger, node_type): @@ -82,6 +87,7 @@ class ApiService(): # Init Sync API endpoints self.api_playlog = self.config.get("sync_api_store_playlog") self.api_healthlog = self.config.get("sync_api_store_healthlog") + self.api_clockinfo = self.config.get("sync_api_store_clockinfo") self.logger.info("Running in '%s' mode. %s." % (self.node_type, msg)) @@ -152,6 +158,7 @@ class ApiService(): try: playlog = PlayLog(data) playlog.save() + playlog = PlayLog.get(data.track_start) self.logger.debug("Stored playlog for '%s'" % playlog.track_start) except sqlalchemy.exc.IntegrityError as e: self.logger.info("Playlog for '%s' is already existing in local database. Skipping..." % playlog.track_start) @@ -176,11 +183,39 @@ class ApiService(): self.logger.info("Ditching playlog sent from an inactive source") - def clock_info(self): + def get_clock_info(self): """ Retrieves the dataset required to render the studio clock. """ - return "studio clock data" + info = ClockInfo.get_info(self.get_active_source()) + clockinfo_schema = ClockInfoSchema() + return clockinfo_schema.dump(info) + + + def set_clock_info(self, body, plain_json): + """ + Sets the clock info for the given source (engine1, engine2, other). + + Args: + source_number (Integer): Number of the engine + """ + if body.engine_source <= 0: + return + + clock_info = ClockInfo(body.engine_source, body.current_playlist, body.current_schedule, body.next_schedule) + clock_info.merge() + + # Main Node: Push to Sync Node, if enabled + if self.node_type == NodeType.MAIN and self.sync_host and self.api_clockinfo: + try: + api_url = self.sync_host + self.api_clockinfo + r = requests.put(api_url, json=plain_json) + if r.status_code == 204: + self.logger.info("Successfully pushed clock info for '%s' to '%s'" % (clock_info.log_time, self.sync_host)) + else: + self.logger.error("HTTP %s | Error while pushing clock info to sync-node: " % (r.status_code, str(r.json()))) + except Exception as e: + self.logger.error("Error while putting clock info to sync-node API '%s'!\n%s" % (api_url, str(e))) def set_default_source(self, source_number):