Skip to content

Daemon

NowPlayingDaemon

The daemon of nowplaying runs at all time and coordinates the i/o.

Source code in nowplaying/daemon.py
class NowPlayingDaemon:
    """The daemon of nowplaying runs at all time and coordinates the i/o."""

    """initialize last_input to a know value."""
    last_input = 1

    def __init__(self, options):
        self.options = options

        self.event_queue = Queue()
        self.saemubox = SaemuBox(
            self.options.saemubox_ip, self.options.check_saemubox_sender
        )

    def main(self):  # pragma: no cover
        # TODO test once there is not saemubox in the loop
        logger.info("Starting up now-playing daemon")
        self.saemubox.run()

        try:
            self.register_signal_handlers()

            input_handler = self.get_input_handler()
        except Exception as e:
            logger.exception("Error: %s", e)
            sys.exit(-1)

        _thread = Thread(target=self._main_loop, args=(input_handler,))
        _thread.daemon = True
        _thread.start()

        self._start_apiserver()  # blocking

    def _start_apiserver(self):
        """Start the API server."""
        self._api = ApiServer(self.options, self.event_queue)
        self._api.run_server()  # blocking

    def _stop_apiserver(self):
        """Stop the API server."""
        logger.info("Stopping API server")
        self._api.stop_server()

    def _main_loop(self, input_handler: InputHandler):  # pragma: no cover
        """
        Run main loop of the daemon.

        Should be run in a thread.
        """
        logger.info("Starting main loop")
        while True:
            try:
                saemubox_id = self.poll_saemubox()

                while not self.event_queue.empty():
                    logger.debug("Queue size: %i" % self.event_queue.qsize())
                    event: CloudEvent = self.event_queue.get()
                    logger.info(
                        "Handling update from event: %s, source: %s"
                        % (event["type"], event["source"])
                    )
                    input_handler.update(saemubox_id, event)

                input_handler.update(saemubox_id)
            except Exception as e:
                logger.exception("Error: %s", e)

            time.sleep(self.options.sleepSeconds)

    def register_signal_handlers(self):
        logger.debug("Registering signal handler")
        signal.signal(signal.SIGINT, self.signal_handler)
        # signal.signal(signal.SIGKIL, self.signal_handler)

    def signal_handler(self, signum, frame):
        logger.debug("Signal %i caught" % signum)

        if signum == signal.SIGINT or signum == signal.SIGKILL:
            logger.info("Signal %i caught, terminating." % signum)
            self._stop_apiserver()
            sys.exit(os.EX_OK)

    def get_track_handler(self):  # pragma: no cover
        # TODO test once options have been refactored with v3
        handler = TrackEventHandler()
        [
            handler.register_observer(
                IcecastTrackObserver(
                    # TODO v3 remove uername and password
                    #          because we mandate specifying via url
                    options=IcecastTrackObserver.Options(
                        url=url,
                        username="source",
                        password=self.options.icecastPassword,
                    )
                )
            )
            for url in self.options.icecast
        ]
        [
            handler.register_observer(
                DabAudioCompanionTrackObserver(
                    options=DabAudioCompanionTrackObserver.Options(
                        url=url, dl_plus=self.options.dab_send_dls
                    )
                )
            )
            for url in self.options.dab
        ]
        handler.register_observer(
            TickerTrackObserver(
                options=TickerTrackObserver.Options(
                    file_path=self.options.tickerOutputFile
                )
            )
        )
        if self.options.dab_smc:
            handler.register_observer(
                SmcFtpTrackObserver(
                    options=SmcFtpTrackObserver.Options(
                        hostname=self.options.dab_smc_ftp_hostname,
                        username=self.options.dab_smc_ftp_username,
                        password=self.options.dab_smc_ftp_password,
                    )
                )
            )

        return handler

    def get_input_handler(self):  # pragma: no cover
        # TODO test once options have been refactored with v3
        handler = InputHandler()
        track_handler = self.get_track_handler()

        klangbecken = inputObservers.KlangbeckenInputObserver(
            self.options.currentShowUrl, self.options.inputFile
        )
        klangbecken.add_track_handler(track_handler)
        handler.register_observer(klangbecken)

        nonklangbecken = inputObservers.NonKlangbeckenInputObserver(
            self.options.currentShowUrl
        )
        nonklangbecken.add_track_handler(track_handler)
        handler.register_observer(nonklangbecken)

        return handler

    def poll_saemubox(self) -> int:  # pragma: no cover
        """
        Poll Saemubox for new data.

        Should be run once per main loop.

        TODO v3 remove once replaced with pathfinder
        """

        saemubox_id = self.saemubox.get_active_output_id()
        logger.debug("Sämubox id: %i" % saemubox_id)

        if self.last_input != saemubox_id:
            logger.info(
                'Sämubox changed from "%s" to "%s"'
                % (
                    self.saemubox.get_id_as_name(self.last_input),
                    self.saemubox.get_id_as_name(saemubox_id),
                )
            )

        self.last_input = saemubox_id
        return saemubox_id

poll_saemubox()

Poll Saemubox for new data.

Should be run once per main loop.

TODO v3 remove once replaced with pathfinder

Source code in nowplaying/daemon.py
def poll_saemubox(self) -> int:  # pragma: no cover
    """
    Poll Saemubox for new data.

    Should be run once per main loop.

    TODO v3 remove once replaced with pathfinder
    """

    saemubox_id = self.saemubox.get_active_output_id()
    logger.debug("Sämubox id: %i" % saemubox_id)

    if self.last_input != saemubox_id:
        logger.info(
            'Sämubox changed from "%s" to "%s"'
            % (
                self.saemubox.get_id_as_name(self.last_input),
                self.saemubox.get_id_as_name(saemubox_id),
            )
        )

    self.last_input = saemubox_id
    return saemubox_id