Merge branch 'devel' of dev.sourcefabric.org:airtime

This commit is contained in:
martin 2011-05-16 11:45:23 -04:00
commit b87e661f96
34 changed files with 1076 additions and 270 deletions

View File

@ -71,8 +71,8 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
$view->headScript()->appendFile($baseUrl.'/js/qtip/jquery.qtip-1.0.0.min.js','text/javascript');
//scripts for now playing bar
$view->headScript()->appendFile($baseUrl.'/js/playlist/helperfunctions.js','text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/playlist/playlist.js','text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/helperfunctions.js','text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/playlist.js','text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/common/common.js','text/javascript');
}

View File

@ -10,6 +10,7 @@ class ApiController extends Zend_Controller_Action
$context->addActionContext('version', 'json')
->addActionContext('recorded-shows', 'json')
->addActionContext('upload-recorded', 'json')
->addActionContext('reload-metadata', 'json')
->initContext();
}
@ -113,8 +114,6 @@ class ApiController extends Zend_Controller_Action
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
$result = Schedule::GetPlayOrderRange(0, 1);
$date = new DateHelper;
$timeNow = $date->getTimestamp();
$result = array("env"=>APPLICATION_ENV,
@ -318,5 +317,35 @@ class ApiController extends Zend_Controller_Action
$this->view->id = $file->getId();
}
public function reloadMetadataAction() {
global $CC_CONFIG;
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$md = $this->_getParam('md');
$file = StoredFile::Recall(null, $md['gunid']);
if (PEAR::isError($file) || is_null($file)) {
$this->view->response = "File not in Airtime's Database";
return;
}
$res = $file->replaceDbMetadata($md);
if (PEAR::isError($res)) {
$this->view->response = "Metadata Change Failed";
}
else {
$this->view->response = "Success!";
}
}
}

View File

@ -0,0 +1,22 @@
<?php
class DashboardController extends Zend_Controller_Action
{
public function init()
{
}
public function indexAction()
{
// action body
}
public function helpAction()
{
// action body
}
}

View File

@ -168,6 +168,10 @@ class LibraryController extends Zend_Controller_Action
$formdata = $form->getValues();
$file->replaceDbMetadata($formdata);
$data = $formdata;
$data['filepath'] = $file->getRealFilePath();
RabbitMq::SendFileMetaData($data);
$this->_helper->redirector('index');
}
}

View File

@ -2,6 +2,24 @@
class Application_Form_EditAudioMD extends Zend_Form
{
/*
"title": "track_title",\
"artist": "artist_name",\
"album": "album_title",\
"genre": "genre",\
"mood": "mood",\
"tracknumber": "track_number",\
"bpm": "bpm",\
"organization": "label",\
"composer": "composer",\
"encodedby": "encoded_by",\
"conductor": "conductor",\
"date": "year",\
"website": "info_url",\
"isrc": "isrc_number",\
"copyright": "copyright",\
*/
public function init()
{
@ -37,6 +55,13 @@ class Application_Form_EditAudioMD extends Zend_Form
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'track_number', array(
'label' => 'Track:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add genre field
$this->addElement('text', 'genre', array(
'label' => 'Genre:',
@ -77,9 +102,30 @@ class Application_Form_EditAudioMD extends Zend_Form
'filters' => array('StringTrim')
));
// Add language field
$this->addElement('text', 'language', array(
'label' => 'Language:',
// Add mood field
$this->addElement('text', 'bpm', array(
'label' => 'BPM:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'copyright', array(
'label' => 'Copyright:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'isrc_number', array(
'label' => 'ISRC Number:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'info_url', array(
'label' => 'Website:',
'class' => 'input_text',
'filters' => array('StringTrim')
));

View File

@ -0,0 +1,115 @@
<?php
class Application_Model_Dashboard
{
public static function GetPreviousItem($p_timeNow){
//get previous show and previous item in the schedule table.
//Compare the two and if the last show was recorded and started
//after the last item in the schedule table, then return the show's
//name. Else return the last item from the schedule.
$showInstance = ShowInstance::GetLastShowInstance($p_timeNow);
$row = Schedule::GetLastScheduleItem($p_timeNow);
if (is_null($showInstance)){
if (count($row) == 0){
return null;
} else {
//should never reach here. Doesnt make sense to have
//a schedule item not within a show_instance.
}
} else {
if (count($row) == 0){
//last item is a show instance
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
} else {
//return the one that started later.
if ($row[0]["starts"] >= $showInstance->getShowStart()){
return array("name"=>$row[0]["artist_name"]." - ".$row[0]["track_title"],
"starts"=>$row[0]["starts"],
"ends"=>$row[0]["ends"]);
} else {
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
}
}
}
}
public static function GetCurrentItem($p_timeNow){
//get previous show and previous item in the schedule table.
//Compare the two and if the last show was recorded and started
//after the last item in the schedule table, then return the show's
//name. Else return the last item from the schedule.
$showInstance = ShowInstance::GetCurrentShowInstance($p_timeNow);
$row = Schedule::GetCurrentScheduleItem($p_timeNow);
if (is_null($showInstance)){
if (count($row) == 0){
return null;
} else {
//should never reach here. Doesnt make sense to have
//a schedule item not within a show_instance.
}
} else {
if (count($row) == 0){
//last item is a show instance
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd(),
"media_item_played"=>false, //TODO
"record"=>$showInstance->isRecorded()); //TODO
} else {
return array("name"=>$row[0]["artist_name"]." - ".$row[0]["track_title"],
"starts"=>$row[0]["starts"],
"ends"=>$row[0]["ends"],
"media_item_played"=>$row[0]["media_item_played"],
"record"=>0);
}
}
}
public static function GetNextItem($p_timeNow){
//get previous show and previous item in the schedule table.
//Compare the two and if the last show was recorded and started
//after the last item in the schedule table, then return the show's
//name. Else return the last item from the schedule.
$showInstance = ShowInstance::GetNextShowInstance($p_timeNow);
$row = Schedule::GetNextScheduleItem($p_timeNow);
if (is_null($showInstance)){
if (count($row) == 0){
return null;
} else {
//should never reach here. Doesnt make sense to have
//a schedule item not within a show_instance.
}
} else {
if (count($row) == 0){
//last item is a show instance
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
} else {
//return the one that starts sooner.
if ($row[0]["starts"] <= $showInstance->getShowStart()){
return array("name"=>$row[0]["artist_name"]." - ".$row[0]["track_title"],
"starts"=>$row[0]["starts"],
"ends"=>$row[0]["ends"]);
} else {
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
}
}
}
}
}

View File

@ -40,5 +40,27 @@ class RabbitMq
}
}
public static function SendFileMetaData($md)
{
global $CC_CONFIG;
$conn = new AMQPConnection($CC_CONFIG["rabbitmq"]["host"],
$CC_CONFIG["rabbitmq"]["port"],
$CC_CONFIG["rabbitmq"]["user"],
$CC_CONFIG["rabbitmq"]["password"]);
$channel = $conn->channel();
$channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true);
$EXCHANGE = 'airtime-media-monitor';
$channel->exchange_declare($EXCHANGE, 'direct', false, true);
$data = json_encode($md);
$msg = new AMQPMessage($data, array('content_type' => 'text/plain'));
$channel->basic_publish($msg, $EXCHANGE);
$channel->close();
$conn->close();
}
}

