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());
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";
Zend_Validate::setDefaultNamespaces("Zend");

View File

@ -41,7 +41,7 @@ class LibraryController extends Zend_Controller_Action
$user = new Application_Model_User($userInfo->id);
//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));

View File

@ -100,6 +100,12 @@ class LoginController extends Zend_Controller_Action
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()) {
$this->_redirect('login');
}

View File

@ -29,5 +29,13 @@ class Application_Form_PasswordRestore extends Zend_Form
'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();
// get all the files on this dir
$sql = "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";
$files = $con->query($sql)->fetchAll();
// 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);
}
}
$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)";
$affected = $con->exec($sql);
// set RemovedFlag to true
if ($userAddedWatchedDir) {
@ -399,14 +391,16 @@ class Application_Model_MusicDir {
* otherwise, it will set "Exists" flag to 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)."/";
if($real_path != "/"){
$p_dir = $real_path;
}
$dir = Application_Model_MusicDir::getDirByPath($p_dir);
if($dir == NULL){
return array("code"=>1,"error"=>"'$p_dir' doesn't exist in the watched list.");
}else{
if (is_null($dir)) {
return array("code"=>1, "error"=>"'$p_dir' doesn't exist in the watched list.");
} else {
$dir->remove($userAddedWatchedDir);
$data = array();
$data["directory"] = $p_dir;

View File

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

View File

@ -550,8 +550,9 @@ class Application_Model_Schedule {
$data["media"][$kick_start]['type'] = "event";
if($kick_time !== $switch_off_time){
$data["media"][$switch_start]['start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time);
$data["media"][$switch_start]['end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time);
$switch_start = 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]['type'] = "event";
}

View File

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

View File

@ -327,7 +327,10 @@ class Application_Model_StoredFile {
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);
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);
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.");
}
else {
} else {
//Martin K.: changed to rename: Much less load + quicker since this is an atomic operation
$r = @rename($audio_file, $audio_stor);

View File

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

View File

@ -69,16 +69,19 @@
<?php echo $this->form->getSubform('live_stream_subform'); ?>
</div>
<div style="float: left; width: 600px;">
<fieldset class="padded">
<legend>Output Stream Settings</legend>
<?php
for($i=1;$i<=$this->num_stream;$i++){
echo $this->form->getSubform("s".$i."_subform");
}
?>
</fieldset>
<?php if($this->enable_stream_conf == "true"){?>
<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" />
</div>
<?php }?>
</div>
</div>
</form>
</div>

View File

@ -16,7 +16,7 @@
<?php echo $this->when; ?>
<?php echo $this->repeats; ?>
</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">
<?php echo $this->live; ?>
</div>

View File

@ -808,6 +808,12 @@ dt.block-display, dd.block-display {
font-size:14px;
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 {
display:none;
}

View File

@ -202,13 +202,19 @@ var AIRTIME = (function(AIRTIME) {
*/
mod.selectCurrentPage = function() {
$.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){
$el = $(this);
mod.selectItem($el);
mod.addToChosen($el);
});
mod.checkToolBarIcons();
};
/*
@ -216,14 +222,20 @@ var AIRTIME = (function(AIRTIME) {
* (behaviour taken from gmail)
*/
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){
$el = $(this);
mod.deselectItem($el);
id = $el.attr("id");
delete chosenItems[id];
});
mod.checkToolBarIcons();
};
mod.selectNone = function() {
@ -252,20 +264,22 @@ var AIRTIME = (function(AIRTIME) {
};
mod.fnDeleteSelectedItems = function() {
var aData = AIRTIME.library.getSelectedData(),
item,
temp,
aMedia = [];
if (confirm('Are you sure you want to delete the selected item(s)?')) {
var aData = AIRTIME.library.getSelectedData(),
item,
temp,
aMedia = [];
//process selected files/playlists.
for (item in aData) {
temp = aData[item];
if (temp !== null && temp.hasOwnProperty('id') ) {
aMedia.push({"id": temp.id, "type": temp.ftype});
}
}
//process selected files/playlists.
for (item in aData) {
temp = aData[item];
if (temp !== null && temp.hasOwnProperty('id') ) {
aMedia.push({"id": temp.id, "type": temp.ftype});
}
}
AIRTIME.library.fnDeleteItems(aMedia);
AIRTIME.library.fnDeleteItems(aMedia);
}
};
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 mod;
@ -37,7 +66,7 @@ var AIRTIME = (function(AIRTIME) {
"aoColumns": [
{"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": "Length", "mDataProp": "length", "sClass": "his_length library_length"}, /* Length */
{"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>',
"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);

View File

@ -91,6 +91,34 @@ function onEndTimeSelect(){
$("#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() {
var form = $("#add-show-form");
@ -548,6 +576,12 @@ function setAddShowEvents() {
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() {

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!");
}
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 (dialog_id === json.show_id) {
$("#add-show-close").click();
}
//if you've deleted the show you are currently editing, close the add show dialog.
if (dialog_id === json.show_id) {
$("#add-show-close").click();
}
}
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
}
@ -31,9 +31,9 @@ function openAddShowForm() {
$("#schedule_calendar").fullCalendar('render');
}
$("#schedule-show-what").show(0, function(){
$add_show_name = $("#add_show_name");
$add_show_name.focus();
$add_show_name.select();
$add_show_name = $("#add_show_name");
$add_show_name.focus();
$add_show_name.select();
});
}
}
@ -59,16 +59,16 @@ function removeAddShowButton(){
}
function makeTimeStamp(date){
var sy, sm, sd, h, m, s, timestamp;
sy = date.getFullYear();
sm = date.getMonth() + 1;
sd = date.getDate();
h = date.getHours();
m = date.getMinutes();
s = date.getSeconds();
var sy, sm, sd, h, m, s, timestamp;
sy = date.getFullYear();
sm = date.getMonth() + 1;
sd = date.getDate();
h = date.getHours();
m = date.getMinutes();
s = date.getSeconds();
timestamp = sy+"-"+ pad(sm, 2) +"-"+ pad(sd, 2) +" "+ pad(h, 2) +":"+ pad(m, 2) +":"+ pad(s, 2);
return timestamp;
timestamp = sy+"-"+ pad(sm, 2) +"-"+ pad(sd, 2) +" "+ pad(h, 2) +":"+ pad(m, 2) +":"+ pad(s, 2);
return timestamp;
}
function dayClick(date, allDay, jsEvent, view){
@ -176,7 +176,7 @@ function viewDisplay( view ) {
//save slotMin value to db
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");
@ -206,71 +206,53 @@ function viewDisplay( 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.
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 0) {
var div = $('<div/>');
div
.height('5px')
.width('95%')
.css('margin-top', '1px')
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 0) {
var div = $('<div/>');
div
.height('5px')
.width('95%')
.css('margin-top', '1px')
.css('margin-left', 'auto')
.css('margin-right', 'auto')
.progressbar({
value: event.percent
});
.progressbar({
value: event.percent
});
$(element).find(".fc-event-content").append(div);
}
$(element).find(".fc-event-content").append(div);
}
//add the record/rebroadcast icons if needed.
//record icon (only if not on soundcloud, will always be true for future events)
//add the record/rebroadcast/soundcloud icons if needed
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) {
$(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
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) {
$(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>');
$(element).find(".fc-event-title").after('<span id="'+event.id+'" class="small-icon rebroadcast"></span>');
}
}
@ -282,56 +264,56 @@ function eventAfterRender( event, element, 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,
{day: dayDelta, min: minuteDelta, showInstanceId: event.id},
function(json){
if(json.show_error == true){
$.post(url,
{day: dayDelta, min: minuteDelta, showInstanceId: event.id},
function(json){
if(json.show_error == true){
alertShowErrorAndReload();
}
if(json.error) {
if(json.error) {
alert(json.error);
revertFunc();
}
});
revertFunc();
}
});
}
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,
{day: dayDelta, min: minuteDelta, showId: event.showId},
function(json){
if(json.show_error == true){
$.post(url,
{day: dayDelta, min: minuteDelta, showId: event.showId},
function(json){
if(json.show_error == true){
alertShowErrorAndReload();
}
if(json.error) {
if(json.error) {
alert(json.error);
revertFunc();
}
revertFunc();
}
scheduleRefetchEvents(json);
});
});
}
function getFullCalendarEvents(start, end, callback) {
var url, start_date, end_date;
var url, start_date, end_date;
start_date = makeTimeStamp(start);
end_date = makeTimeStamp(end);
start_date = makeTimeStamp(start);
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){
callback(json.events);
});
$.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){
callback(json.events);
});
}
function checkSCUploadStatus(){
@ -340,9 +322,9 @@ function checkSCUploadStatus(){
var id = $(this).attr("id");
$.post(url, {format: "json", id: id, type:"show"}, function(json){
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"){
$("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 mod;
var mod;
if (AIRTIME.schedule === undefined) {
AIRTIME.schedule = {};
}
mod = AIRTIME.schedule;
if (AIRTIME.schedule === undefined) {
AIRTIME.schedule = {};
}
mod = AIRTIME.schedule;
return AIRTIME;
return AIRTIME;
}(AIRTIME || {}));
var serverTimezoneOffset = 0;
function closeDialog(event, ui) {
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
$(this).remove();
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
$(this).remove();
}
function checkShowLength(json) {
@ -36,11 +36,11 @@ function confirmCancelShow(show_instance_id){
if (confirm('Cancel Current Show?')) {
var url = "/Schedule/cancel-current-show";
$.ajax({
url: url,
data: {format: "json", id: show_instance_id},
success: function(data){
scheduleRefetchEvents(data);
}
url: url,
data: {format: "json", id: show_instance_id},
success: function(data){
scheduleRefetchEvents(data);
}
});
}
}
@ -49,11 +49,11 @@ function confirmCancelRecordedShow(show_instance_id){
if (confirm('Stop recording current show?')) {
var url = "/Schedule/cancel-current-show";
$.ajax({
url: url,
data: {format: "json", id: show_instance_id},
success: function(data){
scheduleRefetchEvents(data);
}
url: url,
data: {format: "json", id: show_instance_id},
success: function(data){
scheduleRefetchEvents(data);
}
});
}
}
@ -72,18 +72,18 @@ function uploadToSoundCloud(show_instance_id){
if(span.length == 0){
span = $(window.triggerElement).find(".soundcloud");
span.removeClass("soundcloud")
.addClass("progress");
.addClass("progress");
}else{
span.removeClass("recording")
.addClass("progress");
.addClass("progress");
}
}
function checkCalendarSCUploadStatus(){
var url = '/Library/get-upload-to-soundcloud-status',
span,
id;
span,
id;
function checkSCUploadStatusCallback(json) {
@ -108,113 +108,111 @@ function checkCalendarSCUploadStatus(){
}
function findViewportDimensions() {
var viewportwidth,
viewportheight;
var viewportwidth,
viewportheight;
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') {
viewportwidth = window.innerWidth, viewportheight = window.innerHeight;
}
// IE6 in standards compliant mode (i.e. with a valid doctype as the first
// line in the document)
else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth != 'undefined'
&& document.documentElement.clientWidth != 0) {
viewportwidth = document.documentElement.clientWidth;
viewportheight = document.documentElement.clientHeight;
}
// older versions of IE
else {
viewportwidth = document.getElementsByTagName('body')[0].clientWidth;
viewportheight = document.getElementsByTagName('body')[0].clientHeight;
}
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') {
viewportwidth = window.innerWidth, viewportheight = window.innerHeight;
}
// IE6 in standards compliant mode (i.e. with a valid doctype as the first
// line in the document)
else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth != 'undefined'
&& document.documentElement.clientWidth != 0) {
viewportwidth = document.documentElement.clientWidth;
viewportheight = document.documentElement.clientHeight;
}
// older versions of IE
else {
viewportwidth = document.getElementsByTagName('body')[0].clientWidth;
viewportheight = document.getElementsByTagName('body')[0].clientHeight;
}
return {
width: viewportwidth,
height: viewportheight
};
return {
width: viewportwidth,
height: viewportheight
};
}
function buildScheduleDialog (json) {
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = Math.floor(viewport.height * 0.96),
width = Math.floor(viewport.width * 0.96),
fnServer = AIRTIME.showbuilder.fnServerData,
//subtract padding in pixels
widgetWidth = width - 60,
libWidth = Math.floor(widgetWidth * 0.5),
builderWidth = Math.floor(widgetWidth * 0.5);
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = Math.floor(viewport.height * 0.96),
width = Math.floor(viewport.width * 0.96),
fnServer = AIRTIME.showbuilder.fnServerData,
//subtract padding in pixels
widgetWidth = width - 60,
libWidth = Math.floor(widgetWidth * 0.5),
builderWidth = Math.floor(widgetWidth * 0.5);
dialog.find("#library_content")
.height(height - 115)
.width(libWidth);
dialog.find("#library_content")
.height(height - 115)
.width(libWidth);
dialog.find("#show_builder")
.height(height - 115)
.width(builderWidth);
dialog.find("#show_builder")
.height(height - 115)
.width(builderWidth);
dialog.dialog({
autoOpen: false,
title: json.title,
width: width,
height: height,
resizable: false,
draggable: true,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {
$(this).dialog("close");
}}
});
dialog.dialog({
autoOpen: false,
title: json.title,
width: width,
height: height,
resizable: false,
draggable: true,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {
dialog.remove();
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
}}
});
//set the start end times so the builder datatables knows its time range.
fnServer.start = json.start;
fnServer.end = json.end;
//set the start end times so the builder datatables knows its time range.
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")
.css("max-height", height - 90 - 155);
//set max heights of datatables.
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){
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = viewport.height * 2/3,
width = viewport.width * 4/5;
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = viewport.height * 2/3,
width = viewport.width * 4/5;
if (json.show_error == true){
alertShowErrorAndReload();
}
dialog.find("#show_progressbar").progressbar({
value: json.percentFilled
});
dialog.find("#show_progressbar").progressbar({
value: json.percentFilled
});
dialog.dialog({
autoOpen: false,
title: "Contents of Show \"" + json.showTitle + "\"",
width: width,
height: height,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {
dialog.remove();
}}
});
dialog.dialog({
autoOpen: false,
title: "Contents of Show \"" + json.showTitle + "\"",
width: width,
height: height,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {
dialog.remove();
}}
});
dialog.dialog('open');
dialog.dialog('open');
}
/**
@ -279,55 +277,55 @@ function alertShowErrorAndReload(){
}
$(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){}});
setInterval(checkCalendarSCUploadStatus, 5000);
setInterval(checkCalendarSCUploadStatus, 5000);
$.contextMenu({
$.contextMenu({
selector: 'div.fc-event',
trigger: "left",
ignoreRightClick: true,
build: function($el, e) {
var data,
items,
callback;
var data,
items,
callback;
data = $el.data("event");
data = $el.data("event");
function processMenuItems(oItems) {
function processMenuItems(oItems) {
//define a schedule callback.
if (oItems.schedule !== undefined) {
//define a schedule callback.
if (oItems.schedule !== undefined) {
callback = function() {
callback = function() {
$.post(oItems.schedule.url, {format: "json", id: data.id}, function(json){
buildScheduleDialog(json);
});
};
$.post(oItems.schedule.url, {format: "json", id: data.id}, function(json){
buildScheduleDialog(json);
});
};
oItems.schedule.callback = callback;
}
oItems.schedule.callback = callback;
}
//define a clear callback.
if (oItems.clear !== undefined) {
//define a clear callback.
if (oItems.clear !== undefined) {
callback = function() {
if (confirm("Remove all content?")) {
$.post(oItems.clear.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
}
};
oItems.clear.callback = callback;
}
callback = function() {
if (confirm("Remove all content?")) {
$.post(oItems.clear.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
}
};
oItems.clear.callback = callback;
}
//define an edit callback.
if (oItems.edit !== undefined) {
if(oItems.edit.items !== undefined){
var edit = oItems.edit.items;
//define an edit callback.
if (oItems.edit !== undefined) {
if(oItems.edit.items !== undefined){
var edit = oItems.edit.items;
//edit a single instance
callback = function() {
@ -344,55 +342,55 @@ $(document).ready(function() {
});
};
edit.all.callback = callback;
}else{
callback = function() {
}else{
callback = function() {
$.get(oItems.edit.url, {format: "json", id: data.id, type: oItems.edit._type}, function(json){
beginEditShow(json);
});
};
oItems.edit.callback = callback;
}
}
}
}
//define a content callback.
if (oItems.content !== undefined) {
//define a content callback.
if (oItems.content !== undefined) {
callback = function() {
$.get(oItems.content.url, {format: "json", id: data.id}, function(json){
buildContentDialog(json);
});
};
oItems.content.callback = callback;
}
callback = function() {
$.get(oItems.content.url, {format: "json", id: data.id}, function(json){
buildContentDialog(json);
});
};
oItems.content.callback = callback;
}
//define a soundcloud upload callback.
if (oItems.soundcloud_upload !== undefined) {
//define a soundcloud upload callback.
if (oItems.soundcloud_upload !== undefined) {
callback = function() {
uploadToSoundCloud(data.id);
};
oItems.soundcloud_upload.callback = callback;
}
callback = function() {
uploadToSoundCloud(data.id);
};
oItems.soundcloud_upload.callback = callback;
}
//define a view on soundcloud callback.
if (oItems.soundcloud_view !== undefined) {
//define a view on soundcloud callback.
if (oItems.soundcloud_view !== undefined) {
callback = function() {
callback = function() {
window.open(oItems.soundcloud_view.url);
};
oItems.soundcloud_view.callback = callback;
}
oItems.soundcloud_view.callback = callback;
}
//define a cancel recorded show callback.
if (oItems.cancel_recorded !== undefined) {
//define a cancel recorded show callback.
if (oItems.cancel_recorded !== undefined) {
callback = function() {
confirmCancelRecordedShow(data.id);
};
oItems.cancel_recorded.callback = callback;
}
callback = function() {
confirmCancelRecordedShow(data.id);
};
oItems.cancel_recorded.callback = callback;
}
//define a view recorded callback.
//define a view recorded callback.
if (oItems.view_recorded !== undefined) {
callback = function() {
@ -401,70 +399,70 @@ $(document).ready(function() {
oItems.view_recorded.callback = callback;
}
//define a cancel callback.
if (oItems.cancel !== undefined) {
//define a cancel callback.
if (oItems.cancel !== undefined) {
callback = function() {
confirmCancelShow(data.id);
};
oItems.cancel.callback = callback;
}
callback = function() {
confirmCancelShow(data.id);
};
oItems.cancel.callback = callback;
}
//define a delete callback.
if (oItems.del !== undefined) {
//define a delete callback.
if (oItems.del !== undefined) {
//repeating show multiple delete options
if (oItems.del.items !== undefined) {
var del = oItems.del.items;
//repeating show multiple delete options
if (oItems.del.items !== undefined) {
var del = oItems.del.items;
//delete a single instance
callback = function() {
$.post(del.single.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
del.single.callback = callback;
//delete a single instance
callback = function() {
$.post(del.single.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
del.single.callback = callback;
//delete this instance and all following instances.
callback = function() {
$.post(del.following.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
del.following.callback = callback;
//delete this instance and all following instances.
callback = function() {
$.post(del.following.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
del.following.callback = callback;
}
//single show
else {
callback = function() {
$.post(oItems.del.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
oItems.del.callback = callback;
}
}
}
//single show
else {
callback = function() {
$.post(oItems.del.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
oItems.del.callback = callback;
}
}
items = oItems;
}
items = oItems;
}
$.ajax({
url: "/schedule/make-context-menu",
type: "GET",
data: {id : data.id, format: "json"},
dataType: "json",
async: false,
success: function(json){
processMenuItems(json.items);
}
});
$.ajax({
url: "/schedule/make-context-menu",
type: "GET",
data: {id : data.id, format: "json"},
dataType: "json",
async: false,
success: function(json){
processMenuItems(json.items);
}
});
return {
items: items,
determinePosition : function($menu, x, y) {
$menu.css('display', 'block')
.position({ my: "left top", at: "right top", of: this, offset: "-20 10", collision: "fit"})
.css('display', 'none');
$menu.css('display', 'block')
.position({ my: "left top", at: "right top", of: this, offset: "-20 10", collision: "fit"})
.css('display', 'none');
}
};
}

View File

@ -8,7 +8,10 @@ var AIRTIME = (function(AIRTIME){
$sbTable,
$toolbar,
$ul,
$lib;
$lib,
cursors = [],
cursorIds = [];
showInstanceIds = [];
if (AIRTIME.showbuilder === undefined) {
AIRTIME.showbuilder = {};
@ -208,6 +211,18 @@ var AIRTIME = (function(AIRTIME){
mod.fnItemCallback = function(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();
mod.enableUI();
@ -236,11 +251,14 @@ var AIRTIME = (function(AIRTIME){
mod.fnRemove = function(aItems) {
mod.disableUI();
$.post( "/showbuilder/schedule-remove",
{"items": aItems, "format": "json"},
mod.fnItemCallback
);
if (confirm("Delete selected item(s)?")) {
$.post( "/showbuilder/schedule-remove",
{"items": aItems, "format": "json"},
mod.fnItemCallback
);
}else{
mod.enableUI();
}
};
mod.fnRemoveSelectedItems = function() {
@ -401,7 +419,6 @@ var AIRTIME = (function(AIRTIME){
headerIcon;
fnPrepareSeparatorRow = function fnPrepareSeparatorRow(sRowContent, sClass, iNodeIndex) {
$node = $(nRow.children[iNodeIndex]);
$node.html(sRowContent);
$node.attr('colspan',100);
@ -572,11 +589,13 @@ var AIRTIME = (function(AIRTIME){
$nRow.addClass("sb-future");
}
if (aData.allowed !== true) {
if (aData.allowed !== true || aData.header === true) {
$nRow.addClass("sb-not-allowed");
}
else {
$nRow.addClass("sb-allowed");
$nRow.attr("id", aData.id);
$nRow.attr("si_id", aData.instance);
}
//status used to colour tracks.
@ -615,6 +634,7 @@ var AIRTIME = (function(AIRTIME){
$cursorRows,
$table = $(this),
$parent = $table.parent(),
$tr,
//use this array to cache DOM heights then we can detach the table to manipulate it to increase speed.
heights = [];
@ -653,6 +673,13 @@ var AIRTIME = (function(AIRTIME){
});
$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.
@ -678,8 +705,14 @@ var AIRTIME = (function(AIRTIME){
if (temp.length > 0) {
aData = temp.data("aData");
mod.timeout = setTimeout(mod.refresh, aData.refresh * 1000); //need refresh in milliseconds
// max time interval
// 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;
}
}
@ -890,8 +923,8 @@ var AIRTIME = (function(AIRTIME){
$ul = $("<ul/>");
$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-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-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="Remove selected scheduled items"><span class="ui-icon ui-icon-trash"></span></li>');
$toolbar.append($ul);
$ul = $("<ul/>");
@ -1065,7 +1098,7 @@ var AIRTIME = (function(AIRTIME){
if (oItems.del !== undefined) {
callback = function() {
if (confirm("Delete selected Items?")) {
if (confirm("Delete selected item?")) {
AIRTIME.showbuilder.fnRemove([{
id: data.id,
timestamp: data.timestamp,

View File

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

View File

@ -15,8 +15,11 @@ class Version20110711161043 extends AbstractMigration
{
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 */
$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', '');");

View File

@ -319,7 +319,12 @@ class AirtimeInstall
public static function SetDefaultTimezone()
{
$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')";
$result = $con->exec($sql);
if ($result < 1) {

View File

@ -61,6 +61,16 @@ AirtimeInstall::InstallStorageDirectory();
$db_install = getenv("nodb")!="t";
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) {
//call external script. "y" argument means force creation of database tables.
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
USERID=pypo
ROOTUSERID=root
GROUPID=pypo
NAME=Airtime\ Playout
@ -47,7 +48,7 @@ start () {
chown pypo:pypo /etc/airtime
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
liquidsoap_start
@ -80,7 +81,7 @@ monit_restart() {
}
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
}

View File

@ -109,20 +109,6 @@ try:
print e
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
subprocess.call("update-rc.d airtime-playout defaults >/dev/null 2>&1", shell=True)

View File

@ -130,10 +130,10 @@ end
def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point, dj_harbor_input_port, dj_harbor_input_mount_point, s) =
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,
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,
max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect))
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)))
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)))
master_dj = rewrite_metadata([("artist","Airtime"), ("title", "Master Dj")],master_dj)
dj_live = rewrite_metadata([("artist","Airtime"), ("title", "Live Dj")],dj_live)
@ -142,15 +142,17 @@ def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point,
ignore(output.dummy(dj_live, fallible=true))
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
master_dj = mksafe(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))
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)))
ignore(output.dummy(master_dj, fallible=true))
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)])
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,
max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect))
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)))
dj_live = rewrite_metadata([("artist","Airtime"), ("title", "Live Dj")],dj_live)
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)])
else

