communicator.py 28.7 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

class LiquidSoapCommunicator(ExceptionLogger):
    """ 
41
42
43
    LiquidSoapCommunicator Class

    Uses LiquidSoapClient, but introduces more complex commands, transactions and error handling.
44
    """
45
    client = None
46
    logger = None
47
    transaction = 0
48
    channels = None
49
    scheduler = None
50
    #error_data = None #FIXME Can be removed
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
64
65
66
        Initializes the communicator by establishing a Socket connection
        to Liquidsoap.

        Args:
            config (AuraConfig):    The configuration
67
        """
68
        self.config = config
69
70
        self.logger = logging.getLogger("AuraEngine")

71
72
        self.client = LiquidSoapPlayerClient(config, "engine.sock")
        # self.lqcr = LiquidSoapRecorderClient(config, "record.sock")
73

74
75
76
77
78
        #FIXME Can be removed
        # errors_file = self.config.get("install_dir") + "/errormessages/controller_error.js"
        # f = open(errors_file)
        # self.error_data = json.load(f)
        # f.close()
79

80
        self.auramailer = AuraMailer(self.config)
81
82
        self.is_liquidsoap_up_and_running()

83

84
85
86
    #
    #   CHANNELS
    #
87
88


89
90
91
    # ------------------------------------------------------------------------------------------ #
    def get_active_channel(self):
        """
David Trattnig's avatar
David Trattnig committed
92
93
94
95
        Retrieves the active channel from programme.

        Returns:
            (String):   The channel type, empty string if no channel is active.
96
        """
David Trattnig's avatar
David Trattnig committed
97
        active_entry = self.scheduler.get_active_entry()
98
        if active_entry is None:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
99
            return ""
100
        return active_entry.type
101

102
103
104
105
106
107
108
109
110
    # ------------------------------------------------------------------------------------------ #
    def http_start_stop(self, start):
        if start:
            cmd = "start"
        else:
            cmd = "stop"

        try:
            self.enable_transaction()
111
            self.__send_lqc_command__(self.client, "http", cmd)
112
            self.disable_transaction()
113
        except LQConnectionError:
114
115
116
            # we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further
            pass

117
    # ------------------------------------------------------------------------------------------ #
David Trattnig's avatar
David Trattnig committed
118
119
120
121
122
123
    def activate(self, new_entry, cue_in=0.0):
        """
        Activates a new Playlist Entry.

        Args:
            new_entry (PlaylistEntry): The track to be played
124
            cue_in (Float):            Start/cue-time of track (For some reason Liquidsoap doesn't acknowledge this yet)
David Trattnig's avatar
David Trattnig committed
125
126
127
128
129
130
131
132
133
134
135
        
        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
136

137
        try:
138
            self.enable_transaction()
David Trattnig's avatar
David Trattnig committed
139
            if current_channel == new_entry.type:
140
141
142
143

                # 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
144
                self.activate_same_channel(new_entry, cue_in)
145
            else:
David Trattnig's avatar
David Trattnig committed
146
                self.activate_different_channel(new_entry, cue_in, current_channel)
147
            self.disable_transaction()
148

149
            self.scheduler.update_track_service(new_entry)
150
        except LQConnectionError:
David Trattnig's avatar
David Trattnig committed
151
152
            # 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
153
            pass
154

David Trattnig's avatar
David Trattnig committed
155
156
157
158
159
160
161
162
163

    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.
        """
164
        if not activate_different_channel:
165
166
            self.logger.info(TerminalColors.PINK.value + entry.type.value + " already active!" + TerminalColors.ENDC.value)

David Trattnig's avatar
David Trattnig committed
167
        # Check if it needs to be pushed to a filesystem queue or stream
168
        if entry.type == ScheduleEntryType.FILESYSTEM:
David Trattnig's avatar
David Trattnig committed
169
170
171
172
            uri = entry.filename
            if cue_in > 0.0:
                uri = "annotate:liq_cue_in=\"%s\":%s" % (str(cue_in), entry.filename)
            self.playlist_push(uri)
173
174
175
            self.active_channel = entry.type

        elif entry.type == ScheduleEntryType.STREAM:
176
            self.set_http_url(entry.source)
177
            self.http_start_stop(True)
178
179
180
            self.active_channel = entry.type

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

183
184
        self.active_channel = entry.type

David Trattnig's avatar
David Trattnig committed
185
        # Set active channel to wanted volume
186
187
188
        if not activate_different_channel:
            self.channel_volume(entry.type.value, entry.volume)

David Trattnig's avatar
David Trattnig committed
189
190
191
192
193
194
195
196
197
198

    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
        """     
199
        self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is activating " + entry.type.value + " & deactivating " + active_type.value + "!" + TerminalColors.ENDC.value)
200

David Trattnig's avatar
David Trattnig committed
201
202
203
        # 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)
204

David Trattnig's avatar
David Trattnig committed
205
        # Set other channels to zero volume
206
        others = self.all_inputs_but(entry.getChannel())
207
208
        for o in others:
            self.channel_volume(o, 0)
209

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

213
214
215
216
217
218
219
220
221
222


    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", )


223
224
    # ------------------------------------------------------------------------------------------ #
    def all_inputs_but(self, input_type):
225
        try:
226
            activemixer_copy = self.get_all_channels().copy()
227
            activemixer_copy.remove(input_type)
228
229
        except ValueError as e:
            self.logger.error("Requested channel (" + input_type + ") not in channellist. Reason: " + str(e))
230
231
        except AttributeError:
            self.logger.critical("Channellist is None")
232

233
234
235
236
        return activemixer_copy

    # ------------------------------------------------------------------------------------------ #
    def get_all_channels(self):
237
        if self.channels is None or len(self.channels) == 0:
238
            self.channels = self.__send_lqc_command__(self.client, "mixer", "inputs")
239
240

        return self.channels
241

242
243
244
245
246
    # ------------------------------------------------------------------------------------------ #
    def reload_channels(self):
        self.channels = None
        return self.get_all_channels()

247

248

249
250
251
    # ------------------------------------------------------------------------------------------ #
    def channel_activate(self, channel, activate):
        channels = self.get_all_channels()
252
253
254
255

        try:
            index = channels.index(channel)
            if len(channel) < 1:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
256
257
258
259
260
261
                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))
