2012-05-14 22:09:49 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import logging.config
|
|
|
|
import json
|
2011-03-04 02:13:55 +01:00
|
|
|
import telnetlib
|
2012-03-01 23:58:44 +01:00
|
|
|
import copy
|
2012-02-12 05:53:43 +01:00
|
|
|
from threading import Thread
|
2012-08-20 18:11:03 +02:00
|
|
|
import subprocess
|
2011-03-23 06:09:27 +01:00
|
|
|
|
2012-06-28 18:12:22 +02:00
|
|
|
from Queue import Empty
|
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
from api_clients import api_client
|
2012-04-21 00:32:10 +02:00
|
|
|
from std_err_override import LogWriter
|
2011-03-03 06:22:28 +01:00
|
|
|
|
|
|
|
from configobj import ConfigObj
|
|
|
|
|
2011-03-21 00:34:43 +01:00
|
|
|
# configure logging
|
|
|
|
logging.config.fileConfig("logging.cfg")
|
2012-04-21 00:32:10 +02:00
|
|
|
logger = logging.getLogger()
|
|
|
|
LogWriter.override_std_err(logger)
|
|
|
|
|
|
|
|
#need to wait for Python 2.7 for this..
|
|
|
|
#logging.captureWarnings(True)
|
2011-03-21 00:34:43 +01:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
# loading config file
|
|
|
|
try:
|
2011-03-30 00:32:53 +02:00
|
|
|
config = ConfigObj('/etc/airtime/pypo.cfg')
|
2011-03-03 06:22:28 +01:00
|
|
|
LS_HOST = config['ls_host']
|
|
|
|
LS_PORT = config['ls_port']
|
2012-06-27 20:37:16 +02:00
|
|
|
#POLL_INTERVAL = int(config['poll_interval'])
|
|
|
|
POLL_INTERVAL = 1800
|
|
|
|
|
2011-03-21 00:34:43 +01:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
except Exception, e:
|
2011-06-01 18:32:42 +02:00
|
|
|
logger.error('Error loading config file: %s', e)
|
2011-03-03 06:22:28 +01:00
|
|
|
sys.exit()
|
|
|
|
|
2011-03-21 00:34:43 +01:00
|
|
|
class PypoFetch(Thread):
|
2012-03-16 04:14:19 +01:00
|
|
|
def __init__(self, pypoFetch_q, pypoPush_q, media_q, telnet_lock):
|
2011-03-21 00:34:43 +01:00
|
|
|
Thread.__init__(self)
|
2012-07-16 21:33:44 +02:00
|
|
|
self.api_client = api_client.AirtimeApiClient()
|
2012-02-28 20:44:39 +01:00
|
|
|
self.fetch_queue = pypoFetch_q
|
|
|
|
self.push_queue = pypoPush_q
|
2012-03-01 23:58:44 +01:00
|
|
|
self.media_prepare_queue = media_q
|
2012-06-08 20:57:59 +02:00
|
|
|
self.last_update_schedule_timestamp = time.time()
|
2012-06-27 20:37:16 +02:00
|
|
|
self.listener_timeout = POLL_INTERVAL
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-16 04:14:19 +01:00
|
|
|
self.telnet_lock = telnet_lock
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger = logging.getLogger();
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-23 02:41:24 +01:00
|
|
|
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
|
2012-02-29 04:33:19 +01:00
|
|
|
self.logger.debug("Cache dir %s", self.cache_dir)
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2012-02-23 02:41:24 +01:00
|
|
|
try:
|
2012-02-28 21:32:18 +01:00
|
|
|
if not os.path.isdir(dir):
|
|
|
|
"""
|
|
|
|
We get here if path does not exist, or path does exist but
|
2012-08-17 21:38:40 +02:00
|
|
|
is a file. We are not handling the second case, but don't
|
2012-02-28 21:32:18 +01:00
|
|
|
think we actually care about handling it.
|
|
|
|
"""
|
2012-02-29 04:33:19 +01:00
|
|
|
self.logger.debug("Cache dir does not exist. Creating...")
|
2012-02-23 02:41:24 +01:00
|
|
|
os.makedirs(dir)
|
|
|
|
except Exception, e:
|
2012-02-28 21:32:18 +01:00
|
|
|
pass
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-08-15 22:10:46 +02:00
|
|
|
self.schedule_data = []
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("PypoFetch: init complete")
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-08-15 22:10:46 +02:00
|
|
|
"""
|
|
|
|
Handle a message from RabbitMQ, put it into our yucky global var.
|
|
|
|
Hopefully there is a better way to do this.
|
|
|
|
"""
|
2012-02-12 05:53:43 +01:00
|
|
|
def handle_message(self, message):
|
2012-06-27 04:41:11 +02:00
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Received event from Pypo Message Handler: %s" % message)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
|
|
|
m = json.loads(message)
|
2011-09-13 20:56:24 +02:00
|
|
|
command = m['event_type']
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Handling command: " + command)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-09-13 20:56:24 +02:00
|
|
|
if command == 'update_schedule':
|
2012-06-27 04:41:11 +02:00
|
|
|
self.schedule_data = m['schedule']
|
2012-03-17 18:55:56 +01:00
|
|
|
self.process_schedule(self.schedule_data)
|
2012-09-15 00:20:46 +02:00
|
|
|
elif command == 'reset_liquidsoap_bootstrap':
|
|
|
|
self.set_bootstrap_variables()
|
2011-09-13 20:56:24 +02:00
|
|
|
elif command == 'update_stream_setting':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Updating stream setting...")
|
2012-08-19 03:52:06 +02:00
|
|
|
self.regenerate_liquidsoap_conf(m['setting'])
|
2012-02-11 00:43:40 +01:00
|
|
|
elif command == 'update_stream_format':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Updating stream format...")
|
2012-02-11 00:43:40 +01:00
|
|
|
self.update_liquidsoap_stream_format(m['stream_format'])
|
|
|
|
elif command == 'update_station_name':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Updating station name...")
|
2012-02-11 00:43:40 +01:00
|
|
|
self.update_liquidsoap_station_name(m['station_name'])
|
2012-03-21 03:16:17 +01:00
|
|
|
elif command == 'update_transition_fade':
|
|
|
|
self.logger.info("Updating transition_fade...")
|
|
|
|
self.update_liquidsoap_transition_fade(m['transition_fade'])
|
2012-03-08 23:42:38 +01:00
|
|
|
elif command == 'switch_source':
|
|
|
|
self.logger.info("switch_on_source show command received...")
|
2012-03-28 15:39:46 +02:00
|
|
|
self.switch_source(self.logger, self.telnet_lock, m['sourcename'], m['status'])
|
2012-03-14 16:09:59 +01:00
|
|
|
elif command == 'disconnect_source':
|
|
|
|
self.logger.info("disconnect_on_source show command received...")
|
2012-04-11 22:20:27 +02:00
|
|
|
self.disconnect_source(self.logger, self.telnet_lock, m['sourcename'])
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-06-08 20:57:59 +02:00
|
|
|
# update timeout value
|
|
|
|
if command == 'update_schedule':
|
2012-06-27 20:37:16 +02:00
|
|
|
self.listener_timeout = POLL_INTERVAL
|
2012-06-08 20:57:59 +02:00
|
|
|
else:
|
2012-06-27 20:37:16 +02:00
|
|
|
self.listener_timeout = self.last_update_schedule_timestamp - time.time() + POLL_INTERVAL
|
2012-06-08 20:57:59 +02:00
|
|
|
if self.listener_timeout < 0:
|
|
|
|
self.listener_timeout = 0
|
|
|
|
self.logger.info("New timeout: %s" % self.listener_timeout)
|
2011-09-13 20:56:24 +02:00
|
|
|
except Exception, e:
|
2012-02-28 20:44:39 +01:00
|
|
|
import traceback
|
|
|
|
top = traceback.format_exc()
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error('Exception: %s', e)
|
|
|
|
self.logger.error("traceback: %s", top)
|
|
|
|
self.logger.error("Exception in handling Message Handler message: %s", e)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-28 15:39:46 +02:00
|
|
|
@staticmethod
|
|
|
|
def disconnect_source(logger, lock, sourcename):
|
|
|
|
logger.debug('Disconnecting source: %s', sourcename)
|
2012-03-14 16:09:59 +01:00
|
|
|
command = ""
|
|
|
|
if(sourcename == "master_dj"):
|
|
|
|
command += "master_harbor.kick\n"
|
|
|
|
elif(sourcename == "live_dj"):
|
|
|
|
command += "live_dj_harbor.kick\n"
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-28 15:39:46 +02:00
|
|
|
lock.acquire()
|
2012-03-14 16:09:59 +01:00
|
|
|
try:
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
tn.write(command)
|
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
|
|
|
except Exception, e:
|
2012-03-28 15:39:46 +02:00
|
|
|
logger.error(str(e))
|
2012-03-16 04:14:19 +01:00
|
|
|
finally:
|
2012-03-28 15:39:46 +02:00
|
|
|
lock.release()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-28 15:39:46 +02:00
|
|
|
@staticmethod
|
|
|
|
def switch_source(logger, lock, sourcename, status):
|
|
|
|
logger.debug('Switching source: %s to "%s" status', sourcename, status)
|
2012-03-08 23:42:38 +01:00
|
|
|
command = "streams."
|
2012-08-20 18:11:03 +02:00
|
|
|
if sourcename == "master_dj":
|
2012-03-08 23:42:38 +01:00
|
|
|
command += "master_dj_"
|
2012-08-20 18:11:03 +02:00
|
|
|
elif sourcename == "live_dj":
|
2012-03-08 23:42:38 +01:00
|
|
|
command += "live_dj_"
|
2012-08-20 18:11:03 +02:00
|
|
|
elif sourcename == "scheduled_play":
|
2012-03-13 19:34:48 +01:00
|
|
|
command += "scheduled_play_"
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-08-20 18:11:03 +02:00
|
|
|
if status == "on":
|
2012-03-08 23:42:38 +01:00
|
|
|
command += "start\n"
|
|
|
|
else:
|
|
|
|
command += "stop\n"
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-28 15:39:46 +02:00
|
|
|
lock.acquire()
|
2012-03-08 23:42:38 +01:00
|
|
|
try:
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
tn.write(command)
|
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
|
|
|
except Exception, e:
|
2012-03-28 15:39:46 +02:00
|
|
|
logger.error(str(e))
|
2012-03-16 04:14:19 +01:00
|
|
|
finally:
|
2012-03-28 15:39:46 +02:00
|
|
|
lock.release()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-14 15:22:41 +01:00
|
|
|
"""
|
2012-03-20 21:41:15 +01:00
|
|
|
grabs some information that are needed to be set on bootstrap time
|
|
|
|
and configures them
|
2012-03-14 15:22:41 +01:00
|
|
|
"""
|
2012-03-20 21:41:15 +01:00
|
|
|
def set_bootstrap_variables(self):
|
|
|
|
self.logger.debug('Getting information needed on bootstrap from Airtime')
|
|
|
|
info = self.api_client.get_bootstrap_info()
|
2012-09-15 00:20:46 +02:00
|
|
|
if info is None:
|
2012-06-27 22:29:33 +02:00
|
|
|
self.logger.error('Unable to get bootstrap info.. Exiting pypo...')
|
|
|
|
sys.exit(1)
|
2012-03-26 23:44:07 +02:00
|
|
|
else:
|
2012-06-27 04:41:11 +02:00
|
|
|
self.logger.debug('info:%s', info)
|
2012-03-26 23:44:07 +02:00
|
|
|
for k, v in info['switch_status'].iteritems():
|
2012-03-28 15:39:46 +02:00
|
|
|
self.switch_source(self.logger, self.telnet_lock, k, v)
|
2012-03-26 23:44:07 +02:00
|
|
|
self.update_liquidsoap_stream_format(info['stream_label'])
|
|
|
|
self.update_liquidsoap_station_name(info['station_name'])
|
|
|
|
self.update_liquidsoap_transition_fade(info['transition_fade'])
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-05-28 20:27:30 +02:00
|
|
|
def write_liquidsoap_config(self, setting):
|
|
|
|
fh = open('/etc/airtime/liquidsoap.cfg', 'w')
|
|
|
|
self.logger.info("Rewriting liquidsoap.cfg...")
|
|
|
|
fh.write("################################################\n")
|
|
|
|
fh.write("# THIS FILE IS AUTO GENERATED. DO NOT CHANGE!! #\n")
|
|
|
|
fh.write("################################################\n")
|
|
|
|
for k, d in setting:
|
|
|
|
buffer_str = d[u'keyname'] + " = "
|
|
|
|
if d[u'type'] == 'string':
|
|
|
|
temp = d[u'value']
|
|
|
|
buffer_str += '"%s"' % temp
|
|
|
|
else:
|
|
|
|
temp = d[u'value']
|
|
|
|
if temp == "":
|
|
|
|
temp = "0"
|
|
|
|
buffer_str += temp
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-05-28 20:27:30 +02:00
|
|
|
buffer_str += "\n"
|
|
|
|
fh.write(api_client.encode_to(buffer_str))
|
|
|
|
fh.write("log_file = \"/var/log/airtime/pypo-liquidsoap/<script>.log\"\n");
|
|
|
|
fh.close()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-08-19 06:41:25 +02:00
|
|
|
def restart_liquidsoap(self):
|
2012-08-20 18:11:03 +02:00
|
|
|
|
|
|
|
self.telnet_lock.acquire()
|
2012-08-19 03:52:06 +02:00
|
|
|
try:
|
2012-08-20 18:11:03 +02:00
|
|
|
self.logger.info("Restarting Liquidsoap")
|
2012-08-19 06:41:25 +02:00
|
|
|
subprocess.call('/etc/init.d/airtime-liquidsoap restart', shell=True)
|
2012-08-20 18:11:03 +02:00
|
|
|
|
2012-08-20 20:41:34 +02:00
|
|
|
#Wait here and poll Liquidsoap until it has started up
|
2012-08-20 18:11:03 +02:00
|
|
|
self.logger.info("Waiting for Liquidsoap to start")
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
tn.write("exit\n")
|
|
|
|
tn.read_all()
|
|
|
|
self.logger.info("Liquidsoap is up and running")
|
|
|
|
break
|
|
|
|
except Exception, e:
|
|
|
|
#sleep 0.5 seconds and try again
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
2012-08-19 03:52:06 +02:00
|
|
|
except Exception, e:
|
2012-08-19 06:41:25 +02:00
|
|
|
self.logger.error(e)
|
2012-08-20 18:11:03 +02:00
|
|
|
finally:
|
|
|
|
self.telnet_lock.release()
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.set_bootstrap_variables()
|
|
|
|
#get the most up to date schedule, which will #initiate the process
|
|
|
|
#of making sure Liquidsoap is playing the schedule
|
|
|
|
self.manual_schedule_fetch()
|
|
|
|
except Exception, e:
|
|
|
|
self.logger.error(str(e))
|
2012-08-19 03:52:06 +02:00
|
|
|
|
|
|
|
def regenerate_liquidsoap_conf(self, setting):
|
2011-08-15 22:10:46 +02:00
|
|
|
existing = {}
|
|
|
|
# create a temp file
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-05-28 20:27:30 +02:00
|
|
|
setting = sorted(setting.items())
|
|
|
|
try:
|
|
|
|
fh = open('/etc/airtime/liquidsoap.cfg', 'r')
|
|
|
|
except IOError, e:
|
|
|
|
#file does not exist
|
2012-08-19 06:41:25 +02:00
|
|
|
self.restart_liquidsoap()
|
2012-08-20 18:11:03 +02:00
|
|
|
return
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Reading existing config...")
|
2011-08-15 22:10:46 +02:00
|
|
|
# read existing conf file and build dict
|
2012-05-28 20:27:30 +02:00
|
|
|
while True:
|
2011-08-15 22:10:46 +02:00
|
|
|
line = fh.readline()
|
2012-05-28 20:27:30 +02:00
|
|
|
|
|
|
|
# empty line means EOF
|
2011-08-15 22:10:46 +02:00
|
|
|
if not line:
|
2011-08-23 16:12:18 +02:00
|
|
|
break
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-08-29 23:44:28 +02:00
|
|
|
line = line.strip()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-05-28 20:27:30 +02:00
|
|
|
if line[0] == "#":
|
2011-08-23 16:12:18 +02:00
|
|
|
continue
|
2012-05-28 20:27:30 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
key, value = line.split('=', 1)
|
|
|
|
except ValueError:
|
2011-08-29 23:44:28 +02:00
|
|
|
continue
|
2011-08-15 22:10:46 +02:00
|
|
|
key = key.strip()
|
|
|
|
value = value.strip()
|
|
|
|
value = value.replace('"', '')
|
2012-05-28 20:27:30 +02:00
|
|
|
if value == '' or value == "0":
|
2011-08-15 22:10:46 +02:00
|
|
|
value = ''
|
2012-05-28 20:27:30 +02:00
|
|
|
existing[key] = value
|
2011-08-15 22:10:46 +02:00
|
|
|
fh.close()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-08-20 18:11:03 +02:00
|
|
|
# dict flag for any change in config
|
2011-08-18 19:53:12 +02:00
|
|
|
change = {}
|
2012-05-28 20:27:30 +02:00
|
|
|
# this flag is to detect disable -> disable change
|
2012-08-20 18:11:03 +02:00
|
|
|
# in that case, we don't want to restart even if there are changes.
|
2011-08-18 19:53:12 +02:00
|
|
|
state_change_restart = {}
|
|
|
|
#restart flag
|
|
|
|
restart = False
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Looking for changes...")
|
2011-08-15 22:10:46 +02:00
|
|
|
# look for changes
|
2012-03-02 22:55:11 +01:00
|
|
|
for k, s in setting:
|
2011-09-01 22:02:06 +02:00
|
|
|
if "output_sound_device" in s[u'keyname'] or "icecast_vorbis_metadata" in s[u'keyname']:
|
2011-08-18 19:53:12 +02:00
|
|
|
dump, stream = s[u'keyname'].split('_', 1)
|
|
|
|
state_change_restart[stream] = False
|
|
|
|
# This is the case where restart is required no matter what
|
|
|
|
if (existing[s[u'keyname']] != s[u'value']):
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
|
2011-08-18 19:53:12 +02:00
|
|
|
restart = True;
|
2012-03-02 22:55:11 +01:00
|
|
|
elif "master_live_stream_port" in s[u'keyname'] or "master_live_stream_mp" in s[u'keyname'] or "dj_live_stream_port" in s[u'keyname'] or "dj_live_stream_mp" in s[u'keyname']:
|
2012-02-23 17:11:02 +01:00
|
|
|
if (existing[s[u'keyname']] != s[u'value']):
|
2012-03-02 22:55:11 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
|
2012-02-23 17:11:02 +01:00
|
|
|
restart = True;
|
2011-08-18 19:53:12 +02:00
|
|
|
else:
|
2012-06-27 04:41:11 +02:00
|
|
|
stream, dump = s[u'keyname'].split('_', 1)
|
2011-08-24 23:13:56 +02:00
|
|
|
if "_output" in s[u'keyname']:
|
|
|
|
if (existing[s[u'keyname']] != s[u'value']):
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
|
2011-08-24 23:13:56 +02:00
|
|
|
restart = True;
|
2011-08-26 19:13:02 +02:00
|
|
|
state_change_restart[stream] = True
|
2012-06-27 04:41:11 +02:00
|
|
|
elif (s[u'value'] != 'disabled'):
|
2011-08-18 19:53:12 +02:00
|
|
|
state_change_restart[stream] = True
|
2011-08-26 19:13:02 +02:00
|
|
|
else:
|
|
|
|
state_change_restart[stream] = False
|
2011-08-24 23:13:56 +02:00
|
|
|
else:
|
2011-08-18 19:53:12 +02:00
|
|
|
# setting inital value
|
|
|
|
if stream not in change:
|
|
|
|
change[stream] = False
|
|
|
|
if not (s[u'value'] == existing[s[u'keyname']]):
|
2012-08-20 18:11:03 +02:00
|
|
|
self.logger.info("Keyname: %s, Current value: %s, New Value: %s", s[u'keyname'], existing[s[u'keyname']], s[u'value'])
|
2011-08-18 19:53:12 +02:00
|
|
|
change[stream] = True
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-08-18 19:53:12 +02:00
|
|
|
# set flag change for sound_device alway True
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Change:%s, State_Change:%s...", change, state_change_restart)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-08-18 19:53:12 +02:00
|
|
|
for k, v in state_change_restart.items():
|
|
|
|
if k == "sound_device" and v:
|
|
|
|
restart = True
|
|
|
|
elif v and change[k]:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", k)
|
2011-08-18 19:53:12 +02:00
|
|
|
restart = True
|
2011-08-15 22:10:46 +02:00
|
|
|
# rewrite
|
2011-08-18 19:53:12 +02:00
|
|
|
if restart:
|
2012-08-19 06:41:25 +02:00
|
|
|
self.restart_liquidsoap()
|
2011-08-15 22:10:46 +02:00
|
|
|
else:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("No change detected in setting...")
|
2011-12-24 16:59:09 +01:00
|
|
|
self.update_liquidsoap_connection_status()
|
2012-02-29 04:33:19 +01:00
|
|
|
|
2012-02-28 19:58:10 +01:00
|
|
|
def update_liquidsoap_connection_status(self):
|
|
|
|
"""
|
2012-08-20 18:11:03 +02:00
|
|
|
updates the status of Liquidsoap connection to the streaming server
|
|
|
|
This function updates the bootup time variable in Liquidsoap script
|
2012-03-14 15:22:41 +01:00
|
|
|
"""
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-16 04:14:19 +01:00
|
|
|
self.telnet_lock.acquire()
|
|
|
|
try:
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
2012-08-20 18:11:03 +02:00
|
|
|
# update the boot up time of Liquidsoap. Since Liquidsoap is not restarting,
|
2012-03-16 04:14:19 +01:00
|
|
|
# we are manually adjusting the bootup time variable so the status msg will get
|
|
|
|
# updated.
|
|
|
|
current_time = time.time()
|
2012-06-27 04:41:11 +02:00
|
|
|
boot_up_time_command = "vars.bootup_time " + str(current_time) + "\n"
|
2012-03-16 04:14:19 +01:00
|
|
|
tn.write(boot_up_time_command)
|
|
|
|
tn.write("streams.connection_status\n")
|
|
|
|
tn.write('exit\n')
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-16 04:14:19 +01:00
|
|
|
output = tn.read_all()
|
|
|
|
except Exception, e:
|
|
|
|
self.logger.error(str(e))
|
|
|
|
finally:
|
|
|
|
self.telnet_lock.release()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-12-24 16:59:09 +01:00
|
|
|
output_list = output.split("\r\n")
|
|
|
|
stream_info = output_list[2]
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-12-24 16:59:09 +01:00
|
|
|
# streamin info is in the form of:
|
|
|
|
# eg. s1:true,2:true,3:false
|
|
|
|
streams = stream_info.split(",")
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(streams)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2011-12-24 16:59:09 +01:00
|
|
|
fake_time = current_time + 1
|
|
|
|
for s in streams:
|
|
|
|
info = s.split(':')
|
|
|
|
stream_id = info[0]
|
|
|
|
status = info[1]
|
|
|
|
if(status == "true"):
|
|
|
|
self.api_client.notify_liquidsoap_status("OK", stream_id, str(fake_time))
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-11 00:43:40 +01:00
|
|
|
def update_liquidsoap_stream_format(self, stream_format):
|
|
|
|
# Push stream metadata to liquidsoap
|
|
|
|
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
|
|
|
|
try:
|
2012-03-16 04:14:19 +01:00
|
|
|
self.telnet_lock.acquire()
|
2012-03-17 01:47:46 +01:00
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8')
|
|
|
|
self.logger.info(command)
|
|
|
|
tn.write(command)
|
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
2012-02-11 00:43:40 +01:00
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("Exception %s", e)
|
2012-03-17 01:47:46 +01:00
|
|
|
finally:
|
|
|
|
self.telnet_lock.release()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-21 03:16:17 +01:00
|
|
|
def update_liquidsoap_transition_fade(self, fade):
|
|
|
|
# Push stream metadata to liquidsoap
|
|
|
|
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
|
|
|
|
try:
|
|
|
|
self.telnet_lock.acquire()
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
command = ('vars.default_dj_fade %s\n' % fade).encode('utf-8')
|
|
|
|
self.logger.info(command)
|
|
|
|
tn.write(command)
|
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
|
|
|
except Exception, e:
|
|
|
|
self.logger.error("Exception %s", e)
|
|
|
|
finally:
|
|
|
|
self.telnet_lock.release()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-11 00:43:40 +01:00
|
|
|
def update_liquidsoap_station_name(self, station_name):
|
2011-03-23 06:09:27 +01:00
|
|
|
# Push stream metadata to liquidsoap
|
|
|
|
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
|
2011-03-03 06:22:28 +01:00
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(LS_HOST)
|
|
|
|
self.logger.info(LS_PORT)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-16 04:14:19 +01:00
|
|
|
self.telnet_lock.acquire()
|
|
|
|
try:
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
command = ('vars.station_name %s\n' % station_name).encode('utf-8')
|
|
|
|
self.logger.info(command)
|
|
|
|
tn.write(command)
|
|
|
|
tn.write('exit\n')
|
2012-06-27 04:41:11 +02:00
|
|
|
tn.read_all()
|
2012-03-16 04:14:19 +01:00
|
|
|
except Exception, e:
|
|
|
|
self.logger.error(str(e))
|
|
|
|
finally:
|
|
|
|
self.telnet_lock.release()
|
2011-03-23 06:09:27 +01:00
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("Exception %s", e)
|
2012-02-11 00:43:40 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
Process the schedule
|
|
|
|
- Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for")
|
|
|
|
- Saves a serialized file of the schedule
|
|
|
|
- playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied
|
|
|
|
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
|
|
|
|
- runs the cleanup routine, to get rid of unused cached files
|
|
|
|
"""
|
2012-06-27 04:41:11 +02:00
|
|
|
def process_schedule(self, schedule_data):
|
2012-06-18 05:24:15 +02:00
|
|
|
self.last_update_schedule_timestamp = time.time()
|
2012-02-28 21:32:18 +01:00
|
|
|
self.logger.debug(schedule_data)
|
2012-02-27 19:52:35 +01:00
|
|
|
media = schedule_data["media"]
|
2012-03-28 21:12:01 +02:00
|
|
|
media_filtered = {}
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2011-06-15 21:49:42 +02:00
|
|
|
# Download all the media and put playlists in liquidsoap "annotate" format
|
2011-03-21 00:34:43 +01:00
|
|
|
try:
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-01 23:58:44 +01:00
|
|
|
"""
|
|
|
|
Make sure cache_dir exists
|
|
|
|
"""
|
|
|
|
download_dir = self.cache_dir
|
|
|
|
try:
|
|
|
|
os.makedirs(download_dir)
|
|
|
|
except Exception, e:
|
|
|
|
pass
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-01 23:58:44 +01:00
|
|
|
for key in media:
|
|
|
|
media_item = media[key]
|
2012-08-17 21:38:24 +02:00
|
|
|
if (media_item['type'] == 'file'):
|
2012-03-28 21:12:01 +02:00
|
|
|
fileExt = os.path.splitext(media_item['uri'])[1]
|
2012-07-26 20:41:09 +02:00
|
|
|
dst = os.path.join(download_dir, unicode(media_item['id']) + fileExt)
|
2012-03-28 21:12:01 +02:00
|
|
|
media_item['dst'] = dst
|
2012-07-03 23:06:35 +02:00
|
|
|
media_item['file_ready'] = False
|
2012-03-28 21:12:01 +02:00
|
|
|
media_filtered[key] = media_item
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-28 21:12:01 +02:00
|
|
|
self.media_prepare_queue.put(copy.copy(media_filtered))
|
2012-02-27 19:52:35 +01:00
|
|
|
except Exception, e: self.logger.error("%s", e)
|
2011-03-21 00:34:43 +01:00
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
# Send the data to pypo-push
|
2012-03-01 23:58:44 +01:00
|
|
|
self.logger.debug("Pushing to pypo-push")
|
2012-02-27 19:52:35 +01:00
|
|
|
self.push_queue.put(media)
|
|
|
|
|
2012-03-06 01:02:46 +01:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
# cleanup
|
2012-03-17 18:55:56 +01:00
|
|
|
try: self.cache_cleanup(media)
|
2012-02-27 19:52:35 +01:00
|
|
|
except Exception, e: self.logger.error("%s", e)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-17 18:55:56 +01:00
|
|
|
def cache_cleanup(self, media):
|
2012-03-06 01:02:46 +01:00
|
|
|
"""
|
|
|
|
Get list of all files in the cache dir and remove them if they aren't being used anymore.
|
|
|
|
Input dict() media, lists all files that are scheduled or currently playing. Not being in this
|
2012-08-17 21:38:40 +02:00
|
|
|
dict() means the file is safe to remove.
|
2012-03-06 01:02:46 +01:00
|
|
|
"""
|
|
|
|
cached_file_set = set(os.listdir(self.cache_dir))
|
|
|
|
scheduled_file_set = set()
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-03-06 01:02:46 +01:00
|
|
|
for mkey in media:
|
|
|
|
media_item = media[mkey]
|
2012-07-25 22:56:33 +02:00
|
|
|
if media_item['type'] == 'file':
|
|
|
|
fileExt = os.path.splitext(media_item['uri'])[1]
|
2012-07-26 20:41:09 +02:00
|
|
|
scheduled_file_set.add(unicode(media_item["id"]) + fileExt)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-07-25 22:56:33 +02:00
|
|
|
expired_files = cached_file_set - scheduled_file_set
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-07-25 22:56:33 +02:00
|
|
|
self.logger.debug("Files to remove " + str(expired_files))
|
|
|
|
for f in expired_files:
|
|
|
|
try:
|
|
|
|
self.logger.debug("Removing %s" % os.path.join(self.cache_dir, f))
|
|
|
|
os.remove(os.path.join(self.cache_dir, f))
|
|
|
|
except Exception, e:
|
|
|
|
self.logger.error(e)
|
2011-03-23 06:09:27 +01:00
|
|
|
|
2012-08-20 18:11:03 +02:00
|
|
|
def manual_schedule_fetch(self):
|
|
|
|
success, self.schedule_data = self.api_client.get_schedule()
|
|
|
|
if success:
|
|
|
|
self.process_schedule(self.schedule_data)
|
|
|
|
return success
|
|
|
|
|
2011-09-08 18:17:42 +02:00
|
|
|
def main(self):
|
2011-03-23 06:09:27 +01:00
|
|
|
# Bootstrap: since we are just starting up, we need to grab the
|
2012-08-17 21:38:40 +02:00
|
|
|
# most recent schedule. After that we can just wait for updates.
|
2012-08-20 18:11:03 +02:00
|
|
|
success = self.manual_schedule_fetch()
|
2012-02-27 19:52:35 +01:00
|
|
|
if success:
|
|
|
|
self.logger.info("Bootstrap schedule received: %s", self.schedule_data)
|
2012-03-20 21:41:15 +01:00
|
|
|
self.set_bootstrap_variables()
|
2011-09-20 19:25:29 +02:00
|
|
|
|
2012-06-27 04:41:11 +02:00
|
|
|
loops = 1
|
2011-03-21 00:34:43 +01:00
|
|
|
while True:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Loop #%s", loops)
|
2012-06-27 04:41:11 +02:00
|
|
|
try:
|
2011-09-08 18:17:42 +02:00
|
|
|
"""
|
2012-03-12 22:52:17 +01:00
|
|
|
our simple_queue.get() requires a timeout, in which case we
|
|
|
|
fetch the Airtime schedule manually. It is important to fetch
|
2012-08-17 21:38:40 +02:00
|
|
|
the schedule periodically because if we didn't, we would only
|
|
|
|
get schedule updates via RabbitMq if the user was constantly
|
|
|
|
using the Airtime interface.
|
|
|
|
|
2012-03-12 22:52:17 +01:00
|
|
|
If the user is not using the interface, RabbitMq messages are not
|
2012-08-17 21:38:40 +02:00
|
|
|
sent, and we will have very stale (or non-existent!) data about the
|
2012-03-12 22:52:17 +01:00
|
|
|
schedule.
|
2012-08-17 21:38:40 +02:00
|
|
|
|
2012-06-27 20:37:16 +02:00
|
|
|
Currently we are checking every POLL_INTERVAL seconds
|
2011-09-08 18:17:42 +02:00
|
|
|
"""
|
2012-06-27 04:41:11 +02:00
|
|
|
|
|
|
|
|
2012-06-08 20:57:59 +02:00
|
|
|
message = self.fetch_queue.get(block=True, timeout=self.listener_timeout)
|
2012-02-27 19:52:35 +01:00
|
|
|
self.handle_message(message)
|
2012-06-28 18:12:22 +02:00
|
|
|
except Empty, e:
|
|
|
|
self.logger.info("Queue timeout. Fetching schedule manually")
|
2012-08-20 18:11:03 +02:00
|
|
|
self.manual_schedule_fetch()
|
2011-09-08 18:17:42 +02:00
|
|
|
except Exception, e:
|
2012-03-29 22:57:28 +02:00
|
|
|
import traceback
|
|
|
|
top = traceback.format_exc()
|
|
|
|
self.logger.error('Exception: %s', e)
|
|
|
|
self.logger.error("traceback: %s", top)
|
2012-06-27 04:41:11 +02:00
|
|
|
|
2012-02-24 19:12:50 +01:00
|
|
|
loops += 1
|
2011-09-08 18:17:42 +02:00
|
|
|
|
|
|
|
def run(self):
|
2012-02-28 21:32:18 +01:00
|
|
|
"""
|
|
|
|
Entry point of the thread
|
|
|
|
"""
|
|
|
|
self.main()
|