CC-2607: Abilitiy to adjust stream bitrate, type, etc from the UI

interface

- dummy page "Stream Setting" page for the test
- StreamSetting model is added
- set owner and group as 'pypo' for liquidsoap.cfg
- pypofech handle 'update_stream_setting' command
This commit is contained in:
James 2011-08-15 16:10:46 -04:00
parent e18c0903cb
commit 4f2b2dba6d
12 changed files with 174 additions and 30 deletions

View File

@ -58,6 +58,12 @@ $pages = array(
'module' => 'default', 'module' => 'default',
'controller' => 'Preference', 'controller' => 'Preference',
'action' => 'directory-config' 'action' => 'directory-config'
),
array(
'label' => 'Stream Setting',
'module' => 'default',
'controller' => 'Preference',
'action' => 'stream-setting'
) )
) )
), ),

View File

@ -609,7 +609,7 @@ class ApiController extends Zend_Controller_Action
} }
public function getStreamSettingAction() { public function getStreamSettingAction() {
global $CC_CONFIG, $CC_DBC; global $CC_CONFIG;
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key'); $api_key = $request->getParam('api_key');
@ -619,12 +619,8 @@ class ApiController extends Zend_Controller_Action
print 'You are not allowed to access this resource.'; print 'You are not allowed to access this resource.';
exit; exit;
} }
$sql = "SELECT *"
." FROM cc_stream_setting";
$rows = $CC_DBC->getAll($sql); $this->view->msg = Application_Model_StreamSetting::getStreamSetting();
$this->view->msg = $rows;
} }
} }

View File

@ -14,6 +14,7 @@ class PreferenceController extends Zend_Controller_Action
->addActionContext('reload-watch-directory', 'json') ->addActionContext('reload-watch-directory', 'json')
->addActionContext('remove-watch-directory', 'json') ->addActionContext('remove-watch-directory', 'json')
->addActionContext('is-import-in-progress', 'json') ->addActionContext('is-import-in-progress', 'json')
->addActionContext('change-stream-setting', 'json')
->initContext(); ->initContext();
} }
@ -82,6 +83,16 @@ class PreferenceController extends Zend_Controller_Action
$this->view->form = $watched_dirs_pref; $this->view->form = $watched_dirs_pref;
} }
public function streamSettingAction()
{
$request = $this->getRequest();
$baseUrl = $request->getBaseUrl();
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/preferences/streamsetting.js','text/javascript');
$this->view->form = new Application_Form_StreamSetting();
}
public function serverBrowseAction() public function serverBrowseAction()
{ {
@ -134,6 +145,16 @@ class PreferenceController extends Zend_Controller_Action
$this->view->subform = $watched_dirs_form->render(); $this->view->subform = $watched_dirs_form->render();
} }
public function changeStreamSettingAction()
{
$data = array();
$data['setting'] = Application_Model_StreamSetting::getStreamSetting();
RabbitMq::SendMessageToPypo("update_stream_setting", $data);
$form = new Application_Form_StreamSetting();
$this->view->subform = $form->render();
}
public function reloadWatchDirectoryAction() public function reloadWatchDirectoryAction()
{ {

View File

@ -0,0 +1,13 @@
<?php
class Application_Form_StreamSetting extends Zend_Form_SubForm
{
public function init()
{
$this->setDecorators(array(
array('ViewScript', array('viewScript' => 'form/stream_setting.phtml'))
));
}
}

View File

@ -57,7 +57,7 @@ class RabbitMq
$channel = $conn->channel(); $channel = $conn->channel();
$channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true); $channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true);
$EXCHANGE = 'airtime-schedule'; $EXCHANGE = 'airtime-pypo';
$channel->exchange_declare($EXCHANGE, 'direct', false, true); $channel->exchange_declare($EXCHANGE, 'direct', false, true);
$data = json_encode($md); $data = json_encode($md);

View File

@ -0,0 +1,14 @@
<?php
class Application_Model_StreamSetting {
public function __construct(){
}
public static function getStreamSetting(){
global $CC_DBC;
$sql = "SELECT *"
." FROM cc_stream_setting";
$rows = $CC_DBC->getAll($sql);
return $rows;
}
}

View File

@ -0,0 +1 @@
<input id="change_setting" type="button" value="change setting"></input>

View File

@ -0,0 +1,4 @@
<div id="stream-setting-section" class="ui-widget ui-widget-content block-shadow simple-formblock clearfix padded-strong manage-folders">
<h2>Stream Setting</h2>
<?php echo $this->form; ?>
</div>

View File