View File

@ -365,9 +365,12 @@ class Schedule {
$timeNow = $date->getTimestamp();
return array("env"=>APPLICATION_ENV,
"schedulerTime"=>gmdate("Y-m-d H:i:s"),
"previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"),
"current"=>Schedule::GetScheduledItemData($timeNow, 0),
"next"=>Schedule::GetScheduledItemData($timeNow, 1, $next, "48 hours"),
//"previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"),
//"current"=>Schedule::GetScheduledItemData($timeNow, 0),
//"next"=>Schedule::GetScheduledItemData($timeNow, 1, $next, "48 hours"),
"previous"=>Application_Model_Dashboard::GetPreviousItem($timeNow),
"current"=>Application_Model_Dashboard::GetCurrentItem($timeNow),
"next"=>Application_Model_Dashboard::GetNextItem($timeNow),
"currentShow"=>Show_DAL::GetCurrentShow($timeNow),
"nextShow"=>Show_DAL::GetNextShows($timeNow, 1),
"timezone"=> date("T"),
@ -375,6 +378,52 @@ class Schedule {
"apiKey"=>$CC_CONFIG['apiKey'][0]);
}
public static function GetLastScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *"
." FROM $CC_CONFIG[scheduleTable] st"
." LEFT JOIN $CC_CONFIG[filesTable] ft"
." ON st.file_id = ft.id"
." WHERE st.ends < TIMESTAMP '$p_timeNow'"
." ORDER BY st.ends DESC"
." LIMIT 1";
$row = $CC_DBC->GetAll($sql);
return $row;
}
public static function GetCurrentScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *"
." FROM $CC_CONFIG[scheduleTable] st"
." LEFT JOIN $CC_CONFIG[filesTable] ft"
." ON st.file_id = ft.id"
." WHERE st.starts <= TIMESTAMP '$p_timeNow'"
." AND st.ends > TIMESTAMP '$p_timeNow'"
." LIMIT 1";
$row = $CC_DBC->GetAll($sql);
return $row;
}
public static function GetNextScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *"
." FROM $CC_CONFIG[scheduleTable] st"
." LEFT JOIN $CC_CONFIG[filesTable] ft"
." ON st.file_id = ft.id"
." WHERE st.starts > TIMESTAMP '$p_timeNow'"
." ORDER BY st.starts"
." LIMIT 1";
$row = $CC_DBC->GetAll($sql);
return $row;
}
/**
* Builds an SQL Query for accessing scheduled item information from
* the database.

View File

@ -1718,6 +1718,57 @@ class ShowInstance {
return ($diff < 0) ? 0 : $diff;
}
public static function GetLastShowInstance($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT si.id"
." FROM $CC_CONFIG[showInstances] si"
." WHERE si.ends < TIMESTAMP '$p_timeNow'"
." ORDER BY si.ends DESC"
." LIMIT 1";
$id = $CC_DBC->GetOne($sql);
if (is_null($id)){
return null;
} else {
return new ShowInstance($id);
}
}
public static function GetCurrentShowInstance($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT si.id"
." FROM $CC_CONFIG[showInstances] si"
." WHERE si.starts <= TIMESTAMP '$p_timeNow'"
." AND si.ends > TIMESTAMP '$p_timeNow'"
." LIMIT 1";
$id = $CC_DBC->GetOne($sql);
if (is_null($id)){
return null;
} else {
return new ShowInstance($id);
}
}
public static function GetNextShowInstance($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT si.id"
." FROM $CC_CONFIG[showInstances] si"
." WHERE si.starts > TIMESTAMP '$p_timeNow'"
." ORDER BY si.starts"
." LIMIT 1";
$id = $CC_DBC->GetOne($sql);
if (is_null($id)){
return null;
} else {
return new ShowInstance($id);
}
}
}
/* Show Data Access Layer */

View File

