communicator.py 28.3 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
23
24
#
#  engine
#
#  Playout Daemon for autoradio project
#
#
#  Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
#  This file is part of engine.
#
#  engine is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  any later version.
#
#  engine 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with engine. If not, see <http://www.gnu.org/licenses/>.
#

25
26
import time
import logging
27
import json
28

29
from modules.communication.liquidsoap.playerclient import LiquidSoapPlayerClient
30
# from modules.communication.liquidsoap.recorderclient import LiquidSoapRecorderClient
31
from modules.communication.liquidsoap.initthread import LiquidSoapInitThread
32
from modules.communication.mail import AuraMailer
33

34
from libraries.enum.auraenumerations import TerminalColors, ScheduleEntryType
35
from modules.base.exceptions import LQConnectionError
36
from libraries.exceptions.exception_logger import ExceptionLogger
37

38
""" 
39
40
41
    LiquidSoapCommunicator Class

    Uses LiquidSoapClient, but introduces more complex commands, transactions and error handling.
42
"""
43

44
class LiquidSoapCommunicator(ExceptionLogger):
45
    client = None
46
    logger = None
47
    transaction = 0
48
    channels = None
49
    scheduler = None
50
    error_data = None
51
    auramailer = None
52
    is_liquidsoap_running = False
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
53
    connection_attempts = 0
54
    active_channel = None
55
    disable_logging = False
56
57
    fade_in_active = False
    fade_out_active = False
58
59

    # ------------------------------------------------------------------------------------------ #
60
    def __init__(self, config):
61
        """
62
63
        Constructor
        """
64
        self.config = config
65
66
        self.logger = logging.getLogger("AuraEngine")

67
68
        self.client = LiquidSoapPlayerClient(config, "engine.sock")
        # self.lqcr = LiquidSoapRecorderClient(config, "record.sock")
69

70
        errors_file = self.config.get("install_dir") + "/errormessages/controller_error.js"
71
        f = open(errors_file)
72
        self.error_data = json.load(f)
73
        f.close()
74

75
        self.auramailer = AuraMailer(self.config)
76

77
78
79
80
81
82
83
84
85
86
87
88
89
90
        self.is_liquidsoap_up_and_running()

    # ------------------------------------------------------------------------------------------ #
    def is_liquidsoap_up_and_running(self):
        try:
            self.uptime()
            self.is_liquidsoap_running = True
        except LQConnectionError as e:
            self.logger.info("Liquidsoap is not running so far")
            self.is_liquidsoap_running = False
        except Exception as e:
            self.logger.error("Cannot check if Liquidsoap is running. Reason: " + str(e))
            self.is_liquidsoap_running = False

91
92
    # ------------------------------------------------------------------------------------------ #
    def set_volume(self, mixernumber, volume):
93
        return self.__send_lqc_command__(self.client, "mixer", "volume", mixernumber, volume)
94
95
96

    # ------------------------------------------------------------------------------------------ #
    def get_active_mixer(self):
97
98
99
100
        """
        get active mixer in liquidsoap server
        :return:
        """
101
102
        activeinputs = []

103
        # enable more control over the connection
104
        self.enable_transaction()
105

106
        inputs = self.get_all_channels()
107
108
109

        cnt = 0
        for input in inputs:
110
            status = self.__get_mixer_status__(cnt)
111
112
113
114
115
116

            if "selected=true" in status:
                activeinputs.append(input)

            cnt = cnt + 1

117
        self.disable_transaction()
118
119
120

        return activeinputs

121
122
123
    # ------------------------------------------------------------------------------------------ #
    def get_active_channel(self):
        """
David Trattnig's avatar
David Trattnig committed
124
125
126
127
        Retrieves the active channel from programme.

        Returns:
            (String):   The channel type, empty string if no channel is active.
128
        """
David Trattnig's avatar
David Trattnig committed
129
        active_entry = self.scheduler.get_active_entry()
130
        if active_entry is None:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
131
            return ""
132
        return active_entry.type
133

134
135
    # ------------------------------------------------------------------------------------------ #
    def get_mixer_status(self):
136
137
        inputstate = {}

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
138
        self.enable_transaction()
139

