Skip to content
Snippets Groups Projects
service.py 9.56 KiB
Newer Older
  • Learn to ignore specific revisions
  • David Trattnig's avatar
    David Trattnig committed
    #
    # Aura Engine API (https://gitlab.servus.at/aura/engine-api)
    #
    # Copyright (C) 2020 - The Aura Engine Team.
    #
    # This program is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Affero General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU Affero General Public License for more details.
    #
    # You should have received a copy of the GNU Affero General Public License
    # along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    
    David Trattnig's avatar
    David Trattnig committed
    
    
    import datetime
    
    David Trattnig's avatar
    David Trattnig committed
    
    
    from enum import Enum
    
    David Trattnig's avatar
    David Trattnig committed
    
    
    David Trattnig's avatar
    David Trattnig committed
    from models import PlayLog, PlayLogSchema, TrackSchema, ActivityLog, HealthHistory, HealthHistorySchema
    
    David Trattnig's avatar
    David Trattnig committed
    
    
    David Trattnig's avatar
    David Trattnig committed
    
    
    class NodeType(Enum):
        """
        Types of API Server deployment models.
        """
        MAIN = "main"
        SYNC = "sync"
    
    
    
    
    class ApiService():
        """
    
    David Trattnig's avatar
    David Trattnig committed
        Service handling for API actions.
    
        """
    
        config = None
        logger = None
    
        node_type = None
        sync_host = None
        main_hosts = None
    
    David Trattnig's avatar
    David Trattnig committed
        active_source = None
    
    David Trattnig's avatar
    David Trattnig committed
        api_playlog = None
        api_healthlog = None
    
    
    
        def __init__(self, config, logger):
    
            """
            Initialize Service.
            """
    
            self.config = config
            self.logger = logger
    
    
            # Evaluate deployment mode
    
            node_type = NodeType.MAIN
            host_id = config.get("host_id")
            if host_id == 0:
                node_type = NodeType.SYNC
    
    
    David Trattnig's avatar
    David Trattnig committed
            # Configured as Sync Node
    
            if not node_type == NodeType.MAIN:
    
                self.node_type = NodeType.SYNC
                self.main_hosts = [ config.get("main_host_1"), config.get("main_host_2") ]
    
                if not self.main_hosts[0] and not self.main_hosts[1]:
                    self.logger.warn("Not a single main host defined. Be aware what you are doing.")
    
    David Trattnig's avatar
    David Trattnig committed
                    msg = "No sync possible as no host nodes are configured"
    
                else:
                    msg = "Syncing data of hosts '%s'" % (self.main_hosts)
    
    David Trattnig's avatar
    David Trattnig committed
            # Configured as Main Node
    
            else:
                self.node_type = NodeType.MAIN
                self.sync_host = config.get("sync_host")
                if not self.sync_host:
    
    David Trattnig's avatar
    David Trattnig committed
                    msg = "No child node for synchronization defined"
    
                else:
                    msg = "Pushing data to '%s'" % (self.sync_host)
    
    
    David Trattnig's avatar
    David Trattnig committed
            # Set active source
    
    David Trattnig's avatar
    David Trattnig committed
            source = ActivityLog.get_active_source()
            if source: self.active_source = source.source_number
    
    David Trattnig's avatar
    David Trattnig committed
            if not self.active_source:
                if self.node_type == NodeType.MAIN:
                    source_number = self.config.get("host_id")
                else:
                    source_number = self.config.get("default_source")
                self.set_active_source(source_number)
    
            self.logger.info("Active source: %s" % self.active_source)
    
    David Trattnig's avatar
    David Trattnig committed
            # Init Sync API endpoints
            self.api_playlog = self.config.get("sync_api_store_playlog")
            self.api_healthlog = self.config.get("sync_api_store_healthlog")
    
    David Trattnig's avatar
    David Trattnig committed
            self.logger.info("Running in '%s' mode. %s." % (self.node_type, msg))
    
    
        def current_track(self):  
            """
            Retrieves the currently playing track.  
    
            Returns:
    
    David Trattnig's avatar
    David Trattnig committed
                (JSON)
    
            """
            track = PlayLog.select_current()
    
    David Trattnig's avatar
    David Trattnig committed
            track_schema = TrackSchema()
    
            return track_schema.dump(track)
    
    
    
    David Trattnig's avatar
    David Trattnig committed
        def list_tracks(self, page=None, size=None):  
    
    David Trattnig's avatar
    David Trattnig committed
            Lists track-service entries with pagination.
    
    David Trattnig's avatar
    David Trattnig committed
                page (Integer): The number of the page to return
                size (Integer): The numbers of items to return
    
    David Trattnig's avatar
    David Trattnig committed
                (JSON)
    
    David Trattnig's avatar
    David Trattnig committed
            if not size or size > 50 or size < 1: size = 20
    
    David Trattnig's avatar
    David Trattnig committed
            tracklist = PlayLog.paginate(page, size, None, None, False)
    
    David Trattnig's avatar
    David Trattnig committed
            tracklist_schema = TrackSchema(many=True)
            return tracklist_schema.dump(tracklist)
    
    David Trattnig's avatar
    David Trattnig committed
        def list_playlog(self, page=None, size=None, from_time=None, to_time=None, skip_synced=False):  
    
            """
            Get paginated playlog entries for since the given timestamp.  
    
            Args:
                page (Integer): The number of items to skip before starting to collect the result set
                size (Integer): The numbers of items to return per page
    
    David Trattnig's avatar
    David Trattnig committed
                from_time (datetime): Optionally, get entries after this timestamp (e.g. "2020-08-29T09:12:33.001Z")
                from_time (datetime): Optionally, get entries before this timestamp (e.g. "2020-08-29T09:12:33.001Z")
                skip_synced (Boolean): Optionally, don't return entries which have been posted directly before (sync node only)
    
    David Trattnig's avatar
    David Trattnig committed
                (JSON)
    
    David Trattnig's avatar
    David Trattnig committed
            tracklist = PlayLog.paginate(page, size, from_time, to_time, skip_synced)
    
            tracklist_schema = TrackSchema(many=True)
            return tracklist_schema.dump(tracklist)
    
    
    
        def store_playlog(self, data): 
    
            """
            Stores the passed playlog entry.
    
    
    David Trattnig's avatar
    David Trattnig committed
            Args:
                data (Object): The data to store in the playlog
    
            if not data.log_source:
                data.log_source = self.host_id
    
    
    David Trattnig's avatar
    David Trattnig committed
            # Main Node: Alway log entry, independed of the source
            # Sync Node: Only log entry when it's coming from an active source
            if self.node_type == NodeType.MAIN or \
                (self.node_type == NodeType.SYNC and data.log_source == self.active_source):
    
                playlog = PlayLog(data)
                playlog.save()
                self.logger.debug("Stored playlog for '%s'" % playlog.track_start)
    
                # Main Node: Push to Sync Node, if enabled
    
    David Trattnig's avatar
    David Trattnig committed
                if self.node_type == NodeType.MAIN and self.sync_host and self.api_playlog:
    
    David Trattnig's avatar
    David Trattnig committed
                    playlog_schema = PlayLogSchema()
                    json_playlog = playlog_schema.dump(playlog)
    
                    try:
    
    David Trattnig's avatar
    David Trattnig committed
                        api_url = self.sync_host + self.api_playlog
    
    David Trattnig's avatar
    David Trattnig committed
                        r = requests.post(api_url, json=json_playlog)
                        if r.status_code == 200:
                            self.logger.info("Successfully pushed playlog for '%s' to '%s'" % (playlog.track_start, self.sync_host))
                            playlog.is_synced = True
                            playlog.save()
                        else:
                            self.logger.error("Error while pushing playlog to sync-node: " + r.json())
                    except Exception as e:
                        self.logger.error("Error while posting to sync-node API '%s'!" % (api_url), e)
            else:
                self.logger.info("Ditching playlog sent from an inactive source")
    
    David Trattnig's avatar
    David Trattnig committed
    
    
        def clock_info(self):
            """
    
    David Trattnig's avatar
    David Trattnig committed
            Retrieves the dataset required to render the studio clock.  
    
    David Trattnig's avatar
    David Trattnig committed
            return "studio clock data"
    
    David Trattnig's avatar
    David Trattnig committed
        def set_active_source(self, source_number):
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
    David Trattnig's avatar
    David Trattnig committed
            Sets the active source (engine1, engine2, other) identified by its source number.
    
    David Trattnig's avatar
    David Trattnig committed
    
            Args:
    
    David Trattnig's avatar
    David Trattnig committed
                source_number (Integer): Number of the engine
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
            if source_number > 0:
    
                if self.active_source != source_number:
                    self.active_source = source_number
    
                    activity_log = ActivityLog(source_number)
    
                    activity_log.save()
    
    David Trattnig's avatar
    David Trattnig committed
        def get_active_source(self):
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
    David Trattnig's avatar
    David Trattnig committed
            Retrieves number of the currently active source (engine1, engine2, other)
    
    David Trattnig's avatar
    David Trattnig committed
    
            Returns:
    
    David Trattnig's avatar
    David Trattnig committed
                (Integer)
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
    David Trattnig's avatar
    David Trattnig committed
            return self.active_source
    
    David Trattnig's avatar
    David Trattnig committed
        def get_source_health(self, source_number):
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
    David Trattnig's avatar
    David Trattnig committed
            Retrieves the most recent health info of the requested source
    
    David Trattnig's avatar
    David Trattnig committed
    
            Args:
    
    David Trattnig's avatar
    David Trattnig committed
                source_number (Integer): Number of the play-out source
    
    David Trattnig's avatar
    David Trattnig committed
    
    
            Returns:
    
    David Trattnig's avatar
    David Trattnig committed
                (JSON)
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
    David Trattnig's avatar
    David Trattnig committed
            health = HealthHistory.get_latest_entry(source_number)
            health_schema = HealthHistorySchema()
            return health_schema.dump(health)
    
    David Trattnig's avatar
    David Trattnig committed
        def log_source_health(self, source_number, data):
    
    David Trattnig's avatar
    David Trattnig committed
            """
    
    David Trattnig's avatar
    David Trattnig committed
            Logs an health entry for the given source
    
    David Trattnig's avatar
    David Trattnig committed
    
            Args:
    
    David Trattnig's avatar
    David Trattnig committed
                source_number (Integer): Number of the play-out source
    
    David Trattnig's avatar
    David Trattnig committed
                data (Object): The data to store in the health log
    
    David Trattnig's avatar
    David Trattnig committed
    
            """
    
    David Trattnig's avatar
    David Trattnig committed
            if not data.log_source:
                data.log_source = self.host_id
    
            # Main Node: Alway log entry, independed of the source
            # Sync Node: Only log entry when it's coming from an active source
            if self.node_type == NodeType.MAIN or \
                (self.node_type == NodeType.SYNC and data.log_source == self.active_source):
    
                healthlog = HealthHistory(data.log_source, data.is_healthy, data.health_info)
                healthlog.save()
                self.logger.debug("Stored health info for '%s'" % healthlog.log_source)
    
                # Main Node: Push to Sync Node, if enabled
                if self.node_type == NodeType.MAIN and self.sync_host and self.api_healthlog:
    
                    health_schema = HealthHistorySchema()
                    json_healthlog = health_schema.dump(healthlog)
    
                    try:
                        api_url = self.sync_host + api_healthlog + "/" + data.log_source
                        r = requests.post(api_url, json=json_healthlog)
                        if r.status_code == 200:
                            self.logger.info("Successfully pushed healthlog for source '%s' to '%s'" % (healthlog.log_source, self.sync_host))
                            playlog.is_synced = True
                            playlog.save()
                        else:
                            self.logger.error("Error while pushing healthlog to sync-node: " + r.json())
                    except Exception as e:
                        self.logger.error("Error while posting to sync-node API '%s'!" % (api_url), e)
            else:
                self.logger.info("Ditching healthlog sent from an inactive source")
    
    David Trattnig's avatar
    David Trattnig committed