@ -543,16 +543,25 @@ class StoredFile {
public function replaceDbMetadata($p_values)
{
global $CC_CONFIG, $CC_DBC;
$data = array();
foreach ($p_values as $category => $value) {
$escapedValue = pg_escape_string($value);
$columnName = $category;
if (!is_null($columnName)) {
$sql = "UPDATE ".$CC_CONFIG["filesTable"]
." SET $columnName='$escapedValue'"
." WHERE gunid = '".$this->gunid."'";
$CC_DBC->query($sql);
$data[] = "$columnName='$escapedValue'";
}
}
$data = join(",", $data);
$sql = "UPDATE ".$CC_CONFIG["filesTable"]
." SET $data"
." WHERE gunid = '".$this->gunid."'";
$res = $CC_DBC->query($sql);
if (PEAR::isError($res)) {
$CC_DBC->query("ROLLBACK");
return $res;
}
}
public function clearMetadata()

View File

@ -1,9 +1,9 @@
var estimatedSchedulePosixTime = null;
var localRemoteTimeOffset = null;
var previousSongs = new Array();
var currentSong = new Array();
var nextSongs = new Array();
var previousSong = null;
var currentSong = null;
var nextSong = null;
var currentShow = new Array();
var nextShow = new Array();
@ -25,21 +25,8 @@ var nextShowPrepare = true;
var apiKey = "";
function getTrackInfo(song){
var str = "";
if (song.track_title != null)
str += song.track_title;
if (song.artist_name != null)
str += " - " + song.artist_name;
str += ","
return str;
}
function secondsTimer(){
if (localRemoteTimeOffset != null){
if (localRemoteTimeOffset !== null){
var date = new Date();
estimatedSchedulePosixTime = date.getTime() - localRemoteTimeOffset;
updateProgressBarValue();
@ -50,7 +37,8 @@ function secondsTimer(){
function newSongStart(){
nextSongPrepare = true;
currentSong[0] = nextSongs.shift();
currentSong = nextSong;
nextSong = null;
if (typeof notifySongStart == "function")
notifySongStart();
@ -80,13 +68,13 @@ function updateProgressBarValue(){
$('#progress-show').attr("style", "width:"+showPercentDone+"%");
var songPercentDone = 0;
if (currentSong.length > 0){
songPercentDone = (estimatedSchedulePosixTime - currentSong[0].songStartPosixTime)/currentSong[0].songLengthMs*100;
if (currentSong !== null){
songPercentDone = (estimatedSchedulePosixTime - currentSong.songStartPosixTime)/currentSong.songLengthMs*100;
if (songPercentDone < 0 || songPercentDone > 100){
songPercentDone = 0;
currentSong = new Array();
currentSong = null;
} else {
if (currentSong[0].media_item_played == "t" && currentShow.length > 0)
if (currentSong.media_item_played == "t" && currentShow.length > 0)
$('#on-air-info').attr("class", "on-air-info on");
else
$('#on-air-info').attr("class", "on-air-info off");
@ -99,8 +87,8 @@ function updateProgressBarValue(){
$('#progress-bar').attr("style", "width:"+songPercentDone+"%");
//calculate how much time left to next song if there is any
if (nextSongs.length > 0 && nextSongPrepare){
var diff = nextSongs[0].songStartPosixTime - estimatedSchedulePosixTime;
if (nextSong !== null && nextSongPrepare){
var diff = nextSong.songStartPosixTime - estimatedSchedulePosixTime;
if (diff < serverUpdateInterval){
//sometimes the diff is negative (-100ms for example). Still looking
@ -133,20 +121,26 @@ function updatePlaybar(){
$('#current').html("Current: <span style='color:red; font-weight:bold'>Nothing Scheduled</span>");
$('#next').empty();
$('#next-length').empty();
if (previousSongs.length > 0){
$('#previous').text(getTrackInfo(previousSongs[previousSongs.length-1]));
$('#prev-length').text(convertToHHMMSSmm(previousSongs[previousSongs.length-1].songLengthMs));
if (previousSong !== null){
$('#previous').text(previousSong.name+",");
$('#prev-length').text(convertToHHMMSSmm(previousSong.songLengthMs));
}
if (currentSong.length > 0){
$('#current').text(getTrackInfo(currentSong[0]));
} else if (currentShow.length > 0){
if (currentSong !== null){
if (currentSong.record == "1")
$('#current').html("<span style='color:red; font-weight:bold'>Recording: </span>"+currentSong.name+",");
else
$('#current').text(currentSong.name+",");
}
/*
else if (currentShow.length > 0){
if (currentShow[0].record == "1"){
$('#current').html("Current: <span style='color:red; font-weight:bold'>Recording</span>");
}
}
if (nextSongs.length > 0){
$('#next').text(getTrackInfo(nextSongs[0]));
$('#next-length').text(convertToHHMMSSmm(nextSongs[0].songLengthMs));
* */
if (nextSong !== null){
$('#next').text(nextSong.name+",");
$('#next-length').text(convertToHHMMSSmm(nextSong.songLengthMs));
}
$('#start').empty();
@ -154,18 +148,18 @@ function updatePlaybar(){
$('#time-elapsed').empty();
$('#time-remaining').empty();
$('#song-length').empty();
for (var i=0; i<currentSong.length; i++){
$('#start').text(currentSong[i].starts.substring(currentSong[i].starts.indexOf(" ")+1));
$('#end').text(currentSong[i].ends.substring(currentSong[i].starts.indexOf(" ")+1));
if (currentSong !== null){
$('#start').text(currentSong.starts.substring(currentSong.starts.indexOf(" ")+1));
$('#end').text(currentSong.ends.substring(currentSong.starts.indexOf(" ")+1));
/* Get rid of the millisecond accuracy so that the second counters for both
* show and song change at the same time. */
var songStartRoughly = parseInt(Math.round(currentSong[i].songStartPosixTime/1000))*1000;
var songEndRoughly = parseInt(Math.round(currentSong[i].songEndPosixTime/1000))*1000;
var songStartRoughly = parseInt(Math.round(currentSong.songStartPosixTime/1000))*1000;
var songEndRoughly = parseInt(Math.round(currentSong.songEndPosixTime/1000))*1000;
$('#time-elapsed').text(convertToHHMMSS(estimatedSchedulePosixTime - songStartRoughly));
$('#time-remaining').text(convertToHHMMSS(songEndRoughly - estimatedSchedulePosixTime));
$('#song-length').text(convertToHHMMSSmm(currentSong[i].songLengthMs));
$('#song-length').text(convertToHHMMSSmm(currentSong.songLengthMs));
}
/* Column 1 update */
$('#playlist').text("Current Show:");
@ -187,11 +181,9 @@ function updatePlaybar(){
}
function calcAdditionalData(currentItem){
for (var i=0; i<currentItem.length; i++){
currentItem[i].songStartPosixTime = convertDateToPosixTime(currentItem[i].starts);
currentItem[i].songEndPosixTime = convertDateToPosixTime(currentItem[i].ends);
currentItem[i].songLengthMs = currentItem[i].songEndPosixTime - currentItem[i].songStartPosixTime;
}
currentItem.songStartPosixTime = convertDateToPosixTime(currentItem.starts);
currentItem.songEndPosixTime = convertDateToPosixTime(currentItem.ends);
currentItem.songLengthMs = currentItem.songEndPosixTime - currentItem.songStartPosixTime;
}
function calcAdditionalShowData(show){
@ -208,17 +200,18 @@ function parseItems(obj){
$('#time-zone').text(obj.timezone);
previousSongs = obj.previous;
nextSongs = obj.next;
previousSong = obj.previous;
currentSong = obj.current;
nextSong = obj.next;
calcAdditionalData(previousSongs);
calcAdditionalData(nextSongs);
if (previousSong !== null)
calcAdditionalData(previousSong);
if (currentSong !== null)
calcAdditionalData(currentSong);
if (nextSong !== null)
calcAdditionalData(nextSong);
currentShow = obj.currentShow;
if(currentShow.length > 0){
currentSong = obj.current;
calcAdditionalData(currentSong);
}
nextShow = obj.nextShow;
calcAdditionalShowData(obj.currentShow);
@ -238,17 +231,10 @@ function getScheduleFromServer(){
setTimeout(getScheduleFromServer, serverUpdateInterval);
}
function init() {
//begin producer "thread"
getScheduleFromServer();
//begin consumer "thread"
secondsTimer();
function setupQtip(){
var qtipElem = $('#about-link');
if (qtipElem.length > 0)
if (qtipElem.length > 0){
qtipElem.qtip({
content: $('#about-txt').html(),
show: 'mouseover',
@ -268,6 +254,17 @@ function init() {
}
});
}
}
function init() {
//begin producer "thread"
getScheduleFromServer();
//begin consumer "thread"
secondsTimer();
setupQtip();
}
$(document).ready(function() {
if ($('#master-panel').length > 0)

View File

@ -209,11 +209,41 @@ function getId() {
function buildContentDialog(json){
var dialog = $(json.dialog);
var viewportwidth;
var 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;
}
var height = viewportheight * 2/3;
var width = viewportwidth * 4/5;
dialog.dialog({
autoOpen: false,
title: 'Show Contents',
width: 1100,
height: 500,
width: width,
height: height,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {

View File

@ -1,69 +0,0 @@
function createDataGrid(datagridData){
var columnHeaders = [
{ "sTitle": "name" },
{ "sTitle": "date" },
{ "sTitle": "start time" },
{ "sTitle": "end time" }
];
$('#demo').html( '<table cellpadding="0" cellspacing="0" border="0" width="100%" id="nowplayingtable"></table>' );
$('#nowplayingtable').dataTable( {
"bSort" : false,
"bJQueryUI": true,
"bFilter": false,
"bInfo": false,
"bLengthChange": false,
"aaData": datagridData.rows,
"aoColumns": columnHeaders
} );
var options1 = [
{title:"Menu Item 1 - Go TO www.google.com", action:{type:"gourl",url:"http://www.google.com/"}},
{title:"Menu Item 2 - do <b style='color:red;'>nothing</b>"},
{title:"Menu Item 3 - submenu", type:"sub", src:[{title:"Submenu 1"},{title:"Submenu 2"},{title:"Submenu 3"}, {title:"Submenu 4 - submenu", type:"sub", src:[{title:"SubSubmenu 1"},{title:"SubSubmenu 2"}]}]},
{title:"Menu Item 4 - Js function", action:{type:"fn",callback:"(function(){ alert('THIS IS THE TEST'); })"}}
];
var userData = {};
var effects = {
show:"default", //type of show effect
orientation: "auto", //type of menu orientation - to top, to bottom, auto (to bottom, if doesn't fit on screen - to top)
xposition:"mouse", // position of menu (left side or right side of trigger element)
yposition:"mouse"
}
$('#demo').jjmenu('both', options1, userData, effects );
}
function initShowListView(){
$.ajax({ url: "/Schedule/get-show-data/format/json", dataType:"text", success:function(data){
$('#json-string').text(data);
}});
$.ajax({ url: "/Schedule/get-show-data/format/json", dataType:"json", success:function(data){
var temp = data.data;
var rows = new Array();
for (var i=0; i<temp.length; i++){
rows[i] = [temp[i].name.toString(), temp[i].first_show.toString(), temp[i].start_time.toString(), temp[i].end_time.toString()];
var datagridData = {rows:rows};
createDataGrid(datagridData);
}
}});
//setTimeout(initShowListView, 5000);
}
$(document).ready(function() {
initShowListView();
});

View File

@ -0,0 +1,23 @@
<?php
set_include_path(__DIR__.'/../airtime_mvc/library' . PATH_SEPARATOR . get_include_path());
require_once(dirname(__FILE__).'/include/AirtimeIni.php');
require_once(dirname(__FILE__).'/include/AirtimeInstall.php');
require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/conf.php');
//echo PHP_EOL."*** Database Installation ***".PHP_EOL;
AirtimeInstall::CreateDatabaseUser();
AirtimeInstall::CreateDatabase();
AirtimeInstall::DbConnect(true);
AirtimeInstall::InstallPostgresScriptingLanguage();
AirtimeInstall::CreateDatabaseTables();
AirtimeInstall::SetAirtimeVersion(AIRTIME_VERSION);

View File

@ -22,7 +22,8 @@ try {
array(
'help|h' => 'Displays usage information.',
'overwrite|o' => 'Overwrite any existing config files.',
'preserve|p' => 'Keep any existing config files.'
'preserve|p' => 'Keep any existing config files.',
'no-db|n' => 'Turn off database install.'
)
);
$opts->parse();
@ -34,6 +35,10 @@ if (isset($opts->h)) {
echo $opts->getUsageMessage();
exit;
}
$db_install = true;
if (isset($opts->n)){
$db_install = false;
}
$overwrite = false;
if (isset($opts->o)) {
@ -72,9 +77,11 @@ require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/conf.php')
echo "* Airtime Version: ".AIRTIME_VERSION.PHP_EOL;
if ($db_install) {
//echo PHP_EOL."*** Database Installation ***".PHP_EOL;
AirtimeInstall::CreateDatabaseUser();
/* AirtimeInstall::CreateDatabaseUser();
AirtimeInstall::CreateDatabase();
@ -82,7 +89,9 @@ AirtimeInstall::DbConnect(true);
AirtimeInstall::InstallPostgresScriptingLanguage();
AirtimeInstall::CreateDatabaseTables();
AirtimeInstall::CreateDatabaseTables();*/
require( 'airtime-db-install.php' );
}
AirtimeInstall::InstallStorageDirectory();
@ -98,6 +107,9 @@ system("python ".__DIR__."/../python_apps/pypo/install/pypo-install.py");
echo PHP_EOL."*** Recorder Installation ***".PHP_EOL;
system("python ".__DIR__."/../python_apps/show-recorder/install/recorder-install.py");
echo PHP_EOL."*** Media Monitor Installation ***".PHP_EOL;
system("python ".__DIR__."/../python_apps/pytag-fs/install/media-monitor-install.py");
AirtimeInstall::SetAirtimeVersion(AIRTIME_VERSION);
echo "******************************* Install Complete *******************************".PHP_EOL;

View File

@ -29,7 +29,10 @@ AirtimeInstall::UninstallPhpCode();
// still be a connection to the database and you wont be able to delete it.
//------------------------------------------------------------------------
echo " * Dropping the database '".$CC_CONFIG['dsn']['database']."'...".PHP_EOL;
$command = "su postgres -c \"dropdb {$CC_CONFIG['dsn']['database']}\"";
// check if DB exists
$command = "echo \"DROP DATABASE IF EXISTS ".$CC_CONFIG['dsn']['database']."\" | sudo -u postgres psql";
@exec($command, $output, $dbDeleteFailed);
//------------------------------------------------------------------------
@ -66,7 +69,7 @@ if ($dbDeleteFailed) {
// Delete the user
//------------------------------------------------------------------------
echo " * Deleting database user '{$CC_CONFIG['dsn']['username']}'...".PHP_EOL;
$command = "echo \"DROP USER {$CC_CONFIG['dsn']['username']}\" | su postgres -c psql";
$command = "echo \"DROP USER IF EXISTS {$CC_CONFIG['dsn']['username']}\" | su postgres -c psql";
@exec($command, $output, $results);
if ($results == 0) {
echo " * User '{$CC_CONFIG['dsn']['username']}' deleted.".PHP_EOL;
@ -85,6 +88,10 @@ echo PHP_EOL."*** Uninstalling Show Recorder ***".PHP_EOL;
$command = "python ".__DIR__."/../python_apps/show-recorder/install/recorder-uninstall.py";
system($command);
echo PHP_EOL."*** Uninstalling Media Monitor ***".PHP_EOL;
$command = "python ".__DIR__."/../python_apps/pytag-fs/install/media-monitor-uninstall.py";
system($command);
#Disabled as this should be a manual process
#AirtimeIni::RemoveIniFiles();

View File

@ -26,13 +26,15 @@ class AirtimeIni
const CONF_FILE_PYPO = "/etc/airtime/pypo.cfg";
const CONF_FILE_RECORDER = "/etc/airtime/recorder.cfg";
const CONF_FILE_LIQUIDSOAP = "/etc/airtime/liquidsoap.cfg";
const CONF_FILE_MEDIAMONITOR = "/etc/airtime/MediaMonitor.cfg";
public static function IniFilesExist()
{
$configFiles = array(AirtimeIni::CONF_FILE_AIRTIME,
AirtimeIni::CONF_FILE_PYPO,
AirtimeIni::CONF_FILE_RECORDER,
AirtimeIni::CONF_FILE_LIQUIDSOAP);
AirtimeIni::CONF_FILE_LIQUIDSOAP,
AirtimeIni::CONF_FILE_MEDIAMONITOR);
$exist = false;
foreach ($configFiles as $conf) {
if (file_exists($conf)) {
@ -72,6 +74,10 @@ class AirtimeIni
echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting.";
exit(1);
}
if (!copy(__DIR__."/../../python_apps/pytag-fs/MediaMonitor.cfg", AirtimeIni::CONF_FILE_MEDIAMONITOR)){
echo "Could not copy MediaMonitor.cfg to /etc/airtime/. Exiting.";
exit(1);
}
}
/**
@ -96,6 +102,10 @@ class AirtimeIni
unlink(AirtimeIni::CONF_FILE_LIQUIDSOAP);
}
if (file_exists(AirtimeIni::CONF_FILE_MEDIAMONITOR)){
unlink(AirtimeIni::CONF_FILE_MEDIAMONITOR);
}
if (file_exists("etc/airtime")){
rmdir("/etc/airtime/");
}
@ -171,6 +181,7 @@ class AirtimeIni
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_AIRTIME, 'airtime_dir', AirtimeInstall::CONF_DIR_WWW);
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_PYPO, 'api_key', "'$api_key'");
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_RECORDER, 'api_key', "'$api_key'");
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_MEDIAMONITOR, 'api_key', "'$api_key'");
AirtimeIni::UpdateIniValue(AirtimeInstall::CONF_DIR_WWW.'/build/build.properties', 'project.home', AirtimeInstall::CONF_DIR_WWW);
}
}

View File

@ -126,7 +126,7 @@ class AirtimeInstall
$username = $CC_CONFIG['dsn']['username'];
$password = $CC_CONFIG['dsn']['password'];
$command = "echo \"CREATE USER $username ENCRYPTED PASSWORD '$password' LOGIN CREATEDB NOCREATEUSER;\" | su postgres -c psql";
$command = "echo \"CREATE USER $username ENCRYPTED PASSWORD '$password' LOGIN CREATEDB NOCREATEUSER;\" | su postgres -c psql 2>/dev/null";
@exec($command, $output, $results);
if ($results == 0) {
@ -150,7 +150,7 @@ class AirtimeInstall
$database = $CC_CONFIG['dsn']['database'];
$username = $CC_CONFIG['dsn']['username'];
$command = "echo \"CREATE DATABASE $database OWNER $username\" | su postgres -c psql";
$command = "echo \"CREATE DATABASE $database OWNER $username\" | su postgres -c psql 2>/dev/null";
@exec($command, $output, $results);
if ($results == 0) {

View File

@ -19,6 +19,7 @@ import json
import os
from urlparse import urlparse
AIRTIME_VERSION = "1.9.0"
def api_client_factory(config):
if config["api_client"] == "airtime":
@ -30,6 +31,24 @@ def api_client_factory(config):
print
sys.exit()
def recursive_urlencode(d):
def recursion(d, base=None):
pairs = []
for key, value in d.items():
if hasattr(value, 'values'):
pairs += recursion(value, key)
else:
new_pair = None
if base:
new_pair = "%s[%s]=%s" % (base, urllib.quote(unicode(key)), urllib.quote(unicode(value)))
else:
new_pair = "%s=%s" % (urllib.quote(unicode(key)), urllib.quote(unicode(value)))
pairs.append(new_pair)
return pairs
return '&'.join(recursion(d))
class ApiClientInterface:
# Implementation: optional
@ -98,6 +117,9 @@ class ApiClientInterface:
def upload_recorded_show(self):
pass
def update_media_metadata(self, md):
pass
# Put here whatever tests you want to run to make sure your API is working
def test(self):
pass
@ -121,6 +143,8 @@ class AirTimeApiClient(ApiClientInterface):
logger.debug("Trying to contact %s", url)
url = url.replace("%%api_key%%", self.config["api_key"])
version = -1
response = None
try:
response = urllib.urlopen(url)
data = response.read()
@ -129,34 +153,26 @@ class AirTimeApiClient(ApiClientInterface):
version = response_json['version']
logger.debug("Airtime Version %s detected", version)
except Exception, e:
try:
if e[1] == 401:
if (verbose):
print '#####################################'
print '# YOUR API KEY SEEMS TO BE INVALID:'
print '# ' + self.config["api_key"]
print '#####################################'
return False
except Exception, e:
pass
return -1
try:
if e[1] == 404:
if (verbose):
print '#####################################'
print '# Unable to contact the Airtime-API'
print '# ' + url
print '#####################################'
return False
except Exception, e:
pass
return -1
version = 0
logger.error("Unable to detect Airtime Version - %s, Response: %s", e, response)
return version
def test(self):
logger = logging.getLogger()
status, items = self.get_schedule('2010-01-01-00-00-00', '2011-01-01-00-00-00')
@ -175,12 +191,12 @@ class AirTimeApiClient(ApiClientInterface):
def is_server_compatible(self, verbose = True):
version = self.__get_airtime_version(verbose)
if (version == 0 or version == False):
if (version == -1):
if (verbose):
print 'Unable to get Airtime version number.'
print
return False
elif (version[0:4] != "1.9."):
elif (version[0:3] != AIRTIME_VERSION):
if (verbose):
print 'Airtime version: ' + str(version)
print 'pypo not compatible with this version of Airtime.'
@ -198,7 +214,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger()
# Construct the URL
#export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"]
export_url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["export_url"])
logger.info("Fetching schedule from %s", export_url)
@ -220,8 +235,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger()
try:
#src = "http://%s:%s/%s/%s" % \
#(self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_media_url"])
src = uri + "/api_key/%%api_key%%"
logger.info("try to download from %s to %s", src, dst)
src = src.replace("%%api_key%%", self.config["api_key"])
@ -239,7 +252,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger()
playlist = schedule[pkey]
schedule_id = playlist["schedule_id"]
#url = self.config["base_url"] + self.config["api_base"] + self.config["update_item_url"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_item_url"])
url = url.replace("%%schedule_id%%", str(schedule_id))
@ -268,7 +280,6 @@ class AirTimeApiClient(ApiClientInterface):
response = ''
try:
schedule_id = data
#url = self.config["base_url"] + self.config["api_base"] + self.config["update_start_playing_url"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_start_playing_url"])
url = url.replace("%%media_id%%", str(media_id))
url = url.replace("%%schedule_id%%", str(schedule_id))
@ -299,7 +310,6 @@ class AirTimeApiClient(ApiClientInterface):
response = None
try:
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["show_schedule_url"])
#url = self.config["base_url"] + self.config["api_base"] + self.config["show_schedule_url"]
logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
@ -319,7 +329,6 @@ class AirTimeApiClient(ApiClientInterface):
retries = int(self.config["upload_retries"])
retries_wait = int(self.config["upload_wait"])
#url = self.config["base_url"] + self.config["api_base"] + self.config["upload_file_url"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["upload_file_url"])
logger.debug(url)
@ -347,6 +356,26 @@ class AirTimeApiClient(ApiClientInterface):
return response
def update_media_metadata(self, md):
logger = logging.getLogger()
response = None
try:
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_media_url"])
logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
data = recursive_urlencode(md)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req).read()
logger.info("update media %s", response)
response = json.loads(response)
except Exception, e:
logger.error("Exception: %s", e)
return response
################################################################################

View File

@ -64,6 +64,21 @@ def get_current_script_dir():
#print current_script_dir[0:index]
return current_script_dir[0:index]
def is_natty():
try:
f = open('/etc/lsb-release')
except IOError as e:
#File doesn't exist, so we're not even dealing with Ubuntu
return False
for line in f:
split = line.split("=")
split[0] = split[0].strip(" \r\n")
split[1] = split[1].strip(" \r\n")
if split[0] == "DISTRIB_CODENAME" and split[1] == "natty":
return True
return False
try:
# load config file
@ -94,10 +109,19 @@ try:
create_path(config["file_dir"])
create_path(config["tmp_dir"])
if platform.architecture()[0] == '64bit':
architecture = platform.architecture()[0]
natty = is_natty()
if architecture == '64bit' and natty:
print "Installing 64-bit liquidsoap binary (Natty)"
shutil.copy("%s/../liquidsoap/liquidsoap-amd64-natty"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
elif architecture == '32bit' and natty:
print "Installing 32-bit liquidsoap binary (Natty)"
shutil.copy("%s/../liquidsoap/liquidsoap-i386-natty"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
elif architecture == '64bit' and not natty:
print "Installing 64-bit liquidsoap binary"
shutil.copy("%s/../liquidsoap/liquidsoap-amd64"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
elif platform.architecture()[0] == '32bit':
elif architecture == '32bit' and not natty:
print "Installing 32-bit liquidsoap binary"
shutil.copy("%s/../liquidsoap/liquidsoap-i386"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
else:

Binary file not shown.

Binary file not shown.

View File

@ -3,29 +3,14 @@
"""
Python part of radio playout (pypo)
The main functions are "fetch" (./pypo_cli.py -f) and "push" (./pypo_cli.py -p)
"""
import time
#import calendar
#import traceback
from optparse import *
import sys
import os
import signal
#import datetime
import logging
import logging.config
#import shutil
#import urllib
#import urllib2
#import pickle
#import telnetlib
#import random
#import string
#import operator
#import inspect
from Queue import Queue
from pypopush import PypoPush
@ -50,8 +35,6 @@ parser = OptionParser(usage=usage)
parser.add_option("-v", "--compat", help="Check compatibility with server API version", default=False, action="store_true", dest="check_compat")
parser.add_option("-t", "--test", help="Do a test to make sure everything is working properly.", default=False, action="store_true", dest="test")
parser.add_option("-f", "--fetch-scheduler", help="Fetch the schedule from server. This is a polling process that runs forever.", default=False, action="store_true", dest="fetch_scheduler")
parser.add_option("-p", "--push-scheduler", help="Push the schedule to Liquidsoap. This is a polling process that runs forever.", default=False, action="store_true", dest="push_scheduler")
parser.add_option("-b", "--cleanup", help="Cleanup", default=False, action="store_true", dest="cleanup")
parser.add_option("-c", "--check", help="Check the cached schedule and exit", default=False, action="store_true", dest="check")
@ -76,8 +59,7 @@ class Global:
def selfcheck(self):
self.api_client = api_client.api_client_factory(config)
if (not self.api_client.is_server_compatible()):
sys.exit()
return self.api_client.is_server_compatible()
def set_export_source(self, export_source):
self.export_source = export_source
@ -130,7 +112,8 @@ if __name__ == '__main__':
# initialize
g = Global()
g.selfcheck()
while not g.selfcheck(): time.sleep(5000)
logger = logging.getLogger()

View File

@ -7,11 +7,8 @@ base_port = 80
# where the binary files live
bin_dir = '/usr/lib/airtime/media-monitor'
# base path to store recordered shows at
base_recorded_files = '/var/tmp/airtime/show-recorder/'
# where the logging files live
log_dir = '/var/log/airtime/show-recorder'
log_dir = '/var/log/airtime/media-monitor'
# Value needed to access the API
api_key = 'AAA'
@ -22,8 +19,18 @@ api_base = 'api'
# URL to get the version number of the server API
version_url = 'version/api_key/%%api_key%%'
# URL to get the schedule of shows set to record
show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%'
# URL to tell Airtime to update file's meta data
update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%'
# URL to upload the recorded show's file to Airtime
upload_file_url = 'upload-recorded/format/json/api_key/%%api_key%%'
############################################
# RabbitMQ settings #
############################################
rabbitmq_host = 'localhost'
rabbitmq_user = 'guest'
rabbitmq_password = 'guest'
############################################
# Media-Monitor preferences #
############################################
check_filesystem_events = 30 #how long to queue up events performed on the files themselves.
check_airtime_events = 30 #how long to queue metadata input from airtime.

View File

@ -1,6 +1,26 @@
#!/usr/local/bin/python
import logging
import logging.config
import json
import time
import datetime
import os
import sys
import hashlib
import json
from subprocess import Popen, PIPE, STDOUT
from configobj import ConfigObj
import mutagen
import pyinotify
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
from pyinotify import WatchManager, Notifier, ProcessEvent
# For RabbitMQ
from kombu.connection import BrokerConnection
from kombu.messaging import Exchange, Queue, Consumer, Producer
from api_clients import api_client
# configure logging
try:
@ -11,41 +31,183 @@ except Exception, e:
# loading config file
try:
config = ConfigObj('/etc/airtime/recorder.cfg')
config = ConfigObj('/etc/airtime/MediaMonitor.cfg')
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
# watched events
mask = pyinotify.ALL_EVENTS
"""
list of supported easy tags in mutagen version 1.20
['albumartistsort', 'musicbrainz_albumstatus', 'lyricist', 'releasecountry', 'date', 'performer', 'musicbrainz_albumartistid', 'composer', 'encodedby', 'tracknumber', 'musicbrainz_albumid', 'album', 'asin', 'musicbrainz_artistid', 'mood', 'copyright', 'author', 'media', 'length', 'version', 'artistsort', 'titlesort', 'discsubtitle', 'website', 'musicip_fingerprint', 'conductor', 'compilation', 'barcode', 'performer:*', 'composersort', 'musicbrainz_discid', 'musicbrainz_albumtype', 'genre', 'isrc', 'discnumber', 'musicbrainz_trmid', 'replaygain_*_gain', 'musicip_puid', 'artist', 'title', 'bpm', 'musicbrainz_trackid', 'arranger', 'albumsort', 'replaygain_*_peak', 'organization']
"""
wm = WatchManager()
wdd = wm.add_watch('/srv/airtime/stor', mask, rec=True)
def checkRabbitMQ(notifier):
try:
notifier.connection.drain_events(timeout=int(config["check_airtime_events"]))
except Exception, e:
logger = logging.getLogger('root')
logger.info("%s", e)
class AirtimeNotifier(Notifier):
def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, threshold=0, timeout=None):
Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, threshold, timeout)
self.airtime2mutagen = {\
"track_title": "title",\
"artist_name": "artist",\
"album_title": "album",\
"genre": "genre",\
"mood": "mood",\
"track_number": "tracknumber",\
"bpm": "bpm",\
"label": "organization",\
"composer": "composer",\
"encoded_by": "encodedby",\
"conductor": "conductor",\
"year": "date",\
"info_url": "website",\
"isrc_number": "isrc",\
"copyright": "copyright",\
}
schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True)
schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem")
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
channel = self.connection.channel()
consumer = Consumer(channel, schedule_queue)
consumer.register_callback(self.handle_message)
consumer.consume()
def handle_message(self, body, message):
# ACK the message to take it off the queue
message.ack()
logger = logging.getLogger('root')
logger.info("Received md from RabbitMQ: " + body)
m = json.loads(message.body)
airtime_file = mutagen.File(m['filepath'], easy=True)
del m['filepath']
for key in m.keys() :
if m[key] != "" :
airtime_file[self.airtime2mutagen[key]] = m[key]
airtime_file.save()
class MediaMonitor(ProcessEvent):
def my_init(self):
"""
Method automatically called from ProcessEvent.__init__(). Additional
keyworded arguments passed to ProcessEvent.__init__() are then
delegated to my_init().
"""
self.api_client = api_client.api_client_factory(config)
self.mutagen2airtime = {\
"title": "track_title",\
"artist": "artist_name",\
"album": "album_title",\
"genre": "genre",\
"mood": "mood",\
"tracknumber": "track_number",\
"bpm": "bpm",\
"organization": "label",\
"composer": "composer",\
"encodedby": "encoded_by",\
"conductor": "conductor",\
"date": "year",\
"website": "info_url",\
"isrc": "isrc_number",\
"copyright": "copyright",\
}
self.logger = logging.getLogger('root')
self.temp_files = {}
def update_airtime(self, event):
self.logger.info("Updating Change to Airtime")
try:
f = open(event.pathname, 'rb')
m = hashlib.md5()
m.update(f.read())
md5 = m.hexdigest()
gunid = event.name.split('.')[0]
md = {'gunid':gunid, 'md5':md5}
file_info = mutagen.File(event.pathname, easy=True)
attrs = self.mutagen2airtime
for key in file_info.keys() :
if key in attrs :
md[attrs[key]] = file_info[key][0]
data = {'md': md}
response = self.api_client.update_media_metadata(data)
except Exception, e:
self.logger.info("%s", e)
class PTmp(ProcessEvent):
def process_IN_CREATE(self, event):
if event.dir :
global wm
wdd = wm.add_watch(event.pathname, mask, rec=True)
#print wdd.keys()
if not event.dir :
filename_info = event.name.split(".")
print "%s: %s" % (event.maskname, os.path.join(event.path, event.name))
#file created is a tmp file which will be modified and then moved back to the original filename.
if len(filename_info) > 2 :
self.temp_files[event.pathname] = None
#This is a newly imported file.
else :
pass
self.logger.info("%s: %s", event.maskname, event.pathname)
#event.path : /srv/airtime/stor/bd2
#event.name : bd2aa73b58d9c8abcced989621846e99.mp3
#event.pathname : /srv/airtime/stor/bd2/bd2aa73b58d9c8abcced989621846e99.mp3
def process_IN_MODIFY(self, event):
if not event.dir :
print event.path
filename_info = event.name.split(".")
print "%s: %s" % (event.maskname, os.path.join(event.path, event.name))
#file modified is not a tmp file.
if len(filename_info) == 2 :
self.update_airtime(event)
self.logger.info("%s: path: %s name: %s", event.maskname, event.path, event.name)
def process_IN_MOVED_FROM(self, event):
if event.pathname in self.temp_files :
del self.temp_files[event.pathname]
self.temp_files[event.cookie] = event.pathname
self.logger.info("%s: %s", event.maskname, event.pathname)
def process_IN_MOVED_TO(self, event):
if event.cookie in self.temp_files :
del self.temp_files[event.cookie]
self.update_airtime(event)
self.logger.info("%s: %s", event.maskname, event.pathname)
def process_default(self, event):
print "%s: %s" % (event.maskname, os.path.join(event.path, event.name))
self.logger.info("%s: %s", event.maskname, event.pathname)
if __name__ == '__main__':
try:
notifier = Notifier(wm, PTmp(), read_freq=2, timeout=1)
# watched events
mask = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO
#mask = pyinotify.ALL_EVENTS
wm = WatchManager()
wdd = wm.add_watch('/srv/airtime/stor', mask, rec=True, auto_add=True)
notifier = AirtimeNotifier(wm, MediaMonitor(), read_freq=int(config["check_filesystem_events"]), timeout=1)
notifier.coalesce_events()
notifier.loop()
notifier.loop(callback=checkRabbitMQ)
except KeyboardInterrupt:
notifier.stop()

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
try:
print "Starting daemontool script recorder"
os.system("svc -u /etc/service/recorder")
except Exception, e:
print "exception:" + str(e)

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
try:
print "Stopping daemontool script recorder"
p1 = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk", "/recorder.py/ && !/awk/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
recorder_pid = p2.communicate()[0].strip(" \n\r\t")
if (len(recorder_pid) > 0):
os.system("svc -d /etc/service/recorder 1>/dev/null 2>&1")
os.system("svc -d /etc/service/recorder/log 1>/dev/null 2>&1")
os.system("kill -2 %s" % recorder_pid)
print "Success."
else:
print "Not Running."
except Exception, e:
print "exception:" + str(e)

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec setuidgid pypo multilog t /var/log/airtime/media-monitor/main

View File

@ -0,0 +1,17 @@
#!/bin/sh
media_monitor_user="pypo"
# Location of pypo_cli.py Python script
media_monitor_path="/usr/lib/airtime/media-monitor/"
media_monitor_script="MediaMonitor.py"
api_client_path="/usr/lib/airtime/pypo/"
cd ${media_monitor_path}
echo "*** Daemontools: starting daemon"
exec 2>&1
# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr
export PYTHONPATH=${api_client_path}
setuidgid ${media_monitor_user} python -u ${media_monitor_path}${media_monitor_script}
# EOF

View File

@ -0,0 +1,127 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import os
import traceback
from optparse import *
import sys
import time
import datetime
import logging
import logging.config
import shutil
import string
import platform
from configobj import ConfigObj
from subprocess import Popen, PIPE, STDOUT
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
PATH_INI_FILE = '/etc/airtime/MediaMonitor.cfg'
def create_path(path):
if not (os.path.exists(path)):
print "Creating directory " + path
os.makedirs(path)
def create_user(username):
print "Checking for user "+username
p = Popen('id '+username, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
output = p.stdout.read()
if (output[0:3] != "uid"):
# Make the pypo user
print "Creating user "+username
os.system("adduser --system --quiet --group --shell /bin/bash "+username)
#set pypo password
p = os.popen('/usr/bin/passwd pypo 1>/dev/null 2>&1', 'w')
p.write('pypo\n')
p.write('pypo\n')
p.close()
else:
print "User already exists."
#add pypo to audio group
os.system("adduser " + username + " audio 1>/dev/null 2>&1")
#add pypo to www-data group
os.system("adduser " + username + " www-data 1>/dev/null 2>&1")
def copy_dir(src_dir, dest_dir):
if (os.path.exists(dest_dir)) and (dest_dir != "/"):
print "Removing old directory "+dest_dir
shutil.rmtree(dest_dir)
if not (os.path.exists(dest_dir)):
print "Copying directory "+os.path.realpath(src_dir)+" to "+os.path.realpath(dest_dir)
shutil.copytree(src_dir, dest_dir)
def get_current_script_dir():
current_script_dir = os.path.realpath(__file__)
index = current_script_dir.rindex('/')
#print current_script_dir[0:index]
return current_script_dir[0:index]
try:
# load config file
try:
config = ConfigObj(PATH_INI_FILE)
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
current_script_dir = get_current_script_dir()
print "Checking and removing any existing media monitor processes"
os.system("python %s/media-monitor-uninstall.py 1>/dev/null 2>&1"% current_script_dir)
time.sleep(5)
# Create users
create_user("pypo")
print "Creating log directories"
create_path(config["log_dir"])
os.system("chmod -R 755 " + config["log_dir"])
os.system("chown -R pypo:pypo "+config["log_dir"])
copy_dir("%s/.."%current_script_dir, config["bin_dir"])
print "Setting permissions"
os.system("chmod -R 755 "+config["bin_dir"])
os.system("chown -R pypo:pypo "+config["bin_dir"])
print "Creating symbolic links"
os.system("rm -f /usr/bin/airtime-media-monitor-start")
os.system("ln -s "+config["bin_dir"]+"/airtime-media-monitor-start /usr/bin/")
os.system("rm -f /usr/bin/airtime-media-monitor-stop")
os.system("ln -s "+config["bin_dir"]+"/airtime-media-monitor-stop /usr/bin/")
print "Installing recorder daemon"
create_path("/etc/service/media-monitor")
create_path("/etc/service/media-monitor/log")
shutil.copy("%s/media-monitor-daemontools.sh"%current_script_dir, "/etc/service/media-monitor/run")
shutil.copy("%s/media-monitor-daemontools-logger.sh"%current_script_dir, "/etc/service/media-monitor/log/run")
os.system("chmod -R 755 /etc/service/media-monitor")
os.system("chown -R pypo:pypo /etc/service/media-monitor")
print "Waiting for processes to start..."
time.sleep(5)
os.system("python /usr/bin/airtime-media-monitor-start")
time.sleep(2)
found = True
p = Popen('svstat /etc/service/media-monitor', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
output = p.stdout.read()
if (output.find("unable to open supervise/ok: file does not exist") >= 0):
found = False
print output
if not found:
print "Media monitor install has completed, but daemontools is not running, please make sure you have it installed and then reboot."
except Exception, e:
print "exception:" + str(e)
sys.exit(1)

View File

@ -0,0 +1,58 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import time
from configobj import ConfigObj
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
PATH_INI_FILE = '/etc/airtime/MediaMonitor.cfg'
def remove_path(path):
os.system("rm -rf " + path)
def remove_user(username):
os.system("killall -u %s 1>/dev/null 2>&1" % username)
#allow all process to be completely closed before we attempt to delete user
print "Waiting for processes to close..."
time.sleep(5)
os.system("deluser --remove-home " + username + " 1>/dev/null 2>&1")
def get_current_script_dir():
current_script_dir = os.path.realpath(__file__)
index = current_script_dir.rindex('/')
return current_script_dir[0:index]
try:
# load config file
try:
config = ConfigObj(PATH_INI_FILE)
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
os.system("python /usr/bin/airtime-media-monitor-stop")
print "Removing log directories"
remove_path(config["log_dir"])
print "Removing symlinks"
os.system("rm -f /usr/bin/airtime-media-monitor-start")
os.system("rm -f /usr/bin/airtime-media-monitor-stop")
print "Removing application files"
remove_path(config["bin_dir"])
print "Removing daemontool script media-monitor"
remove_path("rm -rf /etc/service/media-monitor")
remove_user("pypo")
print "Uninstall complete."
except Exception, e:
print "exception:" + str(e)

View File

@ -17,7 +17,7 @@ exec 2>&1
export PYTHONPATH=${api_client_path}
#su ${recorder_user} -c "python -u ${recorder_path}${recorder_script}"
setuidgid ${recorder_user} ${recorder_path}${recorder_script}
setuidgid ${recorder_user} python -u ${recorder_path}${recorder_script}
# EOF

View File

@ -55,6 +55,7 @@ class ShowRecorder(Thread):
self.start_time = start_time
self.filetype = filetype
self.show_instance = show_instance
self.logger = logging.getLogger('root')
def record_show(self):
@ -67,11 +68,9 @@ class ShowRecorder(Thread):
#-ge:0.1,0.1,0,-1
args = command.split(" ")
print "starting record"
self.logger.info("starting record")
code = call(args)
print "finishing record, return code %s" % (code)
self.logger.info("finishing record, return code %s", code)
return code, filepath
@ -94,7 +93,7 @@ class ShowRecorder(Thread):
if code == 0:
self.upload_file(filepath)
else:
print "problem recording show"
self.logger.info("problem recording show")
class Record():
@ -102,6 +101,7 @@ class Record():
def __init__(self):
self.api_client = api_client.api_client_factory(config)
self.shows_to_record = {}
self.logger = logging.getLogger('root')
def process_shows(self, shows):
@ -123,21 +123,21 @@ class Record():
start_time = sorted_show_keys[0]
next_show = getDateTimeObj(start_time)
print next_show
print tnow
self.logger.debug("Next show %s", next_show)
self.logger.debug("Now %s", tnow)
delta = next_show - tnow
min_delta = datetime.timedelta(seconds=60)
if delta <= min_delta:
print "sleeping %s seconds until show" % (delta.seconds)
self.logger.debug("sleeping %s seconds until show", delta.seconds)
time.sleep(delta.seconds)
show_length = self.shows_to_record[start_time][0]
show_instance = self.shows_to_record[start_time][1]
show_name = self.shows_to_record[start_time][2]
show = ShowRecorder(show_instance, show_length.seconds, show_name, start_time, filetype="mp3", )
show = ShowRecorder(show_instance, show_length.seconds, show_name, start_time, filetype="mp3")
show.start()
#remove show from shows to record.
@ -165,7 +165,3 @@ if __name__ == '__main__':
recorder.get_shows()
time.sleep(5)