View File

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

View File

@ -1,8 +1,8 @@
[loggers]
keys=root,fetch,push,recorder,message_h,notify
keys=root,fetch,push,recorder,message_h
[handlers]
keys=pypo,recorder,message_h,notify
keys=pypo,recorder,message_h
[formatters]
keys=simpleFormatter
@ -35,18 +35,6 @@ handlers=message_h
qualname=message_h
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]
class=logging.handlers.RotatingFileHandler
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)
"""
from optparse import OptionParser
from datetime import datetime
import telnetlib
import time
from optparse import *
import sys
import signal
import logging
import logging.config
import logging.handlers
import locale
import os
from Queue import Queue
@ -50,6 +52,15 @@ parser.add_option("-c", "--check", help="Check the cached schedule and exit", de
#need to wait for Python 2.7 for this..
#logging.captureWarnings(True)
# configure logging
try:
logging.config.fileConfig("logging.cfg")
logger = logging.getLogger()
LogWriter.override_std_err(logger)
except Exception, e:
print "Couldn't configure logging"
sys.exit()
def configure_locale():
logger.debug("Before %s", locale.nl_langinfo(locale.CODESET))
current_locale = locale.getlocale()
@ -61,8 +72,8 @@ def configure_locale():
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')
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.")
@ -84,14 +95,6 @@ def configure_locale():
logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % current_locale_encoding)
sys.exit(1)
# configure logging
try:
logging.config.fileConfig("logging.cfg")
logger = logging.getLogger()
LogWriter.override_std_err(logger)
except Exception, e:
print "Couldn't configure logging"
sys.exit()
configure_locale()
@ -113,55 +116,57 @@ class Global:
def test_api(self):
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):
logger = logging.getLogger()
logger.info('\nKeyboard Interrupt\n')
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__':
logger = logging.getLogger()
logger.info('###########################################')
logger.info('# *** pypo *** #')
logger.info('# Liquidsoap Scheduled Playout System #')
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)
# initialize
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:
g.test_api()
@ -174,7 +179,7 @@ if __name__ == '__main__':
recorder_q = Queue()
pypoPush_q = Queue()
telnet_lock = Lock()
"""
This queue is shared between pypo-fetch and pypo-file, where pypo-file
@ -204,7 +209,7 @@ if __name__ == '__main__':
recorder.daemon = True
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
#pmh.join()
#recorder.join()
@ -213,14 +218,3 @@ if __name__ == '__main__':
logger.info("pypo fetch 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 sys
import time
import logging
import logging.config
import shutil
import json
import telnetlib
import copy
from threading import Thread
from Queue import Empty
from api_clients import api_client
from std_err_override import LogWriter
@ -29,7 +29,9 @@ try:
config = ConfigObj('/etc/airtime/pypo.cfg')
LS_HOST = config['ls_host']
LS_PORT = config['ls_port']
POLL_INTERVAL = int(config['poll_interval'])
#POLL_INTERVAL = int(config['poll_interval'])
POLL_INTERVAL = 1800
except Exception, e:
logger.error('Error loading config file: %s', e)
@ -43,7 +45,7 @@ class PypoFetch(Thread):
self.push_queue = pypoPush_q
self.media_prepare_queue = media_q
self.last_update_schedule_timestamp = time.time()
self.listener_timeout = 3600
self.listener_timeout = POLL_INTERVAL
self.telnet_lock = telnet_lock
@ -75,12 +77,12 @@ class PypoFetch(Thread):
try:
self.logger.info("Received event from Pypo Message Handler: %s" % message)
m = json.loads(message)
m = json.loads(message)
command = m['event_type']
self.logger.info("Handling command: " + command)
if command == 'update_schedule':
self.schedule_data = m['schedule']
self.schedule_data = m['schedule']
self.process_schedule(self.schedule_data)
elif command == 'update_stream_setting':
self.logger.info("Updating stream setting...")
@ -103,9 +105,9 @@ class PypoFetch(Thread):
# update timeout value
if command == 'update_schedule':
self.listener_timeout = 3600
self.listener_timeout = POLL_INTERVAL
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:
self.listener_timeout = 0
self.logger.info("New timeout: %s" % self.listener_timeout)
@ -171,10 +173,10 @@ class PypoFetch(Thread):
self.logger.debug('Getting information needed on bootstrap from Airtime')
info = self.api_client.get_bootstrap_info()
if info == None:
self.logger.error('Unable to get bootstrap info.. Existing pypo...')
sys.exit(0)
self.logger.error('Unable to get bootstrap info.. Exiting pypo...')
sys.exit(1)
else:
self.logger.debug('info:%s',info)
self.logger.debug('info:%s', info)
for k, v in info['switch_status'].iteritems():
self.switch_source(self.logger, self.telnet_lock, k, v)
self.update_liquidsoap_stream_format(info['stream_label'])
@ -267,13 +269,13 @@ class PypoFetch(Thread):
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
restart = True;
else:
stream, dump = s[u'keyname'].split('_',1)
stream, dump = s[u'keyname'].split('_', 1)
if "_output" in s[u'keyname']:
if (existing[s[u'keyname']] != s[u'value']):
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
restart = True;
state_change_restart[stream] = True
elif ( s[u'value'] != 'disabled'):
elif (s[u'value'] != 'disabled'):
state_change_restart[stream] = True
else:
state_change_restart[stream] = False
@ -314,7 +316,7 @@ class PypoFetch(Thread):
# we are manually adjusting the bootup time variable so the status msg will get
# updated.
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("streams.connection_status\n")
tn.write('exit\n')
@ -425,8 +427,9 @@ class PypoFetch(Thread):
media_item = media[key]
if(media_item['type'] == 'file'):
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['started_copying'] = False
media_filtered[key] = media_item
self.media_prepare_queue.put(copy.copy(media_filtered))
@ -458,9 +461,9 @@ class PypoFetch(Thread):
unneeded_files = cached_file_set - scheduled_file_set
self.logger.debug("Files to remove " + str(unneeded_files))
for file in unneeded_files:
self.logger.debug("Removing %s" % os.path.join(self.cache_dir, file))
os.remove(os.path.join(self.cache_dir, file))
for f in unneeded_files:
self.logger.debug("Removing %s" % os.path.join(self.cache_dir, f))
os.remove(os.path.join(self.cache_dir, f))
def main(self):
# Bootstrap: since we are just starting up, we need to grab the
@ -486,12 +489,14 @@ class PypoFetch(Thread):
sent, and we will have very stale (or non-existent!) data about the
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)
self.handle_message(message)
except Empty, e:
self.logger.info("Queue timeout. Fetching schedule manually")
except Exception, e:
import traceback
top = traceback.format_exc()

