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 libraries.exceptions.auraexceptions import LQConnectionError
36
#from libraries.database.broadcasts import TrackService
37
from libraries.exceptions.exception_logger import ExceptionLogger
38

39
40
41
42
""" 
    LiquidSoapCommunicator Class 
    Uses LiquidSoapClient, but introduces more complex commands, transactions and error handling
"""
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
297
298
299
300
301
302
303
304
305
306
307
    def activate(self, new_entry, cue_in=0.0):
        """
        Activates a new Playlist Entry.

        Args:
            new_entry (PlaylistEntry): The track to be played
        
        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
308

309
        try:
310
            self.enable_transaction()
David Trattnig's avatar
David Trattnig committed
311
312
            if current_channel == new_entry.type:
                self.activate_same_channel(new_entry, cue_in)
313
            else:
David Trattnig's avatar
David Trattnig committed
314
                self.activate_different_channel(new_entry, cue_in, current_channel)
315
            self.disable_transaction()
316

David Trattnig's avatar
David Trattnig committed
317
318
319
            # FIXME Implement TrackService bi-directionally, log fallbacks too.
            self.logger.critical("FIXME: Implement TrackService")
#           self.insert_track_service_entry(new_entry)
320
        except LQConnectionError:
David Trattnig's avatar
David Trattnig committed
321
322
            # 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
323
            pass
324

David Trattnig's avatar
David Trattnig committed
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381

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

        Args:
            new_entry (Playlist): The playlist to be played
        
        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()

            # FIXME Implement TrackService bi-directionally, log fallbacks too.
            self.logger.critical("FIXME: Implement TrackService")
#           self.insert_track_service_entry(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



David Trattnig's avatar
David Trattnig committed
382
383
384
385
386
387
388
389
    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.
        """
390
        if not activate_different_channel:
391
392
            self.logger.info(TerminalColors.PINK.value + entry.type.value + " already active!" + TerminalColors.ENDC.value)

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

        elif entry.type == ScheduleEntryType.STREAM:
402
            self.set_http_url(entry.source)
403
            self.http_start_stop(True)
404
405
406
            self.active_channel = entry.type

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

409
410
        self.active_channel = entry.type

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

David Trattnig's avatar
David Trattnig committed
415
416
417
418
419
420
421
422
423
424

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

David Trattnig's avatar
David Trattnig committed
427
428
429
        # 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)
430

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

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

439
440
441
442
443
444
445
446
447
448
449


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



Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
450
451
    # ------------------------------------------------------------------------------------------ #
    def insert_track_service_entry(self, schedule_entry):
452
        # create trackservice entry
453
        trackservice_entry = TrackService()
454
455

        # set foreign keys
456
457
458
        trackservice_entry.playlist_id = schedule_entry.playlist_id
        trackservice_entry.entry_num = schedule_entry.entry_num
        trackservice_entry.source = schedule_entry.source
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
459

460
        # store
461
        trackservice_entry.store(add=True, commit=True)
462
463
464

    # ------------------------------------------------------------------------------------------ #
    def all_inputs_but(self, input_type):
465
        try:
466
            activemixer_copy = self.get_all_channels().copy()
467
            activemixer_copy.remove(input_type)
468
469
        except ValueError as e:
            self.logger.error("Requested channel (" + input_type + ") not in channellist. Reason: " + str(e))
470
471
        except AttributeError:
            self.logger.critical("Channellist is None")
472

473
474
475
476
        return activemixer_copy

    # ------------------------------------------------------------------------------------------ #
    def get_all_channels(self):
477
        if self.channels is None or len(self.channels) == 0:
478
            self.channels = self.__send_lqc_command__(self.client, "mixer", "inputs")
479
480

        return self.channels
481

482
483
484
485
486
    # ------------------------------------------------------------------------------------------ #
    def reload_channels(self):
        self.channels = None
        return self.get_all_channels()

487
488
    # ------------------------------------------------------------------------------------------ #
    def __get_mixer_status__(self, mixernumber):
489
        return self.__send_lqc_command__(self.client, "mixer", "status", mixernumber)
490
491

    # ------------------------------------------------------------------------------------------ #
492
    def init_player(self):
David Trattnig's avatar
David Trattnig committed
493
494
495
496
497
498
499
        """
        Initializes the LiquidSoap Player after startup of the engine.

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

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

504
        return "LiquidSoapInitThread started!"
505

506
    # ------------------------------------------------------------------------------------------ #
507
508
509
510
511
512
    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
513
514
515
516
517
518
                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))
519

520
521
522
    # ------------------------------------------------------------------------------------------ #
    def channel_volume(self, channel, volume):
        """
523
        set volume of a channel
524
        @type     channel:  string
525
        @param    channel:  Channel
526
        @type     volume:   int
527
        @param    volume:   Volume between 0 and 100