@ -0,0 +1,13 @@
$(document).ready(function() {
$('#change_setting').click(function(){
var url;
url = "/Preference/change-stream-setting";
$.post(url,
{format: "json"}
);
});
});

View File

@ -81,6 +81,11 @@ class AirtimeIni
if (!copy(__DIR__."/../../python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg", AirtimeIni::CONF_FILE_LIQUIDSOAP)){ if (!copy(__DIR__."/../../python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg", AirtimeIni::CONF_FILE_LIQUIDSOAP)){
echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting."; echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting.";
exit(1); exit(1);
}else{
if (!chown(AirtimeIni::CONF_FILE_LIQUIDSOAP, "pypo") || !chgrp(AirtimeIni::CONF_FILE_LIQUIDSOAP, "pypo") ){
echo "Could not set ownership of liquidsoap.cfg to 'pypo'. Exiting.";
exit(1);
}
} }
if (!copy(__DIR__."/../../python_apps/media-monitor/media-monitor.cfg", AirtimeIni::CONF_FILE_MEDIAMONITOR)){ if (!copy(__DIR__."/../../python_apps/media-monitor/media-monitor.cfg", AirtimeIni::CONF_FILE_MEDIAMONITOR)){
echo "Could not copy media-monitor.cfg to /etc/airtime/. Exiting."; echo "Could not copy media-monitor.cfg to /etc/airtime/. Exiting.";

View File

@ -528,7 +528,7 @@ class AirTimeApiClient(ApiClientInterface):
return response return response
def get_stream_setting(self): def get_stream_setting(self):
#logger = logging.getLogger() logger = logging.getLogger()
try: try:
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_stream_setting"]) url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_stream_setting"])
@ -538,7 +538,7 @@ class AirTimeApiClient(ApiClientInterface):
response = json.loads(response) response = json.loads(response)
except Exception, e: except Exception, e:
response = None response = None
#logger.error("Exception: %s", e) logger.error("Exception: %s", e)
return response return response

View File

