client.py 9.04 KB
Newer Older
1
#
David Trattnig's avatar
David Trattnig committed
2
# Aura Engine (https://gitlab.servus.at/aura/engine)
3
#
David Trattnig's avatar
David Trattnig committed
4
# Copyright (C) 2017-2020 - The Aura Engine Team.
5
#
David Trattnig's avatar
David Trattnig committed
6
7
8
9
# 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.
10
#
David Trattnig's avatar
David Trattnig committed
11
12
13
14
# 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.
15
#
David Trattnig's avatar
David Trattnig committed
16
17
18
# 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/>.

19

20
21
22
import socket
import urllib.parse
import configparser
23
import logging
24

25
from multiprocessing        import Lock
26

27
28
from src.base.exceptions    import LQConnectionError
from src.base.utils         import TerminalColors
29

30

31
32


33
class LiquidSoapClient:
34
35
36
37
38
    """ 
    LiquidSoapClient Class
    
    Connects to a LiquidSoap instance over a socket and sends commands to it
    """
39
40
    mutex = None
    logger = None
41
    debug = False
42
    socket_path = ""
43
    disable_logging = True
44

45
    def __init__(self, config, socket_filename):
46
47
        """
        Constructor
48
49
        @type    socket_path: string
        @param   socket_path: Der Pfad zum Socket des Liquidsoap-Scripts
50
        """
51
        self.logger = logging.getLogger("AuraEngine")
52
53
        socket_path = config.get("socket_dir") + "/" + socket_filename
        self.socket_path = config.to_abs_path(socket_path)
54

55
        self.logger.debug("LiquidSoapClient using socketpath: " + self.socket_path)
56

57
        # init
58
        self.mutex = Lock()
59
60
61
        self.connected = False
        self.can_connect = True
        self.message = ''
62
63
        self.socket = None
        self.metareader = configparser.ConfigParser()
64
65
66
67
68
69
70

    # ------------------------------------------------------------------------------------------ #
    def connect(self):
        """
        Verbindung herstellen
        """
        try:
71
72
73
            self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.connect(self.socket_path)
74
        except socket.error as e:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
75
            msg = "Cannot connect to socketpath " + self.socket_path + ". Reason: "+str(e)
76
            self.logger.critical(TerminalColors.RED.value+msg+TerminalColors.ENDC.value)
77
            self.can_connect = False
78
            self.connected = False
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
79
#            raise e
80
81
82
83
84
        else:
            self.can_connect = True
            self.connected = True
            return True

85
86
87

# AttributeError('characters_written')

88
    # ------------------------------------------------------------------------------------------ #
89
    def is_connected(self):
90
91
92
93
94
95
96
97
98
99
        return self.connected

    # ------------------------------------------------------------------------------------------ #
    def write(self, data):
        """
        Auf den Socket schreiben
        @type    data: string
        @param   data: Der String der gesendet wird
        """
        if self.connected:
100
            self.socket.sendall(data.decode("UTF-8"))
101
102
103
104
105
106
107
108
109
110
111
112
113

    # ------------------------------------------------------------------------------------------ #
    def read_all(self, timeout=2):
        """
        Vom Socket lesen, bis dieser "END" sendet
        @type    timeout: int
        @param   timeout: Ein optionales Timeout
        @rtype:  string
        @return: Die Antwort des Liquidsoap-Servers
        """
        # make socket non blocking
        # self.client.setblocking(0)

David Trattnig's avatar
David Trattnig committed
114
        data = ''
115
116

        try:
117
            # set timeout
118
            self.socket.settimeout(timeout)
119
120
121
122
123

            # acquire the lock
            self.mutex.acquire()

            while True:
124
                data += self.socket.recv(1).decode("utf-8")
125
126
127
128
129
130
131
132
133

                # receive as long as we are not at the END or recv a Bye! from liquidsoap
                if data.find("END\r\n") != -1 or data.find("Bye!\r\n") != -1:
                    data.replace("END\r\n", "")
                    break

            # release the lock
            self.mutex.release()

134
        except Exception as e:
135
            self.logger.error(TerminalColors.RED.value+str(e)+TerminalColors.ENDC.value)
136
            self.mutex.release()
