Merge branch '2.1.x' into devel

Conflicts:
	python_apps/pypo/liquidsoap_scripts/ls_script.liq
This commit is contained in:
denise 2012-06-29 10:57:14 -04:00
commit 335dd1516b
40 changed files with 1084 additions and 870 deletions

View file

@ -23,8 +23,9 @@ date_default_timezone_set('UTC');
date_default_timezone_set(Application_Model_Preference::GetTimezone()); date_default_timezone_set(Application_Model_Preference::GetTimezone());
global $CC_CONFIG; global $CC_CONFIG;
$CC_CONFIG['airtime_version'] = Application_Model_Preference::GetAirtimeVersion(); $airtime_version = Application_Model_Preference::GetAirtimeVersion();
$uniqueid = Application_Model_Preference::GetUniqueId();
$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

@ -41,7 +41,7 @@ class LibraryController extends Zend_Controller_Action
$user = new Application_Model_User($userInfo->id); $user = new Application_Model_User($userInfo->id);
//Open a jPlayer window and play the audio clip. //Open a jPlayer window and play the audio clip.
$menu["play"] = array("name"=> "Play", "icon" => "play"); $menu["play"] = array("name"=> "Preview", "icon" => "play");
$isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)); $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));

View file

@ -100,6 +100,12 @@ class LoginController extends Zend_Controller_Action
public function passwordRestoreAction() public function passwordRestoreAction()
{ {
global $CC_CONFIG;
$request = $this->getRequest();
$baseUrl = $request->getBaseUrl();
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/login/password-restore.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
if (!Application_Model_Preference::GetEnableSystemEmail()) { if (!Application_Model_Preference::GetEnableSystemEmail()) {
$this->_redirect('login'); $this->_redirect('login');
} }

View file

@ -29,5 +29,13 @@ class Application_Form_PasswordRestore extends Zend_Form
'ViewHelper' 'ViewHelper'
) )
)); ));
$cancel = new Zend_Form_Element_Button("cancel");
$cancel->class = 'ui-button ui-widget ui-state-default ui-button-text-only center';
$cancel->setLabel("Cancel")
->setIgnore(True)
->setAttrib('onclick', 'redirectToLogin();')
->setDecorators(array('ViewHelper'));
$this->addElement($cancel);
} }
} }

View file

