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

@ -548,10 +548,11 @@ class Application_Model_Schedule {
$data["media"][$kick_start]['end'] = $kick_start; $data["media"][$kick_start]['end'] = $kick_start;
$data["media"][$kick_start]['event_type'] = "kick_out"; $data["media"][$kick_start]['event_type'] = "kick_out";
$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,20 +264,22 @@ var AIRTIME = (function(AIRTIME) {
}; };
mod.fnDeleteSelectedItems = function() { mod.fnDeleteSelectedItems = function() {
var aData = AIRTIME.library.getSelectedData(), if (confirm('Are you sure you want to delete the selected item(s)?')) {
item, var aData = AIRTIME.library.getSelectedData(),
temp, item,
aMedia = []; temp,
aMedia = [];
//process selected files/playlists.
for (item in aData) { //process selected files/playlists.
temp = aData[item]; for (item in aData) {
if (temp !== null && temp.hasOwnProperty('id') ) { temp = aData[item];
aMedia.push({"id": temp.id, "type": temp.ftype}); if (temp !== null && temp.hasOwnProperty('id') ) {
} aMedia.push({"id": temp.id, "type": temp.ftype});
} }
}
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

@ -1,6 +1,6 @@
/** /**
* *
* Full Calendar callback methods. * Full Calendar callback methods.
* *
*/ */
@ -9,12 +9,12 @@ function scheduleRefetchEvents(json) {
alert("The show instance doesn't exist anymore!"); alert("The show instance doesn't exist anymore!");
} }
if(json.show_id) { if(json.show_id) {
var dialog_id = parseInt($("#add_show_id").val(), 10); var dialog_id = parseInt($("#add_show_id").val(), 10);
//if you've deleted the show you are currently editing, close the add show dialog. //if you've deleted the show you are currently editing, close the add show dialog.
if (dialog_id === json.show_id) { if (dialog_id === json.show_id) {
$("#add-show-close").click(); $("#add-show-close").click();
} }
} }
$("#schedule_calendar").fullCalendar( 'refetchEvents' ); $("#schedule_calendar").fullCalendar( 'refetchEvents' );
} }
@ -31,9 +31,9 @@ function openAddShowForm() {
$("#schedule_calendar").fullCalendar('render'); $("#schedule_calendar").fullCalendar('render');
} }
$("#schedule-show-what").show(0, function(){ $("#schedule-show-what").show(0, function(){
$add_show_name = $("#add_show_name"); $add_show_name = $("#add_show_name");
$add_show_name.focus(); $add_show_name.focus();
$add_show_name.select(); $add_show_name.select();
}); });
} }
} }
@ -59,16 +59,16 @@ function removeAddShowButton(){
} }
function makeTimeStamp(date){ function makeTimeStamp(date){
var sy, sm, sd, h, m, s, timestamp; var sy, sm, sd, h, m, s, timestamp;
sy = date.getFullYear(); sy = date.getFullYear();
sm = date.getMonth() + 1; sm = date.getMonth() + 1;
sd = date.getDate(); sd = date.getDate();
h = date.getHours(); h = date.getHours();
m = date.getMinutes(); m = date.getMinutes();
s = date.getSeconds(); s = date.getSeconds();
timestamp = sy+"-"+ pad(sm, 2) +"-"+ pad(sd, 2) +" "+ pad(h, 2) +":"+ pad(m, 2) +":"+ pad(s, 2); timestamp = sy+"-"+ pad(sm, 2) +"-"+ pad(sd, 2) +" "+ pad(h, 2) +":"+ pad(m, 2) +":"+ pad(s, 2);
return timestamp; return timestamp;
} }
function dayClick(date, allDay, jsEvent, view){ function dayClick(date, allDay, jsEvent, view){
@ -76,9 +76,9 @@ function dayClick(date, allDay, jsEvent, view){
// Hence, if the user if DJ then it won't open anything. // Hence, if the user if DJ then it won't open anything.
if(userType == "A" || userType == "P"){ if(userType == "A" || userType == "P"){
var now, today, selected, chosenDate, chosenTime; var now, today, selected, chosenDate, chosenTime;
now = adjustDateToServerDate(new Date(), serverTimezoneOffset); now = adjustDateToServerDate(new Date(), serverTimezoneOffset);
if(view.name === "month") { if(view.name === "month") {
today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
selected = new Date(date.getFullYear(), date.getMonth(), date.getDate()); selected = new Date(date.getFullYear(), date.getMonth(), date.getDate());
@ -87,17 +87,17 @@ function dayClick(date, allDay, jsEvent, view){
today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes()); today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes());
selected = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()); selected = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes());
} }
if(selected >= today) { if(selected >= today) {
var addShow = $('.add-button'); var addShow = $('.add-button');
//remove the +show button if it exists. //remove the +show button if it exists.
if(addShow.length == 1){ if(addShow.length == 1){
var span = $(addShow).parent(); var span = $(addShow).parent();
$(span).prev().remove(); $(span).prev().remove();
$(span).remove(); $(span).remove();
} }
// get current duration value on the form // get current duration value on the form
var duration_string = $.trim($("#add_show_duration").val()); var duration_string = $.trim($("#add_show_duration").val());
var duration_info = duration_string.split(" "); var duration_info = duration_string.split(" ");
@ -111,7 +111,7 @@ function dayClick(date, allDay, jsEvent, view){
} }
// duration in milisec // duration in milisec
var duration = (duration_h * 60 * 60 * 1000) + (duration_m * 60 * 1000); var duration = (duration_h * 60 * 60 * 1000) + (duration_m * 60 * 1000);
var startTime_string, startTime var startTime_string, startTime
// get start time value on the form // get start time value on the form
if(view.name === "month") { if(view.name === "month") {
@ -124,13 +124,13 @@ function dayClick(date, allDay, jsEvent, view){
startTime_string = pad(selected.getHours(),2)+":"+pad(selected.getMinutes(),2) startTime_string = pad(selected.getHours(),2)+":"+pad(selected.getMinutes(),2)
startTime = 0 startTime = 0
} }
// calculate endDateTime // calculate endDateTime
var endDateTime = new Date(selected.getTime() + startTime + duration); var endDateTime = new Date(selected.getTime() + startTime + duration);
chosenDate = selected.getFullYear() + '-' + pad(selected.getMonth()+1,2) + '-' + pad(selected.getDate(),2); chosenDate = selected.getFullYear() + '-' + pad(selected.getMonth()+1,2) + '-' + pad(selected.getDate(),2);
var endDateFormat = endDateTime.getFullYear() + '-' + pad(endDateTime.getMonth()+1,2) + '-' + pad(endDateTime.getDate(),2); var endDateFormat = endDateTime.getFullYear() + '-' + pad(endDateTime.getMonth()+1,2) + '-' + pad(endDateTime.getDate(),2);
$("#add_show_start_date").val(chosenDate); $("#add_show_start_date").val(chosenDate);
$("#add_show_end_date_no_repeat").val(endDateFormat); $("#add_show_end_date_no_repeat").val(endDateFormat);
$("#add_show_end_date").val(endDateFormat); $("#add_show_end_date").val(endDateFormat);
@ -140,7 +140,7 @@ function dayClick(date, allDay, jsEvent, view){
$("#add_show_end_time").val(endTimeString) $("#add_show_end_time").val(endTimeString)
} }
$("#schedule-show-when").show(); $("#schedule-show-when").show();
openAddShowForm(); openAddShowForm();
} }
} }
@ -173,10 +173,10 @@ function viewDisplay( view ) {
.fullCalendar('destroy') .fullCalendar('destroy')
.fullCalendar(opt) .fullCalendar(opt)
.fullCalendar( 'gotoDate', date ); .fullCalendar( 'gotoDate', date );
//save slotMin value to db //save slotMin value to db
var url = '/Schedule/set-time-interval/format/json'; var url = '/Schedule/set-time-interval/format/json';
$.post(url, {timeInterval: slotMin}); $.post(url, {timeInterval: slotMin});
}); });
var topLeft = $(view.element).find("table.fc-agenda-days > thead th:first"); var topLeft = $(view.element).find("table.fc-agenda-days > thead th:first");
@ -192,146 +192,128 @@ function viewDisplay( view ) {
} }
if(($("#add-show-form").length == 1) && ($("#add-show-form").css('display')=='none') && ($('.fc-header-left > span').length == 5)) { if(($("#add-show-form").length == 1) && ($("#add-show-form").css('display')=='none') && ($('.fc-header-left > span').length == 5)) {
//userType is defined in bootstrap.php, and is derived from the currently logged in user. //userType is defined in bootstrap.php, and is derived from the currently logged in user.
if(userType == "A" || userType == "P"){ if(userType == "A" || userType == "P"){
makeAddShowButton(); makeAddShowButton();
} }
} }
//save view name to db //save view name to db
var url = '/Schedule/set-time-scale/format/json'; var url = '/Schedule/set-time-scale/format/json';
$.post(url, {timeScale: view.name}); $.post(url, {timeScale: view.name});
} }
function eventRender(event, element, view) { function eventRender(event, element, view) {
$(element).data("event", event); $(element).data("event", event);
//only put progress bar on shows that aren't being recorded. //only put progress bar on shows that aren't being recorded.
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 0) { if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 0) {
var div = $('<div/>'); var div = $('<div/>');
div div
.height('5px') .height('5px')
.width('95%') .width('95%')
.css('margin-top', '1px') .css('margin-top', '1px')
.css('margin-left', 'auto') .css('margin-left', 'auto')
.css('margin-right', 'auto') .css('margin-right', 'auto')
.progressbar({ .progressbar({
value: event.percent value: event.percent
}); });
$(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>');
} 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>');
}
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon recording"></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>');
} 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>');
}
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon recording"></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) {
$(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon rebroadcast"></span>'); $(element).find(".fc-event-time").before('<span id="'+event.id+'" class="small-icon rebroadcast"></span>');
} }
if(view.name === 'month' && event.rebroadcast === 1) { if(view.name === 'month' && event.rebroadcast === 1) {
$(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 ) {
$(element).find(".small-icon").live('mouseover',function(){ $(element).find(".small-icon").live('mouseover',function(){
addQtipToSCIcons($(this)); addQtipToSCIcons($(this));
}); });
} }
function eventDrop(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) { function eventDrop(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {
var url; var url;
url = '/Schedule/move-show/format/json'; url = '/Schedule/move-show/format/json';
$.post(url, $.post(url,
{day: dayDelta, min: minuteDelta, showInstanceId: event.id}, {day: dayDelta, min: minuteDelta, showInstanceId: event.id},
function(json){ function(json){
if(json.show_error == true){ if(json.show_error == true){
alertShowErrorAndReload(); alertShowErrorAndReload();
} }
if(json.error) { if(json.error) {
alert(json.error); alert(json.error);
revertFunc(); revertFunc();
} }
}); });
} }
function eventResize( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ) { function eventResize( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ) {
var url; var url;
url = '/Schedule/resize-show/format/json'; url = '/Schedule/resize-show/format/json';
$.post(url, $.post(url,
{day: dayDelta, min: minuteDelta, showId: event.showId}, {day: dayDelta, min: minuteDelta, showId: event.showId},
function(json){ function(json){
if(json.show_error == true){ if(json.show_error == true){
alertShowErrorAndReload(); alertShowErrorAndReload();
} }
if(json.error) { if(json.error) {
alert(json.error); alert(json.error);
revertFunc(); revertFunc();
} }
scheduleRefetchEvents(json); scheduleRefetchEvents(json);
}); });
} }
function getFullCalendarEvents(start, end, callback) { function getFullCalendarEvents(start, end, callback) {
var url, start_date, end_date; var url, start_date, end_date;
start_date = makeTimeStamp(start); start_date = makeTimeStamp(start);
end_date = makeTimeStamp(end); end_date = makeTimeStamp(end);
url = '/Schedule/event-feed'; url = '/Schedule/event-feed';
var d = new Date(); var d = new Date();
$.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){ $.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){
callback(json.events); callback(json.events);
}); });
} }
function checkSCUploadStatus(){ function checkSCUploadStatus(){
@ -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

@ -1,20 +1,20 @@
var AIRTIME = (function(AIRTIME){ var AIRTIME = (function(AIRTIME){
var mod; var mod;
if (AIRTIME.schedule === undefined) { if (AIRTIME.schedule === undefined) {
AIRTIME.schedule = {}; AIRTIME.schedule = {};
} }
mod = AIRTIME.schedule; mod = AIRTIME.schedule;
return AIRTIME; return AIRTIME;
}(AIRTIME || {})); }(AIRTIME || {}));
var serverTimezoneOffset = 0; var serverTimezoneOffset = 0;
function closeDialog(event, ui) { function closeDialog(event, ui) {
$("#schedule_calendar").fullCalendar( 'refetchEvents' ); $("#schedule_calendar").fullCalendar( 'refetchEvents' );
$(this).remove(); $(this).remove();
} }
function checkShowLength(json) { function checkShowLength(json) {
@ -36,11 +36,11 @@ function confirmCancelShow(show_instance_id){
if (confirm('Cancel Current Show?')) { if (confirm('Cancel Current Show?')) {
var url = "/Schedule/cancel-current-show"; var url = "/Schedule/cancel-current-show";
$.ajax({ $.ajax({
url: url, url: url,
data: {format: "json", id: show_instance_id}, data: {format: "json", id: show_instance_id},
success: function(data){ success: function(data){
scheduleRefetchEvents(data); scheduleRefetchEvents(data);
} }
}); });
} }
} }
@ -49,11 +49,11 @@ function confirmCancelRecordedShow(show_instance_id){
if (confirm('Stop recording current show?')) { if (confirm('Stop recording current show?')) {
var url = "/Schedule/cancel-current-show"; var url = "/Schedule/cancel-current-show";
$.ajax({ $.ajax({
url: url, url: url,
data: {format: "json", id: show_instance_id}, data: {format: "json", id: show_instance_id},
success: function(data){ success: function(data){
scheduleRefetchEvents(data); scheduleRefetchEvents(data);
} }
}); });
} }
} }
@ -72,21 +72,21 @@ function uploadToSoundCloud(show_instance_id){
if(span.length == 0){ if(span.length == 0){
span = $(window.triggerElement).find(".soundcloud"); span = $(window.triggerElement).find(".soundcloud");
span.removeClass("soundcloud") span.removeClass("soundcloud")
.addClass("progress"); .addClass("progress");
}else{ }else{
span.removeClass("recording") span.removeClass("recording")
.addClass("progress"); .addClass("progress");
} }
} }
function checkCalendarSCUploadStatus(){ function checkCalendarSCUploadStatus(){
var url = '/Library/get-upload-to-soundcloud-status', var url = '/Library/get-upload-to-soundcloud-status',
span, span,
id; id;
function checkSCUploadStatusCallback(json) { function checkSCUploadStatusCallback(json) {
if (json.sc_id > 0) { if (json.sc_id > 0) {
span.removeClass("progress").addClass("soundcloud"); span.removeClass("progress").addClass("soundcloud");
@ -108,113 +108,111 @@ function checkCalendarSCUploadStatus(){
} }
function findViewportDimensions() { function findViewportDimensions() {
var viewportwidth, var viewportwidth,
viewportheight; viewportheight;
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use // the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight // window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') { if (typeof window.innerWidth != 'undefined') {
viewportwidth = window.innerWidth, viewportheight = window.innerHeight; viewportwidth = window.innerWidth, viewportheight = window.innerHeight;
} }
// IE6 in standards compliant mode (i.e. with a valid doctype as the first // IE6 in standards compliant mode (i.e. with a valid doctype as the first
// line in the document) // line in the document)
else if (typeof document.documentElement != 'undefined' else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth != 'undefined' && typeof document.documentElement.clientWidth != 'undefined'
&& document.documentElement.clientWidth != 0) { && document.documentElement.clientWidth != 0) {
viewportwidth = document.documentElement.clientWidth; viewportwidth = document.documentElement.clientWidth;
viewportheight = document.documentElement.clientHeight; viewportheight = document.documentElement.clientHeight;
} }
// older versions of IE // older versions of IE
else { else {
viewportwidth = document.getElementsByTagName('body')[0].clientWidth; viewportwidth = document.getElementsByTagName('body')[0].clientWidth;
viewportheight = document.getElementsByTagName('body')[0].clientHeight; viewportheight = document.getElementsByTagName('body')[0].clientHeight;
} }
return { return {
width: viewportwidth, width: viewportwidth,
height: viewportheight height: viewportheight
}; };
} }
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), width = Math.floor(viewport.width * 0.96),
width = Math.floor(viewport.width * 0.96), fnServer = AIRTIME.showbuilder.fnServerData,
fnServer = AIRTIME.showbuilder.fnServerData, //subtract padding in pixels
//subtract padding in pixels widgetWidth = width - 60,
widgetWidth = width - 60, libWidth = Math.floor(widgetWidth * 0.5),
libWidth = Math.floor(widgetWidth * 0.5), builderWidth = Math.floor(widgetWidth * 0.5);
builderWidth = Math.floor(widgetWidth * 0.5);
dialog.find("#library_content")
dialog.find("#library_content") .height(height - 115)
.height(height - 115) .width(libWidth);
.width(libWidth);
dialog.find("#show_builder")
dialog.find("#show_builder") .height(height - 115)
.height(height - 115) .width(builderWidth);
.width(builderWidth);
dialog.dialog({
dialog.dialog({ autoOpen: false,
autoOpen: false, title: json.title,
title: json.title, width: width,
width: width, height: height,
height: height, resizable: false,
resizable: false, draggable: true,
draggable: true, modal: true,
modal: true, close: closeDialog,
close: closeDialog, buttons: {"Ok": function() {
buttons: {"Ok": function() { $(this).dialog("close");
dialog.remove(); }}
$("#schedule_calendar").fullCalendar( 'refetchEvents' ); });
}}
}); //set the start end times so the builder datatables knows its time range.
fnServer.start = json.start;
//set the start end times so the builder datatables knows its time range. fnServer.end = json.end;
fnServer.start = json.start;
fnServer.end = json.end; AIRTIME.library.libraryInit();
AIRTIME.showbuilder.builderDataTable();
AIRTIME.library.libraryInit();
AIRTIME.showbuilder.builderDataTable(); //set max heights of datatables.
dialog.find(".lib-content .dataTables_scrolling")
//set max heights of datatables. .css("max-height", height - 90 - 155);
dialog.find(".lib-content .dataTables_scrolling")
.css("max-height", height - 90 - 155); dialog.find(".sb-content .dataTables_scrolling")
.css("max-height", height - 90 - 60);
dialog.find(".sb-content .dataTables_scrolling")
.css("max-height", height - 90 - 60); dialog.dialog('open');
dialog.dialog('open');
} }
function buildContentDialog (json){ function buildContentDialog (json){
var dialog = $(json.dialog), var dialog = $(json.dialog),
viewport = findViewportDimensions(), viewport = findViewportDimensions(),
height = viewport.height * 2/3, height = viewport.height * 2/3,
width = viewport.width * 4/5; width = viewport.width * 4/5;
if (json.show_error == true){ if (json.show_error == true){
alertShowErrorAndReload(); alertShowErrorAndReload();
} }
dialog.find("#show_progressbar").progressbar({ dialog.find("#show_progressbar").progressbar({
value: json.percentFilled value: json.percentFilled
}); });
dialog.dialog({ dialog.dialog({
autoOpen: false, autoOpen: false,
title: "Contents of Show \"" + json.showTitle + "\"", title: "Contents of Show \"" + json.showTitle + "\"",
width: width, width: width,
height: height, height: height,
modal: true, modal: true,
close: closeDialog, close: closeDialog,
buttons: {"Ok": function() { buttons: {"Ok": function() {
dialog.remove(); dialog.remove();
}} }}
}); });
dialog.dialog('open'); dialog.dialog('open');
} }
/** /**
@ -279,55 +277,55 @@ function alertShowErrorAndReload(){
} }
$(document).ready(function() { $(document).ready(function() {
$.ajax({ url: "/Api/calendar-init/format/json", dataType:"json", success:createFullCalendar $.ajax({ url: "/Api/calendar-init/format/json", dataType:"json", success:createFullCalendar
, error:function(jqXHR, textStatus, errorThrown){}}); , error:function(jqXHR, textStatus, errorThrown){}});
setInterval(checkCalendarSCUploadStatus, 5000); setInterval(checkCalendarSCUploadStatus, 5000);
$.contextMenu({ $.contextMenu({
selector: 'div.fc-event', selector: 'div.fc-event',
trigger: "left", trigger: "left",
ignoreRightClick: true, ignoreRightClick: true,
build: function($el, e) { build: function($el, e) {
var data, var data,
items, items,
callback; callback;
data = $el.data("event"); data = $el.data("event");
function processMenuItems(oItems) { function processMenuItems(oItems) {
//define a schedule callback. //define a schedule callback.
if (oItems.schedule !== undefined) { if (oItems.schedule !== undefined) {
callback = function() { callback = function() {
$.post(oItems.schedule.url, {format: "json", id: data.id}, function(json){ $.post(oItems.schedule.url, {format: "json", id: data.id}, function(json){
buildScheduleDialog(json); buildScheduleDialog(json);
}); });
}; };
oItems.schedule.callback = callback; oItems.schedule.callback = callback;
} }
//define a clear callback. //define a clear callback.
if (oItems.clear !== undefined) { if (oItems.clear !== undefined) {
callback = function() { callback = function() {
if (confirm("Remove all content?")) { if (confirm("Remove all content?")) {
$.post(oItems.clear.url, {format: "json", id: data.id}, function(json){ $.post(oItems.clear.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json); scheduleRefetchEvents(json);
}); });
} }
}; };
oItems.clear.callback = callback; oItems.clear.callback = callback;
} }
//define an edit callback. //define an edit callback.
if (oItems.edit !== undefined) { if (oItems.edit !== undefined) {
if(oItems.edit.items !== undefined){ if(oItems.edit.items !== undefined){
var edit = oItems.edit.items; var edit = oItems.edit.items;
//edit a single instance //edit a single instance
callback = function() { callback = function() {
@ -344,55 +342,55 @@ $(document).ready(function() {
}); });
}; };
edit.all.callback = callback; edit.all.callback = callback;
}else{ }else{
callback = function() { callback = function() {
$.get(oItems.edit.url, {format: "json", id: data.id, type: oItems.edit._type}, function(json){ $.get(oItems.edit.url, {format: "json", id: data.id, type: oItems.edit._type}, function(json){
beginEditShow(json); beginEditShow(json);
}); });
}; };
oItems.edit.callback = callback; oItems.edit.callback = callback;
} }
} }
//define a content callback. //define a content callback.
if (oItems.content !== undefined) { if (oItems.content !== undefined) {
callback = function() { callback = function() {
$.get(oItems.content.url, {format: "json", id: data.id}, function(json){ $.get(oItems.content.url, {format: "json", id: data.id}, function(json){
buildContentDialog(json); buildContentDialog(json);
}); });
}; };
oItems.content.callback = callback; oItems.content.callback = callback;
} }
//define a soundcloud upload callback. //define a soundcloud upload callback.
if (oItems.soundcloud_upload !== undefined) { if (oItems.soundcloud_upload !== undefined) {
callback = function() { callback = function() {
uploadToSoundCloud(data.id); uploadToSoundCloud(data.id);
}; };
oItems.soundcloud_upload.callback = callback; oItems.soundcloud_upload.callback = callback;
} }
//define a view on soundcloud callback. //define a view on soundcloud callback.
if (oItems.soundcloud_view !== undefined) { if (oItems.soundcloud_view !== undefined) {
callback = function() { callback = function() {
window.open(oItems.soundcloud_view.url); window.open(oItems.soundcloud_view.url);
}; };
oItems.soundcloud_view.callback = callback; oItems.soundcloud_view.callback = callback;
} }
//define a cancel recorded show callback. //define a cancel recorded show callback.
if (oItems.cancel_recorded !== undefined) { if (oItems.cancel_recorded !== undefined) {
callback = function() { callback = function() {
confirmCancelRecordedShow(data.id); confirmCancelRecordedShow(data.id);
}; };
oItems.cancel_recorded.callback = callback; oItems.cancel_recorded.callback = callback;
} }
//define a view recorded callback. //define a view recorded callback.
if (oItems.view_recorded !== undefined) { if (oItems.view_recorded !== undefined) {
callback = function() { callback = function() {
@ -400,71 +398,71 @@ $(document).ready(function() {
}; };
oItems.view_recorded.callback = callback; oItems.view_recorded.callback = callback;
} }
//define a cancel callback. //define a cancel callback.
if (oItems.cancel !== undefined) { if (oItems.cancel !== undefined) {
callback = function() { callback = function() {
confirmCancelShow(data.id); confirmCancelShow(data.id);
}; };
oItems.cancel.callback = callback; oItems.cancel.callback = callback;
} }
//define a delete callback. //define a delete callback.
if (oItems.del !== undefined) { if (oItems.del !== undefined) {
//repeating show multiple delete options //repeating show multiple delete options
if (oItems.del.items !== undefined) { if (oItems.del.items !== undefined) {
var del = oItems.del.items; var del = oItems.del.items;
//delete a single instance //delete a single instance
callback = function() { callback = function() {
$.post(del.single.url, {format: "json", id: data.id}, function(json){ $.post(del.single.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json); scheduleRefetchEvents(json);
}); });
}; };
del.single.callback = callback; del.single.callback = callback;
//delete this instance and all following instances. //delete this instance and all following instances.
callback = function() { callback = function() {
$.post(del.following.url, {format: "json", id: data.id}, function(json){ $.post(del.following.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json); scheduleRefetchEvents(json);
}); });
}; };
del.following.callback = callback; del.following.callback = callback;
} }
//single show //single show
else { else {
callback = function() { callback = function() {
$.post(oItems.del.url, {format: "json", id: data.id}, function(json){ $.post(oItems.del.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json); scheduleRefetchEvents(json);
}); });
}; };
oItems.del.callback = callback; oItems.del.callback = callback;
} }
} }
items = oItems; items = oItems;
} }
$.ajax({ $.ajax({
url: "/schedule/make-context-menu", url: "/schedule/make-context-menu",
type: "GET", type: "GET",
data: {id : data.id, format: "json"}, data: {id : data.id, format: "json"},
dataType: "json", dataType: "json",
async: false, async: false,
success: function(json){ success: function(json){
processMenuItems(json.items); processMenuItems(json.items);
} }
}); });
return { return {
items: items, items: items,
determinePosition : function($menu, x, y) { determinePosition : function($menu, x, y) {
$menu.css('display', 'block') $menu.css('display', 'block')
.position({ my: "left top", at: "right top", of: this, offset: "-20 10", collision: "fit"}) .position({ my: "left top", at: "right top", of: this, offset: "-20 10", collision: "fit"})
.css('display', 'none'); .css('display', 'none');
} }
}; };
} }

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 = {};
@ -127,7 +130,7 @@ var AIRTIME = (function(AIRTIME){
}; };
mod.selectCursor = function($el) { mod.selectCursor = function($el) {
$el.addClass(CURSOR_SELECTED_CLASS); $el.addClass(CURSOR_SELECTED_CLASS);
mod.checkToolBarIcons(); mod.checkToolBarIcons();
}; };
@ -208,11 +211,23 @@ 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();
}; };
mod.fnAdd = function(aMediaIds, aSchedIds) { mod.fnAdd = function(aMediaIds, aSchedIds) {
mod.disableUI(); mod.disableUI();
@ -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);
@ -413,7 +430,7 @@ var AIRTIME = (function(AIRTIME){
$nRow.addClass(sClass); $nRow.addClass(sClass);
}; };
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();
@ -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.
@ -590,7 +609,7 @@ var AIRTIME = (function(AIRTIME){
if (aData.currentShow === true) { if (aData.currentShow === true) {
$nRow.addClass("sb-current-show"); $nRow.addClass("sb-current-show");
} }
//call the context menu so we can prevent the event from propagating. //call the context menu so we can prevent the event from propagating.
$nRow.find('td:gt(1)').click(function(e){ $nRow.find('td:gt(1)').click(function(e){
@ -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 );
} }
}; };
@ -83,6 +83,34 @@ AIRTIME = (function(AIRTIME) {
oTable.fnDraw(); oTable.fnDraw();
} }
} }
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.
@ -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();
@ -220,7 +222,9 @@ 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(){
@ -277,4 +284,4 @@ AIRTIME = (function(AIRTIME) {
} (AIRTIME || {})); } (AIRTIME || {}));
$(document).ready(AIRTIME.builderMain.onReady); $(document).ready(AIRTIME.builderMain.onReady);
$(window).resize(AIRTIME.builderMain.onResize); $(window).resize(AIRTIME.builderMain.onResize);

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

@ -318,8 +318,13 @@ 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,27 +130,29 @@ 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)
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))
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,40 +52,6 @@ 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)
def configure_locale():
logger.debug("Before %s", locale.nl_langinfo(locale.CODESET))
current_locale = locale.getlocale()
if current_locale[1] is None:
logger.debug("No locale currently set. Attempting to get default locale.")
default_locale = locale.getdefaultlocale()
if default_locale[1] is None:
logger.debug("No default locale exists. Let's try loading from /etc/default/locale")
if os.path.exists("/etc/default/locale"):
config = ConfigObj('/etc/default/locale')
lang = config.get('LANG')
new_locale = lang
else:
logger.error("/etc/default/locale could not be found! Please run 'sudo update-locale' from command-line.")
sys.exit(1)
else:
new_locale = default_locale
logger.info("New locale set to: %s", locale.setlocale(locale.LC_ALL, new_locale))
reload(sys)
sys.setdefaultencoding("UTF-8")
current_locale_encoding = locale.getlocale()[1].lower()
logger.debug("sys default encoding %s", sys.getdefaultencoding())
logger.debug("After %s", locale.nl_langinfo(locale.CODESET))
if current_locale_encoding not in ['utf-8', 'utf8']:
logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % current_locale_encoding)
sys.exit(1)
# configure logging # configure logging
try: try:
logging.config.fileConfig("logging.cfg") logging.config.fileConfig("logging.cfg")
@ -92,7 +60,42 @@ try:
except Exception, e: except Exception, e:
print "Couldn't configure logging" print "Couldn't configure logging"
sys.exit() sys.exit()
def configure_locale():
logger.debug("Before %s", locale.nl_langinfo(locale.CODESET))
current_locale = locale.getlocale()
if current_locale[1] is None:
logger.debug("No locale currently set. Attempting to get default locale.")
default_locale = locale.getdefaultlocale()
if default_locale[1] is None:
logger.debug("No default locale exists. Let's try loading from /etc/default/locale")
if os.path.exists("/etc/default/locale"):
locale_config = ConfigObj('/etc/default/locale')
lang = locale_config.get('LANG')
new_locale = lang
else:
logger.error("/etc/default/locale could not be found! Please run 'sudo update-locale' from command-line.")
sys.exit(1)
else:
new_locale = default_locale
logger.info("New locale set to: %s", locale.setlocale(locale.LC_ALL, new_locale))
reload(sys)
sys.setdefaultencoding("UTF-8")
current_locale_encoding = locale.getlocale()[1].lower()
logger.debug("sys default encoding %s", sys.getdefaultencoding())
logger.debug("After %s", locale.nl_langinfo(locale.CODESET))
if current_locale_encoding not in ['utf-8', 'utf8']:
logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % current_locale_encoding)
sys.exit(1)
configure_locale() configure_locale()
# loading config file # loading config file
@ -105,63 +108,65 @@ except Exception, e:
class Global: class Global:
def __init__(self): def __init__(self):
self.api_client = api_client.api_client_factory(config) self.api_client = api_client.api_client_factory(config)
def selfcheck(self): def selfcheck(self):
self.api_client = api_client.api_client_factory(config) self.api_client = api_client.api_client_factory(config)
return self.api_client.is_server_compatible() return self.api_client.is_server_compatible()
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()
@ -173,9 +178,9 @@ if __name__ == '__main__':
pypoFetch_q = Queue() pypoFetch_q = Queue()
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
is the receiver. Pypo-fetch will send every schedule it gets to pypo-file is the receiver. Pypo-fetch will send every schedule it gets to pypo-file
@ -183,19 +188,19 @@ if __name__ == '__main__':
priority, and will retrieve it. priority, and will retrieve it.
""" """
media_q = Queue() media_q = Queue()
pmh = PypoMessageHandler(pypoFetch_q, recorder_q) pmh = PypoMessageHandler(pypoFetch_q, recorder_q)
pmh.daemon = True pmh.daemon = True
pmh.start() pmh.start()
pfile = PypoFile(media_q) pfile = PypoFile(media_q)
pfile.daemon = True pfile.daemon = True
pfile.start() pfile.start()
pf = PypoFetch(pypoFetch_q, pypoPush_q, media_q, telnet_lock) pf = PypoFetch(pypoFetch_q, pypoPush_q, media_q, telnet_lock)
pf.daemon = True pf.daemon = True
pf.start() pf.start()
pp = PypoPush(pypoPush_q, telnet_lock) pp = PypoPush(pypoPush_q, telnet_lock)
pp.daemon = True pp.daemon = True
pp.start() pp.start()
@ -204,23 +209,12 @@ 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()
#pp.join() #pp.join()
pf.join() pf.join()
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,12 +45,12 @@ 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
self.logger = logging.getLogger(); self.logger = logging.getLogger();
self.cache_dir = os.path.join(config["cache_dir"], "scheduler") self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
self.logger.debug("Cache dir %s", self.cache_dir) self.logger.debug("Cache dir %s", self.cache_dir)
@ -63,24 +65,24 @@ class PypoFetch(Thread):
os.makedirs(dir) os.makedirs(dir)
except Exception, e: except Exception, e:
pass pass
self.schedule_data = [] self.schedule_data = []
self.logger.info("PypoFetch: init complete") self.logger.info("PypoFetch: init complete")
""" """
Handle a message from RabbitMQ, put it into our yucky global var. Handle a message from RabbitMQ, put it into our yucky global var.
Hopefully there is a better way to do this. Hopefully there is a better way to do this.
""" """
def handle_message(self, message): def handle_message(self, message):
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...")
@ -100,12 +102,12 @@ class PypoFetch(Thread):
elif command == 'disconnect_source': elif command == 'disconnect_source':
self.logger.info("disconnect_on_source show command received...") self.logger.info("disconnect_on_source show command received...")
self.disconnect_source(self.logger, self.telnet_lock, m['sourcename']) self.disconnect_source(self.logger, self.telnet_lock, m['sourcename'])
# 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)
@ -115,7 +117,7 @@ class PypoFetch(Thread):
self.logger.error('Exception: %s', e) self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top) self.logger.error("traceback: %s", top)
self.logger.error("Exception in handling Message Handler message: %s", e) self.logger.error("Exception in handling Message Handler message: %s", e)
@staticmethod @staticmethod
def disconnect_source(logger, lock, sourcename): def disconnect_source(logger, lock, sourcename):
logger.debug('Disconnecting source: %s', sourcename) logger.debug('Disconnecting source: %s', sourcename)
@ -124,7 +126,7 @@ class PypoFetch(Thread):
command += "master_harbor.kick\n" command += "master_harbor.kick\n"
elif(sourcename == "live_dj"): elif(sourcename == "live_dj"):
command += "live_dj_harbor.kick\n" command += "live_dj_harbor.kick\n"
lock.acquire() lock.acquire()
try: try:
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
@ -135,7 +137,7 @@ class PypoFetch(Thread):
logger.error(str(e)) logger.error(str(e))
finally: finally:
lock.release() lock.release()
@staticmethod @staticmethod
def switch_source(logger, lock, sourcename, status): def switch_source(logger, lock, sourcename, status):
logger.debug('Switching source: %s to "%s" status', sourcename, status) logger.debug('Switching source: %s to "%s" status', sourcename, status)
@ -146,12 +148,12 @@ class PypoFetch(Thread):
command += "live_dj_" command += "live_dj_"
elif(sourcename == "scheduled_play"): elif(sourcename == "scheduled_play"):
command += "scheduled_play_" command += "scheduled_play_"
if(status == "on"): if(status == "on"):
command += "start\n" command += "start\n"
else: else:
command += "stop\n" command += "stop\n"
lock.acquire() lock.acquire()
try: try:
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
@ -162,7 +164,7 @@ class PypoFetch(Thread):
logger.error(str(e)) logger.error(str(e))
finally: finally:
lock.release() lock.release()
""" """
grabs some information that are needed to be set on bootstrap time grabs some information that are needed to be set on bootstrap time
and configures them and configures them
@ -171,16 +173,16 @@ 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'])
self.update_liquidsoap_station_name(info['station_name']) self.update_liquidsoap_station_name(info['station_name'])
self.update_liquidsoap_transition_fade(info['transition_fade']) self.update_liquidsoap_transition_fade(info['transition_fade'])
def write_liquidsoap_config(self, setting): def write_liquidsoap_config(self, setting):
fh = open('/etc/airtime/liquidsoap.cfg', 'w') fh = open('/etc/airtime/liquidsoap.cfg', 'w')
self.logger.info("Rewriting liquidsoap.cfg...") self.logger.info("Rewriting liquidsoap.cfg...")
@ -197,7 +199,7 @@ class PypoFetch(Thread):
if temp == "": if temp == "":
temp = "0" temp = "0"
buffer_str += temp buffer_str += temp
buffer_str += "\n" buffer_str += "\n"
fh.write(api_client.encode_to(buffer_str)) fh.write(api_client.encode_to(buffer_str))
fh.write("log_file = \"/var/log/airtime/pypo-liquidsoap/<script>.log\"\n"); fh.write("log_file = \"/var/log/airtime/pypo-liquidsoap/<script>.log\"\n");
@ -206,18 +208,18 @@ class PypoFetch(Thread):
# we could just restart liquidsoap but it take more time somehow. # we could just restart liquidsoap but it take more time somehow.
self.logger.info("Restarting pypo...") self.logger.info("Restarting pypo...")
sys.exit(0) sys.exit(0)
def regenerateLiquidsoapConf(self, setting): def regenerateLiquidsoapConf(self, setting):
existing = {} existing = {}
# create a temp file # create a temp file
setting = sorted(setting.items()) setting = sorted(setting.items())
try: try:
fh = open('/etc/airtime/liquidsoap.cfg', 'r') fh = open('/etc/airtime/liquidsoap.cfg', 'r')
except IOError, e: except IOError, e:
#file does not exist #file does not exist
self.write_liquidsoap_config(setting) self.write_liquidsoap_config(setting)
self.logger.info("Reading existing config...") self.logger.info("Reading existing config...")
# read existing conf file and build dict # read existing conf file and build dict
while True: while True:
@ -226,9 +228,9 @@ class PypoFetch(Thread):
# empty line means EOF # empty line means EOF
if not line: if not line:
break break
line = line.strip() line = line.strip()
if line[0] == "#": if line[0] == "#":
continue continue
@ -243,7 +245,7 @@ class PypoFetch(Thread):
value = '' value = ''
existing[key] = value existing[key] = value
fh.close() fh.close()
# dict flag for any change in cofig # dict flag for any change in cofig
change = {} change = {}
# this flag is to detect disable -> disable change # this flag is to detect disable -> disable change
@ -251,7 +253,7 @@ class PypoFetch(Thread):
state_change_restart = {} state_change_restart = {}
#restart flag #restart flag
restart = False restart = False
self.logger.info("Looking for changes...") self.logger.info("Looking for changes...")
# look for changes # look for changes
for k, s in setting: for k, s in setting:
@ -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
@ -284,10 +286,10 @@ class PypoFetch(Thread):
if not (s[u'value'] == existing[s[u'keyname']]): if not (s[u'value'] == existing[s[u'keyname']]):
self.logger.info("Keyname: %s, Curent value: %s, New Value: %s", s[u'keyname'], existing[s[u'keyname']], s[u'value']) self.logger.info("Keyname: %s, Curent value: %s, New Value: %s", s[u'keyname'], existing[s[u'keyname']], s[u'value'])
change[stream] = True change[stream] = True
# set flag change for sound_device alway True # set flag change for sound_device alway True
self.logger.info("Change:%s, State_Change:%s...", change, state_change_restart) self.logger.info("Change:%s, State_Change:%s...", change, state_change_restart)
for k, v in state_change_restart.items(): for k, v in state_change_restart.items():
if k == "sound_device" and v: if k == "sound_device" and v:
restart = True restart = True
@ -306,7 +308,7 @@ class PypoFetch(Thread):
updates the status of liquidsoap connection to the streaming server updates the status of liquidsoap connection to the streaming server
This fucntion updates the bootup time variable in liquidsoap script This fucntion updates the bootup time variable in liquidsoap script
""" """
self.telnet_lock.acquire() self.telnet_lock.acquire()
try: try:
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
@ -314,25 +316,25 @@ 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')
output = tn.read_all() output = tn.read_all()
except Exception, e: except Exception, e:
self.logger.error(str(e)) self.logger.error(str(e))
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
output_list = output.split("\r\n") output_list = output.split("\r\n")
stream_info = output_list[2] stream_info = output_list[2]
# streamin info is in the form of: # streamin info is in the form of:
# eg. s1:true,2:true,3:false # eg. s1:true,2:true,3:false
streams = stream_info.split(",") streams = stream_info.split(",")
self.logger.info(streams) self.logger.info(streams)
fake_time = current_time + 1 fake_time = current_time + 1
for s in streams: for s in streams:
info = s.split(':') info = s.split(':')
@ -340,7 +342,7 @@ class PypoFetch(Thread):
status = info[1] status = info[1]
if(status == "true"): if(status == "true"):
self.api_client.notify_liquidsoap_status("OK", stream_id, str(fake_time)) self.api_client.notify_liquidsoap_status("OK", stream_id, str(fake_time))
def update_liquidsoap_stream_format(self, stream_format): def update_liquidsoap_stream_format(self, stream_format):
# Push stream metadata to liquidsoap # Push stream metadata to liquidsoap
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!! # TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
@ -356,7 +358,7 @@ class PypoFetch(Thread):
self.logger.error("Exception %s", e) self.logger.error("Exception %s", e)
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
def update_liquidsoap_transition_fade(self, fade): def update_liquidsoap_transition_fade(self, fade):
# Push stream metadata to liquidsoap # Push stream metadata to liquidsoap
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!! # TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
@ -372,14 +374,14 @@ class PypoFetch(Thread):
self.logger.error("Exception %s", e) self.logger.error("Exception %s", e)
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
def update_liquidsoap_station_name(self, station_name): def update_liquidsoap_station_name(self, station_name):
# Push stream metadata to liquidsoap # Push stream metadata to liquidsoap
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!! # TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
try: try:
self.logger.info(LS_HOST) self.logger.info(LS_HOST)
self.logger.info(LS_PORT) self.logger.info(LS_PORT)
self.telnet_lock.acquire() self.telnet_lock.acquire()
try: try:
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
@ -387,7 +389,7 @@ class PypoFetch(Thread):
self.logger.info(command) self.logger.info(command)
tn.write(command) tn.write(command)
tn.write('exit\n') tn.write('exit\n')
tn.read_all() tn.read_all()
except Exception, e: except Exception, e:
self.logger.error(str(e)) self.logger.error(str(e))
finally: finally:
@ -403,7 +405,7 @@ class PypoFetch(Thread):
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss) to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
- runs the cleanup routine, to get rid of unused cached files - runs the cleanup routine, to get rid of unused cached files
""" """
def process_schedule(self, schedule_data): def process_schedule(self, schedule_data):
self.last_update_schedule_timestamp = time.time() self.last_update_schedule_timestamp = time.time()
self.logger.debug(schedule_data) self.logger.debug(schedule_data)
media = schedule_data["media"] media = schedule_data["media"]
@ -411,7 +413,7 @@ class PypoFetch(Thread):
# Download all the media and put playlists in liquidsoap "annotate" format # Download all the media and put playlists in liquidsoap "annotate" format
try: try:
""" """
Make sure cache_dir exists Make sure cache_dir exists
""" """
@ -420,15 +422,16 @@ class PypoFetch(Thread):
os.makedirs(download_dir) os.makedirs(download_dir)
except Exception, e: except Exception, e:
pass pass
for key in media: for key in media:
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))
except Exception, e: self.logger.error("%s", e) except Exception, e: self.logger.error("%s", e)
@ -440,7 +443,7 @@ class PypoFetch(Thread):
# cleanup # cleanup
try: self.cache_cleanup(media) try: self.cache_cleanup(media)
except Exception, e: self.logger.error("%s", e) except Exception, e: self.logger.error("%s", e)
def cache_cleanup(self, media): def cache_cleanup(self, media):
""" """
Get list of all files in the cache dir and remove them if they aren't being used anymore. Get list of all files in the cache dir and remove them if they aren't being used anymore.
@ -449,18 +452,18 @@ class PypoFetch(Thread):
""" """
cached_file_set = set(os.listdir(self.cache_dir)) cached_file_set = set(os.listdir(self.cache_dir))
scheduled_file_set = set() scheduled_file_set = set()
for mkey in media: for mkey in media:
media_item = media[mkey] media_item = media[mkey]
fileExt = os.path.splitext(media_item['uri'])[1] fileExt = os.path.splitext(media_item['uri'])[1]
scheduled_file_set.add(media_item["id"] + fileExt) scheduled_file_set.add(media_item["id"] + fileExt)
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
@ -471,10 +474,10 @@ class PypoFetch(Thread):
self.process_schedule(self.schedule_data) self.process_schedule(self.schedule_data)
self.set_bootstrap_variables() self.set_bootstrap_variables()
loops = 1 loops = 1
while True: while True:
self.logger.info("Loop #%s", loops) self.logger.info("Loop #%s", loops)
try: try:
""" """
our simple_queue.get() requires a timeout, in which case we our simple_queue.get() requires a timeout, in which case we
fetch the Airtime schedule manually. It is important to fetch fetch the Airtime schedule manually. It is important to fetch
@ -486,18 +489,20 @@ 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()
self.logger.error('Exception: %s', e) self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top) self.logger.error("traceback: %s", top)
success, self.schedule_data = self.api_client.get_schedule() success, self.schedule_data = self.api_client.get_schedule()
if success: if success:
self.process_schedule(self.schedule_data) self.process_schedule(self.schedule_data)

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
@ -33,21 +33,21 @@ except Exception, e:
class PypoFile(Thread): class PypoFile(Thread):
def __init__(self, schedule_queue): def __init__(self, schedule_queue):
Thread.__init__(self) Thread.__init__(self)
self.logger = logging.getLogger() self.logger = logging.getLogger()
self.media_queue = schedule_queue self.media_queue = schedule_queue
self.media = None self.media = None
self.cache_dir = os.path.join(config["cache_dir"], "scheduler") self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
def copy_file(self, media_item): def copy_file(self, media_item):
""" """
Copy media_item from local library directory to local cache directory. Copy media_item from local library directory to local cache directory.
""" """
src = media_item['uri'] src = media_item['uri']
dst = media_item['dst'] dst = media_item['dst']
try: try:
src_size = os.path.getsize(src) src_size = os.path.getsize(src)
except Exception, e: except Exception, e:
@ -59,24 +59,31 @@ class PypoFile(Thread):
dst_size = os.path.getsize(dst) dst_size = os.path.getsize(dst)
except Exception, e: except Exception, e:
dst_exists = False dst_exists = False
do_copy = False do_copy = False
if dst_exists: if dst_exists:
if src_size != dst_size: if src_size != dst_size:
do_copy = True do_copy = True
else: else:
do_copy = True do_copy = True
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):
""" """
Get highest priority media_item in the queue. Currently the highest Get highest priority media_item in the queue. Currently the highest
@ -84,17 +91,17 @@ class PypoFile(Thread):
""" """
if schedule is None or len(schedule) == 0: if schedule is None or len(schedule) == 0:
return None return None
sorted_keys = sorted(schedule.keys()) sorted_keys = sorted(schedule.keys())
if len(sorted_keys) == 0: if len(sorted_keys) == 0:
return None return None
highest_priority = sorted_keys[0] highest_priority = sorted_keys[0]
media_item = schedule[highest_priority] media_item = schedule[highest_priority]
self.logger.debug("Highest priority item: %s" % highest_priority) self.logger.debug("Highest priority item: %s" % highest_priority)
""" """
Remove this media_item from the dictionary. On the next iteration Remove this media_item from the dictionary. On the next iteration
(from the main function) we won't consider it for prioritization (from the main function) we won't consider it for prioritization
@ -103,11 +110,11 @@ class PypoFile(Thread):
again. In this situation, the worst possible case is that we try to again. In this situation, the worst possible case is that we try to
copy the file again and realize we already have it (thus aborting the copy). copy the file again and realize we already have it (thus aborting the copy).
""" """
del schedule[highest_priority] del schedule[highest_priority]
return media_item return media_item
def main(self): def main(self):
while True: while True:
try: try:
@ -128,7 +135,7 @@ class PypoFile(Thread):
self.media = self.media_queue.get_nowait() self.media = self.media_queue.get_nowait()
except Empty, e: except Empty, e:
pass pass
media_item = self.get_highest_priority_media_item(self.media) media_item = self.get_highest_priority_media_item(self.media)
if media_item is not None: if media_item is not None:
@ -139,7 +146,7 @@ class PypoFile(Thread):
self.logger.error(str(e)) self.logger.error(str(e))
self.logger.error(top) self.logger.error(top)
raise raise
def run(self): def run(self):
""" """
Entry point of the thread Entry point of the thread

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
@ -39,7 +38,7 @@ class PypoMessageHandler(Thread):
self.logger = logging.getLogger('message_h') self.logger = logging.getLogger('message_h')
self.pypo_queue = pq self.pypo_queue = pq
self.recorder_queue = rq self.recorder_queue = rq
def init_rabbit_mq(self): def init_rabbit_mq(self):
self.logger.info("Initializing RabbitMQ stuff") self.logger.info("Initializing RabbitMQ stuff")
try: try:
@ -51,21 +50,21 @@ class PypoMessageHandler(Thread):
except Exception, e: except Exception, e:
self.logger.error(e) self.logger.error(e)
return False return False
return True return True
""" """
Handle a message from RabbitMQ, put it into our yucky global var. Handle a message from RabbitMQ, put it into our yucky global var.
Hopefully there is a better way to do this. Hopefully there is a better way to do this.
""" """
def handle_message(self, message): def handle_message(self, message):
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)
if command == 'update_schedule': if command == 'update_schedule':
self.logger.info("Updating schdule...") self.logger.info("Updating schdule...")
self.pypo_queue.put(message) self.pypo_queue.put(message)
@ -121,15 +120,13 @@ class PypoMessageHandler(Thread):
while loop and eat all the CPU while loop and eat all the CPU
""" """
time.sleep(5) time.sleep(5)
""" """
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..
@ -69,54 +56,54 @@ LogWriter.override_std_err(logger)
# loading config file # loading config file
try: try:
config = ConfigObj('/etc/airtime/pypo.cfg') config = ConfigObj('/etc/airtime/pypo.cfg')
except Exception, e: except Exception, e:
logger.error('Error loading config file: %s', e) logger.error('Error loading config file: %s', e)
sys.exit() sys.exit()
class Notify: class Notify:
def __init__(self): def __init__(self):
self.api_client = api_client.api_client_factory(config) self.api_client = api_client.api_client_factory(config)
def notify_media_start_playing(self, data, media_id): def notify_media_start_playing(self, data, media_id):
logger = logging.getLogger("notify") logger = logging.getLogger("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):
logger = logging.getLogger("notify") logger = logging.getLogger("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")
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
print '#########################################' print '#########################################'
print '# *** pypo *** #' print '# *** pypo *** #'
print '# pypo notification gateway #' print '# pypo notification gateway #'
print '#########################################' print '#########################################'
# initialize # initialize
logger = logging.getLogger("notify") logger = logging.getLogger("notify")
if options.error and options.stream_id: if options.error and options.stream_id:
@ -141,11 +128,11 @@ if __name__ == '__main__':
if not options.data: if not options.data:
print "NOTICE: 'data' command-line argument not given." print "NOTICE: 'data' command-line argument not given."
sys.exit() sys.exit()
if not options.media_id: if not options.media_id:
print "NOTICE: 'media_id' command-line argument not given." print "NOTICE: 'media_id' command-line argument not given."
sys.exit() sys.exit()
try: try:
n = Notify() n = Notify()
n.notify_media_start_playing(options.data, options.media_id) n.notify_media_start_playing(options.data, options.media_id)

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
@ -51,52 +49,58 @@ class PypoPush(Thread):
self.pushed_objects = {} self.pushed_objects = {}
self.logger = logging.getLogger('push') self.logger = logging.getLogger('push')
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
time_until_next_play = None time_until_next_play = None
chains = None chains = None
while True: while True:
try: try:
if time_until_next_play is None: if time_until_next_play is None:
media_schedule = self.queue.get(block=True) media_schedule = self.queue.get(block=True)
else: else:
media_schedule = self.queue.get(block=True, timeout=time_until_next_play) media_schedule = self.queue.get(block=True, timeout=time_until_next_play)
chains = self.get_all_chains(media_schedule) chains = self.get_all_chains(media_schedule)
#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:
chains.remove(next_media_item_chain) try:
tnow = datetime.utcnow() chains.remove(next_media_item_chain)
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")
@ -104,17 +108,18 @@ class PypoPush(Thread):
except Empty, e: except Empty, e:
#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")
loops = 0 loops = 0
@ -127,7 +132,7 @@ class PypoPush(Thread):
try: try:
self.telnet_lock.acquire() self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
msg = 'queue.queue\n' msg = 'queue.queue\n'
tn.write(msg) tn.write(msg)
response = tn.read_until("\r\n").strip(" \r\n") response = tn.read_until("\r\n").strip(" \r\n")
@ -138,14 +143,14 @@ class PypoPush(Thread):
response = [] response = []
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
liquidsoap_queue_approx = [] liquidsoap_queue_approx = []
if len(response) > 0: if len(response) > 0:
items_in_queue = response.split(" ") items_in_queue = response.split(" ")
self.logger.debug("items_in_queue: %s", items_in_queue) self.logger.debug("items_in_queue: %s", items_in_queue)
for item in items_in_queue: for item in items_in_queue:
if item in self.pushed_objects: if item in self.pushed_objects:
liquidsoap_queue_approx.append(self.pushed_objects[item]) liquidsoap_queue_approx.append(self.pushed_objects[item])
@ -158,9 +163,9 @@ class PypoPush(Thread):
self.clear_liquidsoap_queue() self.clear_liquidsoap_queue()
liquidsoap_queue_approx = [] liquidsoap_queue_approx = []
break break
return liquidsoap_queue_approx return liquidsoap_queue_approx
def handle_new_media_schedule(self, media_schedule, liquidsoap_queue_approx, media_chain): def handle_new_media_schedule(self, media_schedule, liquidsoap_queue_approx, media_chain):
""" """
This function's purpose is to gracefully handle situations where This function's purpose is to gracefully handle situations where
@ -169,27 +174,27 @@ class PypoPush(Thread):
call other functions that will connect to Liquidsoap and alter its call other functions that will connect to Liquidsoap and alter its
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
#and remove these items. #and remove these items.
self.logger.debug("Change in link %s of current chain", problem_at_iteration) self.logger.debug("Change in link %s of current chain", problem_at_iteration)
self.remove_from_liquidsoap_queue(problem_at_iteration, liquidsoap_queue_approx[problem_at_iteration:]) self.remove_from_liquidsoap_queue(problem_at_iteration, liquidsoap_queue_approx[problem_at_iteration:])
if problem_at_iteration is None and len(media_chain) > len(liquidsoap_queue_approx): if problem_at_iteration is None and len(media_chain) > len(liquidsoap_queue_approx):
self.logger.debug("New schedule has longer current chain.") self.logger.debug("New schedule has longer current chain.")
problem_at_iteration = len(liquidsoap_queue_approx) problem_at_iteration = len(liquidsoap_queue_approx)
if problem_at_iteration is not None: if problem_at_iteration is not None:
self.logger.debug("Change in chain at link %s", problem_at_iteration) self.logger.debug("Change in chain at link %s", problem_at_iteration)
chain_to_push = media_chain[problem_at_iteration:] chain_to_push = media_chain[problem_at_iteration:]
if len(chain_to_push) > 0: if len(chain_to_push) > 0:
self.modify_cue_point(chain_to_push[0]) self.modify_cue_point(chain_to_push[0])
self.push_to_liquidsoap(chain_to_push) self.push_to_liquidsoap(chain_to_push)
""" """
Compare whats in the liquidsoap_queue to the new schedule we just Compare whats in the liquidsoap_queue to the new schedule we just
received in media_schedule. This function only iterates over liquidsoap_queue_approx received in media_schedule. This function only iterates over liquidsoap_queue_approx
@ -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,33 +215,30 @@ 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
def get_all_chains(self, media_schedule): def get_all_chains(self, media_schedule):
chains = [] chains = []
current_chain = [] current_chain = []
sorted_keys = sorted(media_schedule.keys()) sorted_keys = sorted(media_schedule.keys())
for mkey in sorted_keys: for mkey in sorted_keys:
media_item = media_schedule[mkey] media_item = media_schedule[mkey]
if media_item['type'] == "event": if media_item['type'] == "event":
@ -251,54 +252,68 @@ class PypoPush(Thread):
#Start a new one instead #Start a new one instead
chains.append(current_chain) chains.append(current_chain)
current_chain = [media_item] current_chain = [media_item]
if len(current_chain) > 0: if len(current_chain) > 0:
chains.append(current_chain) chains.append(current_chain)
return chains return chains
def modify_cue_point(self, link): def modify_cue_point(self, link):
tnow = datetime.utcnow() tnow = datetime.utcnow()
link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S") link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S")
diff_td = tnow - link_start diff_td = tnow - link_start
diff_sec = self.date_interval_to_seconds(diff_td) diff_sec = self.date_interval_to_seconds(diff_td)
if diff_sec > 0: if diff_sec > 0:
self.logger.debug("media item was supposed to start %s ago. Preparing to start..", diff_sec) self.logger.debug("media item was supposed to start %s ago. Preparing to start..", diff_sec)
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.
We return original chain because the user of this function may want to clean
up the input 'chains' list
def get_current_chain(self, chains): chain, original = get_current_chain(chains)
tnow = datetime.utcnow()
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
for link in chain: for link in chain:
link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S") link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S")
link_end = datetime.strptime(link['end'], "%Y-%m-%d-%H-%M-%S") link_end = datetime.strptime(link['end'], "%Y-%m-%d-%H-%M-%S")
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
pypo-fetch and return the next chain of media_items. A chain is defined as a sequence pypo-fetch and return the next chain of media_items. A chain is defined as a sequence
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:
@ -307,19 +322,32 @@ class PypoPush(Thread):
if (closest_start == None or chain_start < closest_start) and chain_start > tnow: if (closest_start == None or chain_start < closest_start) and chain_start > tnow:
closest_start = chain_start closest_start = chain_start
closest_chain = chain closest_chain = chain
return closest_chain return closest_chain
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")
@ -327,27 +355,27 @@ class PypoPush(Thread):
PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off") PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off")
except Exception, e: except Exception, e:
self.logger.error('Pypo Push Exception: %s', e) self.logger.error('Pypo Push Exception: %s', e)
def clear_liquidsoap_queue(self): def clear_liquidsoap_queue(self):
self.logger.debug("Clearing Liquidsoap queue") self.logger.debug("Clearing Liquidsoap queue")
try: try:
self.telnet_lock.acquire() self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
msg = "source.skip\n" msg = "source.skip\n"
tn.write(msg) tn.write(msg)
tn.write("exit\n") tn.write("exit\n")
tn.read_all() tn.read_all()
except Exception, e: except Exception, e:
self.logger.error(str(e)) self.logger.error(str(e))
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
def remove_from_liquidsoap_queue(self, problem_at_iteration, liquidsoap_queue_approx): def remove_from_liquidsoap_queue(self, problem_at_iteration, liquidsoap_queue_approx):
try: try:
self.telnet_lock.acquire() self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
if problem_at_iteration == 0: if problem_at_iteration == 0:
msg = "source.skip\n" msg = "source.skip\n"
self.logger.debug(msg) self.logger.debug(msg)
@ -355,13 +383,13 @@ class PypoPush(Thread):
else: else:
# Remove things in reverse order. # Remove things in reverse order.
queue_copy = liquidsoap_queue_approx[::-1] queue_copy = liquidsoap_queue_approx[::-1]
for queue_item in queue_copy: for queue_item in queue_copy:
msg = "queue.remove %s\n" % queue_item['queue_id'] msg = "queue.remove %s\n" % queue_item['queue_id']
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
response = tn.read_until("\r\n").strip("\r\n") response = tn.read_until("\r\n").strip("\r\n")
if "No such request in my queue" in response: if "No such request in my queue" in response:
""" """
Cannot remove because Liquidsoap started playing the item. Need Cannot remove because Liquidsoap started playing the item. Need
@ -370,33 +398,33 @@ class PypoPush(Thread):
msg = "source.skip\n" msg = "source.skip\n"
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
msg = "queue.queue\n" msg = "queue.queue\n"
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
tn.write("exit\n") tn.write("exit\n")
self.logger.debug(tn.read_all()) self.logger.debug(tn.read_all())
except Exception, e: except Exception, e:
self.logger.error(str(e)) self.logger.error(str(e))
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
def sleep_until_start(self, media_item): def sleep_until_start(self, media_item):
""" """
The purpose of this function is to look at the difference between The purpose of this function is to look at the difference between
"now" and when the media_item starts, and sleep for that period of time. "now" and when the media_item starts, and sleep for that period of time.
After waking from sleep, this function returns. After waking from sleep, this function returns.
""" """
mi_start = media_item['start'][0:19] mi_start = media_item['start'][0:19]
#strptime returns struct_time in local time #strptime returns struct_time in local time
epoch_start = calendar.timegm(time.strptime(mi_start, '%Y-%m-%d-%H-%M-%S')) epoch_start = calendar.timegm(time.strptime(mi_start, '%Y-%m-%d-%H-%M-%S'))
#Return the time as a floating point number expressed in seconds since the epoch, in UTC. #Return the time as a floating point number expressed in seconds since the epoch, in UTC.
epoch_now = time.time() epoch_now = time.time()
self.logger.debug("Epoch start: %s" % epoch_start) self.logger.debug("Epoch start: %s" % epoch_start)
self.logger.debug("Epoch now: %s" % epoch_now) self.logger.debug("Epoch now: %s" % epoch_now)
@ -417,43 +445,43 @@ class PypoPush(Thread):
try: try:
self.telnet_lock.acquire() self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
#tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8')) #tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8'))
annotation = self.create_liquidsoap_annotation(media_item) annotation = self.create_liquidsoap_annotation(media_item)
msg = 'queue.push %s\n' % annotation.encode('utf-8') msg = 'queue.push %s\n' % annotation.encode('utf-8')
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
queue_id = tn.read_until("\r\n").strip("\r\n") queue_id = tn.read_until("\r\n").strip("\r\n")
#remember the media_item's queue id which we may use #remember the media_item's queue id which we may use
#later if we need to remove it from the queue. #later if we need to remove it from the queue.
media_item['queue_id'] = queue_id media_item['queue_id'] = queue_id
#add media_item to the end of our queue #add media_item to the end of our queue
self.pushed_objects[queue_id] = media_item self.pushed_objects[queue_id] = media_item
show_name = media_item['show_name'] show_name = media_item['show_name']
msg = 'vars.show_name %s\n' % show_name.encode('utf-8') msg = 'vars.show_name %s\n' % show_name.encode('utf-8')
tn.write(msg) tn.write(msg)
self.logger.debug(msg) self.logger.debug(msg)
tn.write("exit\n") tn.write("exit\n")
self.logger.debug(tn.read_all()) self.logger.debug(tn.read_all())
except Exception, e: except Exception, e:
self.logger.error(str(e)) self.logger.error(str(e))
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
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()
except Exception, e: except Exception, e:
import traceback import traceback
top = traceback.format_exc() top = traceback.format_exc()
self.logger.error('Pypo Push Exception: %s', top) self.logger.error('Pypo Push Exception: %s', top)

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':
@ -190,10 +189,10 @@ class Recorder(Thread):
else: else:
self.process_recorder_schedule(msg) self.process_recorder_schedule(msg)
self.loops = 0 self.loops = 0
if self.shows_to_record: if self.shows_to_record:
self.start_record() self.start_record()
def process_recorder_schedule(self, m): def process_recorder_schedule(self, m):
self.logger.info("Parsing recording show schedules...") self.logger.info("Parsing recording show schedules...")
temp_shows_to_record = {} temp_shows_to_record = {}
@ -217,7 +216,7 @@ class Recorder(Thread):
delta = next_show - tnow delta = next_show - tnow
s = '%s.%s' % (delta.seconds, delta.microseconds) s = '%s.%s' % (delta.seconds, delta.microseconds)
out = float(s) out = float(s)
if out < 5: if out < 5:
self.logger.debug("Shows %s", self.shows_to_record) self.logger.debug("Shows %s", self.shows_to_record)
self.logger.debug("Next show %s", next_show) self.logger.debug("Next show %s", next_show)
@ -231,26 +230,26 @@ class Recorder(Thread):
if delta < 5: if delta < 5:
self.logger.debug("sleeping %s seconds until show", delta) self.logger.debug("sleeping %s seconds until show", delta)
time.sleep(delta) time.sleep(delta)
sorted_show_keys = sorted(self.shows_to_record.keys()) sorted_show_keys = sorted(self.shows_to_record.keys())
start_time = sorted_show_keys[0] start_time = sorted_show_keys[0]
show_length = self.shows_to_record[start_time][0] show_length = self.shows_to_record[start_time][0]
show_instance = self.shows_to_record[start_time][1] show_instance = self.shows_to_record[start_time][1]
show_name = self.shows_to_record[start_time][2] show_name = self.shows_to_record[start_time][2]
server_timezone = self.shows_to_record[start_time][3] server_timezone = self.shows_to_record[start_time][3]
T = pytz.timezone(server_timezone) T = pytz.timezone(server_timezone)
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)
@ -273,12 +272,12 @@ class Recorder(Thread):
self.logger.info("Bootstrap recorder schedule received: %s", temp) self.logger.info("Bootstrap recorder schedule received: %s", temp)
except Exception, e: except Exception, e:
self.logger.error(e) self.logger.error(e)
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:
self.logger.info("heartbeat") self.logger.info("heartbeat")
@ -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)