262

263
264
265
    # ------------------------------------------------------------------------------------------ #
    def channel_volume(self, channel, volume):
        """
266
        set volume of a channel
267
        @type     channel:  string
268
        @param    channel:  Channel
269
        @type     volume:   int
270
        @param    volume:   Volume between 0 and 100
271
        """
272

273
        try:
274
            channels = self.get_all_channels()
275
            index = channels.index(channel)
276
        except ValueError as e:
277
            self.logger.error("Cannot set volume of channel " + channel + " to " + str(volume) + "!. Reason: " + str(e))
278
279
280
            return

        try:
281
            if len(channel) < 1:
282
                self.logger.warning("Cannot set volume of channel " + channel + " to " + str(volume) + "! There are no channels.")
283
            else:
284
                message = self.__send_lqc_command__(self.client, "mixer", "volume", str(index), str(int(volume)))
285

286
287
288
289
290
                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)
291

292
                return message
293
        except AttributeError as e: #(LQConnectionError, AttributeError):
294
            self.disable_transaction(force=True)
295
            self.logger.error("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e))
296

297

298

299
300
    # ------------------------------------------------------------------------------------------ #
    def set_http_url(self, uri):
301
        return self.__send_lqc_command__(self.client, "http", "url", uri)
302

303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
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

    #
    #   Playlist 
    #


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

        Args:
            new_entry (Playlist): The playlist to be played
            cue_in (Float):            Start/cue-time of track (For some reason Liquidsoap doesn't acknowledge this yet)
        
        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")
            #self.scheduler.update_track_service(new_entry)
        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


363
364
365
    # ------------------------------------------------------------------------------------------ #
    def playlist_push(self, uri):
        """
David Trattnig's avatar
David Trattnig committed
366
367
368
369
370
371
        Adds an filesystem URI to the playlist

        Args:
            uri (String):   The URI of the file
        Returns:
            LiquidSoap Response
372
        """
373
        return self.__send_lqc_command__(self.client, "fs", "push", uri)
374
375
376

    # ------------------------------------------------------------------------------------------ #
    def playlist_seek(self, seconds_to_seek):
David Trattnig's avatar
David Trattnig committed
377
378
379
380
381
382
383
        """
        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))
384

385
386
387
388
389
390

    #
    #   Mixer
    # 


391
    # ------------------------------------------------------------------------------------------ #
392
393
394
395
396
    def set_volume(self, mixernumber, volume):
        return self.__send_lqc_command__(self.client, "mixer", "volume", mixernumber, volume)

    # ------------------------------------------------------------------------------------------ #
    def get_active_mixer(self):
397
        """
398
399
        get active mixer in liquidsoap server
        :return:
400
        """
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        activeinputs = []

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

        inputs = self.get_all_channels()

        cnt = 0
        for input in inputs:
            status = self.__get_mixer_status__(cnt)

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

            cnt = cnt + 1

        self.disable_transaction()

        return activeinputs
420
421

    # ------------------------------------------------------------------------------------------ #
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
    def get_mixer_status(self):
        inputstate = {}

        self.enable_transaction()

        inputs = self.get_all_channels()

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

        self.disable_transaction()

        return inputstate


    # ------------------------------------------------------------------------------------------ #
    def get_mixer_volume(self, channel):
        return False

    # ------------------------------------------------------------------------------------------ #
    def __get_mixer_status__(self, mixernumber):
        return self.__send_lqc_command__(self.client, "mixer", "status", mixernumber)


    #
    #   Fading 
    #

    # ------------------------------------------------------------------------------------------ #
    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

                step = fade_in_time / target_volume

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

                self.disable_logging = True
                self.client.disable_logging = True

                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))

        return True

    # ------------------------------------------------------------------------------------------ #
    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

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

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

                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))

        return True



    #
    #   Recording
    #


    # ------------------------------------------------------------------------------------------ #
    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):
        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

        self.enable_transaction()

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

        self.disable_transaction()

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

        self.enable_transaction()
        for i in range(5):
            self.recorder_start_one(i)
        self.disable_transaction()

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

        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")

    # ------------------------------------------------------------------------------------------ #
    def get_recorder_status(self):
        self.enable_transaction(self.client)
        recorder_state = self.__send_lqc_command__(self.client, "record", "status")
        self.disable_transaction(self.client)

        return recorder_state



    #
    #   Basic Methods
    #


    def init_player(self):
587
        """
588
589
590
591
        Initializes the LiquidSoap Player after startup of the engine.

        Returns:
            (String):   Message that the player is started.
592
        """
593
594
595
596
597
598
599
        active_entry = self.scheduler.get_active_entry()

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

        return "LiquidSoapInitThread started!"

600
601

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

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

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

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

636
637
            return result

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

668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
    # ------------------------------------------------------------------------------------------ #
    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

    # ------------------------------------------------------------------------------------------ #
    def auraengine_state(self):
        state = self.__send_lqc_command__(self.client, "auraengine", "state")
        return state

    # ------------------------------------------------------------------------------------------ #
    def liquidsoap_help(self):
        data = self.__send_lqc_command__(self.client, "help", "")
        if not data:
            self.logger.warning("Could not get Liquidsoap's help")
        else:
            self.logger.debug("Got Liquidsoap's help")
        return data

    # ------------------------------------------------------------------------------------------ #
    def version(self):
        """
        get version
        """
        data = self.__send_lqc_command__(self.client, "version", "")
        self.logger.debug("Got Liquidsoap's version")
        return data

    # ------------------------------------------------------------------------------------------ #
    def uptime(self):
        """
        get uptime
        """
        data = self.__send_lqc_command__(self.client, "uptime", "")
        self.logger.debug("Got Liquidsoap's uptime")
        return data


    #
    #   Connection and Transaction Handling
    #


718
    # ------------------------------------------------------------------------------------------ #
719
720
721
722
723
724
725
726
    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:
727
            socket = self.client
728

729
730
        self.transaction = self.transaction + 1

731
        self.logger.debug(TerminalColors.WARNING.value + "Enabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
732
733
734
735

        if self.transaction > 1:
            return

736
737
738
739
740
        try:
            self.__open_conn(socket)
        except FileNotFoundError:
            self.disable_transaction(socket=socket, force=True)

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
741
742
743
            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)
744

745
    # ------------------------------------------------------------------------------------------ #
746
747
748
749
750
    def disable_transaction(self, socket=None, force=False):
        if not force:
            # nothing to disable
            if self.transaction == 0:
                return
751

752
753
            # decrease transaction counter
            self.transaction = self.transaction - 1
754

755
756
757
758
759
760
761
762
            # 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)
763

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

768
    # ------------------------------------------------------------------------------------------ #
769
770
    def __open_conn(self, socket):
        # already connected
771
        if self.transaction > 1:
772
            return
773

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

776
        # try to connect
777
778
        socket.connect()

779
    # ------------------------------------------------------------------------------------------ #
780
781
    def __close_conn(self, socket):
        # set socket to playout
782
        if socket is None:
783
            socket = self.client
784

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

789
        # say bye
790
        socket.byebye()
791

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