@ -82,17 +82,9 @@ class Application_Model_MusicDir {
$show_instances = $con->query($sql)->fetchAll(); $show_instances = $con->query($sql)->fetchAll();
// get all the files on this dir // get all the files on this dir
$sql = "SELECT f.id FROM cc_music_dirs as md " $sql = "UPDATE cc_files SET file_exists = 'f' WHERE id IN (SELECT f.id FROM cc_music_dirs as md "
." LEFT JOIN cc_files as f on f.directory = md.id WHERE md.id = $music_dir_id"; ." LEFT JOIN cc_files as f on f.directory = md.id WHERE md.id = $music_dir_id)";
$files = $con->query($sql)->fetchAll(); $affected = $con->exec($sql);
// set file_exist flag to false
foreach ($files as $file_row) {
$temp_file = Application_Model_StoredFile::Recall($file_row['id']);
if($temp_file != null){
$temp_file->setFileExistsFlag(false);
}
}
// set RemovedFlag to true // set RemovedFlag to true
if ($userAddedWatchedDir) { if ($userAddedWatchedDir) {
@ -399,14 +391,16 @@ class Application_Model_MusicDir {
* otherwise, it will set "Exists" flag to true * otherwise, it will set "Exists" flag to true
**/ **/
public static function removeWatchedDir($p_dir, $userAddedWatchedDir=true){ public static function removeWatchedDir($p_dir, $userAddedWatchedDir=true){
//make sure that $p_dir has a trailing "/"
$real_path = Application_Common_OsPath::normpath($p_dir)."/"; $real_path = Application_Common_OsPath::normpath($p_dir)."/";
if($real_path != "/"){ if($real_path != "/"){
$p_dir = $real_path; $p_dir = $real_path;
} }
$dir = Application_Model_MusicDir::getDirByPath($p_dir); $dir = Application_Model_MusicDir::getDirByPath($p_dir);
if($dir == NULL){ if (is_null($dir)) {
return array("code"=>1,"error"=>"'$p_dir' doesn't exist in the watched list."); return array("code"=>1, "error"=>"'$p_dir' doesn't exist in the watched list.");
}else{ } else {
$dir->remove($userAddedWatchedDir); $dir->remove($userAddedWatchedDir);
$data = array(); $data = array();
$data["directory"] = $p_dir; $data["directory"] = $p_dir;

View file

@ -618,7 +618,7 @@ class Application_Model_Preference
if (defined('APPLICATION_ENV') && APPLICATION_ENV == "development" && function_exists('exec')){ if (defined('APPLICATION_ENV') && APPLICATION_ENV == "development" && function_exists('exec')){
$version = exec("git rev-parse --short HEAD 2>/dev/null", $out, $return_code); $version = exec("git rev-parse --short HEAD 2>/dev/null", $out, $return_code);
if ($return_code == 0){ if ($return_code == 0){
return self::GetValue("system_version")."+".$version; return self::GetValue("system_version")."+".$version.":".time();
} }
} }
return self::GetValue("system_version"); return self::GetValue("system_version");

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

@ -221,6 +221,7 @@ class Application_Model_ShowBuilder {
$row["title"] = $p_item["show_name"]; $row["title"] = $p_item["show_name"];
$row["instance"] = intval($p_item["si_id"]); $row["instance"] = intval($p_item["si_id"]);
$row["image"] = ''; $row["image"] = '';
$row["id"] = -1;
$this->getScheduledStatus($startsEpoch, $endsEpoch, $row); $this->getScheduledStatus($startsEpoch, $endsEpoch, $row);
@ -310,6 +311,7 @@ class Application_Model_ShowBuilder {
$row = $this->defaultRowArray; $row = $this->defaultRowArray;
$row["footer"] = true; $row["footer"] = true;
$row["instance"] = intval($p_item["si_id"]); $row["instance"] = intval($p_item["si_id"]);
$row["id"] = -1;
$this->getRowTimestamp($p_item, $row); $this->getRowTimestamp($p_item, $row);
$showEndDT = new DateTime($p_item["si_ends"], new DateTimeZone("UTC")); $showEndDT = new DateTime($p_item["si_ends"], new DateTimeZone("UTC"));

View file

@ -327,7 +327,10 @@ class Application_Model_StoredFile {
throw new DeleteScheduledFileException(); throw new DeleteScheduledFileException();
} }
if (file_exists($filepath)) { $music_dir = Application_Model_MusicDir::getDirByPK($this->_file->getDbDirectory());
$type = $music_dir->getType();
if (file_exists($filepath) && $type == "stor") {
$data = array("filepath" => $filepath, "delete" => 1); $data = array("filepath" => $filepath, "delete" => 1);
Application_Model_RabbitMq::SendMessageToMediaMonitor("file_delete", $data); Application_Model_RabbitMq::SendMessageToMediaMonitor("file_delete", $data);
} }
@ -889,10 +892,9 @@ Logging::log("getting media! - 2");
$command = sprintf("/usr/bin/airtime-liquidsoap -c 'output.dummy(audio_to_stereo(single(\"%s\")))' 2>&1", $audio_file); $command = sprintf("/usr/bin/airtime-liquidsoap -c 'output.dummy(audio_to_stereo(single(\"%s\")))' 2>&1", $audio_file);
exec($command, $output, $rv); exec($command, $output, $rv);
if ($rv != 0 || $output[0] == 'TagLib: MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream.') { if ($rv != 0 || (!empty($output) && $output[0] == 'TagLib: MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream.')) {
$result = array("code" => 110, "message" => "This file appears to be corrupted and will not be added to media library."); $result = array("code" => 110, "message" => "This file appears to be corrupted and will not be added to media library.");
} } else {
else {
//Martin K.: changed to rename: Much less load + quicker since this is an atomic operation //Martin K.: changed to rename: Much less load + quicker since this is an atomic operation
$r = @rename($audio_file, $audio_stor); $r = @rename($audio_file, $audio_stor);

View file

@ -21,5 +21,8 @@
<dd id="submit-element"> <dd id="submit-element">
<?php echo $this->element->getElement('submit') ?> <?php echo $this->element->getElement('submit') ?>
</dd> </dd>
<dd id="cancel-element">
<?php echo $this->element->getElement('cancel') ?>
</dd>
</dl> </dl>
</form> </form>

View file

@ -69,16 +69,19 @@
<?php echo $this->form->getSubform('live_stream_subform'); ?> <?php echo $this->form->getSubform('live_stream_subform'); ?>
</div> </div>
<div style="float: left; width: 600px;"> <div style="float: left; width: 600px;">
<fieldset class="padded">
<legend>Output Stream Settings</legend>
<?php <?php
for($i=1;$i<=$this->num_stream;$i++){ for($i=1;$i<=$this->num_stream;$i++){
echo $this->form->getSubform("s".$i."_subform"); echo $this->form->getSubform("s".$i."_subform");
} }
?> ?>
</fieldset>
<?php if($this->enable_stream_conf == "true"){?> <?php if($this->enable_stream_conf == "true"){?>
<div class="button-bar bottom" id="submit-element"> <div class="button-bar bottom" id="submit-element">
<input type="submit" class="ui-button ui-state-default right-floated" value="Save" id="Save" name="Save" /> <input type="submit" class="ui-button ui-state-default right-floated" value="Save" id="Save" name="Save" />
</div> </div>
<?php }?> <?php }?>
</div> </div>
</form> </form>
</div> </div>

View file

@ -16,7 +16,7 @@
<?php echo $this->when; ?> <?php echo $this->when; ?>
<?php echo $this->repeats; ?> <?php echo $this->repeats; ?>
</div> </div>
<h3 class="collapsible-header"><span class="arrow-icon"></span>Live Stream</h3> <h3 class="collapsible-header"><span class="arrow-icon"></span>Live Stream Input</h3>
<div id="live-stream-override" class="collapsible-content"> <div id="live-stream-override" class="collapsible-content">
<?php echo $this->live; ?> <?php echo $this->live; ?>
</div> </div>

View file

@ -808,6 +808,12 @@ dt.block-display, dd.block-display {
font-size:14px; font-size:14px;
padding: 6px 10px 6px; padding: 6px 10px 6px;
} }
.login-content dd button.ui-button, .login-content dd button.btn {
width:100%;
font-size:14px;
padding: 6px 10px 6px;
}
.login-content .hidden, .hidden { .login-content .hidden, .hidden {
display:none; display:none;
} }

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() {
@ -252,6 +264,7 @@ var AIRTIME = (function(AIRTIME) {
}; };
mod.fnDeleteSelectedItems = function() { mod.fnDeleteSelectedItems = function() {
if (confirm('Are you sure you want to delete the selected item(s)?')) {
var aData = AIRTIME.library.getSelectedData(), var aData = AIRTIME.library.getSelectedData(),
item, item,
temp, temp,
@ -266,6 +279,7 @@ var AIRTIME = (function(AIRTIME) {
} }
AIRTIME.library.fnDeleteItems(aMedia); AIRTIME.library.fnDeleteItems(aMedia);
}
}; };
libraryInit = function() { libraryInit = function() {

View file

@ -0,0 +1,3 @@
function redirectToLogin(){
window.location = "/Login"
}

View file

@ -1,3 +1,32 @@
function getFileName(ext){
var filename = $("#his_date_start").val()+"_"+$("#his_time_start").val()+"m--"+$("#his_date_end").val()+"_"+$("#his_time_end").val()+"m"
filename = filename.replace(/:/g,"h")
if(ext == "pdf"){
filename = filename+".pdf"
}else{
filename = filename+".csv"
}
return filename;
}
function setFlashFileName( nButton, oConfig, oFlash ) {
var filename = getFileName(oConfig.sExtends)
oFlash.setFileName( filename );
if(oConfig.sExtends == "pdf"){
this.fnSetText( oFlash,
"title:"+ this.fnGetTitle(oConfig) +"\n"+
"message:"+ oConfig.sPdfMessage +"\n"+
"colWidth:"+ this.fnCalcColRatios(oConfig) +"\n"+
"orientation:"+ oConfig.sPdfOrientation +"\n"+
"size:"+ oConfig.sPdfSize +"\n"+
"--/TableToolsOpts--\n" +
this.fnGetTableData(oConfig));
}else{
this.fnSetText( oFlash,
this.fnGetTableData(oConfig));
}
}
var AIRTIME = (function(AIRTIME) { var AIRTIME = (function(AIRTIME) {
var mod; var mod;
@ -37,7 +66,7 @@ var AIRTIME = (function(AIRTIME) {
"aoColumns": [ "aoColumns": [
{"sTitle": "Title", "mDataProp": "title", "sClass": "his_title"}, /* Title */ {"sTitle": "Title", "mDataProp": "title", "sClass": "his_title"}, /* Title */
{"sTitle": "Artist", "mDataProp": "artist", "sClass": "his_artist"}, /* Creator */ {"sTitle": "Creator", "mDataProp": "artist", "sClass": "his_artist"}, /* Creator */
{"sTitle": "Played", "mDataProp": "played", "sClass": "his_artist"}, /* times played */ {"sTitle": "Played", "mDataProp": "played", "sClass": "his_artist"}, /* times played */
{"sTitle": "Length", "mDataProp": "length", "sClass": "his_length library_length"}, /* Length */ {"sTitle": "Length", "mDataProp": "length", "sClass": "his_length library_length"}, /* Length */
{"sTitle": "Composer", "mDataProp": "composer", "sClass": "his_composer"}, /* Composer */ {"sTitle": "Composer", "mDataProp": "composer", "sClass": "his_composer"}, /* Composer */
@ -65,7 +94,19 @@ var AIRTIME = (function(AIRTIME) {
"sDom": 'lf<"dt-process-rel"r><"H"T><"dataTables_scrolling"t><"F"ip>', "sDom": 'lf<"dt-process-rel"r><"H"T><"dataTables_scrolling"t><"F"ip>',
"oTableTools": { "oTableTools": {
"sSwfPath": "/js/datatables/plugin/TableTools/swf/copy_cvs_xls_pdf.swf" "sSwfPath": "/js/datatables/plugin/TableTools/swf/copy_cvs_xls_pdf.swf",
"aButtons": [
"copy",
{
"sExtends": "csv",
"fnClick": setFlashFileName
},
{
"sExtends": "pdf",
"fnClick": setFlashFileName
},
"print"
]
} }
}); });
oTable.fnSetFilteringDelay(350); oTable.fnSetFilteringDelay(350);

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

@ -224,16 +224,27 @@ function eventRender(event, element, view) {
$(element).find(".fc-event-content").append(div); $(element).find(".fc-event-content").append(div);
} }
//add the record/rebroadcast icons if needed. //add the record/rebroadcast/soundcloud icons if needed
//record icon (only if not on soundcloud, will always be true for future events)
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1 && event.soundcloud_id === -1) { if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1 && event.soundcloud_id === -1) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon recording"></span>'); $(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon recording"></span>');
} else if ((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1 && event.soundcloud_id > 0) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon recording"></span><span id="'+event.id+'" class="small-icon soundcloud"></span>');
} else if ((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1 && event.soundcloud_id === -2) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon recording"></span><span id="'+event.id+'" class="small-icon progress"></span>');
} else if ((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1 && event.soundcloud_id === -3) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon recording"></span><span id="'+event.id+'" class="small-icon sc-error"></span>');
} }
if(view.name === 'month' && event.record === 1 && event.soundcloud_id === -1) {
if(view.name === 'month' && event.record === 1 && event.soundcloud_id === -1) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon recording"></span>'); $(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon recording"></span>');
} else if (view.name === 'month' && event.record === 1 && event.soundcloud_id > 0) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon recording"></span><span id="'+event.id+'" class="small-icon soundcloud"></span>');
} else if (view.name === 'month' && event.record === 1 && event.soundcloud_id === -2) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon recording"></span><span id="'+event.id+'" class="small-icon progress"></span>');
} else if (view.name === 'month' && event.record === 1 && event.soundcloud_id === -3) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon recording"></span><span id="'+event.id+'" class="small-icon sc-error"></span>');
} }
//rebroadcast icon //rebroadcast icon
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.rebroadcast === 1) { if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.rebroadcast === 1) {
@ -243,35 +254,6 @@ function eventRender(event, element, view) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon rebroadcast"></span>'); $(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon rebroadcast"></span>');
} }
//soundcloud icon
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.soundcloud_id > 0 && event.record === 1) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon soundcloud"></span>');
}
if(view.name === 'month' && event.soundcloud_id > 0 && event.record === 1) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon soundcloud"></span>');
}
//progress icon
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.soundcloud_id === -2 && event.record === 1) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon progress"></span>');
}
if(view.name === 'month' && event.soundcloud_id === -2 && event.record === 1) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon progress"></span>');
}
//error icon
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.soundcloud_id === -3 && event.record === 1) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon sc-error"></span>');
}
if(view.name === 'month' && event.soundcloud_id === -3 && event.record === 1) {
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon sc-error"></span>');
}
} }
function eventAfterRender( event, element, view ) { function eventAfterRender( event, element, view ) {
@ -340,9 +322,9 @@ function checkSCUploadStatus(){
var id = $(this).attr("id"); var id = $(this).attr("id");
$.post(url, {format: "json", id: id, type:"show"}, function(json){ $.post(url, {format: "json", id: id, type:"show"}, function(json){
if(json.sc_id > 0){ if(json.sc_id > 0){
$("span[id="+id+"]").removeClass("progress").addClass("soundcloud"); $("span[id="+id+"]:not(.recording)").removeClass("progress").addClass("soundcloud");
}else if(json.sc_id == "-3"){ }else if(json.sc_id == "-3"){
$("span[id="+id+"]").removeClass("progress").addClass("sc-error"); $("span[id="+id+"]:not(.recording)").removeClass("progress").addClass("sc-error");
} }
}); });
}); });