137
138
139
140
141
142

        return data

    # ------------------------------------------------------------------------------------------ #
    def read(self):
        """
143
        read from socket and store return value in self.message
144
        @rtype:  string
145
        @return: The answer of liquidsoap server
146
147
        """
        if self.connected:
148
            ret = self.read_all().splitlines()
149
150

            try:
151
                last = ret.pop() # pop out end
152

153
154
155
156
                if len(ret) > 1:
                    self.message = str.join(" - ", ret)
                elif len(ret) == 1:
                    self.message = ret[0]
157

158
159
                if last == "Bye!":
                    self.message = last
160

161
            except Exception as e:
162
                self.logger.error(str(e))
163
164
165
166
167
168
169
170
171
172

            return self.message

    # ------------------------------------------------------------------------------------------ #
    def close(self):
        """
        Quit senden und Verbindung schließen
        """
        if self.connected:
            message = "quit\r"
173
174
            self.socket.sendall(message.decode("UTF-8"))
            self.socket.close()
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
            self.connected = False

    # ------------------------------------------------------------------------------------------ #
    def command(self, namespace, command, param=""):
        """
        Kommando an Liquidosap senden
        @type    command:   string
        @param   command:   Kommando
        @type    namespace: string
        @param   namespace: Namespace/Kanal der angesprochen wird
        @type    param:     mixed
        @param   param:     ein optionaler Parameter
        @rtype:  string
        @return: Die Antwort des Liquidsoap-Servers
        """

        param = (param.strip() if param.strip() == "" else " " + urllib.parse.unquote(param.strip()))
        if self.connected:
            # print namespace + '.' + command + param + "\n"
            if namespace is "":
195
                message = str(command) + str(param) + str("\n")
196
            else:
197
                message = str(namespace) + str(".") + str(command) + str(param) + str("\n")
198
199

            try:
200
                if not self.disable_logging:
David Trattnig's avatar
David Trattnig committed
201
                    self.logger.debug("LiquidSoapClient sending to LiquidSoap Server: " + message[0:len(message)-1])
202
203

                # send all the stuff over the socket to liquidsoap server
204
                self.socket.sendall(message.encode())
205

206
                if not self.disable_logging:
207
                    self.logger.debug("LiquidSoapClient waiting for reply from LiquidSoap Server")
208
209

                # wait for reply
210
                self.read()
211

212
                if not self.disable_logging:
David Trattnig's avatar
David Trattnig committed
213
                    self.logger.debug("LiquidSoapClient got reply: " + self.message)
214
            except BrokenPipeError as e:
215
                self.logger.error(TerminalColors.RED.value+"Detected a problem with liquidsoap connection while sending: " + message + ". Reason: " + str(e) + "! Trying to reconnect."+TerminalColors.RED.value)
216
217
218
                self.connect()
                raise

219
220
            except Exception as e:
                self.logger.error("Unexpected error: " + str(e))
221
222
223
224
                raise

            return self.message
        else:
225
            msg = "LiquidsoapClient not connected to LiquidSoap Server"
226
            self.logger.error(msg)
227
            raise LQConnectionError(msg)
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

    # ------------------------------------------------------------------------------------------ #
    def help(self):
        """
        get liquidsoap server help
        @rtype:  string
        @return: the response of the liquidsoap server
        """
        if self.connected:
            self.command('help', '')
            return self.message

    # ------------------------------------------------------------------------------------------ #
    def version(self):
        """
        Liquidsoap get version
        @rtype:  string
        @return: the response of the liquidsoap server
        """
        if self.connected:
            message = 'version'
            self.command(message, '')
            return self.message

    # ------------------------------------------------------------------------------------------ #
    def uptime(self):
        """
        Liquidsoap get uptime
        @rtype:  string
        @return: Die Antwort des Liquidsoap-Servers
        """

        if self.connected:
            self.command('uptime', '')
            return self.message

    # ------------------------------------------------------------------------------------------ #
    def byebye(self):
        """
        Liquidsoap say byebye
        @rtype:  string
        @return: Die Antwort des Liquidsoap-Servers
        """

        if self.connected:
273
            self.command("", "quit")
274
            return self.message