140
        inputs = self.get_all_channels()
141
142
143

        cnt = 0
        for input in inputs:
144
            inputstate[input] = self.__get_mixer_status__(cnt)
145
146
            cnt = cnt + 1

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
147
        self.disable_transaction()
148
149
150

        return inputstate

151
152
153
154
    # ------------------------------------------------------------------------------------------ #
    def get_mixer_volume(self, channel):
        return False

155
156
    # ------------------------------------------------------------------------------------------ #
    def get_recorder_status(self):
157
158
159
        self.enable_transaction(self.client)
        recorder_state = self.__send_lqc_command__(self.client, "record", "status")
        self.disable_transaction(self.client)
160
161
162

        return recorder_state

163
164
165
166
167
168
169
170
171
    # ------------------------------------------------------------------------------------------ #
    def http_start_stop(self, start):
        if start:
            cmd = "start"
        else:
            cmd = "stop"

        try:
            self.enable_transaction()
172
            self.__send_lqc_command__(self.client, "http", cmd)
173
            self.disable_transaction()
174
        except LQConnectionError:
175
176
177
            # we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further
            pass

178
179
180
181
182
183
184
185
186
187
188
189
    # ------------------------------------------------------------------------------------------ #
    def recorder_stop(self):
        self.enable_transaction()

        for i in range(5):
            if self.config.get("rec_" + str(i)) == "y":
                self.__send_lqc_command__(self.client, "recorder_" + str(i), "stop")

        self.disable_transaction()

    # ------------------------------------------------------------------------------------------ #
    def recorder_start(self, num=-1):
190
191
192
193
194
195
196
197
        if not self.is_liquidsoap_running:
            if num==-1:
                msg = "Want to start recorder, but LiquidSoap is not running"
            else:
                msg = "Want to start recorder " + str(num) + ", but LiquidSoap is not running"
            self.logger.warning(msg)
            return False

198
199
200
201
202
203
204
        self.enable_transaction()

        if num == -1:
            self.recorder_start_all()
        else:
            self.recorder_start_one(num)

205
206
        self.disable_transaction()

207
208
    # ------------------------------------------------------------------------------------------ #
    def recorder_start_all(self):
209
210
211
212
        if not self.is_liquidsoap_running:
            self.logger.warning("Want to start all recorder, but LiquidSoap is not running")
            return False

213
        self.enable_transaction()
214
215
        for i in range(5):
            self.recorder_start_one(i)
216
        self.disable_transaction()
217
218
219

    # ------------------------------------------------------------------------------------------ #
    def recorder_start_one(self, num):
220
221
        if not self.is_liquidsoap_running:
            return False
222

223
224
225
226
227
        if self.config.get("rec_" + str(num)) == "y":
            returnvalue = self.__send_lqc_command__(self.client, "recorder", str(num), "status")

            if returnvalue == "off":
                self.__send_lqc_command__(self.client, "recorder", str(num), "start")
228

229
    # ------------------------------------------------------------------------------------------ #
230
231
232
233
234
235
236
    def fade_in(self, new_entry):
        try:
            fade_in_time = float(self.config.get("fade_in_time"))

            if fade_in_time > 0:
                self.fade_in_active = True
                target_volume = new_entry.volume
David Trattnig's avatar
David Trattnig committed
237

238
                step = fade_in_time / target_volume
239

240
                self.logger.info("Starting to fading " + new_entry.type.value + " in. step is " + str(step) + "s. target volume is " + str(target_volume))
241

242
243
                self.disable_logging = True
                self.client.disable_logging = True
244

245
246
247
248
249
250
251
252
253
254
255
256
                for i in range(target_volume):
                    self.channel_volume(new_entry.type.value, i + 1)
                    time.sleep(step)

                self.logger.info("Finished with fading " + new_entry.type.value + " in.")

                self.fade_in_active = False
                if not self.fade_out_active:
                    self.disable_logging = False
                    self.client.disable_logging = False
        except LQConnectionError as e:
            self.logger.critical(str(e))
257
258
259
260

        return True

    # ------------------------------------------------------------------------------------------ #
