connector.py 8.08 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-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/>.


import logging
import time

23
24
25
from src.base.config                import AuraConfig
from src.base.utils                 import TerminalColors, SimpleUtil as SU
from src.base.exceptions            import LQConnectionError                                       
David Trattnig's avatar
David Trattnig committed
26
from src.client.playerclient        import LiquidSoapPlayerClient
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42



class PlayerConnector():
    """
    Establishes a Socket connection to Liquidsoap.
    """
    client = None
    logger = None
    transaction = 0
    connection_attempts = 0
    disable_logging = False
    event_dispatcher = None



43
    def __init__(self, event_dispatcher):
44
45
46
47
48
49
        """
        Constructor

        Args:
            config (AuraConfig):    The configuration
        """
50
        self.config = AuraConfig.config()
51
        self.logger = logging.getLogger("AuraEngine")
52
        self.client = LiquidSoapPlayerClient(self.config, "engine.sock")
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
        self.event_dispatcher = event_dispatcher



    def send_lqc_command(self, namespace, command, *args):
        """
        Ein Kommando an Liquidsoap senden
        @type  lqs_instance: object
        @param lqs_instance: Instance of LiquidSoap Client
        @type  namespace:    string
        @param namespace:    Namespace of function
        @type  command:      string
        @param command:      Function name
        @type args:          list
        @param args:         List of parameters
        @rtype:              string
        @return:             Response from LiquidSoap
        """
        lqs_instance = self.client
        try:
            if not self.disable_logging:
74
75
                if command == "":
                    self.logger.debug("LiquidSoapCommunicator is calling " + str(namespace) + str(args))
76
                else:
77
                    self.logger.debug("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args))
78
79
80
81
82
83
84
85
86
87

            # call wanted function ...

            # FIXME REFACTOR all calls in a common way
            if command in  [
                    "queue_push", 
                    "queue_seek", 
                    "queue_clear", 
                    "playlist_uri_set", 
                    "playlist_uri_clear",
88
                    "stream_set_url", 
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
                    "stream_start", 
                    "stream_stop", 
                    "stream_status",
                    ]:

                func = getattr(lqs_instance, command)
                result = func(str(namespace), *args)

            elif namespace == "mixer" or namespace == "mixer_fallback":                
                func = getattr(lqs_instance, command)
                result = func(str(namespace), *args)
            else:
                func = getattr(lqs_instance, namespace)
                result = func(command, *args)            


            if not self.disable_logging:
                self.logger.debug("LiquidSoapCommunicator got response " + str(result))

            self.connection_attempts = 0

            return result

        except LQConnectionError as e:
            self.logger.error("Connection Error when sending " + str(namespace) + "." + str(command) + str(args))
            if self.try_to_reconnect():
                time.sleep(0.2)
                self.connection_attempts += 1
                if self.connection_attempts < 5:
                    # reconnect
                    self.__open_conn(self.client)
                    self.logger.info("Trying to resend " + str(namespace) + "." + str(command) + str(args))
                    # grab return value
                    retval = self.send_lqc_command(namespace, command, *args)
                    # disconnect
                    self.__close_conn(self.client)
                    # return the val
                    return retval
                else:
                    if command == "":
                        msg = "Rethrowing Exception while trying to send " + str(namespace) + str(args)
                    else:
                        msg = "Rethrowing Exception while trying to send " + str(namespace) + "." + str(command) + str(args)

                    self.logger.info(msg)
                    self.disable_transaction(socket=self.client, force=True)
                    raise e
            else:
                self.event_dispatcher.on_critical("Criticial Liquidsoap connection issue", \
138
                    "Could not connect to Liquidsoap after multiple attempts", e)
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
                raise e


    # ------------------------------------------------------------------------------------------ #
    def try_to_reconnect(self):
        self.enable_transaction()
        return self.transaction > 0

    # ------------------------------------------------------------------------------------------ #
    def enable_transaction(self, socket=None):
        # set socket to playout if nothing else is given
        if socket is None:
            socket = self.client

        self.transaction = self.transaction + 1

        self.logger.debug(TerminalColors.WARNING.value + "Enabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)

        if self.transaction > 1:
            return

        try:
            self.__open_conn(socket)
        except FileNotFoundError:
            self.disable_transaction(socket=socket, force=True)
            subject = "CRITICAL Exception when connecting to Liquidsoap"
            msg = "socket file " + socket.socket_path + " not found. Is liquidsoap running?"
            self.logger.critical(SU.red(msg))
167
168
            # Not using this for now, as it should be triggered by "on_sick(..)" as well
            #self.event_dispatcher.on_critical(subject, msg, None)
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219


    # ------------------------------------------------------------------------------------------ #
    def disable_transaction(self, socket=None, force=False):
        if not force:
            # nothing to disable
            if self.transaction == 0:
                return

            # decrease transaction counter
            self.transaction = self.transaction - 1

            # debug msg
            self.logger.debug(TerminalColors.WARNING.value + "DISabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)

            # return if connection is still needed
            if self.transaction > 0:
                return
        else:
            self.logger.debug(TerminalColors.WARNING.value + "Forcefully DISabling transaction! " + TerminalColors.ENDC.value)

        # close conn and set transactioncounter to 0
        self.__close_conn(socket)
        self.transaction = 0

    # ------------------------------------------------------------------------------------------ #
    def __open_conn(self, socket):
        # already connected
        if self.transaction > 1:
            return

        self.logger.debug(TerminalColors.GREEN.value + "LiquidSoapCommunicator opening conn" + TerminalColors.ENDC.value)

        # try to connect
        socket.connect()

    # ------------------------------------------------------------------------------------------ #
    def __close_conn(self, socket):
        # set socket to playout
        if socket is None:
            socket = self.client

        # do not disconnect if a transaction is going on
        if self.transaction > 0:
            return

        # say bye
        socket.byebye()

        # debug msg
        self.logger.debug(TerminalColors.BLUE.value + "LiquidSoapCommunicator closed conn" + TerminalColors.ENDC.value)