View file

@ -137,7 +137,6 @@ function findViewportDimensions() {
} }
function buildScheduleDialog (json) { function buildScheduleDialog (json) {
var dialog = $(json.dialog), var dialog = $(json.dialog),
viewport = findViewportDimensions(), viewport = findViewportDimensions(),
height = Math.floor(viewport.height * 0.96), height = Math.floor(viewport.height * 0.96),
@ -166,8 +165,7 @@ function buildScheduleDialog (json) {
modal: true, modal: true,
close: closeDialog, close: closeDialog,
buttons: {"Ok": function() { buttons: {"Ok": function() {
dialog.remove(); $(this).dialog("close");
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
}} }}
}); });

View file

@ -8,7 +8,10 @@ var AIRTIME = (function(AIRTIME){
$sbTable, $sbTable,
$toolbar, $toolbar,
$ul, $ul,
$lib; $lib,
cursors = [],
cursorIds = [];
showInstanceIds = [];
if (AIRTIME.showbuilder === undefined) { if (AIRTIME.showbuilder === undefined) {
AIRTIME.showbuilder = {}; AIRTIME.showbuilder = {};
@ -208,6 +211,18 @@ var AIRTIME = (function(AIRTIME){
mod.fnItemCallback = function(json) { mod.fnItemCallback = function(json) {
checkError(json); checkError(json);
cursorIds = [];
/* We need to keep record of which show the cursor belongs to
* in the case where more than one show is displayed in the show builder
* because header and footer rows have the same id
*/
showInstanceIds = [];
cursors = $(".cursor-selected-row");
for (i = 0; i < cursors.length; i++) {
cursorIds.push(($(cursors.get(i)).attr("id")));
showInstanceIds.push(($(cursors.get(i)).attr("si_id")));
}
oSchedTable.fnDraw(); oSchedTable.fnDraw();
mod.enableUI(); mod.enableUI();
@ -236,11 +251,14 @@ var AIRTIME = (function(AIRTIME){
mod.fnRemove = function(aItems) { mod.fnRemove = function(aItems) {
mod.disableUI(); mod.disableUI();
if (confirm("Delete selected item(s)?")) {
$.post( "/showbuilder/schedule-remove", $.post( "/showbuilder/schedule-remove",
{"items": aItems, "format": "json"}, {"items": aItems, "format": "json"},
mod.fnItemCallback mod.fnItemCallback
); );
}else{
mod.enableUI();
}
}; };
mod.fnRemoveSelectedItems = function() { mod.fnRemoveSelectedItems = function() {
@ -401,7 +419,6 @@ var AIRTIME = (function(AIRTIME){
headerIcon; headerIcon;
fnPrepareSeparatorRow = function fnPrepareSeparatorRow(sRowContent, sClass, iNodeIndex) { fnPrepareSeparatorRow = function fnPrepareSeparatorRow(sRowContent, sClass, iNodeIndex) {
$node = $(nRow.children[iNodeIndex]); $node = $(nRow.children[iNodeIndex]);
$node.html(sRowContent); $node.html(sRowContent);
$node.attr('colspan',100); $node.attr('colspan',100);
@ -572,11 +589,13 @@ 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);
$nRow.attr("si_id", aData.instance);
} }
//status used to colour tracks. //status used to colour tracks.
@ -615,6 +634,7 @@ var AIRTIME = (function(AIRTIME){
$cursorRows, $cursorRows,
$table = $(this), $table = $(this),
$parent = $table.parent(), $parent = $table.parent(),
$tr,
//use this array to cache DOM heights then we can detach the table to manipulate it to increase speed. //use this array to cache DOM heights then we can detach the table to manipulate it to increase speed.
heights = []; heights = [];
@ -653,6 +673,13 @@ var AIRTIME = (function(AIRTIME){
}); });
$td.append(markerDiv).wrapInner(wrapperDiv); $td.append(markerDiv).wrapInner(wrapperDiv);
}
//re-highlight selected cursors before draw took place
for (i = 0; i < cursorIds.length; i++) {
$tr = $table.find("tr[id="+cursorIds[i]+"][si_id="+showInstanceIds[i]+"]");
mod.selectCursor($tr);
} }
//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.
@ -678,8 +705,14 @@ var AIRTIME = (function(AIRTIME){
if (temp.length > 0) { if (temp.length > 0) {
aData = temp.data("aData"); aData = temp.data("aData");
// max time interval
mod.timeout = setTimeout(mod.refresh, aData.refresh * 1000); //need refresh in milliseconds // setTimeout allows only up to (2^31)-1 millisecs timeout value
maxRefreshInterval = Math.pow(2, 31) - 1;
refreshInterval = aData.refresh * 1000;
if(refreshInterval > maxRefreshInterval){
refreshInterval = maxRefreshInterval;
}
mod.timeout = setTimeout(mod.refresh, refreshInterval); //need refresh in milliseconds
break; break;
} }
} }
@ -890,8 +923,8 @@ var AIRTIME = (function(AIRTIME){
$ul = $("<ul/>"); $ul = $("<ul/>");
$ul.append('<li class="ui-state-default sb-button-select" title="Select"><span class="ui-icon ui-icon-document-b"></span></li>') $ul.append('<li class="ui-state-default sb-button-select" title="Select"><span class="ui-icon ui-icon-document-b"></span></li>')
.append('<li class="ui-state-default ui-state-disabled sb-button-trim" title="Delete all overbooked tracks"><span class="ui-icon ui-icon-scissors"></span></li>') .append('<li class="ui-state-default ui-state-disabled sb-button-trim" title="Remove all overbooked tracks"><span class="ui-icon ui-icon-scissors"></span></li>')
.append('<li class="ui-state-default ui-state-disabled sb-button-delete" title="Delete selected scheduled items"><span class="ui-icon ui-icon-trash"></span></li>'); .append('<li class="ui-state-default ui-state-disabled sb-button-delete" title="Remove selected scheduled items"><span class="ui-icon ui-icon-trash"></span></li>');
$toolbar.append($ul); $toolbar.append($ul);
$ul = $("<ul/>"); $ul = $("<ul/>");
@ -1065,7 +1098,7 @@ var AIRTIME = (function(AIRTIME){
if (oItems.del !== undefined) { if (oItems.del !== undefined) {
callback = function() { callback = function() {
if (confirm("Delete selected Items?")) { if (confirm("Delete selected item?")) {
AIRTIME.showbuilder.fnRemove([{ AIRTIME.showbuilder.fnRemove([{
id: data.id, id: data.id,
timestamp: data.timestamp, timestamp: data.timestamp,

View file

@ -30,7 +30,7 @@ AIRTIME = (function(AIRTIME) {
oBaseDatePickerSettings = { oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd', dateFormat: 'yy-mm-dd',
onSelect: function(sDate, oDatePicker) { onClick: function(sDate, oDatePicker) {
$(this).datepicker( "setDate", sDate ); $(this).datepicker( "setDate", sDate );
} }
}; };
@ -84,6 +84,34 @@ AIRTIME = (function(AIRTIME) {
} }
} }
function showSearchSubmit() {
var fn,
oRange,
op,
oTable = $('#show_builder_table').dataTable();
//reset timestamp value since input values could have changed.
AIRTIME.showbuilder.resetTimestamp();
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
fn = oTable.fnSettings().fnServerData;
fn.start = oRange.start;
fn.end = oRange.end;
op = $("div.sb-advanced-options");
if (op.is(":visible")) {
if (fn.ops === undefined) {
fn.ops = {};
}
fn.ops.showFilter = op.find("#sb_show_filter").val();
fn.ops.myShows = op.find("#sb_my_shows").is(":checked") ? 1 : 0;
}
oTable.fnDraw();
}
mod.onReady = function() { mod.onReady = function() {
//define module vars. //define module vars.
$lib = $("#library_content"); $lib = $("#library_content");
@ -118,33 +146,7 @@ AIRTIME = (function(AIRTIME) {
$builder.find('.dataTables_scrolling').css("max-height", widgetHeight - 95); $builder.find('.dataTables_scrolling').css("max-height", widgetHeight - 95);
$builder.on("click", "#sb_submit", function(ev){ $builder.on("click", "#sb_submit", showSearchSubmit);
var fn,
oRange,
op,
oTable = $('#show_builder_table').dataTable();
//reset timestamp value since input values could have changed.
AIRTIME.showbuilder.resetTimestamp();
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
fn = oTable.fnSettings().fnServerData;
fn.start = oRange.start;
fn.end = oRange.end;
op = $("div.sb-advanced-options");
if (op.is(":visible")) {
if (fn.ops === undefined) {
fn.ops = {};
}
fn.ops.showFilter = op.find("#sb_show_filter").val();
fn.ops.myShows = op.find("#sb_my_shows").is(":checked") ? 1 : 0;
}
oTable.fnDraw();
});
$builder.on("click","#sb_edit", function (ev){ $builder.on("click","#sb_edit", function (ev){
var schedTable = $("#show_builder_table").dataTable(); var schedTable = $("#show_builder_table").dataTable();
@ -221,6 +223,8 @@ AIRTIME = (function(AIRTIME) {
if ($(this).is(':checked')) { if ($(this).is(':checked')) {
$(ev.delegateTarget).find('#sb_show_filter').val(0); $(ev.delegateTarget).find('#sb_show_filter').val(0);
} }
showSearchSubmit();
}); });
//set select event for choosing a show. //set select event for choosing a show.
@ -229,6 +233,9 @@ AIRTIME = (function(AIRTIME) {
if ($(this).val() !== 0) { if ($(this).val() !== 0) {
$(ev.delegateTarget).find('#sb_my_shows').attr("checked", false); $(ev.delegateTarget).find('#sb_my_shows').attr("checked", false);
} }
showSearchSubmit();
}); });
function checkScheduleUpdates(){ function checkScheduleUpdates(){

View file

@ -15,8 +15,11 @@ class Version20110711161043 extends AbstractMigration
{ {
public function up(Schema $schema) public function up(Schema $schema)
{ {
$ini = parse_ini_file(__DIR__."/../include/airtime-install.ini");
$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 */
$this->_addSql("INSERT INTO cc_music_dirs (type, directory) VALUES ('stor', '/srv/airtime/stor/');"); $this->_addSql("INSERT INTO cc_music_dirs (type, directory) VALUES ('stor', $stor_dir);");
$this->_addSql("INSERT INTO cc_music_dirs (type, directory) VALUES ('link', '');"); $this->_addSql("INSERT INTO cc_music_dirs (type, directory) VALUES ('link', '');");

View file

@ -319,7 +319,12 @@ class AirtimeInstall
public static function SetDefaultTimezone() public static function SetDefaultTimezone()
{ {
$con = Propel::getConnection(); $con = Propel::getConnection();
$defaultTimezone = exec("cat /etc/timezone"); // we need to run php as commandline because we want to get the timezone in cli php.ini file
$defaultTimezone = exec("php -r 'echo date_default_timezone_get().PHP_EOL;'");
$defaultTimezone = trim($defaultTimezone);
if((!in_array($defaultTimezone, DateTimeZone::listIdentifiers()))){
$defaultTimezone = "UTC";
}
$sql = "INSERT INTO cc_pref (keystr, valstr) VALUES ('timezone', '$defaultTimezone')"; $sql = "INSERT INTO cc_pref (keystr, valstr) VALUES ('timezone', '$defaultTimezone')";
$result = $con->exec($sql); $result = $con->exec($sql);
if ($result < 1) { if ($result < 1) {

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

@ -0,0 +1,24 @@
<?php
/* All functions other than start() should be marked as
* private.
*/
class AirtimeDatabaseUpgrade{
public static function start($p_dbValues){
echo "* Updating Database".PHP_EOL;
self::task0($p_dbValues);
echo " * Complete".PHP_EOL;
}
private static function task0($p_dbValues){
$username = $p_dbValues['database']['dbuser'];
$password = $p_dbValues['database']['dbpass'];
$host = $p_dbValues['database']['host'];
$database = $p_dbValues['database']['dbname'];
$dir = __DIR__;
passthru("export PGPASSWORD=$password && psql -h $host -U $username -q -f $dir/data/upgrade.sql $database 2>&1 | grep -v \"will create implicit index\"");
}
}

View file

@ -0,0 +1,8 @@
<?php
require_once 'DbUpgrade.php';
$filename = "/etc/airtime/airtime.conf";
$values = parse_ini_file($filename, true);
AirtimeDatabaseUpgrade::start($values);

View file

@ -0,0 +1,4 @@
DELETE FROM cc_pref WHERE keystr = 'system_version';
INSERT INTO cc_pref (keystr, valstr) VALUES ('system_version', '2.1.3');
UPDATE cc_show_instances SET time_filled='00:00:00' WHERE time_filled IS NULL;

View file

@ -10,6 +10,7 @@
### END INIT INFO ### END INIT INFO
USERID=pypo USERID=pypo
ROOTUSERID=root
GROUPID=pypo GROUPID=pypo
NAME=Airtime\ Playout NAME=Airtime\ Playout
@ -47,7 +48,7 @@ start () {
chown pypo:pypo /etc/airtime chown pypo:pypo /etc/airtime
chown pypo:pypo /etc/airtime/liquidsoap.cfg chown pypo:pypo /etc/airtime/liquidsoap.cfg
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE0 --startas $DAEMON0 start-stop-daemon --start --background --quiet --chuid $ROOTUSERID:$ROOTUSERID --make-pidfile --pidfile $PIDFILE0 --startas $DAEMON0
monit monitor airtime-playout >/dev/null 2>&1 monit monitor airtime-playout >/dev/null 2>&1
liquidsoap_start liquidsoap_start
@ -80,7 +81,7 @@ monit_restart() {
} }
start_no_monit() { start_no_monit() {
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE0 --startas $DAEMON0 start-stop-daemon --start --background --quiet --chuid $ROOTUSERID:$ROOTUSERID --make-pidfile --pidfile $PIDFILE0 --startas $DAEMON0
liquidsoap_start liquidsoap_start
} }

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,10 +130,10 @@ 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)))
master_dj = rewrite_metadata([("artist","Airtime"), ("title", "Master Dj")],master_dj) master_dj = rewrite_metadata([("artist","Airtime"), ("title", "Master Dj")],master_dj)
dj_live = rewrite_metadata([("artist","Airtime"), ("title", "Live Dj")],dj_live) dj_live = rewrite_metadata([("artist","Airtime"), ("title", "Live Dj")],dj_live)
@ -142,15 +142,17 @@ def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point,
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))
master_dj = rewrite_metadata([("artist","Airtime"), ("title", "Master Dj")],master_dj) master_dj = rewrite_metadata([("artist","Airtime"), ("title", "Master Dj")],master_dj)
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)))
dj_live = rewrite_metadata([("artist","Airtime"), ("title", "Live Dj")],dj_live) dj_live = rewrite_metadata([("artist","Airtime"), ("title", "Live Dj")],dj_live)
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

@ -1,8 +1,8 @@
[loggers] [loggers]
keys=root,fetch,push,recorder,message_h,notify keys=root,fetch,push,recorder,message_h
[handlers] [handlers]
keys=pypo,recorder,message_h,notify keys=pypo,recorder,message_h
[formatters] [formatters]
keys=simpleFormatter keys=simpleFormatter
@ -35,18 +35,6 @@ handlers=message_h
qualname=message_h qualname=message_h
propagate=0 propagate=0
[logger_notify]
level=DEBUG
handlers=notify
qualname=notify
propagate=0
[handler_notify]
class=logging.handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=("/var/log/airtime/pypo/notify.log", 'a', 1000000, 5,)
[handler_pypo] [handler_pypo]
class=logging.handlers.RotatingFileHandler class=logging.handlers.RotatingFileHandler
level=DEBUG level=DEBUG

View file

@ -0,0 +1,28 @@
[loggers]
keys=root,notify
[handlers]
keys=notify
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=notify
[logger_notify]
level=DEBUG
handlers=notify
qualname=notify
propagate=0
[handler_notify]
class=logging.handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=("/var/log/airtime/pypo/notify.log", 'a', 1000000, 5,)
[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
datefmt=

View file

@ -2,13 +2,15 @@
Python part of radio playout (pypo) Python part of radio playout (pypo)
""" """
from optparse import OptionParser
from datetime import datetime
import telnetlib
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
@ -50,6 +52,15 @@ parser.add_option("-c", "--check", help="Check the cached schedule and exit", de
#need to wait for Python 2.7 for this.. #need to wait for Python 2.7 for this..
#logging.captureWarnings(True) #logging.captureWarnings(True)
# configure logging
try:
logging.config.fileConfig("logging.cfg")
logger = logging.getLogger()
LogWriter.override_std_err(logger)
except Exception, e:
print "Couldn't configure logging"
sys.exit()
def configure_locale(): def configure_locale():
logger.debug("Before %s", locale.nl_langinfo(locale.CODESET)) logger.debug("Before %s", locale.nl_langinfo(locale.CODESET))
current_locale = locale.getlocale() current_locale = locale.getlocale()
@ -61,8 +72,8 @@ def configure_locale():
if default_locale[1] is None: if default_locale[1] is None:
logger.debug("No default locale exists. Let's try loading from /etc/default/locale") logger.debug("No default locale exists. Let's try loading from /etc/default/locale")
if os.path.exists("/etc/default/locale"): if os.path.exists("/etc/default/locale"):
config = ConfigObj('/etc/default/locale') locale_config = ConfigObj('/etc/default/locale')
lang = config.get('LANG') lang = locale_config.get('LANG')
new_locale = lang new_locale = lang
else: else:
logger.error("/etc/default/locale could not be found! Please run 'sudo update-locale' from command-line.") logger.error("/etc/default/locale could not be found! Please run 'sudo update-locale' from command-line.")
@ -84,14 +95,6 @@ def configure_locale():
logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % current_locale_encoding) logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % current_locale_encoding)
sys.exit(1) sys.exit(1)
# configure logging
try:
logging.config.fileConfig("logging.cfg")
logger = logging.getLogger()
LogWriter.override_std_err(logger)
except Exception, e:
print "Couldn't configure logging"
sys.exit()
configure_locale() configure_locale()
@ -113,55 +116,57 @@ 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')
sys.exit(0) sys.exit(0)
def liquidsoap_running_test(telnet_lock, host, port, logger):
logger.debug("Checking to see if Liquidsoap is running")
success = True
try:
telnet_lock.acquire()
tn = telnetlib.Telnet(host, port)
msg = "version\n"
tn.write(msg)
tn.write("exit\n")
logger.info("Liquidsoap version %s", tn.read_all())
except Exception, e:
logger.error(str(e))
success = False
finally:
telnet_lock.release()
return success
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger()
logger.info('###########################################') logger.info('###########################################')
logger.info('# *** pypo *** #') logger.info('# *** pypo *** #')
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" % str(time.tzname))
logger.info("UTC time: %s" % str(datetime.utcnow()))
signal.signal(signal.SIGINT, keyboardInterruptHandler) signal.signal(signal.SIGINT, keyboardInterruptHandler)
# initialize # initialize
g = Global() g = Global()
while not g.selfcheck(): time.sleep(5) while not g.selfcheck():
time.sleep(5)
logger = logging.getLogger() telnet_lock = Lock()
ls_host = config['ls_host']
ls_port = config['ls_port']
while not liquidsoap_running_test(telnet_lock, ls_host, ls_port, logger):
logger.warning("Liquidsoap not started yet. Sleeping one second and trying again")
time.sleep(1)
if options.test: if options.test:
g.test_api() g.test_api()
@ -174,7 +179,7 @@ if __name__ == '__main__':
recorder_q = Queue() recorder_q = Queue()
pypoPush_q = Queue() pypoPush_q = Queue()
telnet_lock = Lock()
""" """
This queue is shared between pypo-fetch and pypo-file, where pypo-file This queue is shared between pypo-fetch and pypo-file, where pypo-file
@ -204,7 +209,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 +218,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
@ -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,10 +5,10 @@ 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
import stat
from std_err_override import LogWriter from std_err_override import LogWriter
@ -70,12 +70,19 @@ 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)
except:
#make file world readable
os.chmod(dst, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
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)
def get_highest_priority_media_item(self, schedule): def get_highest_priority_media_item(self, schedule):
""" """

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
@ -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)
@ -59,8 +46,8 @@ parser.add_option("-y", "--source-status", help="source connection stauts", meta
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
# configure logging # configure logging
logging.config.fileConfig("logging.cfg") logging.config.fileConfig("notify_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
@ -73,30 +71,36 @@ class PypoPush(Thread):
#We get to the following lines only if a schedule was received. #We get to the following lines only if a schedule was received.
liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap()
current_event_chain = self.get_current_chain(chains) tnow = datetime.utcnow()
current_event_chain, original_chain = self.get_current_chain(chains, tnow)
if len(current_event_chain) > 0 and len(liquidsoap_queue_approx) == 0: if len(current_event_chain) > 0 and len(liquidsoap_queue_approx) == 0:
#Something is scheduled but Liquidsoap is not playing anything! #Something is scheduled but Liquidsoap is not playing anything!
#Need to schedule it immediately..this might happen if Liquidsoap crashed. #Need to schedule it immediately..this might happen if Liquidsoap crashed.
chains.remove(current_event_chain) try:
chains.remove(original_chain)
except ValueError, e:
self.logger.error(str(e))
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)
self.handle_new_media_schedule(media_schedule, liquidsoap_queue_approx, media_chain) self.handle_new_media_schedule(media_schedule, liquidsoap_queue_approx, media_chain)
next_media_item_chain = self.get_next_schedule_chain(chains) next_media_item_chain = self.get_next_schedule_chain(chains, tnow)
self.logger.debug("Next schedule chain: %s", next_media_item_chain) self.logger.debug("Next schedule chain: %s", next_media_item_chain)
if next_media_item_chain is not None: if next_media_item_chain is not None:
try:
chains.remove(next_media_item_chain) chains.remove(next_media_item_chain)
tnow = datetime.utcnow() except ValueError, e:
self.logger.error(str(e))
chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
time_until_next_play = self.date_interval_to_seconds(chain_start - tnow) time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow())
self.logger.debug("Blocking %s seconds until show start", time_until_next_play) self.logger.debug("Blocking %s seconds until show start", time_until_next_play)
else: else:
self.logger.debug("Blocking indefinitely since no show scheduled") self.logger.debug("Blocking indefinitely since no show scheduled")
@ -105,15 +109,16 @@ class PypoPush(Thread):
#We only get here when a new chain of tracks are ready to be played. #We only get here when a new chain of tracks are ready to be played.
self.push_to_liquidsoap(next_media_item_chain) self.push_to_liquidsoap(next_media_item_chain)
next_media_item_chain = self.get_next_schedule_chain(chains) next_media_item_chain = self.get_next_schedule_chain(chains, datetime.utcnow())
if next_media_item_chain is not None: if next_media_item_chain is not None:
tnow = datetime.utcnow()
chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
time_until_next_play = self.date_interval_to_seconds(chain_start - tnow) time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow())
self.logger.debug("Blocking %s seconds until show start", time_until_next_play) self.logger.debug("Blocking %s seconds until show start", time_until_next_play)
else: else:
self.logger.debug("Blocking indefinitely since no show scheduled next") self.logger.debug("Blocking indefinitely since no show scheduled next")
time_until_next_play = None time_until_next_play = None
except Exception, e:
self.logger.error(str(e))
if loops % heartbeat_period == 0: if loops % heartbeat_period == 0:
self.logger.info("heartbeat") self.logger.info("heartbeat")
@ -170,7 +175,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 +206,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 +215,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
@ -270,10 +271,24 @@ class PypoPush(Thread):
original_cue_in_td = timedelta(seconds=float(link['cue_in'])) original_cue_in_td = timedelta(seconds=float(link['cue_in']))
link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec
"""
Returns two chains, original chain and current_chain. current_chain is a subset of
original_chain but can also be equal to original chain.
def get_current_chain(self, chains): We return original chain because the user of this function may want to clean
tnow = datetime.utcnow() up the input 'chains' list
chain, original = get_current_chain(chains)
and
chains.remove(chain) can throw a ValueError exception
but
chains.remove(original) won't
"""
def get_current_chain(self, chains, tnow):
current_chain = [] current_chain = []
original_chain = None
for chain in chains: for chain in chains:
iteration = 0 iteration = 0
@ -284,10 +299,11 @@ class PypoPush(Thread):
self.logger.debug("tnow %s, chain_start %s", tnow, link_start) self.logger.debug("tnow %s, chain_start %s", tnow, link_start)
if link_start <= tnow and tnow < link_end: if link_start <= tnow and tnow < link_end:
current_chain = chain[iteration:] current_chain = chain[iteration:]
original_chain = chain
break break
iteration += 1 iteration += 1
return current_chain return current_chain, original_chain
""" """
The purpose of this function is to take a look at the last received schedule from The purpose of this function is to take a look at the last received schedule from
@ -295,10 +311,9 @@ class PypoPush(Thread):
of media_items where the end time of media_item 'n' is the start time of media_item of media_items where the end time of media_item 'n' is the start time of media_item
'n+1' 'n+1'
""" """
def get_next_schedule_chain(self, chains): def get_next_schedule_chain(self, chains, tnow):
#all media_items are now divided into chains. Let's find the one that #all media_items are now divided into chains. Let's find the one that
#starts closest in the future. #starts closest in the future.
tnow = datetime.utcnow()
closest_start = None closest_start = None
closest_chain = None closest_chain = None
for chain in chains: for chain in chains:
@ -312,14 +327,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":
"""
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) 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 +476,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
@ -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)