Merge branch '2.1.x' of dev.sourcefabric.org:airtime into 2.1.x

This commit is contained in:
James 2012-06-28 12:25:38 -04:00
commit ffe3bdab0c
18 changed files with 339 additions and 329 deletions

View file

@ -25,8 +25,7 @@ date_default_timezone_set(Application_Model_Preference::GetTimezone());
global $CC_CONFIG; global $CC_CONFIG;
$airtime_version = Application_Model_Preference::GetAirtimeVersion(); $airtime_version = Application_Model_Preference::GetAirtimeVersion();
$uniqueid = Application_Model_Preference::GetUniqueId(); $uniqueid = Application_Model_Preference::GetUniqueId();
$CC_CONFIG['airtime_version'] = md5($airtime_version + $uniqueid); $CC_CONFIG['airtime_version'] = md5($airtime_version.$uniqueid);
require_once __DIR__."/configs/navigation.php"; require_once __DIR__."/configs/navigation.php";
Zend_Validate::setDefaultNamespaces("Zend"); Zend_Validate::setDefaultNamespaces("Zend");

View file

@ -129,6 +129,12 @@ class ApiController extends Zend_Controller_Action
if(is_file($filepath)){ if(is_file($filepath)){
$full_path = $media->getPropelOrm()->getDbFilepath(); $full_path = $media->getPropelOrm()->getDbFilepath();
$file_base_name = strrchr($full_path, '/'); $file_base_name = strrchr($full_path, '/');
/* If $full_path does not contain a '/', strrchr will return false,
* in which case we can use $full_path as the base name.
*/
if (!$file_base_name) {
$file_base_name = $full_path;
}
$file_base_name = substr($file_base_name, 1); $file_base_name = substr($file_base_name, 1);
// possibly use fileinfo module here in the future. // possibly use fileinfo module here in the future.
// http://www.php.net/manual/en/book.fileinfo.php // http://www.php.net/manual/en/book.fileinfo.php

View file

@ -550,8 +550,9 @@ class Application_Model_Schedule {
$data["media"][$kick_start]['type'] = "event"; $data["media"][$kick_start]['type'] = "event";
if($kick_time !== $switch_off_time){ if($kick_time !== $switch_off_time){
$data["media"][$switch_start]['start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time); $switch_start = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time);
$data["media"][$switch_start]['end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time); $data["media"][$switch_start]['start'] = $switch_start;
$data["media"][$switch_start]['end'] = $switch_start;
$data["media"][$switch_start]['event_type'] = "switch_off"; $data["media"][$switch_start]['event_type'] = "switch_off";
$data["media"][$switch_start]['type'] = "event"; $data["media"][$switch_start]['type'] = "event";
} }

View file

@ -202,13 +202,19 @@ var AIRTIME = (function(AIRTIME) {
*/ */
mod.selectCurrentPage = function() { mod.selectCurrentPage = function() {
$.fn.reverse = [].reverse; $.fn.reverse = [].reverse;
var $trs = $libTable.find("tbody input:checkbox").parents("tr").reverse(); var $inputs = $libTable.find("tbody input:checkbox"),
$trs = $inputs.parents("tr").reverse();
$inputs.attr("checked", true);
$trs.addClass(LIB_SELECTED_CLASS);
$trs.each(function(i, el){ $trs.each(function(i, el){
$el = $(this); $el = $(this);
mod.addToChosen($el);
mod.selectItem($el);
}); });
mod.checkToolBarIcons();
}; };
/* /*
@ -216,14 +222,20 @@ var AIRTIME = (function(AIRTIME) {
* (behaviour taken from gmail) * (behaviour taken from gmail)
*/ */
mod.deselectCurrentPage = function() { mod.deselectCurrentPage = function() {
var $inputs = $libTable.find("tbody input:checkbox"),
$trs = $inputs.parents("tr"),
id;
var $trs = $libTable.find("tbody input:checkbox").filter(":checked").parents("tr"); $inputs.attr("checked", false);
$trs.removeClass(LIB_SELECTED_CLASS);
$trs.each(function(i, el){ $trs.each(function(i, el){
$el = $(this); $el = $(this);
id = $el.attr("id");
mod.deselectItem($el); delete chosenItems[id];
}); });
mod.checkToolBarIcons();
}; };
mod.selectNone = function() { mod.selectNone = function() {

View file

@ -91,6 +91,34 @@ function onEndTimeSelect(){
$("#add_show_end_time").trigger('input'); $("#add_show_end_time").trigger('input');
} }
function padZeroes(number, length)
{
var str = '' + number;
while (str.length < length) {str = '0' + str;}
return str;
}
function hashCode(str) { // java String#hashCode
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
return hash;
}
function intToRGB(i){
return (padZeroes(((i>>16)&0xFF).toString(16), 2) +
padZeroes(((i>>8)&0xFF).toString(16), 2)+
padZeroes((i&0xFF).toString(16), 2)
);
}
function stringToColor(s)
{
return intToRGB(hashCode(s));
}
function setAddShowEvents() { function setAddShowEvents() {
var form = $("#add-show-form"); var form = $("#add-show-form");
@ -548,6 +576,12 @@ function setAddShowEvents() {
loadingIcon.hide(); loadingIcon.hide();
}); });
} }
var bgColorEle = $("#add_show_background_color");
$('#add_show_name').bind('input', 'change', function(){
var colorCode = stringToColor($(this).val());
bgColorEle.val(colorCode);
});
} }
function showErrorSections() { function showErrorSections() {

View file

@ -211,6 +211,7 @@ var AIRTIME = (function(AIRTIME){
mod.fnItemCallback = function(json) { mod.fnItemCallback = function(json) {
checkError(json); checkError(json);
cursorIds = [];
cursors = $(".cursor-selected-row"); cursors = $(".cursor-selected-row");
for (i = 0; i < cursors.length; i++) { for (i = 0; i < cursors.length; i++) {
cursorIds.push(($(cursors.get(i)).attr("id"))); cursorIds.push(($(cursors.get(i)).attr("id")));
@ -423,8 +424,6 @@ var AIRTIME = (function(AIRTIME){
$nRow.addClass(sClass); $nRow.addClass(sClass);
}; };
$nRow.attr("id", aData.id);
if (aData.header === true) { if (aData.header === true) {
//remove the column classes from all tds. //remove the column classes from all tds.
$nRow.find('td').removeClass(); $nRow.find('td').removeClass();
@ -583,11 +582,12 @@ var AIRTIME = (function(AIRTIME){
$nRow.addClass("sb-future"); $nRow.addClass("sb-future");
} }
if (aData.allowed !== true) { if (aData.allowed !== true || aData.header === true) {
$nRow.addClass("sb-not-allowed"); $nRow.addClass("sb-not-allowed");
} }
else { else {
$nRow.addClass("sb-allowed"); $nRow.addClass("sb-allowed");
$nRow.attr("id", aData.id);
} }
//status used to colour tracks. //status used to colour tracks.
@ -673,7 +673,6 @@ var AIRTIME = (function(AIRTIME){
$tr = $table.find("tr[id="+cursorIds[i]+"]"); $tr = $table.find("tr[id="+cursorIds[i]+"]");
mod.selectCursor($tr); mod.selectCursor($tr);
} }
cursorIds = [];
//if there is only 1 cursor on the page highlight it by default. //if there is only 1 cursor on the page highlight it by default.
if ($cursorRows.length === 1) { if ($cursorRows.length === 1) {
@ -699,7 +698,7 @@ var AIRTIME = (function(AIRTIME){
if (temp.length > 0) { if (temp.length > 0) {
aData = temp.data("aData"); aData = temp.data("aData");
// max time interval // max time interval
// setTimeout allow only up to 2^21 millisecs timeout value // setTimeout allows only up to (2^31)-1 millisecs timeout value
maxRefreshInterval = Math.pow(2, 31) - 1; maxRefreshInterval = Math.pow(2, 31) - 1;
refreshInterval = aData.refresh * 1000; refreshInterval = aData.refresh * 1000;
if(refreshInterval > maxRefreshInterval){ if(refreshInterval > maxRefreshInterval){

View file

@ -15,7 +15,7 @@ class Version20110711161043 extends AbstractMigration
{ {
public function up(Schema $schema) public function up(Schema $schema)
{ {
$ini = parse_ini_file(__DIR__."../include/airtime-install.ini"); $ini = parse_ini_file(__DIR__."/../include/airtime-install.ini");
$stor_dir = $ini["storage_dir"]; $stor_dir = $ini["storage_dir"];
/* 1) update cc_files table to include to "directory" column */ /* 1) update cc_files table to include to "directory" column */

View file

@ -61,6 +61,16 @@ AirtimeInstall::InstallStorageDirectory();
$db_install = getenv("nodb")!="t"; $db_install = getenv("nodb")!="t";
if ($db_install) { if ($db_install) {
echo "* Checking database for correct encoding".PHP_EOL;
exec('su -c \'psql -t -c "SHOW SERVER_ENCODING"\' postgres | grep -i "UTF.*8"', $out, $return_code);
if ($return_code != 0){
echo " * Unfortunately your postgresql database has not been created using a UTF-8 encoding.".PHP_EOL;
echo " * As of Airtime 2.1, installs will fail unless the encoding has been set to UTF-8. Please verify this is the case".PHP_EOL;
echo " * and try the install again".PHP_EOL;
exit(1);
}
if($newInstall) { if($newInstall) {
//call external script. "y" argument means force creation of database tables. //call external script. "y" argument means force creation of database tables.
passthru('php '.__DIR__.'/airtime-db-install.php y'); passthru('php '.__DIR__.'/airtime-db-install.php y');

View file

@ -109,20 +109,6 @@ try:
print e print e
sys.exit(1) sys.exit(1)
"""
logging.basicConfig(format='%(message)s')
#generate liquidsoap config file
#access the DB and generate liquidsoap.cfg under /etc/airtime/
ac = api_client.api_client_factory(config, logging.getLogger())
ss = ac.get_stream_setting()
if ss is not None:
generate_liquidsoap_config(ss)
else:
print "Unable to connect to the Airtime server."
"""
#initialize init.d scripts #initialize init.d scripts
subprocess.call("update-rc.d airtime-playout defaults >/dev/null 2>&1", shell=True) subprocess.call("update-rc.d airtime-playout defaults >/dev/null 2>&1", shell=True)

View file

@ -130,21 +130,21 @@ end
def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point, dj_harbor_input_port, dj_harbor_input_mount_point, s) = def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point, dj_harbor_input_port, dj_harbor_input_mount_point, s) =
if master_harbor_input_port != 0 and master_harbor_input_mount_point != "" and dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then if master_harbor_input_port != 0 and master_harbor_input_mount_point != "" and dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then
master_dj = mksafe(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, master_dj = mksafe(audio_to_stereo(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client,
max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)) max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)))
dj_live = mksafe(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, dj_live = mksafe(audio_to_stereo(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client,
max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)) max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)))
ignore(output.dummy(master_dj, fallible=true)) ignore(output.dummy(master_dj, fallible=true))
ignore(output.dummy(dj_live, fallible=true)) ignore(output.dummy(dj_live, fallible=true))
switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition, transition], [({!master_dj_enabled},master_dj), ({!live_dj_enabled},dj_live), ({true}, s)]) switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition, transition], [({!master_dj_enabled},master_dj), ({!live_dj_enabled},dj_live), ({true}, s)])
elsif master_harbor_input_port != 0 and master_harbor_input_mount_point != "" then elsif master_harbor_input_port != 0 and master_harbor_input_mount_point != "" then
master_dj = mksafe(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, master_dj = mksafe(audio_to_stereo(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client,
max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)) max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)))
ignore(output.dummy(master_dj, fallible=true)) ignore(output.dummy(master_dj, fallible=true))
switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!master_dj_enabled},master_dj), ({true}, s)]) switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!master_dj_enabled},master_dj), ({true}, s)])
elsif dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then elsif dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then
dj_live = mksafe(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, dj_live = mksafe(audio_to_stereo(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client,
max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)) max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)))
ignore(output.dummy(dj_live, fallible=true)) ignore(output.dummy(dj_live, fallible=true))
switch(id="live_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!live_dj_enabled},dj_live), ({true}, s)]) switch(id="live_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!live_dj_enabled},dj_live), ({true}, s)])
else else

View file

@ -10,4 +10,4 @@ SCRIPT=`readlink -f $0`
# Absolute path this script is in # Absolute path this script is in
SCRIPTPATH=`dirname $SCRIPT` SCRIPTPATH=`dirname $SCRIPT`
cd ${SCRIPTPATH}/../ && python pypo-notify.py "$@" cd ${SCRIPTPATH}/../ && python pyponotify.py "$@"

View file

@ -2,13 +2,13 @@
Python part of radio playout (pypo) Python part of radio playout (pypo)
""" """
from optparse import OptionParser
from datetime import datetime
import time import time
from optparse import *
import sys import sys
import signal import signal
import logging import logging
import logging.config
import logging.handlers
import locale import locale
import os import os
from Queue import Queue from Queue import Queue
@ -113,34 +113,6 @@ class Global:
def test_api(self): def test_api(self):
self.api_client.test() self.api_client.test()
"""
def check_schedule(self):
logger = logging.getLogger()
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
schedule_file.close()
except Exception, e:
logger.error("%s", e)
schedule = None
for pkey in sorted(schedule.iterkeys()):
playlist = schedule[pkey]
print '*****************************************'
print '\033[0;32m%s %s\033[m' % ('scheduled at:', str(pkey))
print 'cached at : ' + self.cache_dir + str(pkey)
print 'played: ' + str(playlist['played'])
print 'schedule id: ' + str(playlist['schedule_id'])
print 'duration: ' + str(playlist['duration'])
print 'source id: ' + str(playlist['x_ident'])
print '-----------------------------------------'
for media in playlist['medias']:
print media
"""
def keyboardInterruptHandler(signum, frame): def keyboardInterruptHandler(signum, frame):
logger = logging.getLogger() logger = logging.getLogger()
logger.info('\nKeyboard Interrupt\n') logger.info('\nKeyboard Interrupt\n')
@ -154,6 +126,12 @@ if __name__ == '__main__':
logger.info('# Liquidsoap Scheduled Playout System #') logger.info('# Liquidsoap Scheduled Playout System #')
logger.info('###########################################') logger.info('###########################################')
#Although all of our calculations are in UTC, it is useful to know what timezone
#the local machine is, so that we have a reference for what time the actual
#log entries were made
logger.info("Timezone: %s" % time.tzname)
logger.info("UTC time: %s" % datetime.utcnow())
signal.signal(signal.SIGINT, keyboardInterruptHandler) signal.signal(signal.SIGINT, keyboardInterruptHandler)
# initialize # initialize
@ -204,7 +182,7 @@ if __name__ == '__main__':
recorder.daemon = True recorder.daemon = True
recorder.start() recorder.start()
# all join() are commented out becase we want to exit entire pypo # all join() are commented out because we want to exit entire pypo
# if pypofetch is exiting # if pypofetch is exiting
#pmh.join() #pmh.join()
#recorder.join() #recorder.join()
@ -213,14 +191,3 @@ if __name__ == '__main__':
logger.info("pypo fetch exit") logger.info("pypo fetch exit")
sys.exit() sys.exit()
"""
if options.check:
try: g.check_schedule()
except Exception, e:
print e
if options.cleanup:
try: pf.cleanup('scheduler')
except Exception, e:
print e
"""

View file

@ -3,14 +3,14 @@
import os import os
import sys import sys
import time import time
import logging
import logging.config import logging.config
import shutil
import json import json
import telnetlib import telnetlib
import copy import copy
from threading import Thread from threading import Thread
from Queue import Empty
from api_clients import api_client from api_clients import api_client
from std_err_override import LogWriter from std_err_override import LogWriter
@ -29,7 +29,9 @@ try:
config = ConfigObj('/etc/airtime/pypo.cfg') config = ConfigObj('/etc/airtime/pypo.cfg')
LS_HOST = config['ls_host'] LS_HOST = config['ls_host']
LS_PORT = config['ls_port'] LS_PORT = config['ls_port']
POLL_INTERVAL = int(config['poll_interval']) #POLL_INTERVAL = int(config['poll_interval'])
POLL_INTERVAL = 1800
except Exception, e: except Exception, e:
logger.error('Error loading config file: %s', e) logger.error('Error loading config file: %s', e)
@ -43,7 +45,7 @@ class PypoFetch(Thread):
self.push_queue = pypoPush_q self.push_queue = pypoPush_q
self.media_prepare_queue = media_q self.media_prepare_queue = media_q
self.last_update_schedule_timestamp = time.time() self.last_update_schedule_timestamp = time.time()
self.listener_timeout = 3600 self.listener_timeout = POLL_INTERVAL
self.telnet_lock = telnet_lock self.telnet_lock = telnet_lock
@ -75,12 +77,12 @@ class PypoFetch(Thread):
try: try:
self.logger.info("Received event from Pypo Message Handler: %s" % message) self.logger.info("Received event from Pypo Message Handler: %s" % message)
m = json.loads(message) m = json.loads(message)
command = m['event_type'] command = m['event_type']
self.logger.info("Handling command: " + command) self.logger.info("Handling command: " + command)
if command == 'update_schedule': if command == 'update_schedule':
self.schedule_data = m['schedule'] self.schedule_data = m['schedule']
self.process_schedule(self.schedule_data) self.process_schedule(self.schedule_data)
elif command == 'update_stream_setting': elif command == 'update_stream_setting':
self.logger.info("Updating stream setting...") self.logger.info("Updating stream setting...")
@ -103,9 +105,9 @@ class PypoFetch(Thread):
# update timeout value # update timeout value
if command == 'update_schedule': if command == 'update_schedule':
self.listener_timeout = 3600 self.listener_timeout = POLL_INTERVAL
else: else:
self.listener_timeout = self.last_update_schedule_timestamp - time.time() + 3600 self.listener_timeout = self.last_update_schedule_timestamp - time.time() + POLL_INTERVAL
if self.listener_timeout < 0: if self.listener_timeout < 0:
self.listener_timeout = 0 self.listener_timeout = 0
self.logger.info("New timeout: %s" % self.listener_timeout) self.logger.info("New timeout: %s" % self.listener_timeout)
@ -171,10 +173,10 @@ class PypoFetch(Thread):
self.logger.debug('Getting information needed on bootstrap from Airtime') self.logger.debug('Getting information needed on bootstrap from Airtime')
info = self.api_client.get_bootstrap_info() info = self.api_client.get_bootstrap_info()
if info == None: if info == None:
self.logger.error('Unable to get bootstrap info.. Existing pypo...') self.logger.error('Unable to get bootstrap info.. Exiting pypo...')
sys.exit(0) sys.exit(1)
else: else:
self.logger.debug('info:%s',info) self.logger.debug('info:%s', info)
for k, v in info['switch_status'].iteritems(): for k, v in info['switch_status'].iteritems():
self.switch_source(self.logger, self.telnet_lock, k, v) self.switch_source(self.logger, self.telnet_lock, k, v)
self.update_liquidsoap_stream_format(info['stream_label']) self.update_liquidsoap_stream_format(info['stream_label'])
@ -267,13 +269,13 @@ class PypoFetch(Thread):
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname']) self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
restart = True; restart = True;
else: else:
stream, dump = s[u'keyname'].split('_',1) stream, dump = s[u'keyname'].split('_', 1)
if "_output" in s[u'keyname']: if "_output" in s[u'keyname']:
if (existing[s[u'keyname']] != s[u'value']): if (existing[s[u'keyname']] != s[u'value']):
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname']) self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
restart = True; restart = True;
state_change_restart[stream] = True state_change_restart[stream] = True
elif ( s[u'value'] != 'disabled'): elif (s[u'value'] != 'disabled'):
state_change_restart[stream] = True state_change_restart[stream] = True
else: else:
state_change_restart[stream] = False state_change_restart[stream] = False
@ -314,7 +316,7 @@ class PypoFetch(Thread):
# we are manually adjusting the bootup time variable so the status msg will get # we are manually adjusting the bootup time variable so the status msg will get
# updated. # updated.
current_time = time.time() current_time = time.time()
boot_up_time_command = "vars.bootup_time "+str(current_time)+"\n" boot_up_time_command = "vars.bootup_time " + str(current_time) + "\n"
tn.write(boot_up_time_command) tn.write(boot_up_time_command)
tn.write("streams.connection_status\n") tn.write("streams.connection_status\n")
tn.write('exit\n') tn.write('exit\n')
@ -425,8 +427,9 @@ class PypoFetch(Thread):
media_item = media[key] media_item = media[key]
if(media_item['type'] == 'file'): if(media_item['type'] == 'file'):
fileExt = os.path.splitext(media_item['uri'])[1] fileExt = os.path.splitext(media_item['uri'])[1]
dst = os.path.join(download_dir, media_item['id']+fileExt) dst = os.path.join(download_dir, media_item['id'] + fileExt)
media_item['dst'] = dst media_item['dst'] = dst
media_item['started_copying'] = False
media_filtered[key] = media_item media_filtered[key] = media_item
self.media_prepare_queue.put(copy.copy(media_filtered)) self.media_prepare_queue.put(copy.copy(media_filtered))
@ -458,9 +461,9 @@ class PypoFetch(Thread):
unneeded_files = cached_file_set - scheduled_file_set unneeded_files = cached_file_set - scheduled_file_set
self.logger.debug("Files to remove " + str(unneeded_files)) self.logger.debug("Files to remove " + str(unneeded_files))
for file in unneeded_files: for f in unneeded_files:
self.logger.debug("Removing %s" % os.path.join(self.cache_dir, file)) self.logger.debug("Removing %s" % os.path.join(self.cache_dir, f))
os.remove(os.path.join(self.cache_dir, file)) os.remove(os.path.join(self.cache_dir, f))
def main(self): def main(self):
# Bootstrap: since we are just starting up, we need to grab the # Bootstrap: since we are just starting up, we need to grab the
@ -486,12 +489,14 @@ class PypoFetch(Thread):
sent, and we will have very stale (or non-existent!) data about the sent, and we will have very stale (or non-existent!) data about the
schedule. schedule.
Currently we are checking every 3600 seconds (1 hour) Currently we are checking every POLL_INTERVAL seconds
""" """
message = self.fetch_queue.get(block=True, timeout=self.listener_timeout) message = self.fetch_queue.get(block=True, timeout=self.listener_timeout)
self.handle_message(message) self.handle_message(message)
except Empty, e:
self.logger.info("Queue timeout. Fetching schedule manually")
except Exception, e: except Exception, e:
import traceback import traceback
top = traceback.format_exc() top = traceback.format_exc()

View file

@ -5,7 +5,6 @@ from Queue import Empty
from configobj import ConfigObj from configobj import ConfigObj
import logging import logging
import logging.config
import shutil import shutil
import os import os
import sys import sys
@ -71,13 +70,16 @@ class PypoFile(Thread):
if do_copy: if do_copy:
self.logger.debug("copying from %s to local cache %s" % (src, dst)) self.logger.debug("copying from %s to local cache %s" % (src, dst))
try: try:
media_item['started_copying'] = True
""" """
copy will overwrite dst if it already exists copy will overwrite dst if it already exists
""" """
shutil.copy(src, dst) shutil.copy(src, dst)
#make file world readable #make file world readable
os.chmod(dst, stat.S_IRUSR | stat.S_IRGRP | stat.S_IXOTH) os.chmod(dst, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
except Exception, e: except Exception, e:
self.logger.error("Could not copy from %s to %s" % (src, dst)) self.logger.error("Could not copy from %s to %s" % (src, dst))
self.logger.error(e) self.logger.error(e)

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import logging.config
import sys import sys
from configobj import ConfigObj from configobj import ConfigObj
from threading import Thread from threading import Thread
@ -62,7 +61,7 @@ class PypoMessageHandler(Thread):
try: try:
self.logger.info("Received event from RabbitMQ: %s" % message) self.logger.info("Received event from RabbitMQ: %s" % message)
m = json.loads(message) m = json.loads(message)
command = m['event_type'] command = m['event_type']
self.logger.info("Handling command: " + command) self.logger.info("Handling command: " + command)
@ -126,10 +125,8 @@ class PypoMessageHandler(Thread):
There is a problem with the RabbitMq messenger service. Let's There is a problem with the RabbitMq messenger service. Let's
log the error and get the schedule via HTTP polling log the error and get the schedule via HTTP polling
""" """
import traceback
top = traceback.format_exc()
self.logger.error('Exception: %s', e) self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top) self.logger.error("traceback: %s", traceback.format_exc())
loops += 1 loops += 1

View file

@ -15,19 +15,9 @@ Main case:
""" """
# python defaults (debian default) from optparse import OptionParser
import time
import os
import traceback
from optparse import *
import sys import sys
import time
import datetime
import logging
import logging.config import logging.config
import urllib
import urllib2
import string
import json import json
# additional modules (should be checked) # additional modules (should be checked)
@ -38,9 +28,6 @@ from configobj import ConfigObj
from api_clients import * from api_clients import *
from std_err_override import LogWriter from std_err_override import LogWriter
# Set up command-line options
parser = OptionParser()
# help screeen / info # help screeen / info
usage = "%prog [options]" + " - notification gateway" usage = "%prog [options]" + " - notification gateway"
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
@ -60,7 +47,7 @@ parser.add_option("-y", "--source-status", help="source connection stauts", meta
# configure logging # configure logging
logging.config.fileConfig("logging.cfg") logging.config.fileConfig("logging.cfg")
logger = logging.getLogger() logger = logging.getLogger('notify')
LogWriter.override_std_err(logger) LogWriter.override_std_err(logger)
#need to wait for Python 2.7 for this.. #need to wait for Python 2.7 for this..
@ -85,9 +72,9 @@ class Notify:
logger.debug('#################################################') logger.debug('#################################################')
logger.debug('# Calling server to update about what\'s playing #') logger.debug('# Calling server to update about what\'s playing #')
logger.debug('#################################################') logger.debug('#################################################')
logger.debug('data = '+ str(data)) logger.debug('data = ' + str(data))
response = self.api_client.notify_media_item_start_playing(data, media_id) response = self.api_client.notify_media_item_start_playing(data, media_id)
logger.debug("Response: "+json.dumps(response)) logger.debug("Response: " + json.dumps(response))
# @pram time: time that LS started # @pram time: time that LS started
def notify_liquidsoap_status(self, msg, stream_id, time): def notify_liquidsoap_status(self, msg, stream_id, time):
@ -96,9 +83,9 @@ class Notify:
logger.debug('#################################################') logger.debug('#################################################')
logger.debug('# Calling server to update liquidsoap status #') logger.debug('# Calling server to update liquidsoap status #')
logger.debug('#################################################') logger.debug('#################################################')
logger.debug('msg = '+ str(msg)) logger.debug('msg = ' + str(msg))
response = self.api_client.notify_liquidsoap_status(msg, stream_id, time) response = self.api_client.notify_liquidsoap_status(msg, stream_id, time)
logger.debug("Response: "+json.dumps(response)) logger.debug("Response: " + json.dumps(response))
def notify_source_status(self, source_name, status): def notify_source_status(self, source_name, status):
logger = logging.getLogger("notify") logger = logging.getLogger("notify")
@ -106,9 +93,9 @@ class Notify:
logger.debug('#################################################') logger.debug('#################################################')
logger.debug('# Calling server to update source status #') logger.debug('# Calling server to update source status #')
logger.debug('#################################################') logger.debug('#################################################')
logger.debug('msg = '+ str(source_name) + ' : ' + str(status)) logger.debug('msg = ' + str(source_name) + ' : ' + str(status))
response = self.api_client.notify_source_status(source_name, status) response = self.api_client.notify_source_status(source_name, status)
logger.debug("Response: "+json.dumps(response)) logger.debug("Response: " + json.dumps(response))
if __name__ == '__main__': if __name__ == '__main__':
print print

View file

@ -5,11 +5,9 @@ from datetime import timedelta
import sys import sys
import time import time
import logging
import logging.config import logging.config
import telnetlib import telnetlib
import calendar import calendar
import json
import math import math
from pypofetch import PypoFetch from pypofetch import PypoFetch
@ -54,7 +52,7 @@ class PypoPush(Thread):
def main(self): def main(self):
loops = 0 loops = 0
heartbeat_period = math.floor(30/PUSH_INTERVAL) heartbeat_period = math.floor(30 / PUSH_INTERVAL)
next_media_item_chain = None next_media_item_chain = None
media_schedule = None media_schedule = None
@ -82,8 +80,7 @@ class PypoPush(Thread):
self.modify_cue_point(current_event_chain[0]) self.modify_cue_point(current_event_chain[0])
next_media_item_chain = current_event_chain next_media_item_chain = current_event_chain
time_until_next_play = 0 time_until_next_play = 0
#sleep for 0.2 seconds to give pypo-file time to copy. This is a quick #sleep for 0.2 seconds to give pypo-file time to copy.
#fix that will be improved in 2.1.1
time.sleep(0.2) time.sleep(0.2)
else: else:
media_chain = filter(lambda item: (item["type"] == "file"), current_event_chain) media_chain = filter(lambda item: (item["type"] == "file"), current_event_chain)
@ -170,7 +167,7 @@ class PypoPush(Thread):
queue. queue.
""" """
problem_at_iteration, problem_start_time = self.find_removed_items(media_schedule, liquidsoap_queue_approx) problem_at_iteration = self.find_removed_items(media_schedule, liquidsoap_queue_approx)
if problem_at_iteration is not None: if problem_at_iteration is not None:
#Items that are in Liquidsoap's queue aren't scheduled anymore. We need to connect #Items that are in Liquidsoap's queue aren't scheduled anymore. We need to connect
@ -201,7 +198,6 @@ class PypoPush(Thread):
#see if they are the same as the newly received schedule #see if they are the same as the newly received schedule
iteration = 0 iteration = 0
problem_at_iteration = None problem_at_iteration = None
problem_start_time = None
for queue_item in liquidsoap_queue_approx: for queue_item in liquidsoap_queue_approx:
if queue_item['start'] in media_schedule.keys(): if queue_item['start'] in media_schedule.keys():
media_item = media_schedule[queue_item['start']] media_item = media_schedule[queue_item['start']]
@ -211,23 +207,20 @@ class PypoPush(Thread):
pass pass
else: else:
problem_at_iteration = iteration problem_at_iteration = iteration
problem_start_time = queue_item['start']
break break
else: else:
#A different item has been scheduled at the same time! Need to remove #A different item has been scheduled at the same time! Need to remove
#all tracks from the Liquidsoap queue starting at this point, and re-add #all tracks from the Liquidsoap queue starting at this point, and re-add
#them. #them.
problem_at_iteration = iteration problem_at_iteration = iteration
problem_start_time = queue_item['start']
break break
else: else:
#There are no more items scheduled for this time! The user has shortened #There are no more items scheduled for this time! The user has shortened
#the playlist, so we simply need to remove tracks from the queue. #the playlist, so we simply need to remove tracks from the queue.
problem_at_iteration = iteration problem_at_iteration = iteration
problem_start_time = queue_item['start']
break break
iteration+=1 iteration += 1
return (problem_at_iteration, problem_start_time) return problem_at_iteration
@ -312,14 +305,27 @@ class PypoPush(Thread):
def date_interval_to_seconds(self, interval): def date_interval_to_seconds(self, interval):
return (interval.microseconds + (interval.seconds + interval.days * 24 * 3600) * 10**6) / float(10**6) return (interval.microseconds + (interval.seconds + interval.days * 24 * 3600) * 10 ** 6) / float(10 ** 6)
def push_to_liquidsoap(self, event_chain): def push_to_liquidsoap(self, event_chain):
try: try:
for media_item in event_chain: for media_item in event_chain:
if media_item['type'] == "file": if media_item['type'] == "file":
self.telnet_to_liquidsoap(media_item)
"""
Wait maximum 5 seconds (50 iterations) for file to become ready, otherwise
give up on it.
"""
iter_num = 0
while not media_item['started_copying'] and iter_num < 50:
time.sleep(0.1)
iter_num += 1
if media_item['started_copying']:
self.telnet_to_liquidsoap(media_item)
else:
self.logger.warn("File %s did not become ready in less than 5 seconds. Skipping...", media_item['dst'])
elif media_item['type'] == "event": elif media_item['type'] == "event":
if media_item['event_type'] == "kick_out": if media_item['event_type'] == "kick_out":
PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj") PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj")
@ -448,7 +454,7 @@ class PypoPush(Thread):
def create_liquidsoap_annotation(self, media): def create_liquidsoap_annotation(self, media):
# we need lia_start_next value in the annotate. That is the value that controlls overlap duration of crossfade. # we need lia_start_next value in the annotate. That is the value that controlls overlap duration of crossfade.
return 'annotate:media_id="%s",liq_start_next="0",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \ return 'annotate:media_id="%s",liq_start_next="0",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \
% (media['id'], float(media['fade_in'])/1000, float(media['fade_out'])/1000, float(media['cue_in']), float(media['cue_out']), media['row_id'], media['dst']) % (media['id'], float(media['fade_in']) / 1000, float(media['fade_out']) / 1000, float(media['cue_in']), float(media['cue_out']), media['row_id'], media['dst'])
def run(self): def run(self):
try: self.main() try: self.main()

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import logging.config
import json import json
import time import time
import datetime import datetime
@ -55,7 +54,7 @@ class ShowRecorder(Thread):
self.p = None self.p = None
def record_show(self): def record_show(self):
length = str(self.filelength)+".0" length = str(self.filelength) + ".0"
filename = self.start_time filename = self.start_time
filename = filename.replace(" ", "-") filename = filename.replace(" ", "-")
@ -128,7 +127,7 @@ class ShowRecorder(Thread):
time = md[1].replace(":", "-") time = md[1].replace(":", "-")
self.logger.info("time: %s" % time) self.logger.info("time: %s" % time)
name = time+"-"+self.show_name name = time + "-" + self.show_name
artist = "Airtime Show Recorder" artist = "Airtime Show Recorder"
#set some metadata for our file daemon #set some metadata for our file daemon
@ -181,7 +180,7 @@ class Recorder(Thread):
def handle_message(self): def handle_message(self):
if not self.queue.empty(): if not self.queue.empty():
message = self.queue.get() message = self.queue.get()
msg = json.loads(message) msg = json.loads(message)
command = msg["event_type"] command = msg["event_type"]
self.logger.info("Received msg from Pypo Message Handler: %s", msg) self.logger.info("Received msg from Pypo Message Handler: %s", msg)
if command == 'cancel_recording': if command == 'cancel_recording':
@ -243,14 +242,14 @@ class Recorder(Thread):
start_time_on_UTC = getDateTimeObj(start_time) start_time_on_UTC = getDateTimeObj(start_time)
start_time_on_server = start_time_on_UTC.replace(tzinfo=pytz.utc).astimezone(T) start_time_on_server = start_time_on_UTC.replace(tzinfo=pytz.utc).astimezone(T)
start_time_formatted = '%(year)d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(sec)02d' % \ start_time_formatted = '%(year)d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(sec)02d' % \
{'year': start_time_on_server.year, 'month': start_time_on_server.month, 'day': start_time_on_server.day,\ {'year': start_time_on_server.year, 'month': start_time_on_server.month, 'day': start_time_on_server.day, \
'hour': start_time_on_server.hour, 'min': start_time_on_server.minute, 'sec': start_time_on_server.second} 'hour': start_time_on_server.hour, 'min': start_time_on_server.minute, 'sec': start_time_on_server.second}
self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time_formatted) self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time_formatted)
self.sr.start() self.sr.start()
#remove show from shows to record. #remove show from shows to record.
del self.shows_to_record[start_time] del self.shows_to_record[start_time]
#self.time_till_next_show = self.get_time_till_next_show() #self.time_till_next_show = self.get_time_till_next_show()
except Exception,e : except Exception, e :
import traceback import traceback
top = traceback.format_exc() top = traceback.format_exc()
self.logger.error('Exception: %s', e) self.logger.error('Exception: %s', e)
@ -277,7 +276,7 @@ class Recorder(Thread):
self.logger.info("Bootstrap complete: got initial copy of the schedule") self.logger.info("Bootstrap complete: got initial copy of the schedule")
self.loops = 0 self.loops = 0
heartbeat_period = math.floor(30/PUSH_INTERVAL) heartbeat_period = math.floor(30 / PUSH_INTERVAL)
while True: while True:
if self.loops % heartbeat_period == 0: if self.loops % heartbeat_period == 0:
@ -299,7 +298,7 @@ class Recorder(Thread):
self.logger.error('Pypo Recorder Exception: %s', e) self.logger.error('Pypo Recorder Exception: %s', e)
time.sleep(PUSH_INTERVAL) time.sleep(PUSH_INTERVAL)
self.loops += 1 self.loops += 1
except Exception,e : except Exception, e :
import traceback import traceback
top = traceback.format_exc() top = traceback.format_exc()
self.logger.error('Exception: %s', e) self.logger.error('Exception: %s', e)