261
262
263
264
265
266
    def fade_out(self, old_entry):
        try:
            fade_out_time = float(self.config.get("fade_out_time"))

            if fade_out_time > 0:
                step = abs(fade_out_time) / old_entry.volume
267

268
                self.logger.info("Starting to fading " + old_entry.type.value + " out. step is " + str(step) + "s")
269

270
271
272
                # disable logging... it is going to be enabled again after fadein and -out is finished
                self.disable_logging = True
                self.client.disable_logging = True
273

274
275
276
277
278
279
280
281
282
283
284
285
286
                for i in range(old_entry.volume):
                    self.channel_volume(old_entry.type.value, old_entry.volume-i-1)
                    time.sleep(step)

                self.logger.info("Finished with fading " + old_entry.type.value + " out.")

                # enable logging again
                self.fade_out_active = False
                if not self.fade_in_active:
                    self.disable_logging = False
                    self.client.disable_logging = False
        except LQConnectionError as e:
            self.logger.critical(str(e))
287

288
289
        return True

290
    # ------------------------------------------------------------------------------------------ #
David Trattnig's avatar
David Trattnig committed
291
292
293
294
295
296
    def activate(self, new_entry, cue_in=0.0):
        """
        Activates a new Playlist Entry.

        Args:
            new_entry (PlaylistEntry): The track to be played
297
            cue_in (Float):            Start/cue-time of track (For some reason Liquidsoap doesn't acknowledge this yet)
David Trattnig's avatar
David Trattnig committed
298
299
300
301
302
303
304
305
306
307
308
        
        Raises:
            (LQConnectionError): In case connecting to LiquidSoap isn't possible
        """
        
        # Grab the actual active entry
        active_entry = self.scheduler.get_active_entry()
        # Set default channel, if no previous track is available
        current_channel = ScheduleEntryType.FILESYSTEM 
        if active_entry:
            current_channel = active_entry.type
309

310
        try:
311
            self.enable_transaction()
David Trattnig's avatar
David Trattnig committed
312
            if current_channel == new_entry.type:
313
314
315
316

                # TODO Add logic, if some track on the same channel isn't finished yet,
                #      it should be transitioned using a second filesystem channel.
                #      
David Trattnig's avatar
David Trattnig committed
317
                self.activate_same_channel(new_entry, cue_in)
318
            else:
David Trattnig's avatar
David Trattnig committed
319
                self.activate_different_channel(new_entry, cue_in, current_channel)
320
            self.disable_transaction()
321

322
            self.scheduler.update_track_service(new_entry)
323
        except LQConnectionError:
David Trattnig's avatar
David Trattnig committed
324
325
            # we already caught and handled this error in __send_lqc_command__, 
            # but we do not want to execute this function further and pass the exception
326
            pass
327

David Trattnig's avatar
David Trattnig committed
328

329
330
331
332
333
334
335

    def activate_playlist(self, playlist, cue_in=0.0):
        """
        Activates a new Playlist.

        Args:
            new_entry (Playlist): The playlist to be played
336
            cue_in (Float):            Start/cue-time of track (For some reason Liquidsoap doesn't acknowledge this yet)
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
        
        Raises:
            (LQConnectionError): In case connecting to LiquidSoap isn't possible
        """
        
        # Grab the actual active entry
        active_entry = self.scheduler.get_active_entry()
        # Set default channel, if no previous track is available
        current_channel = ScheduleEntryType.FILESYSTEM 
        if active_entry:
            current_channel = active_entry.type

        try:
            # FIXME clearing creates some serious timing issues
            # To activate this feature we'd need some more sophisticated
            # Liquidsoap logic, such as >= 2 filesystem channels and
            # possiblities to pause pre-queued channels or cleaning them
            # after each completed schedule.

            # self.enable_transaction()
            # #if active_entry:
            #     #self.fade_out(active_entry)
            # res = self.clear_queue()
            # self.logger.info("Clear Queue Response: "+res)
            # self.disable_transaction()


            self.enable_transaction()
            self.reload_channels()
            # self.fade_in(playlist.entries[0])
            for new_entry in playlist.entries:
                if current_channel == new_entry.type:
                    self.activate_same_channel(new_entry, cue_in)
                else:
                    self.activate_different_channel(new_entry, cue_in, current_channel)
                    current_channel = new_entry.type

            self.disable_transaction()

            self.logger.critical("FIXME: Implement TrackService")