528
        """
529

530
        try:
531
            channels = self.get_all_channels()
532
            index = channels.index(channel)
533
        except ValueError as e:
534
            self.logger.error("Cannot set volume of channel " + channel + " to " + str(volume) + "!. Reason: " + str(e))
535
536
537
            return

        try:
538
            if len(channel) < 1:
539
                self.logger.warning("Cannot set volume of channel " + channel + " to " + str(volume) + "! There are no channels.")
540
            else:
541
                message = self.__send_lqc_command__(self.client, "mixer", "volume", str(index), str(int(volume)))
542

543
544
545
546
547
                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)
548

549
                return message
550
        except AttributeError as e: #(LQConnectionError, AttributeError):
551
            self.disable_transaction(force=True)
552
            self.logger.error("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e))
553

554
555
556
557
558
    # ------------------------------------------------------------------------------------------ #
    def auraengine_state(self):
        state = self.__send_lqc_command__(self.client, "auraengine", "state")
        return state

559
560
    # ------------------------------------------------------------------------------------------ #
    def liquidsoap_help(self):
David Trattnig's avatar
David Trattnig committed
561
        data = self.__send_lqc_command__(self.client, "help", "")
562
        if not data:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
563
            self.logger.warning("Could not get Liquidsoap's help")
564
        else:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
565
566
            self.logger.debug("Got Liquidsoap's help")
        return data
567

568
569
    # ------------------------------------------------------------------------------------------ #
    def set_http_url(self, uri):
570
        return self.__send_lqc_command__(self.client, "http", "url", uri)
571

572
573
574
    # ------------------------------------------------------------------------------------------ #
    def playlist_push(self, uri):
        """
David Trattnig's avatar
David Trattnig committed
575
576
577
578
579
580
        Adds an filesystem URI to the playlist

        Args:
            uri (String):   The URI of the file
        Returns:
            LiquidSoap Response
581
        """
582
        return self.__send_lqc_command__(self.client, "fs", "push", uri)
583
584
585

    # ------------------------------------------------------------------------------------------ #
    def playlist_seek(self, seconds_to_seek):
David Trattnig's avatar
David Trattnig committed
586
587
588
589
590
591
592
        """
        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))
593
594
595
596
597
598

    # ------------------------------------------------------------------------------------------ #
    def version(self):
        """
        get version
        """
599
        data = self.__send_lqc_command__(self.client, "version", "")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
600
601
        self.logger.debug("Got Liquidsoap's version")
        return data
602
603

    # ------------------------------------------------------------------------------------------ #
604
605
606
607
    def uptime(self):
        """
        get uptime
        """
608
609
        data = self.__send_lqc_command__(self.client, "uptime", "")
        self.logger.debug("Got Liquidsoap's uptime")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
610
        return data
611
612

    # ------------------------------------------------------------------------------------------ #
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
613
    def __send_lqc_command__(self, lqs_instance, namespace, command, *args):
614
615
616
617
618
619
620
621
622
623
624
625
626
627
        """
        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:
628
629
630
            if not self.disable_logging:
                if namespace == "recorder":
                    self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + "_" + str(command) + "." + str(args))
631
                else:
632
633
634
635
                    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))
636

637
638
639
640
641
            # call wanted function ...
            func = getattr(lqs_instance, namespace)
            # ... and fetch the result
            result = func(command, *args)

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

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
645
            self.connection_attempts = 0
646

647
648
            return result

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

679
    # ------------------------------------------------------------------------------------------ #
680
681
682
683
684
685
686
687
    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:
688
            socket = self.client
689

690
691
        self.transaction = self.transaction + 1

692
        self.logger.debug(TerminalColors.WARNING.value + "ENabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
693
694
695
696

        if self.transaction > 1:
            return

697
698
699
700
701
        try:
            self.__open_conn(socket)
        except FileNotFoundError:
            self.disable_transaction(socket=socket, force=True)

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
702
703
704
            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)
705

706
    # ------------------------------------------------------------------------------------------ #
707
708
709
710
711
    def disable_transaction(self, socket=None, force=False):
        if not force:
            # nothing to disable
            if self.transaction == 0:
                return
712

713
714
            # decrease transaction counter
            self.transaction = self.transaction - 1
715

716
717
718
719
720
721
722
723
            # 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)
724

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

729
    # ------------------------------------------------------------------------------------------ #
730
731
    def __open_conn(self, socket):
        # already connected
732
        if self.transaction > 1:
733
            return
734

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

737
        # try to connect
738
739
        socket.connect()

740
    # ------------------------------------------------------------------------------------------ #
741
742
    def __close_conn(self, socket):
        # set socket to playout
743
        if socket is None:
744
            socket = self.client
745

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

750
        # say bye
751
        socket.byebye()
752

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