View File

@ -5,10 +5,10 @@ from Queue import Empty
from configobj import ConfigObj
import logging
import logging.config
import shutil
import os
import sys
import stat
from std_err_override import LogWriter
@ -70,12 +70,19 @@ class PypoFile(Thread):
if do_copy:
self.logger.debug("copying from %s to local cache %s" % (src, dst))
try:
media_item['started_copying'] = True
"""
copy will overwrite dst if it already exists
"""
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(e)
def get_highest_priority_media_item(self, schedule):
"""

View File

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

View File

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

View File

@ -5,11 +5,9 @@ from datetime import timedelta
import sys
import time
import logging
import logging.config
import telnetlib
import calendar
import json
import math
from pypofetch import PypoFetch
@ -54,7 +52,7 @@ class PypoPush(Thread):
def main(self):
loops = 0
heartbeat_period = math.floor(30/PUSH_INTERVAL)
heartbeat_period = math.floor(30 / PUSH_INTERVAL)
next_media_item_chain = None
media_schedule = None
@ -73,30 +71,36 @@ class PypoPush(Thread):
#We get to the following lines only if a schedule was received.
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:
#Something is scheduled but Liquidsoap is not playing anything!
#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])
next_media_item_chain = current_event_chain
time_until_next_play = 0
#sleep for 0.2 seconds to give pypo-file time to copy. This is a quick
#fix that will be improved in 2.1.1
#sleep for 0.2 seconds to give pypo-file time to copy.
time.sleep(0.2)
else:
media_chain = filter(lambda item: (item["type"] == "file"), current_event_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)
if next_media_item_chain is not None:
chains.remove(next_media_item_chain)
tnow = datetime.utcnow()
try:
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")
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)
else:
self.logger.debug("Blocking indefinitely since no show scheduled")
@ -105,15 +109,16 @@ class PypoPush(Thread):
#We only get here when a new chain of tracks are ready to be played.
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:
tnow = datetime.utcnow()
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)
else:
self.logger.debug("Blocking indefinitely since no show scheduled next")
time_until_next_play = None
except Exception, e:
self.logger.error(str(e))
if loops % heartbeat_period == 0:
self.logger.info("heartbeat")
@ -170,7 +175,7 @@ class PypoPush(Thread):
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:
#Items that are in Liquidsoap's queue aren't scheduled anymore. We need to connect
@ -201,7 +206,6 @@ class PypoPush(Thread):
#see if they are the same as the newly received schedule
iteration = 0
problem_at_iteration = None
problem_start_time = None
for queue_item in liquidsoap_queue_approx:
if queue_item['start'] in media_schedule.keys():
media_item = media_schedule[queue_item['start']]
@ -211,23 +215,20 @@ class PypoPush(Thread):
pass
else:
problem_at_iteration = iteration
problem_start_time = queue_item['start']
break
else:
#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
#them.
problem_at_iteration = iteration
problem_start_time = queue_item['start']
break
else:
#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.
problem_at_iteration = iteration
problem_start_time = queue_item['start']
break
iteration+=1
return (problem_at_iteration, problem_start_time)
iteration += 1
return problem_at_iteration
@ -270,10 +271,24 @@ class PypoPush(Thread):
original_cue_in_td = timedelta(seconds=float(link['cue_in']))
link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec
"""
Returns two chains, original chain and current_chain. current_chain is a subset of
original_chain but can also be equal to original chain.
def get_current_chain(self, chains):
tnow = datetime.utcnow()
We return original chain because the user of this function may want to clean
up the input 'chains' list
chain, original = get_current_chain(chains)
and
chains.remove(chain) can throw a ValueError exception
but
chains.remove(original) won't
"""
def get_current_chain(self, chains, tnow):
current_chain = []
original_chain = None
for chain in chains:
iteration = 0
@ -284,10 +299,11 @@ class PypoPush(Thread):
self.logger.debug("tnow %s, chain_start %s", tnow, link_start)
if link_start <= tnow and tnow < link_end:
current_chain = chain[iteration:]
original_chain = chain
break
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
@ -295,10 +311,9 @@ class PypoPush(Thread):
of media_items where the end time of media_item 'n' is the start time of media_item
'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
#starts closest in the future.
tnow = datetime.utcnow()
closest_start = None
closest_chain = None
for chain in chains:
@ -312,14 +327,27 @@ class PypoPush(Thread):
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):
try:
for media_item in event_chain:
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":
if media_item['event_type'] == "kick_out":
PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj")
@ -448,7 +476,7 @@ class PypoPush(Thread):
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.
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):
try: self.main()

View File

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