377
            #self.scheduler.update_track_service(new_entry)
378
379
380
381
382
383
384
        except LQConnectionError:
            # we already caught and handled this error in __send_lqc_command__, 
            # but we do not want to execute this function further and pass the exception
            pass



David Trattnig's avatar
David Trattnig committed
385
386
387
388
389
390
391
392
    def activate_same_channel(self, entry, cue_in=0.0, activate_different_channel=False):
        """
        Activates a playlist entry for the current channel.

        Args:
            entry (PlaylistEntry):  The entry to play.
            cue_in (Float):         A value in seconds where to cue the start of the entry.
        """
393
        if not activate_different_channel:
394
395
            self.logger.info(TerminalColors.PINK.value + entry.type.value + " already active!" + TerminalColors.ENDC.value)

David Trattnig's avatar
David Trattnig committed
396
        # Check if it needs to be pushed to a filesystem queue or stream
397
        if entry.type == ScheduleEntryType.FILESYSTEM:
David Trattnig's avatar
David Trattnig committed
398
399
400
401
            uri = entry.filename
            if cue_in > 0.0:
                uri = "annotate:liq_cue_in=\"%s\":%s" % (str(cue_in), entry.filename)
            self.playlist_push(uri)
402
403
404
            self.active_channel = entry.type

        elif entry.type == ScheduleEntryType.STREAM:
405
            self.set_http_url(entry.source)
406
            self.http_start_stop(True)
407
408
409
            self.active_channel = entry.type

        # else: # live
David Trattnig's avatar
David Trattnig committed
410
        # Nothing to do when we are live => just leave it as is
411

412
413
        self.active_channel = entry.type

David Trattnig's avatar
David Trattnig committed
414
        # Set active channel to wanted volume
415
416
417
        if not activate_different_channel:
            self.channel_volume(entry.type.value, entry.volume)

David Trattnig's avatar
David Trattnig committed
418
419
420
421
422
423
424
425
426
427

    def activate_different_channel(self, entry, cue_in, active_type):
        """
        Activates a playlist entry for a channel other then the currently active one.

        Args:
            entry (PlaylistEntry):            The entry to play.
            cue_in (Float):                   A value in seconds where to cue the start of the entry.
            active_type (ScheduleEntryType):  The type of the currently active channel
        """     
428
        self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is activating " + entry.type.value + " & deactivating " + active_type.value + "!" + TerminalColors.ENDC.value)
429

David Trattnig's avatar
David Trattnig committed
430
431
432
        # Reuse of this function, because activate_same_channel and activate_different_channel 
        # are doing pretty the same except setting of the volume to zero
        self.activate_same_channel(entry, cue_in, True)
433

David Trattnig's avatar
David Trattnig committed
434
        # Set other channels to zero volume
435
        others = self.all_inputs_but(entry.getChannel())
436
437
        for o in others:
            self.channel_volume(o, 0)
438

David Trattnig's avatar
David Trattnig committed
439
        # Set active channel to wanted volume
440
        self.channel_volume(entry.type.value, entry.volume)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
441

442
443
444
445
446
447
448
449
450
451


    def clear_queue(self):
        """
        Removes all tracks currently queued.
        """
        self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is clearing the filesystem queue!" + TerminalColors.ENDC.value)
        return self.__send_lqc_command__(self.client, "fs", "clear", )


452
453
    # ------------------------------------------------------------------------------------------ #
    def all_inputs_but(self, input_type):
454
        try:
455
            activemixer_copy = self.get_all_channels().copy()
456
            activemixer_copy.remove(input_type)
457
458
        except ValueError as e:
            self.logger.error("Requested channel (" + input_type + ") not in channellist. Reason: " + str(e))
459
460
        except AttributeError:
            self.logger.critical("Channellist is None")
461

462
463
464
465
        return activemixer_copy

    # ------------------------------------------------------------------------------------------ #
    def get_all_channels(self):
466
        if self.channels is None or len(self.channels) == 0:
467
            self.channels = self.__send_lqc_command__(self.client, "mixer", "inputs")
468
469

        return self.channels
470

471
472
473
474
475
    # ------------------------------------------------------------------------------------------ #
    def reload_channels(self):
        self.channels = None
        return self.get_all_channels()

476
477
    # ------------------------------------------------------------------------------------------ #
    def __get_mixer_status__(self, mixernumber):
478
        return self.__send_lqc_command__(self.client, "mixer", "status", mixernumber)
479
480

    # ------------------------------------------------------------------------------------------ #
481

482
    def init_player(self):
David Trattnig's avatar
David Trattnig committed
483
484
485
486
487
488
489
        """
        Initializes the LiquidSoap Player after startup of the engine.

        Returns:
            (String):   Message that the player is started.
        """
        active_entry = self.scheduler.get_active_entry()
490
491

        t = LiquidSoapInitThread(self, active_entry)
492
        t.start()
493

494
        return "LiquidSoapInitThread started!"
495

496
    # ------------------------------------------------------------------------------------------ #
497
498
499
500
501
502
    def channel_activate(self, channel, activate):
        channels = self.get_all_channels()

        try:
            index = channels.index(channel)
            if len(channel) < 1:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
503
504
505
506
507
508
                self.logger.critical("Cannot activate channel. There are no channels!")
            else:
                message = self.__send_lqc_command__(self.client, "mixer", "select", index, activate)
                return message
        except Exception as e:
            self.logger.critical("Ran into exception when activating channel. Reason: " + str(e))
509

510
511
512
    # ------------------------------------------------------------------------------------------ #
    def channel_volume(self, channel, volume):
        """
513
        set volume of a channel
514
        @type     channel:  string
515
        @param    channel:  Channel
516
        @type     volume:   int
517
        @param    volume:   Volume between 0 and 100
518
        """
519

520
        try:
521
            channels = self.get_all_channels()
522
            index = channels.index(channel)
523
        except ValueError as e:
524
            self.logger.error("Cannot set volume of channel " + channel + " to " + str(volume) + "!. Reason: " + str(e))
525
526
527
            return

        try:
528
            if len(channel) < 1:
529
                self.logger.warning("Cannot set volume of channel " + channel + " to " + str(volume) + "! There are no channels.")
530
            else:
531
                message = self.__send_lqc_command__(self.client, "mixer", "volume", str(index), str(int(volume)))
532

533
534
535
536
537
                if not self.disable_logging:
                    if message.find('volume=' + str(volume) + '%'):
                        self.logger.debug("Set volume of channel " + channel + " to " + str(volume))
                    else:
                        self.logger.warning("Setting volume of channel " + channel + " gone wrong! Liquidsoap message: " + message)
538

539
                return message
540
        except AttributeError as e: #(LQConnectionError, AttributeError):
541
            self.disable_transaction(force=True)
542
            self.logger.error("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e))
543

544
545
546
547
548
    # ------------------------------------------------------------------------------------------ #
    def auraengine_state(self):
        state = self.__send_lqc_command__(self.client, "auraengine", "state")
        return state

549
550
    # ------------------------------------------------------------------------------------------ #
    def liquidsoap_help(self):
David Trattnig's avatar
David Trattnig committed
551
        data = self.__send_lqc_command__(self.client, "help", "")
552
        if not data:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
553
            self.logger.warning("Could not get Liquidsoap's help")
554
        else:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
555
556
            self.logger.debug("Got Liquidsoap's help")
        return data
557

558
559
    # ------------------------------------------------------------------------------------------ #
    def set_http_url(self, uri):
560
        return self.__send_lqc_command__(self.client, "http", "url", uri)
561

562
563
564
    # ------------------------------------------------------------------------------------------ #
    def playlist_push(self, uri):
        """
David Trattnig's avatar
David Trattnig committed
565
566
567
568
569
570
        Adds an filesystem URI to the playlist

        Args:
            uri (String):   The URI of the file
        Returns:
            LiquidSoap Response
571
        """
572
        return self.__send_lqc_command__(self.client, "fs", "push", uri)
573
574
575

    # ------------------------------------------------------------------------------------------ #
    def playlist_seek(self, seconds_to_seek):
David Trattnig's avatar
David Trattnig committed
576
577
578
579
580
581
582
        """
        Forwards the player (n) seconds.

        Args:
            seconds_to_seeks (Float):   The seconds to skip
        """
        return self.__send_lqc_command__(self.client, "fs", "seek", str(seconds_to_seek))
583
584
585
586
587
588

    # ------------------------------------------------------------------------------------------ #
    def version(self):
        """
        get version
        """
589
        data = self.__send_lqc_command__(self.client, "version", "")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
590
591
        self.logger.debug("Got Liquidsoap's version")
        return data
592
593

    # ------------------------------------------------------------------------------------------ #
594
595
596
597
    def uptime(self):
        """
        get uptime
        """
598
599
        data = self.__send_lqc_command__(self.client, "uptime", "")
        self.logger.debug("Got Liquidsoap's uptime")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
600
        return data
601
602

    # ------------------------------------------------------------------------------------------ #
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
603
    def __send_lqc_command__(self, lqs_instance, namespace, command, *args):
604
605
606
607
608
609
610
611
612
613
614
615
616
617
        """
        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
        """
        try:
618
619
620
            if not self.disable_logging:
                if namespace == "recorder":
                    self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + "_" + str(command) + "." + str(args))