@ -13,6 +13,7 @@ from threading import Thread
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
import filecmp
# For RabbitMQ # For RabbitMQ
from kombu.connection import BrokerConnection from kombu.connection import BrokerConnection
@ -37,17 +38,14 @@ except Exception, e:
logger.error('Error loading config file: %s', e) logger.error('Error loading config file: %s', e)
sys.exit() sys.exit()
# Yuk - using a global, i know!
SCHEDULE_PUSH_MSG = []
""" """
Handle a message from RabbitMQ, put it into our yucky global var. Handle a message from RabbitMQ, put it into our yucky global var.
Hopefully there is a better way to do this. Hopefully there is a better way to do this.
""" """
def handle_message(body, message): """def handle_message(body, message):
logger = logging.getLogger('fetch') logger = logging.getLogger('fetch')
global SCHEDULE_PUSH_MSG global SCHEDULE_PUSH_MSG
logger.info("Received schedule from RabbitMQ: " + message.body) logger.info("Received event from RabbitMQ: " + message.body)
m = json.loads(message.body) m = json.loads(message.body)
command = m['event_type'] command = m['event_type']
@ -59,10 +57,10 @@ def handle_message(body, message):
logger.info("Setting timezone to %s", m['timezone']) logger.info("Setting timezone to %s", m['timezone'])
os.environ['TZ'] = m['timezone'] os.environ['TZ'] = m['timezone']
time.tzset() time.tzset()
elif (command == 'update_stream_setting'):
logger.info("Updating stream setting: %s", m['setting'])
# ACK the message to take it off the queue # ACK the message to take it off the queue
message.ack() message.ack()"""
class PypoFetch(Thread): class PypoFetch(Thread):
def __init__(self, q): def __init__(self, q):
@ -71,26 +69,103 @@ class PypoFetch(Thread):
self.api_client = api_client.api_client_factory(config) self.api_client = api_client.api_client_factory(config)
self.set_export_source('scheduler') self.set_export_source('scheduler')
self.queue = q self.queue = q
self.schedule_data = []
logger.info("PypoFetch: init complete") logger.info("PypoFetch: init complete")
def init_rabbit_mq(self): def init_rabbit_mq(self):
logger = logging.getLogger('fetch') logger = logging.getLogger('fetch')
logger.info("Initializing RabbitMQ stuff") logger.info("Initializing RabbitMQ stuff")
try: try:
schedule_exchange = Exchange("airtime-schedule", "direct", durable=True, auto_delete=True) schedule_exchange = Exchange("airtime-pypo", "direct", durable=True, auto_delete=True)
schedule_queue = Queue("pypo-fetch", exchange=schedule_exchange, key="foo") schedule_queue = Queue("pypo-fetch", exchange=schedule_exchange, key="foo")
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
channel = self.connection.channel() channel = self.connection.channel()
consumer = Consumer(channel, schedule_queue) consumer = Consumer(channel, schedule_queue)
consumer.register_callback(handle_message) consumer.register_callback(self.handle_message)
consumer.consume() consumer.consume()
except Exception, e: except Exception, e:
logger.error(e) logger.error(e)
return False return False
return True return True
"""
Handle a message from RabbitMQ, put it into our yucky global var.
Hopefully there is a better way to do this.
"""
def handle_message(self, body, message):
logger = logging.getLogger('fetch')
logger.info("Received event from RabbitMQ: " + message.body)
m = json.loads(message.body)
command = m['event_type']
logger.info("Handling command: " + command)
if(command == 'update_schedule'):
self.schedule_data = m['schedule']
self.process_schedule(self.schedule_data, "scheduler", False)
elif (command == 'update_timezone'):
logger.info("Setting timezone to %s", m['timezone'])
os.environ['TZ'] = m['timezone']
time.tzset()
elif (command == 'update_stream_setting'):
logger.info("Updating stream setting: %s", m['setting'])
self.regenerateLiquidsoapConf(m['setting'])
# ACK the message to take it off the queue
message.ack()
def regenerateLiquidsoapConf(self, setting):
logger = logging.getLogger('fetch')
existing = {}
# create a temp file
fh = open('/etc/airtime/liquidsoap.cfg', 'r')
# read existing conf file and build dict
while 1:
line = fh.readline()
if not line:
break;
key, value = line.split('=')
key = key.strip()
value = value.strip()
value = value.replace('"', '')
if value == "dummy_string" or value == "0":
value = ''
existing[key] = value
fh.close()
# flag for any change in cofig
change = False
# look for changes
for s in setting:
if not s[u'value'] == existing[s[u'keyname']]:
logger.info("Keyname: %s, Curent value: %s, New Value: %s", s[u'keyname'], s[u'value'], existing[s[u'keyname']])
change = True
# rewrite
if change:
fh = open('/etc/airtime/liquidsoap.cfg', 'w')
logger.info("Rewriting liquidsoap.cfg...")
for d in setting:
buffer = d[u'keyname'] + " = "
if(d[u'type'] == 'string'):
temp = d[u'value']
if(temp == ""):
temp = "dummy_string"
buffer += "\"" + temp + "\""
else:
temp = d[u'value']
if(temp == ""):
temp = "0"
buffer += temp
buffer += "\n"
fh.write(buffer)
fh.close()
# restart playout
logger.info("Restarting airtime-playout...")
p = Popen("/etc/init.d/airtime-playout restart >/dev/null 2>&1", shell=True)
sts = os.waitpid(p.pid, 0)[1]
else:
logger.info("No change detected in setting...")
def set_export_source(self, export_source): def set_export_source(self, export_source):
logger = logging.getLogger('fetch') logger = logging.getLogger('fetch')
self.export_source = export_source self.export_source = export_source
@ -379,9 +454,9 @@ class PypoFetch(Thread):
# Bootstrap: since we are just starting up, we need to grab the # Bootstrap: since we are just starting up, we need to grab the
# most recent schedule. After that we can just wait for updates. # most recent schedule. After that we can just wait for updates.
status, schedule_data = self.api_client.get_schedule() status, self.schedule_data = self.api_client.get_schedule()
if status == 1: if status == 1:
self.process_schedule(schedule_data, "scheduler", True) self.process_schedule(self.schedule_data , "scheduler", True)
logger.info("Bootstrap complete: got initial copy of the schedule") logger.info("Bootstrap complete: got initial copy of the schedule")
loops = 1 loops = 1
@ -391,15 +466,11 @@ class PypoFetch(Thread):
# Wait for messages from RabbitMQ. Timeout if we # Wait for messages from RabbitMQ. Timeout if we
# dont get any after POLL_INTERVAL. # dont get any after POLL_INTERVAL.
self.connection.drain_events(timeout=POLL_INTERVAL) self.connection.drain_events(timeout=POLL_INTERVAL)
# Hooray for globals! except Exception, e:
schedule_data = SCHEDULE_PUSH_MSG
status = 1
except:
# We didnt get a message for a while, so poll the server # We didnt get a message for a while, so poll the server
# to get an updated schedule. # to get an updated schedule.
status, schedule_data = self.api_client.get_schedule() logger.info("Exception %s", e)
status, self.schedule_data = self.api_client.get_schedule()
if status == 1: self.process_schedule(self.schedule_data, "scheduler", False)
self.process_schedule(schedule_data, "scheduler", False)
loops += 1 loops += 1