621
                else:
622
623
624
625
                    if command == "":
                        self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + str(args))
                    else:
                        self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args))
626

627
628
629
630
631
            # call wanted function ...
            func = getattr(lqs_instance, namespace)
            # ... and fetch the result
            result = func(command, *args)

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

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
635
            self.connection_attempts = 0
636

637
638
            return result

639
        except LQConnectionError as e:
640
            self.logger.error("Connection Error when sending " + str(namespace) + "." + str(command) + str(args))
641
            if self.try_to_reconnect():
642
                time.sleep(0.2)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
643
644
                self.connection_attempts += 1
                if self.connection_attempts < 5:
645
646
647
648
649
650
651
652
653
                    # 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__(lqs_instance, namespace, command, *args)
                    # disconnect
                    self.__close_conn(self.client)
                    # return the val
                    return retval
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
654
                else:
655
656
657
658
659
660
                    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)
661
                    self.disable_transaction(socket=self.client, force=True)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
662
                    raise e
663
            else:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
664
                # also store when was last admin mail sent with which content...
David Trattnig's avatar
David Trattnig committed
665
                # FIXME implement admin mail sending
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
666
                self.logger.critical("SEND ADMIN MAIL AT THIS POINT")
667
668
                raise e

669
    # ------------------------------------------------------------------------------------------ #
670
671
672
673
674
675
676
677
    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:
678
            socket = self.client
679

680
681
        self.transaction = self.transaction + 1

682
        self.logger.debug(TerminalColors.WARNING.value + "Enabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
683
684
685
686

        if self.transaction > 1:
            return

687
688
689
690
691
        try:
            self.__open_conn(socket)
        except FileNotFoundError:
            self.disable_transaction(socket=socket, force=True)

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
692
693
694
            msg = "socket file " + socket.socket_path + " not found. Is liquidsoap running?"
            self.logger.critical(TerminalColors.RED.value + msg + TerminalColors.ENDC.value)
            self.auramailer.send_admin_mail("CRITICAL Exception when connecting to Liquidsoap", msg)
695

696
    # ------------------------------------------------------------------------------------------ #
697
698
699
700
701
    def disable_transaction(self, socket=None, force=False):
        if not force:
            # nothing to disable
            if self.transaction == 0:
                return
702

703
704
            # decrease transaction counter
            self.transaction = self.transaction - 1
705

706
707
708
709
710
711
712
713
            # 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)
714

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

719
    # ------------------------------------------------------------------------------------------ #
720
721
    def __open_conn(self, socket):
        # already connected
722
        if self.transaction > 1:
723
            return
724

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

727
        # try to connect
728
729
        socket.connect()

730
    # ------------------------------------------------------------------------------------------ #
731
732
    def __close_conn(self, socket):
        # set socket to playout
733
        if socket is None:
734
            socket = self.client
735

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

740
        # say bye
741
        socket.byebye()
742

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