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

Conflicts:
	install/airtime-install.php
This commit is contained in:
Paul Baranowski 2011-03-28 15:41:49 -04:00
commit c17ee178d1
237 changed files with 2270 additions and 23932 deletions

4
.gitignore vendored
View File

@ -1,5 +1,5 @@
.*
*.pyc
files/
pypo/liquidsoap/liquidsoap
/files
python_apps/pypo/liquidsoap/liquidsoap
build/build.properties

View File

@ -1,7 +1,7 @@
<?php
require_once (__DIR__."/configs/navigation.php");
require_once (__DIR__."/configs/ACL.php");
require_once __DIR__."/configs/navigation.php";
require_once __DIR__."/configs/ACL.php";
require_once 'propel/runtime/lib/Propel.php';
Propel::init(__DIR__."/configs/airtime-conf.php");
@ -10,17 +10,20 @@ Propel::init(__DIR__."/configs/airtime-conf.php");
$tz = ini_get('date.timezone') ? ini_get('date.timezone') : 'UTC';
date_default_timezone_set($tz);
require_once (__DIR__."/configs/constants.php");
require_once (__DIR__."/configs/conf.php");
require_once __DIR__."/configs/constants.php";
require_once __DIR__."/configs/conf.php";
require_once 'DB.php';
require_once 'Soundcloud.php';
require_once 'Playlist.php';
require_once 'StoredFile.php';
require_once 'Schedule.php';
require_once 'Shows.php';
require_once 'Users.php';
require_once 'RabbitMq.php';
require_once __DIR__.'/controllers/plugins/RabbitMqPlugin.php';
global $CC_CONFIG, $CC_DBC;
global $CC_CONFIG, $CC_DBC;
$dsn = $CC_CONFIG['dsn'];
$CC_DBC = DB::connect($dsn, FALSE);
@ -33,6 +36,9 @@ $CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC);
//Zend_Session::start();
Zend_Validate::setDefaultNamespaces("Zend");
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new RabbitMqPlugin());
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initDoctype()
@ -45,9 +51,7 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
protected function _initHeadLink()
{
$view = $this->getResource('view');
$view->headLink()->appendStylesheet('/css/redmond/jquery-ui-1.8.8.custom.css');
$this->view->headLink()->appendStylesheet('/css/pro_dropdown_3.css');
$this->view->headLink()->appendStylesheet('/css/styles.css');
}
@ -64,17 +68,20 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
$this->view->headScript()->appendFile('/js/playlist/helperfunctions.js','text/javascript');
$this->view->headScript()->appendFile('/js/playlist/playlist.js','text/javascript');
$view->headScript()->appendFile('/js/airtime/common/common.js','text/javascript');
$view->headScript()->appendFile('/js/airtime/common/common.js','text/javascript');
}
protected function _initViewHelpers(){
protected function _initViewHelpers()
{
$view = $this->getResource('view');
$view->addHelperPath('../application/views/helpers', 'Airtime_View_Helper');
}
protected function _initTitle(){
protected function _initTitle()
{
$view = $this->getResource('view');
$view->headTitle(Application_Model_Preference::GetHeadTitle());
}
}

View File

@ -1,14 +1,15 @@
<?php
define('AIRTIME_VERSION', '1.7.0 alpha');
define('AIRTIME_VERSION', '1.7.0-alpha');
define('AIRTIME_COPYRIGHT_DATE', '2010-2011');
define('AIRTIME_REST_VERSION', '1.1');
// These are the default values for the config.
global $CC_CONFIG;
$values = load_airtime_config();
// **********************************
// **********************************
// ***** START CUSTOMIZING HERE *****
// **********************************
// **********************************
// Set the location where you want to store all of your audio files.
//
@ -25,10 +26,16 @@ $CC_CONFIG = array(
// Name of the web server user
'webServerUser' => 'www-data',
// ***********************************************************************
'rabbitmq' => array("host" => "127.0.0.1",
"port" => "5672",
"user" => "guest",
"password" => "guest",
"vhost" => "/"),
// ***********************************************************************
// STOP CUSTOMIZING HERE
//
// You don't need to touch anything below this point.
// You don't need to touch anything below this point.
// ***********************************************************************
'baseFilesDir' => $baseFilesDir,
@ -46,6 +53,9 @@ $CC_CONFIG = array(
'apiKey' => $values['api_key'],
'apiPath' => '/api/',
'soundcloud-client-id' => '2CLCxcSXYzx7QhhPVHN4A',
'soundcloud-client-secret' => 'pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs',
"rootDir" => __DIR__."/../..",
'pearPath' => dirname(__FILE__).'/../../library/pear',
'zendPath' => dirname(__FILE__).'/../../library/Zend',
@ -55,26 +65,26 @@ $CC_CONFIG = array(
//'AdminsGr' => 'Admins',
// name of station preferences group
'StationPrefsGr'=> 'StationPrefs',
// 'StationPrefsGr'=> 'StationPrefs',
// name of 'all users' group
//'AllGr' => 'All',
/* ==================================== application-specific configuration */
'objtypes' => array(
'Storage' => array(/*'Folder',*/ 'File' /*, 'Replica'*/),
'File' => array(),
'audioclip' => array(),
'playlist' => array(),
),
'allowedActions'=> array(
'File' => array('editPrivs', 'write', 'read'),
'audioclip' => array('editPrivs', 'write', 'read'),
'playlist' => array('editPrivs', 'write', 'read'),
),
'allActions' => array(
'editPrivs', 'write', 'read', 'subjects'
),
// 'objtypes' => array(
// 'Storage' => array(/*'Folder',*/ 'File' /*, 'Replica'*/),
// 'File' => array(),
// 'audioclip' => array(),
// 'playlist' => array(),
// ),
// 'allowedActions'=> array(
// 'File' => array('editPrivs', 'write', 'read'),
// 'audioclip' => array('editPrivs', 'write', 'read'),
// 'playlist' => array('editPrivs', 'write', 'read'),
// ),
// 'allActions' => array(
// 'editPrivs', 'write', 'read', 'subjects'
// ),
/* =================================================== cron configuration */
'cronUserName' => 'www-data',
@ -82,7 +92,7 @@ $CC_CONFIG = array(
'lockfile' => dirname(__FILE__).'/stor/buffer/cron.lock',
'cronfile' => dirname(__FILE__).'/cron/croncall.php',
'paramdir' => dirname(__FILE__).'/cron/params',
'systemPrefId' => "0", // ID for system prefs in prefs table
// 'systemPrefId' => "0", // ID for system prefs in prefs table
);
// Add database table names
@ -94,10 +104,8 @@ $CC_CONFIG['permTable'] = $CC_CONFIG['tblNamePrefix'].'perms';
$CC_CONFIG['sessTable'] = $CC_CONFIG['tblNamePrefix'].'sess';
$CC_CONFIG['subjTable'] = $CC_CONFIG['tblNamePrefix'].'subjs';
$CC_CONFIG['smembTable'] = $CC_CONFIG['tblNamePrefix'].'smemb';
$CC_CONFIG['transTable'] = $CC_CONFIG['tblNamePrefix'].'trans';
$CC_CONFIG['prefTable'] = $CC_CONFIG['tblNamePrefix'].'pref';
$CC_CONFIG['scheduleTable'] = $CC_CONFIG['tblNamePrefix'].'schedule';
$CC_CONFIG['backupTable'] = $CC_CONFIG['tblNamePrefix'].'backup';
$CC_CONFIG['playListTimeView'] = $CC_CONFIG['tblNamePrefix'].'playlisttimes';
$CC_CONFIG['showSchedule'] = $CC_CONFIG['tblNamePrefix'].'show_schedule';
$CC_CONFIG['showDays'] = $CC_CONFIG['tblNamePrefix'].'show_days';
@ -106,16 +114,15 @@ $CC_CONFIG['showInstances'] = $CC_CONFIG['tblNamePrefix'].'show_instances';
$CC_CONFIG['playListSequence'] = $CC_CONFIG['playListTable'].'_id';
$CC_CONFIG['filesSequence'] = $CC_CONFIG['filesTable'].'_id';
$CC_CONFIG['transSequence'] = $CC_CONFIG['transTable'].'_id';
$CC_CONFIG['prefSequence'] = $CC_CONFIG['prefTable'].'_id';
$CC_CONFIG['permSequence'] = $CC_CONFIG['permTable'].'_id';
$CC_CONFIG['subjSequence'] = $CC_CONFIG['subjTable'].'_id';
$CC_CONFIG['smembSequence'] = $CC_CONFIG['smembTable'].'_id';
// System users/groups - they cannot be deleted
$CC_CONFIG['sysSubjs'] = array(
'root', /*$CC_CONFIG['AdminsGr'],*/ /*$CC_CONFIG['AllGr'],*/ $CC_CONFIG['StationPrefsGr']
);
//$CC_CONFIG['sysSubjs'] = array(
// 'root', /*$CC_CONFIG['AdminsGr'],*/ /*$CC_CONFIG['AllGr'],*/ $CC_CONFIG['StationPrefsGr']
//);
// Add libs to the PHP path
$old_include_path = get_include_path();
@ -125,9 +132,9 @@ set_include_path('.'.PATH_SEPARATOR.$CC_CONFIG['pearPath']
function load_airtime_config(){
$ini_array = parse_ini_file(dirname(__FILE__).'/../../build/airtime.conf', true);
return array(
'database' => array(
'database' => array(
'username' => $ini_array['database']['dbuser'],
'password' => $ini_array['database']['dbpass'],
'hostspec' => $ini_array['database']['host'],

View File

@ -2,7 +2,7 @@
/*
* Navigation container (config/array)
* Each element in the array will be passed to
* Zend_Navigation_Page::factory() when constructing
* the navigation container below.
@ -16,7 +16,7 @@ $pages = array(
'resource' => 'nowplaying'
),
array(
'label' => 'Add Audio',
'label' => 'Add Media',
'module' => 'default',
'controller' => 'Plupload',
'action' => 'plupload',
@ -51,7 +51,7 @@ $pages = array(
'module' => 'default',
'controller' => 'user',
'action' => 'add-user',
'resource' => 'user'
'resource' => 'user'
)
)
),
@ -64,10 +64,10 @@ $pages = array(
)
);
// Create container from array
$container = new Zend_Navigation($pages);
$container->id = "nav";
//store it in the registry:
Zend_Registry::set('Zend_Navigation', $container);

View File

@ -6,8 +6,10 @@ class ApiController extends Zend_Controller_Action
public function init()
{
/* Initialize action controller here */
$ajaxContext = $this->_helper->getHelper('AjaxContext');
$ajaxContext->addActionContext('version', 'json')
$context = $this->_helper->getHelper('contextSwitch');
$context->addActionContext('version', 'json')
->addActionContext('recorded-shows', 'json')
->addActionContext('upload-recorded', 'json')
->initContext();
}
@ -24,7 +26,7 @@ class ApiController extends Zend_Controller_Action
* in application/conf.php
*
* @return void
*
*
*/
public function versionAction()
{
@ -101,6 +103,29 @@ class ApiController extends Zend_Controller_Action
exit;
}
public function liveInfoAction(){
global $CC_CONFIG;
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
$result = Schedule::GetPlayOrderRange(0, 1);
$date = new Application_Model_DateHelper;
$timeNow = $date->getDate();
$result = array("env"=>APPLICATION_ENV,
"schedulerTime"=>gmdate("Y-m-d H:i:s"),
"currentShow"=>Show_DAL::GetCurrentShow($timeNow),
"nextShow"=>Show_DAL::GetNextShows($timeNow, 5),
"timezone"=> date("T"),
"timezoneOffset"=> date("Z"));
//echo json_encode($result);
header("Content-type: text/javascript");
echo $_GET['callback'].'('.json_encode($result).')';
}
public function scheduleAction()
{
global $CC_CONFIG;
@ -110,6 +135,7 @@ class ApiController extends Zend_Controller_Action
$this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if(!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
@ -119,18 +145,10 @@ class ApiController extends Zend_Controller_Action
PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$from = $this->_getParam("from");
$to = $this->_getParam("to");
if (Schedule::ValidPypoTimeFormat($from) && Schedule::ValidPypoTimeFormat($to)) {
$result = Schedule::ExportRangeAsJson($from, $to);
$result['stream_metadata'] = array();
$result['stream_metadata']['format'] = Application_Model_Preference::GetStreamLabelFormat();
$result['stream_metadata']['station_name'] = Application_Model_Preference::GetStationName();
echo json_encode($result);
}
$result = Schedule::GetScheduledPlaylists();
echo json_encode($result);
}
public function notifyMediaItemStartPlayAction()
{
global $CC_CONFIG;
@ -197,5 +215,53 @@ class ApiController extends Zend_Controller_Action
exit;
}
}
public function recordedShowsAction()
{
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;
}
$today_timestamp = date("Y-m-d H:i:s");
$this->view->shows = Show::getShows($today_timestamp, null, $excludeInstance=NULL, $onlyRecord=TRUE);
}
public function uploadRecordedAction()
{
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;
}
$upload_dir = ini_get("upload_tmp_dir");
$file = StoredFile::uploadFile($upload_dir);
$show_instance = $this->_getParam('show_instance');
$show_inst = new ShowInstance($show_instance);
$show_inst->setRecordedFile($file->getId());
if(Application_Model_Preference::GetDoSoundCloudUpload())
{
$show = new Show($show_inst->getShowId());
$description = $show->getDescription();
$soundcloud = new ATSoundcloud();
$soundcloud->uploadTrack($file->getRealFilePath(), $file->getName(), $description);
}
$this->view->id = $file->getId();
}
}

View File

@ -11,144 +11,6 @@ class PluploadController extends Zend_Controller_Action
->initContext();
}
public function upload($targetDir)
{
// HTTP headers for no cache etc
header('Content-type: text/plain; charset=UTF-8');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
// Settings
//$targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload";
$cleanupTargetDir = false; // Remove old files
$maxFileAge = 60 * 60; // Temp file age in seconds
// 5 minutes execution time
@set_time_limit(5 * 60);
// usleep(5000);
// Get parameters
$chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0;
$chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0;
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
// Clean the fileName for security reasons
//$fileName = preg_replace('/[^\w\._]+/', '', $fileName);
// Create target dir
if (!file_exists($targetDir))
@mkdir($targetDir);
// Remove old temp files
if (is_dir($targetDir) && ($dir = opendir($targetDir))) {
while (($file = readdir($dir)) !== false) {
$filePath = $targetDir . DIRECTORY_SEPARATOR . $file;
// Remove temp files if they are older than the max age
if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge))
@unlink($filePath);
}
closedir($dir);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
// Look for the content type header
if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
$contentType = $_SERVER["HTTP_CONTENT_TYPE"];
if (isset($_SERVER["CONTENT_TYPE"]))
$contentType = $_SERVER["CONTENT_TYPE"];
if (strpos($contentType, "multipart") !== false) {
if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen($_FILES['file']['tmp_name'], "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
unlink($_FILES['file']['tmp_name']);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
} else {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen("php://input", "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
$audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName;
$md5 = md5_file($audio_file);
$duplicate = StoredFile::RecallByMd5($md5);
if ($duplicate) {
if (PEAR::isError($duplicate)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}');
}
else {
$duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE);
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}');
}
}
$metadata = Metadata::LoadFromFile($audio_file);
if (PEAR::isError($metadata)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}');
}
// #2196 no id tag -> use the original filename
if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) {
$metadata[UI_MDATA_KEY_TITLE] = basename($audio_file);
$metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file);
}
// setMetadataBatch doesnt like these values
unset($metadata['audio']);
unset($metadata['playtime_seconds']);
$values = array(
"filename" => basename($audio_file),
"filepath" => $audio_file,
"filetype" => "audioclip",
"mime" => $metadata[UI_MDATA_KEY_FORMAT],
"md5" => $md5
);
$storedFile = StoredFile::Insert($values);
if (PEAR::isError($storedFile)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}');
}
$storedFile->setMetadataBatch($metadata);
// Return JSON-RPC response
die('{"jsonrpc" : "2.0", "id" : '.$storedFile->getId().' }');
}
public function indexAction()
{
@ -158,13 +20,9 @@ class PluploadController extends Zend_Controller_Action
public function uploadAction()
{
$upload_dir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload";
$this->upload($upload_dir);
}
$file = StoredFile::uploadFile($upload_dir);
public function uploadRecordedAction()
{
$upload_dir = ini_get("upload_tmp_dir");
$this->upload($upload_dir);
die('{"jsonrpc" : "2.0", "id" : '.$file->getId().' }');
}
public function pluploadAction()

View File

@ -29,7 +29,11 @@ class PreferenceController extends Zend_Controller_Action
$values = $form->getValues();
Application_Model_Preference::SetHeadTitle($values["stationName"], $this->view);
Application_Model_Preference::SetDefaultFade($values["stationDefaultFade"]);
Application_Model_Preference::SetStreamLabelFormat($values["streamFormat"]);
Application_Model_Preference::SetStreamLabelFormat($values["streamFormat"]);
Application_Model_Preference::SetDoSoundCloudUpload($values["UseSoundCloud"]);
Application_Model_Preference::SetSoundCloudUser($values["SoundCloudUser"]);
Application_Model_Preference::SetSoundCloudPassword($values["SoundCloudPassword"]);
Application_Model_Preference::SetSoundCloudTags($values["SoundCloudTags"]);
$this->view->statusMsg = "Preferences Updated.";
}

View File

@ -1,28 +0,0 @@
<?php
class RecorderController extends Zend_Controller_Action
{
public function init()
{
$ajaxContext = $this->_helper->getHelper('contextSwitch');
$ajaxContext->addActionContext('get-show-schedule', 'json')
->addActionContext('get-uploaded-file', 'json')
->initContext();
}
public function indexAction()
{
// action body
}
public function getShowScheduleAction()
{
$today_timestamp = date("Y-m-d H:i:s");
$this->view->shows = Show::getShows($today_timestamp, null, $excludeInstance=NULL, $onlyRecord=TRUE);
}
}

View File

@ -19,9 +19,9 @@ class ScheduleController extends Zend_Controller_Action
->addActionContext('schedule-show-dialog', 'json')
->addActionContext('show-content-dialog', 'json')
->addActionContext('clear-show', 'json')
->addActionContext('get-current-playlist', 'json')
->addActionContext('get-current-playlist', 'json')
->addActionContext('find-playlists', 'json')
->addActionContext('remove-group', 'json')
->addActionContext('remove-group', 'json')
->addActionContext('edit-show', 'json')
->addActionContext('add-show', 'json')
->addActionContext('cancel-show', 'json')
@ -64,7 +64,7 @@ class ScheduleController extends Zend_Controller_Action
$formRepeats->removeDecorator('DtDdWrapper');
$formStyle->removeDecorator('DtDdWrapper');
$formRecord->removeDecorator('DtDdWrapper');
$this->view->what = $formWhat;
$this->view->when = $formWhen;
@ -84,7 +84,7 @@ class ScheduleController extends Zend_Controller_Action
{
$start = $this->_getParam('start', null);
$end = $this->_getParam('end', null);
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
if($user->isAdmin())
@ -111,6 +111,7 @@ class ScheduleController extends Zend_Controller_Action
if(isset($error))
$this->view->error = $error;
}
public function resizeShowAction()
@ -134,7 +135,7 @@ class ScheduleController extends Zend_Controller_Action
public function deleteShowAction()
{
$showInstanceId = $this->_getParam('id');
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
@ -158,41 +159,41 @@ class ScheduleController extends Zend_Controller_Action
if (strtotime($today_timestamp) < strtotime($show->getShowStart())) {
if (($user->isHost($show->getShowId()) || $user->isAdmin()) && !$show->isRecorded() && !$show->isRebroadcast()) {
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/schedule-show-dialog'.$params,
'callback' => 'window["buildScheduleDialog"]'), 'title' => 'Add Content');
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/clear-show'.$params,
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/schedule-show-dialog'.$params,
'callback' => 'window["buildScheduleDialog"]'), 'title' => 'Add / Remove Content');
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/clear-show'.$params,
'callback' => 'window["scheduleRefetchEvents"]'), 'title' => 'Remove All Content');
}
}
if(!$show->isRecorded()) {
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/show-content-dialog'.$params,
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/show-content-dialog'.$params,
'callback' => 'window["buildContentDialog"]'), 'title' => 'Show Content');
}
if (strtotime($show->getShowStart()) <= strtotime($today_timestamp) &&
strtotime($today_timestamp) < strtotime($show->getShowEnd())) {
$menu[] = array('action' => array('type' => 'fn',
//'url' => '/Schedule/cancel-current-show'.$params,
'callback' => "window['confirmCancelShow']($id)"),
'title' => 'Cancel Current Show');
'callback' => "window['confirmCancelShow']($id)"),
'title' => 'Cancel Current Show');
}
if (strtotime($today_timestamp) < strtotime($show->getShowStart())) {
if ($user->isAdmin()) {
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/delete-show'.$params,
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/delete-show'.$params,
'callback' => 'window["scheduleRefetchEvents"]'), 'title' => 'Delete This Instance');
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/cancel-show'.$params,
$menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/cancel-show'.$params,
'callback' => 'window["scheduleRefetchEvents"]'), 'title' => 'Delete This Instance and All Following');
}
}
//returns format jjmenu is looking for.
die(json_encode($menu));
}
@ -219,7 +220,7 @@ class ScheduleController extends Zend_Controller_Action
$this->view->timeFilled = $show->getTimeScheduled();
$this->view->percentFilled = $show->getPercentScheduled();
$this->view->chosen = $this->view->render('schedule/scheduled-content.phtml');
$this->view->chosen = $this->view->render('schedule/scheduled-content.phtml');
unset($this->view->showContent);
}
@ -242,7 +243,7 @@ class ScheduleController extends Zend_Controller_Action
public function findPlaylistsAction()
{
$post = $this->getRequest()->getPost();
$show = new ShowInstance($this->sched_sess->showInstanceId);
$playlists = $show->searchPlaylistsForShow($post);
@ -267,7 +268,7 @@ class ScheduleController extends Zend_Controller_Action
$this->view->showContent = $show->getShowContent();
$this->view->timeFilled = $show->getTimeScheduled();
$this->view->percentFilled = $show->getPercentScheduled();
$this->view->chosen = $this->view->render('schedule/scheduled-content.phtml');
$this->view->chosen = $this->view->render('schedule/scheduled-content.phtml');
unset($this->view->showContent);
}
@ -275,7 +276,7 @@ class ScheduleController extends Zend_Controller_Action
{
$showInstanceId = $this->_getParam('id');
$this->sched_sess->showInstanceId = $showInstanceId;
$show = new ShowInstance($showInstanceId);
$start_timestamp = $show->getShowStart();
$end_timestamp = $show->getShowEnd();
@ -285,14 +286,14 @@ class ScheduleController extends Zend_Controller_Action
$this->view->error = "cannot schedule an overlapping show.";
return;
}
$start = explode(" ", $start_timestamp);
$end = explode(" ", $end_timestamp);
$startTime = explode(":", $start[1]);
$endTime = explode(":", $end[1]);
$dateInfo_s = getDate(strtotime($start_timestamp));
$dateInfo_e = getDate(strtotime($end_timestamp));
$this->view->showContent = $show->getShowContent();
$this->view->timeFilled = $show->getTimeScheduled();
$this->view->showName = $show->getName();
@ -308,7 +309,7 @@ class ScheduleController extends Zend_Controller_Action
$this->view->startTime = sprintf("%d:%02d", $startTime[0], $startTime[1]);
$this->view->endTime = sprintf("%d:%02d", $endTime[0], $endTime[1]);
$this->view->chosen = $this->view->render('schedule/scheduled-content.phtml');
$this->view->chosen = $this->view->render('schedule/scheduled-content.phtml');
$this->view->dialog = $this->view->render('schedule/schedule-show-dialog.phtml');
unset($this->view->showContent);
}
@ -335,7 +336,7 @@ class ScheduleController extends Zend_Controller_Action
{
$js = $this->_getParam('data');
$data = array();
//need to convert from serialized jQuery array.
foreach($js as $j){
$data[$j["name"]] = $j["value"];
@ -392,8 +393,8 @@ class ScheduleController extends Zend_Controller_Action
$rebroadAb = $formAbsoluteRebroadcast->isValid($data);
$rebroad = $formRebroadcast->isValid($data);
if ($what && $when && $repeats && $who && $style && $record && $rebroadAb && $rebroad) {
if ($what && $when && $repeats && $who && $style && $record && $rebroadAb && $rebroad) {
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
if($user->isAdmin()) {
@ -413,7 +414,7 @@ class ScheduleController extends Zend_Controller_Action
$formRecord->reset();
$formAbsoluteRebroadcast->reset();
$formRebroadcast->reset();
$this->view->newForm = $this->view->render('schedule/add-show-form.phtml');
}
else {
@ -426,7 +427,7 @@ class ScheduleController extends Zend_Controller_Action
{
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
if($user->isAdmin()) {
$showInstanceId = $this->_getParam('id');
@ -434,14 +435,14 @@ class ScheduleController extends Zend_Controller_Action
$show = new Show($showInstance->getShowId());
$show->cancelShow($showInstance->getShowStart());
}
}
}
public function cancelCurrentShowAction()
{
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
if($user->isAdmin()) {
$showInstanceId = $this->_getParam('id');
$show = new ShowInstance($showInstanceId);

View File

@ -23,6 +23,8 @@ class UserController extends Zend_Controller_Action
$this->view->headScript()->appendFile('/js/airtime/user/user.js','text/javascript');
$request = $this->getRequest();
$form = new Application_Form_AddUser();
$this->view->successMessage = "";
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
@ -42,6 +44,12 @@ class UserController extends Zend_Controller_Action
$user->save();
$form->reset();
if (strlen($formdata['user_id']) == 0){
$this->view->successMessage = "<div class='success'>User added successfully!</div>";
} else {
$this->view->successMessage = "<div class='success'>User updated successfully!</div>";
}
}
}
}

View File

@ -110,7 +110,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
$controller = strtolower($request->getControllerName());
if ($controller == 'api' || $controller == 'recorder' || $controller == 'plupload' && $request->getActionName() == 'upload-recorded'){
if ($controller == 'api'){
$this->setRoleName("G");
}

View File

@ -0,0 +1,9 @@
<?php
class RabbitMqPlugin extends Zend_Controller_Plugin_Abstract
{
public function dispatchLoopShutdown()
{
RabbitMq::PushScheduleFinal();
}
}

View File

@ -9,17 +9,18 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm
array('ViewScript', array('viewScript' => 'form/add-show-rebroadcast.phtml'))
));
$relativeDates = array();
$relativeDates[""] = "";
for($i=0; $i <=30; $i++) {
$relativeDates["$i days"] = "+$i days";
}
//Add date select
$this->addElement('select', 'add_show_rebroadcast_date_1', array(
'required' => false,
'class' => ' input_select',
'multiOptions' => array(
"" => "",
"0 days" => "+0 days",
"1 day" => "+1 day",
"2 days" => "+2 days",
"3 days" => "+3 days"
),
'multiOptions' => $relativeDates,
'decorators' => array(
'ViewHelper'
)
@ -44,13 +45,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm
$this->addElement('select', 'add_show_rebroadcast_date_2', array(
'required' => false,
'class' => ' input_select',
'multiOptions' => array(
"" => "",
"0 days" => "+0 days",
"1 day" => "+1 day",
"2 days" => "+2 days",
"3 days" => "+3 days"
),
'multiOptions' => $relativeDates,
'decorators' => array(
'ViewHelper'
)
@ -75,13 +70,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm
$this->addElement('select', 'add_show_rebroadcast_date_3', array(
'required' => false,
'class' => ' input_select',
'multiOptions' => array(
"" => "",
"0 days" => "+0 days",
"1 day" => "+1 day",
"2 days" => "+2 days",
"3 days" => "+3 days"
),
'multiOptions' => $relativeDates,
'decorators' => array(
'ViewHelper'
)
@ -106,13 +95,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm
$this->addElement('select', 'add_show_rebroadcast_date_4', array(
'required' => false,
'class' => ' input_select',
'multiOptions' => array(
"" => "",
"0 days" => "+0 days",
"1 day" => "+1 day",
"2 days" => "+2 days",
"3 days" => "+3 days"
),
'multiOptions' => $relativeDates,
'decorators' => array(
'ViewHelper'
)
@ -137,13 +120,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm
$this->addElement('select', 'add_show_rebroadcast_date_5', array(
'required' => false,
'class' => ' input_select',
'multiOptions' => array(
"" => "",
"0 days" => "+0 days",
"1 day" => "+1 day",
"2 days" => "+2 days",
"3 days" => "+3 days"
),
'multiOptions' => $relativeDates,
'decorators' => array(
'ViewHelper'
)

View File

@ -16,7 +16,7 @@ class Application_Form_AddShowWhat extends Zend_Form_SubForm
// Add URL element
$this->addElement('text', 'add_show_url', array(
'label' => 'Show URL:',
'label' => 'Website:',
'class' => 'input_text',
'required' => false,
'filters' => array('StringTrim'),

View File

@ -68,12 +68,13 @@ class Application_Form_AddUser extends Zend_Form
$this->addElement($jabber);
$select = new Zend_Form_Element_Select('type');
$select->setLabel('User Type:');
$select->setAttrib('class', 'input_select');
$select->setAttrib('style', 'width: 40%');
$select->setMultiOptions(array(
"A" => "admin",
"H" => "host",
"G" => "guest",
"G" => "Guest",
"H" => "Host",
"A" => "Admin"
));
$select->setRequired(true);
$this->addElement($select);

View File

@ -7,7 +7,7 @@ class Application_Form_Preferences extends Zend_Form
{
$this->setAction('/Preference/update')->setMethod('post');
// Add login element
//Station name
$this->addElement('text', 'stationName', array(
'class' => 'input_text',
'label' => 'Station Name:',
@ -17,12 +17,12 @@ class Application_Form_Preferences extends Zend_Form
'value' => Application_Model_Preference::GetValue("station_name")
));
$defaultFade = Application_Model_Preference::GetValue("default_fade");
$defaultFade = Application_Model_Preference::GetDefaultFade();
if($defaultFade == ""){
$defaultFade = '00:00:00.000000';
}
// Add login element
//Default station fade
$this->addElement('text', 'stationDefaultFade', array(
'class' => 'input_text',
'label' => 'Default Fade:',
@ -42,11 +42,46 @@ class Application_Form_Preferences extends Zend_Form
$stream_format->setValue(Application_Model_Preference::GetStreamLabelFormat());
$this->addElement($stream_format);
$this->addElement('checkbox', 'UseSoundCloud', array(
'label' => 'Automatically Upload Recorded Shows To SoundCloud',
'required' => false,
'value' => Application_Model_Preference::GetDoSoundCloudUpload()
));
//SoundCloud Username
$this->addElement('text', 'SoundCloudUser', array(
'class' => 'input_text',
'label' => 'SoundCloud Email:',
'required' => false,
'filters' => array('StringTrim'),
'value' => Application_Model_Preference::GetSoundCloudUser()
));
//SoundCloud Password
$this->addElement('text', 'SoundCloudPassword', array(
'class' => 'input_text',
'label' => 'SoundCloud Password:',
'required' => false,
'filters' => array('StringTrim'),
'value' => Application_Model_Preference::GetSoundCloudPassword()
));
// Add the description element
$this->addElement('textarea', 'SoundCloudTags', array(
'label' => 'space separated SoundCloud Tags',
'required' => false,
'class' => 'input_text_area',
'value' => Application_Model_Preference::GetSoundCloudTags()
));
$this->addElement('submit', 'submit', array(
'class' => 'ui-button ui-state-default',
'ignore' => true,
'label' => 'Submit',
));
}
}

View File

@ -2,61 +2,85 @@
class Application_Model_DateHelper
{
private $_timestamp;
function __construct() {
private $_timestamp;
function __construct()
{
$this->_timestamp = date("U");
}
function getDate(){
return date("Y-m-d H:i:s", $this->_timestamp);
}
function getTime(){
return date("H:i:s", $this->_timestamp);
}
function setDate($dateString){
}
/**
* Get time of object construction in the format
* YYYY-MM-DD HH:mm:ss
*/
function getDate()
{
return date("Y-m-d H:i:s", $this->_timestamp);
}
/**
* Get time of object construction in the format
* HH:mm:ss
*/
function getTime()
{
return date("H:i:s", $this->_timestamp);
}
/**
* Set the internal timestamp of the object.
*/
function setDate($dateString)
{
$this->_timestamp = strtotime($dateString);
}
function getNowDayStartDiff(){
$dayStartTS = strtotime(date("Y-m-d", $this->_timestamp));
return $this->_timestamp - $dayStartTS;
}
}
function getNowDayEndDiff(){
$dayEndTS = strtotime(date("Y-m-d", $this->_timestamp+(86400)));
return $dayEndTS - $this->_timestamp;
}
/**
*
* Enter description here ...
*/
function getNowDayStartDiff()
{
$dayStartTS = strtotime(date("Y-m-d", $this->_timestamp));
return $this->_timestamp - $dayStartTS;
}
function getEpochTime(){
function getNowDayEndDiff()
{
$dayEndTS = strtotime(date("Y-m-d", $this->_timestamp+(86400)));
return $dayEndTS - $this->_timestamp;
}
function getEpochTime()
{
return $this->_timestamp;
}
}
public static function TimeDiff($time1, $time2){
public static function TimeDiff($time1, $time2)
{
return strtotime($time2) - strtotime($time1);
}
public static function ConvertMSToHHMMSSmm($time){
}
public static function ConvertMSToHHMMSSmm($time)
{
$hours = floor($time / 3600000);
$time -= 3600000*$hours;
$minutes = floor($time / 60000);
$time -= 60000*$minutes;
$seconds = floor($time / 1000);
$time -= 1000*$seconds;
$ms = $time;
if (strlen($hours) == 1)
$hours = "0".$hours;
$hours = "0".$hours;
if (strlen($minutes) == 1)
$minutes = "0".$minutes;
$minutes = "0".$minutes;
if (strlen($seconds) == 1)
$seconds = "0".$seconds;
$seconds = "0".$seconds;
return $hours.":".$minutes.":".$seconds.".".$ms;
}
}

View File

@ -8,30 +8,30 @@ class Application_Model_Preference
$auth = Zend_Auth::getInstance();
$id = $auth->getIdentity()->id;
//Check if key already exists
$sql = "SELECT COUNT(*) FROM cc_pref"
." WHERE keystr = '$key'";
$result = $CC_DBC->GetOne($sql);
if ($result == 1){
$sql = "UPDATE cc_pref"
." SET subjid = $id, valstr = '$value'"
." WHERE keystr = '$key'";
." WHERE keystr = '$key'";
} else {
$sql = "INSERT INTO cc_pref (subjid, keystr, valstr)"
." VALUES ($id, '$key', '$value')";
}
return $CC_DBC->query($sql);
}
public static function GetValue($key){
global $CC_CONFIG, $CC_DBC;
//Check if key already exists
$sql = "SELECT COUNT(*) FROM cc_pref"
." WHERE keystr = '$key'";
$result = $CC_DBC->GetOne($sql);
if ($result == 0)
return "";
else {
@ -40,9 +40,9 @@ class Application_Model_Preference
$result = $CC_DBC->GetOne($sql);
return $result;
}
}
public static function GetHeadTitle(){
/* Caches the title name as a session variable so we dont access
* the database on every page load. */
@ -55,31 +55,32 @@ class Application_Model_Preference
}
if (strlen($title) > 0)
$title .= " - ";
return $title."Airtime";
}
public static function SetHeadTitle($title, $view){
Application_Model_Preference::SetValue("station_name", $title);
$defaultNamespace = new Zend_Session_Namespace('title_name');
Application_Model_Preference::SetValue("station_name", $title);
$defaultNamespace = new Zend_Session_Namespace('title_name');
$defaultNamespace->title = $title;
RabbitMq::PushSchedule();
//set session variable to new station name so that html title is updated.
//should probably do this in a view helper to keep this controller as minimal as possible.
$view->headTitle()->exchangeArray(array()); //clear headTitle ArrayObject
$view->headTitle(Application_Model_Preference::GetHeadTitle());
}
public static function SetShowsPopulatedUntil($timestamp) {
Application_Model_Preference::SetValue("shows_populated_until", $timestamp);
public static function SetShowsPopulatedUntil($timestamp) {
Application_Model_Preference::SetValue("shows_populated_until", $timestamp);
}
public static function GetShowsPopulatedUntil() {
return Application_Model_Preference::GetValue("shows_populated_until");
}
public static function SetDefaultFade($fade) {
Application_Model_Preference::SetValue("default_fade", $fade);
public static function SetDefaultFade($fade) {
Application_Model_Preference::SetValue("default_fade", $fade);
}
public static function GetDefaultFade() {
@ -88,6 +89,7 @@ class Application_Model_Preference
public static function SetStreamLabelFormat($type){
Application_Model_Preference::SetValue("stream_label_format", $type);
RabbitMq::PushSchedule();
}
public static function GetStreamLabelFormat(){
@ -97,5 +99,38 @@ class Application_Model_Preference
public static function GetStationName(){
return Application_Model_Preference::getValue("station_name");
}
public static function SetDoSoundCloudUpload($upload) {
Application_Model_Preference::SetValue("soundcloud_upload", $upload);
}
public static function GetDoSoundCloudUpload() {
return Application_Model_Preference::GetValue("soundcloud_upload");
}
public static function SetSoundCloudUser($user) {
Application_Model_Preference::SetValue("soundcloud_user", $user);
}
public static function GetSoundCloudUser() {
return Application_Model_Preference::GetValue("soundcloud_user");
}
public static function SetSoundCloudPassword($password) {
Application_Model_Preference::SetValue("soundcloud_password", $password);
}
public static function GetSoundCloudPassword() {
return Application_Model_Preference::GetValue("soundcloud_password");
}
public static function SetSoundCloudTags($tags) {
Application_Model_Preference::SetValue("soundcloud_tags", $tags);
}
public static function GetSoundCloudTags() {
return Application_Model_Preference::GetValue("soundcloud_tags");
}
}

View File

@ -0,0 +1,44 @@
<?php
require_once 'php-amqplib/amqp.inc';
class RabbitMq
{
static private $doPush = FALSE;
/**
* Sets a flag to push the schedule at the end of the request.
*/
public static function PushSchedule() {
RabbitMq::$doPush = TRUE;
}
/**
* Push the current schedule to RabbitMQ, to be picked up by Pypo.
* Will push the schedule in the range from 24 hours ago to 24 hours
* in the future.
*/
public static function PushScheduleFinal()
{
global $CC_CONFIG;
if (RabbitMq::$doPush) {
$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-schedule';
$channel->exchange_declare($EXCHANGE, 'direct', false, true);
$data = json_encode(Schedule::GetScheduledPlaylists());
$msg = new AMQPMessage($data, array('content_type' => 'text/plain'));
$channel->basic_publish($msg, $EXCHANGE);
$channel->close();
$conn->close();
}
}
}

View File

@ -24,41 +24,6 @@ class ScheduleGroup {
return $result != "0";
}
/**
* Convert a date to an ID by stripping out all characters
* and padding with zeros.
*
* @param string $p_dateStr
*/
public static function dateToId($p_dateStr) {
$p_dateStr = str_replace(":", "", $p_dateStr);
$p_dateStr = str_replace(" ", "", $p_dateStr);
$p_dateStr = str_replace(".", "", $p_dateStr);
$p_dateStr = str_replace("-", "", $p_dateStr);
$p_dateStr = substr($p_dateStr, 0, 17);
$p_dateStr = str_pad($p_dateStr, 17, "0");
return $p_dateStr;
}
/**
* Add the two times together, return the result.
*
* @param string $p_baseTime
* Specified as YYYY-MM-DD HH:MM:SS
*
* @param string $p_addTime
* Specified as HH:MM:SS.nnnnnn
*
* @return string
* The end time, to the nearest second.
*/
// protected function calculateEndTime($p_startTime, $p_trackTime) {
// $p_trackTime = substr($p_startTime, 0, );
// $start = new DateTime();
// $interval = new DateInterval()
//
// }
/**
* Add a music clip or playlist to the schedule.
*
@ -77,6 +42,7 @@ class ScheduleGroup {
*/
public function add($show_instance, $p_datetime, $p_audioFileId = null, $p_playlistId = null, $p_options = null) {
global $CC_CONFIG, $CC_DBC;
if (!is_null($p_audioFileId)) {
// Schedule a single audio track
@ -92,27 +58,24 @@ class ScheduleGroup {
if (empty($length)) {
return new PEAR_Error("Length is empty.");
}
if (!Schedule::isScheduleEmptyInRange($p_datetime, $length)) {
return new PEAR_Error("Schedule conflict.", 555);
}
// Insert into the table
$this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')");
$id = $this->dateToId($p_datetime);
$sql = "INSERT INTO ".$CC_CONFIG["scheduleTable"]
." (playlist_id, starts, ends, clip_length, group_id, file_id)"
." VALUES (0, TIMESTAMP '$p_datetime', "
." (instance_id, starts, ends, clip_length, group_id, file_id, cue_out)"
." VALUES ($show_instance, TIMESTAMP '$p_datetime', "
." (TIMESTAMP '$p_datetime' + INTERVAL '$length'),"
." '$length',"
." {$this->groupId}, $p_audioFileId)";
." {$this->groupId}, $p_audioFileId, '$length')";
$result = $CC_DBC->query($sql);
if (PEAR::isError($result)) {
//var_dump($sql);
return $result;
}
return $this->groupId;
} elseif (!is_null($p_playlistId)){
}
elseif (!is_null($p_playlistId)){
// Schedule a whole playlist
// Load existing playlist
@ -130,7 +93,6 @@ class ScheduleGroup {
// Insert all items into the schedule
$this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')");
$id = $this->dateToId($p_datetime);
$itemStartTime = $p_datetime;
$plItems = $playlist->getContents();
@ -151,13 +113,13 @@ class ScheduleGroup {
return $result;
}
$itemStartTime = $CC_DBC->getOne("SELECT TIMESTAMP '$itemStartTime' + INTERVAL '$trackLength'");
$id = $this->dateToId($itemStartTime);
}
return $this->groupId;
}
RabbitMq::PushSchedule();
return $this->groupId;
}
public function addAfter($show_instance, $p_groupId, $p_audioFileId) {
public function addFileAfter($show_instance, $p_groupId, $p_audioFileId) {
global $CC_CONFIG, $CC_DBC;
// Get the end time for the given entry
$sql = "SELECT MAX(ends) FROM ".$CC_CONFIG["scheduleTable"]
@ -176,10 +138,6 @@ class ScheduleGroup {
return $this->add($show_instance, $startTime, null, $p_playlistId);
}
public function update() {
}
/**
* Remove the group from the schedule.
* Note: does not check if it is in the past, you can remove anything.
@ -195,7 +153,9 @@ class ScheduleGroup {
$sql = "DELETE FROM ".$CC_CONFIG["scheduleTable"]
." WHERE group_id = ".$this->groupId;
//echo $sql;
return $CC_DBC->query($sql);
$retVal = $CC_DBC->query($sql);
RabbitMq::PushSchedule();
return $retVal;
}
/**
@ -231,17 +191,13 @@ class ScheduleGroup {
return $CC_DBC->GetAll($sql);
}
public function reschedule($toDateTime) {
global $CC_CONFIG, $CC_DBC;
// $sql = "UPDATE ".$CC_CONFIG["scheduleTable"]. " SET id=, starts=,ends="
}
public function notifyGroupStartPlay() {
global $CC_CONFIG, $CC_DBC;
$sql = "UPDATE ".$CC_CONFIG['scheduleTable']
." SET schedule_group_played=TRUE"
." WHERE group_id=".$this->groupId;
return $CC_DBC->query($sql);
$retVal = $CC_DBC->query($sql);
return $retVal;
}
public function notifyMediaItemStartPlay($p_fileId) {
@ -250,7 +206,8 @@ class ScheduleGroup {
." SET media_item_played=TRUE"
." WHERE group_id=".$this->groupId
." AND file_id=".pg_escape_string($p_fileId);
return $CC_DBC->query($sql);
$retVal = $CC_DBC->query($sql);
return $retVal;
}
}
@ -334,9 +291,10 @@ class Schedule {
return $res;
}
public static function GetPercentScheduled($instance_id, $s_datetime, $e_datetime){
public static function GetPercentScheduled($instance_id, $s_datetime, $e_datetime)
{
$time = Schedule::GetTotalShowTime($instance_id);
$s_epoch = strtotime($s_datetime);
$e_epoch = strtotime($e_datetime);
@ -396,12 +354,14 @@ class Schedule {
* @return array
* Returns empty array if nothing found
*/
public static function GetItems($p_fromDateTime, $p_toDateTime, $p_playlistsOnly = true) {
public static function GetItems($p_currentDateTime, $p_toDateTime, $p_playlistsOnly = true)
{
global $CC_CONFIG, $CC_DBC;
$rows = array();
if (!$p_playlistsOnly) {
$sql = "SELECT * FROM ".$CC_CONFIG["scheduleTable"]
." WHERE (starts >= TIMESTAMP '$p_fromDateTime') "
." WHERE (starts >= TIMESTAMP '$p_currentDateTime') "
." AND (ends <= TIMESTAMP '$p_toDateTime')";
$rows = $CC_DBC->GetAll($sql);
foreach ($rows as &$row) {
@ -429,11 +389,11 @@ class Schedule {
." ON st.instance_id = si.id"
." LEFT JOIN $CC_CONFIG[showTable] as sh"
." ON si.show_id = sh.id"
." WHERE (st.starts >= TIMESTAMP '$p_fromDateTime')"
." WHERE (st.ends >= TIMESTAMP '$p_currentDateTime')"
." AND (st.ends <= TIMESTAMP '$p_toDateTime')"
//next line makes sure that we aren't returning items that
//are past the show's scheduled timeslot.
." AND (st.starts < si.ends)"
." AND (st.starts < si.ends)"
." GROUP BY st.group_id"
." ORDER BY starts";
@ -457,7 +417,8 @@ class Schedule {
* @param int $next
* @return date
*/
public static function GetPlayOrderRange($prev = 1, $next = 1) {
public static function GetPlayOrderRange($prev = 1, $next = 1)
{
if (!is_int($prev) || !is_int($next)){
//must enter integers to specify ranges
return array();
@ -469,11 +430,11 @@ class Schedule {
$timeNow = $date->getDate();
return array("env"=>APPLICATION_ENV,
"schedulerTime"=>gmdate("Y-m-d H:i:s"),
"previous"=>Schedule::Get_Scheduled_Item_Data($timeNow, -1, $prev, "24 hours"),
"current"=>Schedule::Get_Scheduled_Item_Data($timeNow, 0),
"next"=>Schedule::Get_Scheduled_Item_Data($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"),
"currentShow"=>Show_DAL::GetCurrentShow($timeNow),
"nextShow"=>Show_DAL::GetNextShow($timeNow),
"nextShow"=>Show_DAL::GetNextShows($timeNow, 1),
"timezone"=> date("T"),
"timezoneOffset"=> date("Z"),
"apiKey"=>$CC_CONFIG['apiKey'][0]);
@ -501,7 +462,8 @@ class Schedule {
* want to search the database. For example "5 days", "18 hours", "60 minutes",
* "30 seconds" etc.
*/
public static function Get_Scheduled_Item_Data($timeStamp, $timePeriod=0, $count = 0, $interval="0 hours"){
public static function GetScheduledItemData($timeStamp, $timePeriod=0, $count = 0, $interval="0 hours")
{
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT DISTINCT pt.name, ft.track_title, ft.artist_name, ft.album_title, st.starts, st.ends, st.clip_length, st.media_item_played, st.group_id, show.name as show_name, st.instance_id"
@ -531,7 +493,8 @@ class Schedule {
return $rows;
}
public static function GetShowInstanceItems($instance_id){
public static function GetShowInstanceItems($instance_id)
{
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT DISTINCT pt.name, ft.track_title, ft.artist_name, ft.album_title, st.starts, st.ends, st.clip_length, st.media_item_played, st.group_id, show.name as show_name, st.instance_id"
@ -547,12 +510,14 @@ class Schedule {
return $rows;
}
public static function UpdateMediaPlayedStatus($id){
public static function UpdateMediaPlayedStatus($p_id)
{
global $CC_CONFIG, $CC_DBC;
$sql = "UPDATE ".$CC_CONFIG['scheduleTable']
." SET media_item_played=TRUE"
." WHERE id=$id";
return $CC_DBC->query($sql);
." WHERE id=$p_id";
$retVal = $CC_DBC->query($sql);
return $retVal;
}
@ -563,7 +528,7 @@ class Schedule {
* @param string $p_time
* @return string
*/
private static function CcTimeToPypoTime($p_time)
private static function AirtimeTimeToPypoTime($p_time)
{
$p_time = substr($p_time, 0, 19);
$p_time = str_replace(" ", "-", $p_time);
@ -578,7 +543,7 @@ class Schedule {
* @param string $p_time
* @return string
*/
private static function PypoTimeToCcTime($p_time)
private static function PypoTimeToAirtimeTime($p_time)
{
$t = explode("-", $p_time);
return $t[0]."-".$t[1]."-".$t[2]." ".$t[3].":".$t[4].":00";
@ -658,17 +623,21 @@ class Schedule {
/**
* Export the schedule in json formatted for pypo (the liquidsoap scheduler)
*
* @param string $range
* In the format "YYYY-MM-DD HH:mm:ss"
* @param string $source
* In the format "YYYY-MM-DD HH:mm:ss"
* @param string $p_fromDateTime
* In the format "YYYY-MM-DD-HH-mm-SS"
* @param string $p_toDateTime
* In the format "YYYY-MM-DD-HH-mm-SS"
*/
public static function ExportRangeAsJson($p_fromDateTime, $p_toDateTime)
public static function GetScheduledPlaylists()
{
global $CC_CONFIG, $CC_DBC;
$range_start = Schedule::PypoTimeToCcTime($p_fromDateTime);
$range_end = Schedule::PypoTimeToCcTime($p_toDateTime);
$t1 = new DateTime();
$range_start = $t1->format("Y-m-d H:i:s");
$t2 = new DateTime();
$t2->add(new DateInterval("PT24H"));
$range_end = $t2->format("Y-m-d H:i:s");
// Scheduler wants everything in a playlist
$data = Schedule::GetItems($range_start, $range_end, true);
@ -684,10 +653,10 @@ class Schedule {
$start = substr($start, 0, 19);
//Start time is the array key, needs to be in the format "YYYY-MM-DD-HH-mm-ss"
$pkey = Schedule::CcTimeToPypoTime($start);
$pkey = Schedule::AirtimeTimeToPypoTime($start);
$timestamp = strtotime($start);
$playlists[$pkey]['source'] = "PLAYLIST";
$playlists[$pkey]['x_ident'] = $dx["playlist_id"];
$playlists[$pkey]['x_ident'] = $dx['group_id'];
$playlists[$pkey]['subtype'] = '1'; // Just needs to be between 1 and 4 inclusive
$playlists[$pkey]['timestamp'] = $timestamp;
$playlists[$pkey]['duration'] = $dx['clip_length'];
@ -695,9 +664,9 @@ class Schedule {
$playlists[$pkey]['schedule_id'] = $dx['group_id'];
$playlists[$pkey]['show_name'] = $dx['show_name'];
$playlists[$pkey]['user_id'] = 0;
$playlists[$pkey]['id'] = $dx["playlist_id"];
$playlists[$pkey]['start'] = Schedule::CcTimeToPypoTime($dx["start"]);
$playlists[$pkey]['end'] = Schedule::CcTimeToPypoTime($dx["end"]);
$playlists[$pkey]['id'] = $dx['group_id'];
$playlists[$pkey]['start'] = Schedule::AirtimeTimeToPypoTime($dx["start"]);
$playlists[$pkey]['end'] = Schedule::AirtimeTimeToPypoTime($dx["end"]);
}
}
@ -734,30 +703,15 @@ class Schedule {
$result = array();
$result['status'] = array('range' => array('start' => $range_start, 'end' => $range_end),
'version' => "1.1");
'version' => AIRTIME_REST_VERSION);
$result['playlists'] = $playlists;
$result['check'] = 1;
$result['stream_metadata'] = array();
$result['stream_metadata']['format'] = Application_Model_Preference::GetStreamLabelFormat();
$result['stream_metadata']['station_name'] = Application_Model_Preference::GetStationName();
$result['server_timezone'] = date('O');
return $result;
}
/**
* Remove all items from the schedule in the given range.
*
* @param string $p_start
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
* @param string $p_end
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
*/
public static function RemoveItemsInRange($p_start, $p_end)
{
$items = Schedule::GetItems($p_start, $p_end);
foreach ($items as $item) {
$scheduleGroup = new ScheduleGroup($item["group_id"]);
$scheduleGroup->remove();
}
}
}

View File

@ -6,50 +6,60 @@ class Show {
public function __construct($showId=NULL)
{
$this->_showId = $showId;
$this->_showId = $showId;
}
public function getName() {
public function getName()
{
$show = CcShowQuery::create()->findPK($this->_showId);
return $show->getDbName();
}
public function setName($name) {
public function setName($name)
{
$show = CcShowQuery::create()->findPK($this->_showId);
$show->setDbName($name);
RabbitMq::PushSchedule();
}
public function getDescription() {
public function getDescription()
{
$show = CcShowQuery::create()->findPK($this->_showId);
return $show->getDbDescription();
}
public function setDescription($description) {
public function setDescription($description)
{
$show = CcShowQuery::create()->findPK($this->_showId);
$show->setDbDescription($description);
}
public function getColor() {
public function getColor()
{
$show = CcShowQuery::create()->findPK($this->_showId);
return $show->getDbColor();
}
public function setColor($color) {
public function setColor($color)
{
$show = CcShowQuery::create()->findPK($this->_showId);
$show->setDbColor($color);
}
public function getBackgroundColor() {
public function getBackgroundColor()
{
$show = CcShowQuery::create()->findPK($this->_showId);
return $show->getDbBackgroundColor();
}
public function setBackgroundColor($backgroundColor) {
public function setBackgroundColor($backgroundColor)
{
$show = CcShowQuery::create()->findPK($this->_showId);
$show->setDbBackgroundColor($backgroundColor);
}
public function cancelShow($day_timestamp) {
public function cancelShow($day_timestamp)
{
global $CC_DBC;
$timeinfo = explode(" ", $day_timestamp);
@ -62,20 +72,21 @@ class Show {
WHERE starts >= '{$day_timestamp}' AND show_id = {$this->_showId}";
$CC_DBC->query($sql);
RabbitMq::PushSchedule();
}
//end dates are non inclusive.
public static function addShow($data) {
public static function addShow($data)
{
$con = Propel::getConnection(CcShowPeer::DATABASE_NAME);
$sql = "SELECT time '{$data['add_show_start_time']}' + INTERVAL '{$data['add_show_duration']} hour' ";
$r = $con->query($sql);
$endTime = $r->fetchColumn(0);
$endTime = $r->fetchColumn(0);
$sql = "SELECT EXTRACT(DOW FROM TIMESTAMP '{$data['add_show_start_date']} {$data['add_show_start_time']}')";
$r = $con->query($sql);
$startDow = $r->fetchColumn(0);
$startDow = $r->fetchColumn(0);
if($data['add_show_no_end']) {
$endDate = NULL;
@ -84,13 +95,13 @@ class Show {
else if($data['add_show_repeats']) {
$sql = "SELECT date '{$data['add_show_end_date']}' + INTERVAL '1 day' ";
$r = $con->query($sql);
$endDate = $r->fetchColumn(0);
$endDate = $r->fetchColumn(0);
}
else {
$sql = "SELECT date '{$data['add_show_start_date']}' + INTERVAL '1 day' ";
$r = $con->query($sql);
$endDate = $r->fetchColumn(0);
}
}
//only want the day of the week from the start date.
if(!$data['add_show_repeats']) {
@ -98,7 +109,7 @@ class Show {
}
else if($data['add_show_repeats'] && $data['add_show_day_check'] == "") {
$data['add_show_day_check'] = array($startDow);
}
}
//find repeat type or set to a non repeating show.
if($data['add_show_repeats']) {
@ -114,7 +125,7 @@ class Show {
$show->setDbUrl($data['add_show_url']);
$show->setDbColor($data['add_show_color']);
$show->setDbBackgroundColor($data['add_show_background_color']);
$show->save();
$show->save();
$showId = $show->getDbId();
@ -127,7 +138,6 @@ class Show {
//don't set day for monthly repeat type, it's invalid.
if($data['add_show_repeats'] && $data["add_show_repeat_type"] == 2) {
$showDay = new CcShowDays();
$showDay->setDbFirstShow($data['add_show_start_date']);
$showDay->setDbLastShow($endDate);
@ -137,29 +147,25 @@ class Show {
$showDay->setDbShowId($showId);
$showDay->setDbRecord($isRecorded);
$showDay->save();
}
else {
foreach ($data['add_show_day_check'] as $day) {
if($startDow !== $day){
if($startDow > $day)
if ($startDow > $day)
$daysAdd = 6 - $startDow + 1 + $day;
else
$daysAdd = $day - $startDow;
$daysAdd = $day - $startDow;
$sql = "SELECT date '{$data['add_show_start_date']}' + INTERVAL '{$daysAdd} day' ";
$r = $con->query($sql);
$start = $r->fetchColumn(0);
$start = $r->fetchColumn(0);
}
else {
$start = $data['add_show_start_date'];
}
if(strtotime($start) < strtotime($endDate) || is_null($endDate)) {
$showDay = new CcShowDays();
$showDay->setDbFirstShow($start);
$showDay->setDbLastShow($endDate);
@ -180,7 +186,6 @@ class Show {
for($i=1; $i<=5; $i++) {
if($data['add_show_rebroadcast_date_'.$i]) {
$showRebroad = new CcShowRebroadcast();
$showRebroad->setDbDayOffset($data['add_show_rebroadcast_date_'.$i]);
$showRebroad->setDbStartTime($data['add_show_rebroadcast_time_'.$i]);
@ -190,14 +195,13 @@ class Show {
}
}
else if($data['add_show_record'] && $data['add_show_rebroadcast'] && $repeat_type == -1){
for($i=1; $i<=5; $i++) {
if($data['add_show_rebroadcast_absolute_date_'.$i]) {
$sql = "SELECT date '{$data['add_show_rebroadcast_absolute_date_'.$i]}' - date '{$data['add_show_start_date']}' ";
$r = $con->query($sql);
$offset_days = $r->fetchColumn(0);
$offset_days = $r->fetchColumn(0);
$showRebroad = new CcShowRebroadcast();
$showRebroad->setDbDayOffset($offset_days." days");
@ -207,7 +211,7 @@ class Show {
}
}
}
if(is_array($data['add_show_hosts'])) {
//add selected hosts to cc_show_hosts table.
foreach ($data['add_show_hosts'] as $host) {
@ -219,18 +223,20 @@ class Show {
}
Show::populateShowUntilLastGeneratedDate($showId);
RabbitMq::PushSchedule();
}
public static function getShows($start_timestamp, $end_timestamp, $excludeInstance=NULL, $onlyRecord=FALSE) {
public static function getShows($start_timestamp, $end_timestamp, $excludeInstance=NULL, $onlyRecord=FALSE)
{
global $CC_DBC;
$sql = "SELECT starts, ends, record, rebroadcast, instance_id, show_id, name, description,
color, background_color, cc_show_instances.id AS instance_id
FROM cc_show_instances
$sql = "SELECT starts, ends, record, rebroadcast, instance_id, show_id, name, description,
color, background_color, cc_show_instances.id AS instance_id
FROM cc_show_instances
LEFT JOIN cc_show ON cc_show.id = cc_show_instances.show_id";
//only want shows that are starting at the time or later.
if($onlyRecord) {
if ($onlyRecord) {
$sql = $sql." WHERE (starts >= '{$start_timestamp}' AND starts < timestamp '{$start_timestamp}' + interval '2 hours')";
$sql = $sql." AND (record = 1)";
@ -240,10 +246,10 @@ class Show {
$sql = $sql." WHERE ((starts >= '{$start_timestamp}' AND starts < '{$end_timestamp}')
OR (ends > '{$start_timestamp}' AND ends <= '{$end_timestamp}')
OR (starts <= '{$start_timestamp}' AND ends >= '{$end_timestamp}'))";
}
}
if(isset($excludeInstance)) {
if (isset($excludeInstance)) {
foreach($excludeInstance as $instance) {
$sql_exclude[] = "cc_show_instances.id != {$instance}";
}
@ -257,8 +263,8 @@ class Show {
return $CC_DBC->GetAll($sql);
}
private static function setNextPop($next_date, $show_id, $day) {
private static function setNextPop($next_date, $show_id, $day)
{
$nextInfo = explode(" ", $next_date);
$repeatInfo = CcShowDaysQuery::create()
@ -271,15 +277,16 @@ class Show {
}
//for a show with repeat_type == -1
private static function populateNonRepeatingShow($show_id, $first_show, $start_time, $duration, $day, $record, $end_timestamp) {
private static function populateNonRepeatingShow($show_id, $first_show, $start_time, $duration, $day, $record, $end_timestamp)
{
global $CC_DBC;
$next_date = $first_show." ".$start_time;
if(strtotime($next_date) < strtotime($end_timestamp)) {
$start = $next_date;
$sql = "SELECT timestamp '{$start}' + interval '{$duration}'";
$end = $CC_DBC->GetOne($sql);
@ -298,10 +305,10 @@ class Show {
foreach($rebroadcasts as $rebroadcast) {
$timeinfo = explode(" ", $start);
$sql = "SELECT timestamp '{$timeinfo[0]}' + interval '{$rebroadcast["day_offset"]}' + interval '{$rebroadcast["start_time"]}'";
$rebroadcast_start_time = $CC_DBC->GetOne($sql);
$sql = "SELECT timestamp '{$rebroadcast_start_time}' + interval '{$duration}'";
$rebroadcast_end_time = $CC_DBC->GetOne($sql);
@ -315,12 +322,13 @@ class Show {
$newRebroadcastInstance->save();
}
}
RabbitMq::PushSchedule();
}
//for a show with repeat_type == 0,1,2
private static function populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
private static function populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
$start_time, $duration, $day, $record, $end_timestamp, $interval) {
global $CC_DBC;
global $CC_DBC;
if(isset($next_pop_date)) {
$next_date = $next_pop_date." ".$start_time;
@ -333,9 +341,9 @@ class Show {
$rebroadcasts = $CC_DBC->GetAll($sql);
while(strtotime($next_date) < strtotime($end_timestamp) && (strtotime($last_show) > strtotime($next_date) || is_null($last_show))) {
$start = $next_date;
$sql = "SELECT timestamp '{$start}' + interval '{$duration}'";
$end = $CC_DBC->GetOne($sql);
@ -351,10 +359,10 @@ class Show {
foreach($rebroadcasts as $rebroadcast) {
$timeinfo = explode(" ", $next_date);
$sql = "SELECT timestamp '{$timeinfo[0]}' + interval '{$rebroadcast["day_offset"]}' + interval '{$rebroadcast["start_time"]}'";
$rebroadcast_start_time = $CC_DBC->GetOne($sql);
$sql = "SELECT timestamp '{$rebroadcast_start_time}' + interval '{$duration}'";
$rebroadcast_end_time = $CC_DBC->GetOne($sql);
@ -373,64 +381,65 @@ class Show {
}
Show::setNextPop($next_date, $show_id, $day);
RabbitMq::PushSchedule();
}
private static function populateShow($repeat_type, $show_id, $next_pop_date,
private static function populateShow($repeat_type, $show_id, $next_pop_date,
$first_show, $last_show, $start_time, $duration, $day, $record, $end_timestamp) {
if($repeat_type == -1) {
Show::populateNonRepeatingShow($show_id, $first_show, $start_time, $duration, $day, $record, $end_timestamp);
}
else if($repeat_type == 0) {
Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
$start_time, $duration, $day, $record, $end_timestamp, '7 days');
}
else if($repeat_type == 1) {
Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
$start_time, $duration, $day, $record, $end_timestamp, '14 days');
}
else if($repeat_type == 2) {
Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show,
$start_time, $duration, $day, $record, $end_timestamp, '1 month');
}
}
}
//used to catch up a newly added show
private static function populateShowUntilLastGeneratedDate($show_id) {
global $CC_DBC;
$showsPopUntil = Application_Model_Preference::GetShowsPopulatedUntil();
$sql = "SELECT * FROM cc_show_days WHERE show_id = {$show_id}";
$res = $CC_DBC->GetAll($sql);
$res = $CC_DBC->GetAll($sql);
foreach($res as $row) {
Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"],
$row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $showsPopUntil);
}
Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"],
$row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $showsPopUntil);
}
}
public static function populateShowsUntil($pop_timestamp, $end_timestamp) {
global $CC_DBC;
if($pop_timestamp != "") {
$sql = "SELECT * FROM cc_show_days
WHERE last_show IS NULL
$sql = "SELECT * FROM cc_show_days
WHERE last_show IS NULL
OR first_show < '{$end_timestamp}' AND last_show > '{$pop_timestamp}'";
}
else {
$today_timestamp = date("Y-m-d");
$sql = "SELECT * FROM cc_show_days
WHERE last_show IS NULL
$sql = "SELECT * FROM cc_show_days
WHERE last_show IS NULL
OR first_show < '{$end_timestamp}' AND last_show > '{$today_timestamp}'";
}
$res = $CC_DBC->GetAll($sql);
$res = $CC_DBC->GetAll($sql);
foreach($res as $row) {
Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"],
$row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $end_timestamp);
}
Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"],
$row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $end_timestamp);
}
}
public static function getFullCalendarEvents($start, $end, $editable=false) {
@ -460,19 +469,15 @@ class Show {
private static function makeFullCalendarEvent($show, $options=array()) {
global $CC_DBC;
$event = array();
if($show["rebroadcast"]) {
$title = "REBROADCAST ".$show["name"];
$event["disableResizing"] = true;
}
else {
$title = $show["name"];
}
$event["id"] = $show["instance_id"];
$event["title"] = $title;
$event["title"] = $show["name"];
$event["start"] = $show["starts"];
$event["end"] = $show["ends"];
$event["allDay"] = false;
@ -500,56 +505,68 @@ class ShowInstance {
public function __construct($instanceId)
{
$this->_instanceId = $instanceId;
$this->_instanceId = $instanceId;
}
public function getShowId() {
public function getShowId()
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
return $showInstance->getDbShowId();
}
public function getShowInstanceId() {
public function getShowInstanceId()
{
return $this->_instanceId;
}
public function isRebroadcast() {
public function isRebroadcast()
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
return $showInstance->getDbOriginalShow();
}
public function isRecorded() {
public function isRecorded()
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
return $showInstance->getDbRecord();
}
public function getName() {
public function getName()
{
$show = CcShowQuery::create()->findPK($this->getShowId());
return $show->getDbName();
}
public function getShowStart() {
public function getShowStart()
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
return $showInstance->getDbStarts();
}
public function getShowEnd() {
public function getShowEnd()
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
return $showInstance->getDbEnds();
}
public function setShowStart($start) {
public function setShowStart($start)
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
$showInstance->setDbStarts($start)
->save();
RabbitMq::PushSchedule();
}
public function setShowEnd($end) {
public function setShowEnd($end)
{
$showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId);
$showInstance->setDbEnds($end)
->save();
->save();
RabbitMq::PushSchedule();
}
public function moveScheduledShowContent($deltaDay, $deltaHours, $deltaMin) {
public function moveScheduledShowContent($deltaDay, $deltaHours, $deltaMin)
{
global $CC_DBC;
$sql = "UPDATE cc_schedule
@ -558,9 +575,11 @@ class ShowInstance {
WHERE instance_id = '{$this->_instanceId}'";
$CC_DBC->query($sql);
RabbitMq::PushSchedule();
}
public function moveShow($deltaDay, $deltaMin){
public function moveShow($deltaDay, $deltaMin)
{
global $CC_DBC;
$hours = $deltaMin/60;
@ -572,7 +591,7 @@ class ShowInstance {
$mins = abs($deltaMin%60);
$starts = $this->getShowStart();
$ends = $this->getShowEnd();
$ends = $this->getShowEnd();
$sql = "SELECT timestamp '{$starts}' + interval '{$deltaDay} days' + interval '{$hours}:{$mins}'";
$new_starts = $CC_DBC->GetOne($sql);
@ -595,18 +614,20 @@ class ShowInstance {
if($rebroadcast) {
$sql = "SELECT timestamp '{$new_starts}' < (SELECT starts FROM cc_show_instances WHERE id = {$rebroadcast})";
$isBeforeRecordedOriginal = $CC_DBC->GetOne($sql);
if($isBeforeRecordedOriginal === 't'){
return "Cannot move a rebroadcast show before its original";
}
}
$this->moveScheduledShowContent($deltaDay, $hours, $mins);
$this->setShowStart($new_starts);
$this->setShowEnd($new_ends);
$this->setShowEnd($new_ends);
RabbitMq::PushSchedule();
}
public function resizeShow($deltaDay, $deltaMin){
public function resizeShow($deltaDay, $deltaMin)
{
global $CC_DBC;
$hours = $deltaMin/60;
@ -618,7 +639,7 @@ class ShowInstance {
$mins = abs($deltaMin%60);
$starts = $this->getShowStart();
$ends = $this->getShowEnd();
$ends = $this->getShowEnd();
$sql = "SELECT timestamp '{$ends}' + interval '{$deltaDay} days' + interval '{$hours}:{$mins}'";
$new_ends = $CC_DBC->GetOne($sql);
@ -639,106 +660,142 @@ class ShowInstance {
WHERE rebroadcast = 1 AND instance_id = {$this->_instanceId}";
$CC_DBC->query($sql);
}
$this->setShowEnd($new_ends);
RabbitMq::PushSchedule();
}
private function getLastGroupId() {
private function getLastGroupId()
{
global $CC_DBC;
$sql = "SELECT group_id FROM cc_schedule WHERE instance_id = '{$this->_instanceId}' ORDER BY ends DESC LIMIT 1";
$res = $CC_DBC->GetOne($sql);
$res = $CC_DBC->GetOne($sql);
return $res;
}
public function addPlaylistToShow($plId) {
public function addPlaylistToShow($plId)
{
$sched = new ScheduleGroup();
$lastGroupId = $this->getLastGroupId();
if(is_null($lastGroupId)) {
$groupId = $sched->add($this->_instanceId, $this->getShowStart(), null, $plId);
$groupId = $sched->add($this->_instanceId, $this->getShowStart(), null, $plId);
}
else {
$groupId = $sched->addPlaylistAfter($this->_instanceId, $lastGroupId, $plId);
}
RabbitMq::PushSchedule();
}
public function addFileToShow($file_id)
{
$sched = new ScheduleGroup();
$lastGroupId = $this->getLastGroupId();
if(is_null($lastGroupId)) {
$groupId = $sched->add($this->_instanceId, $this->getShowStart(), $file_id);
}
else {
$groupId = $sched->addFileAfter($this->_instanceId, $lastGroupId, $file_id);
}
RabbitMq::PushSchedule();
}
public function scheduleShow($plIds) {
foreach($plIds as $plId) {
$this->addPlaylistToShow($plId);
}
}
public function removeGroupFromShow($group_id){
public function removeGroupFromShow($group_id)
{
global $CC_DBC;
$sql = "SELECT MAX(ends) as end_timestamp, (MAX(ends) - MIN(starts)) as length
FROM cc_schedule
FROM cc_schedule
WHERE group_id = '{$group_id}'";
$groupBoundry = $CC_DBC->GetRow($sql);
$group = CcScheduleQuery::create()
->filterByDbGroupId($group_id)
->delete();
$sql = "UPDATE cc_schedule
SET starts = (starts - INTERVAL '{$groupBoundry["length"]}'), ends = (ends - INTERVAL '{$groupBoundry["length"]}')
$sql = "UPDATE cc_schedule
SET starts = (starts - INTERVAL '{$groupBoundry["length"]}'), ends = (ends - INTERVAL '{$groupBoundry["length"]}')
WHERE starts >= '{$groupBoundry["end_timestamp"]}' AND instance_id = {$this->_instanceId}";
$CC_DBC->query($sql);
RabbitMq::PushSchedule();
}
public function clearShow() {
public function clearShow()
{
CcScheduleQuery::create()
->filterByDbInstanceId($this->_instanceId)
->delete();
RabbitMq::PushSchedule();
}
public function deleteShow() {
public function deleteShow()
{
CcShowInstancesQuery::create()
->findPK($this->_instanceId)
->delete();
RabbitMq::PushSchedule();
}
public function getTimeScheduled() {
public function setRecordedFile($file_id)
{
$showInstance = CcShowInstancesQuery::create()
->findPK($this->_instanceId);
$showInstance->setDbRecordedFile($file_id)
->save();
$rebroadcasts = CcShowInstancesQuery::create()
->filterByDbOriginalShow($this->_instanceId)
->find();
foreach ($rebroadcasts as $rebroadcast) {
$rebroad = new ShowInstance($rebroadcast->getDbId());
$rebroad->addFileToShow($file_id);
RabbitMq::PushSchedule();
}
}
public function getTimeScheduled()
{
$instance_id = $this->getShowInstanceId();
$time = Schedule::GetTotalShowTime($instance_id);
return $time;
}
public function getTimeUnScheduled() {
$start_timestamp = $this->getShowStart();
public function getTimeUnScheduled()
{
$start_timestamp = $this->getShowStart();
$end_timestamp = $this->getShowEnd();
$instance_id = $this->getShowInstanceId();
$time = Schedule::getTimeUnScheduledInRange($instance_id, $start_timestamp, $end_timestamp);
return $time;
}
public function getPercentScheduled() {
$start_timestamp = $this->getShowStart();
public function getPercentScheduled()
{
$start_timestamp = $this->getShowStart();
$end_timestamp = $this->getShowEnd();
$instance_id = $this->getShowInstanceId();
return Schedule::GetPercentScheduled($instance_id, $start_timestamp, $end_timestamp);
}
public function getShowLength() {
public function getShowLength()
{
global $CC_DBC;
$start_timestamp = $this->getShowStart();
$start_timestamp = $this->getShowStart();
$end_timestamp = $this->getShowEnd();
$sql = "SELECT TIMESTAMP '{$end_timestamp}' - TIMESTAMP '{$start_timestamp}' ";
@ -747,26 +804,27 @@ class ShowInstance {
return $length;
}
public function searchPlaylistsForShow($datatables){
public function searchPlaylistsForShow($datatables)
{
$time_remaining = $this->getTimeUnScheduled();
return StoredFile::searchPlaylistsForSchedule($time_remaining, $datatables);
}
public function getShowListContent() {
public function getShowListContent()
{
global $CC_DBC;
$sql = "SELECT *
$sql = "SELECT *
FROM (cc_schedule AS s LEFT JOIN cc_files AS f ON f.id = s.file_id
LEFT JOIN cc_playlist AS p ON p.id = s.playlist_id )
WHERE s.instance_id = '{$this->_instanceId}' ORDER BY starts";
return $CC_DBC->GetAll($sql);
return $CC_DBC->GetAll($sql);
}
public function getShowContent() {
public function getShowContent()
{
global $CC_DBC;
$res = $this->getShowListContent();
@ -788,7 +846,7 @@ class ShowInstance {
$items[$pl_counter]["pl_name"] = $row["name"];
$items[$pl_counter]["pl_creator"] = $row["creator"];
$items[$pl_counter]["pl_description"] = $row["description"];
$items[$pl_counter]["pl_group"] = $row["group_id"];
$items[$pl_counter]["pl_group"] = $row["group_id"];
$sql = "SELECT SUM(clip_length) FROM cc_schedule WHERE group_id = '{$currGroupId}'";
$length = $CC_DBC->GetOne($sql);
@ -802,46 +860,49 @@ class ShowInstance {
$items[$pl_counter]["pl_content"][$f_counter]["f_length"] = $row["length"];
}
return $items;
return $items;
}
}
/* Show Data Access Layer */
class Show_DAL{
public static function GetCurrentShow($timeNow) {
class Show_DAL {
public static function GetCurrentShow($timeNow)
{
global $CC_CONFIG, $CC_DBC;
$timestamp = explode(" ", $timeNow);
$date = $timestamp[0];
$time = $timestamp[1];
$sql = "SELECT si.starts as start_timestamp, si.ends as end_timestamp, s.name, s.id, si.id as instance_id, si.record"
." FROM $CC_CONFIG[showInstances] si, $CC_CONFIG[showTable] s"
." WHERE si.show_id = s.id"
." AND si.starts <= TIMESTAMP '$timeNow'"
." AND si.ends > TIMESTAMP '$timeNow'";
$rows = $CC_DBC->GetAll($sql);
return $rows;
}
public static function GetNextShow($timeNow) {
public static function GetNextShows($timeNow, $limit)
{
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *, si.starts as start_timestamp, si.ends as end_timestamp FROM "
." $CC_CONFIG[showInstances] si, $CC_CONFIG[showTable] s"
." WHERE si.show_id = s.id"
." AND si.starts >= TIMESTAMP '$timeNow'"
." AND si.starts < TIMESTAMP '$timeNow' + INTERVAL '48 hours'"
." ORDER BY si.starts"
." LIMIT 1";
." LIMIT $limit";
$rows = $CC_DBC->GetAll($sql);
return $rows;
}
public static function GetShowsInRange($timeNow, $start, $end){
public static function GetShowsInRange($timeNow, $start, $end)
{
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT"
." si.starts as show_starts,"
@ -873,5 +934,5 @@ class Show_DAL{
return $CC_DBC->GetAll($sql);
}
}

View File

@ -0,0 +1,69 @@
<?php
require_once 'soundcloud-api/Services/Soundcloud.php';
class ATSoundcloud {
private $_soundcloud;
public function __construct()
{
global $CC_CONFIG;
$this->_soundcloud = new Services_Soundcloud($CC_CONFIG['soundcloud-client-id'], $CC_CONFIG['soundcloud-client-secret']);
}
private function getToken()
{
$username = Application_Model_Preference::GetSoundCloudUser();
$password = Application_Model_Preference::GetSoundCloudPassword();
if($username === "" || $password === "")
{
return false;
}
$token = $this->_soundcloud->accessTokenResourceOwner($username, $password);
return $token;
}
public function uploadTrack($filepath, $filename, $description, $tags=array())
{
if($this->getToken())
{
if(count($tags)) {
$tags = join(" ", $tags);
$tags = $tags." ".Application_Model_Preference::GetSoundCloudTags();
}
else {
$tags = Application_Model_Preference::GetSoundCloudTags();
}
$track_data = array(
'track[sharing]' => 'private',
'track[title]' => $filename,
'track[asset_data]' => '@' . $filepath,
'track[tag_list]' => $tags,
'track[description]' => $description
);
try {
$response = json_decode(
$this->_soundcloud->post('tracks', $track_data),
true
);
echo var_dump($response);
}
catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
echo $e->getMessage();
}
}
else
{
echo "could not get soundcloud token";
}
}
}

View File

@ -1647,5 +1647,143 @@ class StoredFile {
return array("sEcho" => intval($data["sEcho"]), "iTotalDisplayRecords" => $totalDisplayRows, "iTotalRecords" => $totalRows, "aaData" => $results);
}
public static function uploadFile($targetDir) {
// HTTP headers for no cache etc
header('Content-type: text/plain; charset=UTF-8');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
// Settings
//$targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload";
$cleanupTargetDir = false; // Remove old files
$maxFileAge = 60 * 60; // Temp file age in seconds
// 5 minutes execution time
@set_time_limit(5 * 60);
// usleep(5000);
// Get parameters
$chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0;
$chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0;
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
// Clean the fileName for security reasons
//$fileName = preg_replace('/[^\w\._]+/', '', $fileName);
// Create target dir
if (!file_exists($targetDir))
@mkdir($targetDir);
// Remove old temp files
if (is_dir($targetDir) && ($dir = opendir($targetDir))) {
while (($file = readdir($dir)) !== false) {
$filePath = $targetDir . DIRECTORY_SEPARATOR . $file;
// Remove temp files if they are older than the max age
if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge))
@unlink($filePath);
}
closedir($dir);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
// Look for the content type header
if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
$contentType = $_SERVER["HTTP_CONTENT_TYPE"];
if (isset($_SERVER["CONTENT_TYPE"]))
$contentType = $_SERVER["CONTENT_TYPE"];
if (strpos($contentType, "multipart") !== false) {
if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen($_FILES['file']['tmp_name'], "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
unlink($_FILES['file']['tmp_name']);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
} else {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen("php://input", "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
$audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName;
$md5 = md5_file($audio_file);
$duplicate = StoredFile::RecallByMd5($md5);
if ($duplicate) {
if (PEAR::isError($duplicate)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}');
}
else {
$duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE);
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}');
}
}
$metadata = Metadata::LoadFromFile($audio_file);
if (PEAR::isError($metadata)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}');
}
// #2196 no id tag -> use the original filename
if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) {
$metadata[UI_MDATA_KEY_TITLE] = basename($audio_file);
$metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file);
}
// setMetadataBatch doesnt like these values
unset($metadata['audio']);
unset($metadata['playtime_seconds']);
$values = array(
"filename" => basename($audio_file),
"filepath" => $audio_file,
"filetype" => "audioclip",
"mime" => $metadata[UI_MDATA_KEY_FORMAT],
"md5" => $md5
);
$storedFile = StoredFile::Insert($values);
if (PEAR::isError($storedFile)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}');
}
$storedFile->setMetadataBatch($metadata);
return $storedFile;
}
}

View File

@ -39,7 +39,7 @@ class CcScheduleTableMap extends TableMap {
$this->setPrimaryKeyMethodInfo('cc_schedule_id_seq');
// columns
$this->addPrimaryKey('ID', 'DbId', 'INTEGER', true, null, null);
$this->addColumn('PLAYLIST_ID', 'DbPlaylistId', 'INTEGER', true, null, null);
$this->addColumn('PLAYLIST_ID', 'DbPlaylistId', 'INTEGER', false, null, null);
$this->addColumn('STARTS', 'DbStarts', 'TIMESTAMP', true, null, null);
$this->addColumn('ENDS', 'DbEnds', 'TIMESTAMP', true, null, null);
$this->addColumn('GROUP_ID', 'DbGroupId', 'INTEGER', false, null, null);

View File

@ -110,6 +110,7 @@ class SchedulerTests extends PHPUnit_TestCase {
}
}
/*
function testGetItems() {
$i1 = new ScheduleGroup();
$groupId1 = $i1->add('2008-01-01 12:00:00.000', $this->storedFile->getId());
@ -123,5 +124,6 @@ class SchedulerTests extends PHPUnit_TestCase {
$i1->remove();
$i2->remove();
}
*/
}

View File

@ -1,22 +0,0 @@
<?php
require_once 'soundcloud-api/Services/Soundcloud.php';
class Airtime_View_Helper_SoundCloudLink extends Zend_View_Helper_Abstract
{
public function soundCloudLink()
{
$request = Zend_Controller_Front::getInstance()->getRequest();
$host = $request->getHttpHost();
$controller = $request->getControllerName();
$action = $request->getActionName();
$redirectUrl = "http://{$host}/{$controller}/{$action}";
$soundcloud = new Services_Soundcloud('2CLCxcSXYzx7QhhPVHN4A', 'pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs', $redirectUrl);
$authorizeUrl = $soundcloud->getAuthorizeUrl();
return $authorizeUrl;
}
}

View File

@ -14,7 +14,7 @@
<div class="now-playing-info show">
<div class="recording-show" style="display: none;"></div>
<span id="playlist"></span>
<span class="lenght">00:00</span>
<span id="show-length" class="show-length"></span>
</div>
<div class="progressbar">
<div class="progress-show" id='progress-show' style="width:0%;"></div>
@ -43,6 +43,6 @@
<div id="jquery_jplayer_1" class="jp-jplayer" style="width:0px; height:0px;"></div>
<div id="about-txt" style="display: none;">
<a href="http://airtime.sourcefabric.org">Airtime</a>, the open radio software for scheduling and remote station management.<br>
<a href="http://airtime.sourcefabric.org">Airtime</a> <?php echo AIRTIME_VERSION ?>, the open radio software for scheduling and remote station management.<br>
© 2011 <a href="http://www.sourcefabric.org">Sourcefabric</a> o.p.s 2011. Airtime is distributed under the <a href="http://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU GPL v.3</a>
</div>

View File

@ -10,6 +10,7 @@
<div class="text-row top">
<span class="spl_playlength"><?php echo $item["cliplength"] ?></span>
<span class="spl_cue ui-state-default"></span>
<span class="spl_title"><?php echo $item["CcFiles"]['track_title'] ?></span>
</div>
<div class="text-row">

View File

@ -1,7 +1,10 @@
<div id="schedule-add-show" class="tabs ui-widget ui-widget-content block-shadow alpha-block padded">
<div class="button-bar">
<a href="#" id="add-show-close" class="icon-link"><span class="ui-icon ui-icon-circle-close"></span>Close</a>
<button id="add-show-submit" class="right-floated">Add this show</button>
<button aria-disabled="false" role="button" id="add-show-submit" class="right-floated ui-button ui-widget ui-state-default ui-button-text-icon-primary">
<span class="ui-icon ui-icon-plusthick"></span>
<span class="ui-button-text">Add this show</span>
</button>
</div>
<div class="clear"></div>
<h3 class="collapsible-header"><span class="arrow-icon"></span>What</h3>

View File

@ -7,7 +7,9 @@
<div id="users_wrapper" class="dataTables_wrapper">
<div class="button-holder">
<button type="button" id="add_user_button" name="search_add_group" class="ui-button ui-widget ui-state-default ui-button-text-icon-primary"><span class="ui-button-text">New User</span></button>
<button type="button" id="add_user_button" name="search_add_group" class="ui-button ui-widget ui-state-default ui-button-text-icon-primary">
<span class="ui-icon ui-icon-plusthick"></span>
<span class="ui-button-text">New User</span></button>
</div>
<table cellspacing="0" cellpadding="0" style="" id="users_datatable" class="datatable">
@ -26,6 +28,7 @@
</div>
</div>
<div class="user-data simple-formblock" id="user_details">
<?php echo $this->successMessage ?>
<fieldset class="padded">
<?php echo $this->form ?>
</fieldset>

View File

@ -234,7 +234,7 @@
</table>
<table name="cc_schedule" phpName="CcSchedule">
<column name="id" phpName="DbId" type="INTEGER" primaryKey="true" autoIncrement="true" required="true"/>
<column name="playlist_id" phpName="DbPlaylistId" type="INTEGER" required="true"/>
<column name="playlist_id" phpName="DbPlaylistId" type="INTEGER" required="false"/>
<column name="starts" phpName="DbStarts" type="TIMESTAMP" required="true"/>
<column name="ends" phpName="DbEnds" type="TIMESTAMP" required="true"/>
<column name="group_id" phpName="DbGroupId" type="INTEGER" required="false"/>

View File

@ -345,7 +345,7 @@ DROP TABLE "cc_schedule" CASCADE;
CREATE TABLE "cc_schedule"
(
"id" serial NOT NULL,
"playlist_id" INTEGER NOT NULL,
"playlist_id" INTEGER,
"starts" TIMESTAMP NOT NULL,
"ends" TIMESTAMP NOT NULL,
"group_id" INTEGER,

View File

@ -1,3 +0,0 @@
#!/bin/sh
su -l pypo -c "tail -F /etc/service/pypo-fetch/log/main/current"

View File

@ -1,3 +0,0 @@
#!/bin/sh
su -l pypo -c "tail -F /etc/service/pypo-push/log/main/current"

3
dev_tools/pr.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
su -l pypo -c "tail -F /etc/service/recorder/log/main/current"

3
dev_tools/pypoless.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
su -l pypo -c "less /etc/service/pypo/log/main/current"

3
dev_tools/pypotail.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
su -l pypo -c "tail -F /etc/service/pypo/log/main/current"

View File

@ -0,0 +1,19 @@
<?php
namespace DoctrineMigrations;
use Doctrine\DBAL\Migrations\AbstractMigration,
Doctrine\DBAL\Schema\Schema;
class Version20110312121200 extends AbstractMigration
{
public function up(Schema $schema)
{
$schema->dropTable("cc_backup");
$schema->dropTable("cc_trans");
}
public function down(Schema $schema)
{
}
}

View File

@ -39,7 +39,6 @@ AirtimeInstall::InstallPostgresScriptingLanguage();
echo "* Creating database tables".PHP_EOL;
AirtimeInstall::CreateDatabaseTables();
AirtimeInstall::MigrateTables(__DIR__);
echo "* Storage directory setup".PHP_EOL;
AirtimeInstall::SetupStorageDirectory($CC_CONFIG);
@ -47,11 +46,20 @@ AirtimeInstall::SetupStorageDirectory($CC_CONFIG);
echo "* Giving Apache permission to access the storage directory".PHP_EOL;
AirtimeInstall::ChangeDirOwnerToWebserver($CC_CONFIG["storageDir"]);
echo "* Creating /usr/bin symlinks".PHP_EOL;
AirtimeInstall::CreateSymlinks($CC_CONFIG["storageDir"]);
echo "* Importing sample audio clips".PHP_EOL;
system(__DIR__."/../utils/airtime-import --copy ../audio_samples/ > /dev/null");
echo "* Python eggs Setup".PHP_EOL;
AirtimeInstall::SetUpPythonEggs();
echo PHP_EOL."*** Pypo Installation ***".PHP_EOL;
system("python ".__DIR__."/../pypo/install/pypo-install.py");
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 "******************************* Install Complete *******************************".PHP_EOL;

View File

@ -20,6 +20,7 @@ require_once(dirname(__FILE__).'/installInit.php');
// Need to check that we are superuser before running this.
AirtimeInstall::ExitIfNotRoot();
AirtimeInstall::RemoveSymlinks();
echo "******************************* Uninstall Begin ********************************".PHP_EOL;
//------------------------------------------------------------------------
@ -38,7 +39,7 @@ $command = "sudo -u postgres dropdb {$CC_CONFIG['dsn']['database']} 2> /dev/null
//------------------------------------------------------------------------
if ($dbDeleteFailed) {
echo " * Couldn't delete the database, so deleting all the DB tables...".PHP_EOL;
AirtimeInstall::DbConnect(true);
AirtimeInstall::DbConnect(false);
if (!PEAR::isError($CC_DBC)) {
$sql = "select * from pg_tables where tableowner = 'airtime'";
@ -81,7 +82,10 @@ if ($results == 0) {
AirtimeInstall::DeleteFilesRecursive($CC_CONFIG['storageDir']);
$command = "python ".__DIR__."/../pypo/install/pypo-uninstall.py";
$command = "python ".__DIR__."/../python_apps/pypo/install/pypo-uninstall.py";
system($command);
$command = "python ".__DIR__."/../python_apps/show-recorder/install/recorder-uninstall.py";
system($command);
echo "****************************** Uninstall Complete ******************************".PHP_EOL;

View File

@ -79,7 +79,8 @@ class AirtimeInstall {
{
$api_key = AirtimeInstall::GenerateRandomString();
AirtimeInstall::UpdateIniValue(__DIR__.'/../build/airtime.conf', 'api_key', $api_key);
AirtimeInstall::UpdateIniValue(__DIR__.'/../pypo/config.cfg', 'api_key', "'$api_key'");
AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/pypo/config.cfg', 'api_key', "'$api_key'");
AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/show-recorder/config.cfg', 'api_key', "'$api_key'");
}
public static function ExitIfNotRoot()
@ -112,7 +113,7 @@ class AirtimeInstall {
public static function SetupStorageDirectory($CC_CONFIG)
{
global $CC_CONFIG, $CC_DBC;
echo PHP_EOL."*** Directory Setup ***".PHP_EOL;
foreach (array('baseFilesDir', 'storageDir') as $d) {
if ( !file_exists($CC_CONFIG[$d]) ) {
@ -141,7 +142,7 @@ class AirtimeInstall {
// Create the database user
$command = "sudo -u postgres psql postgres --command \"CREATE USER {$CC_CONFIG['dsn']['username']} "
." ENCRYPTED PASSWORD '{$CC_CONFIG['dsn']['password']}' LOGIN CREATEDB NOCREATEUSER;\" 2>/dev/null";
@exec($command, $output, $results);
if ($results == 0) {
echo "* Database user '{$CC_CONFIG['dsn']['username']}' created.".PHP_EOL;
@ -159,7 +160,7 @@ class AirtimeInstall {
public static function CreateDatabase()
{
global $CC_CONFIG;
$command = "sudo -u postgres createdb {$CC_CONFIG['dsn']['database']} --owner {$CC_CONFIG['dsn']['username']} 2> /dev/null";
@exec($command, $output, $results);
if ($results == 0) {
@ -202,10 +203,33 @@ class AirtimeInstall {
system($command);
}
public static function SetUpPythonEggs()
{
//install poster streaming upload
$command = "sudo easy_install poster";
@exec($command);
}
public static function DeleteFilesRecursive($p_path)
{
$command = "rm -rf $p_path";
exec($command);
}
}
public static function CreateSymlinks(){
AirtimeInstall::RemoveSymlinks();
$dir = realpath(__DIR__."/../utils/airtime-import");
exec("ln -s $dir /usr/bin/airtime-import");
$dir = realpath(__DIR__."/../utils/airtime-clean-storage");
exec("ln -s $dir /usr/bin/airtime-clean-storage");
}
public static function RemoveSymlinks(){
exec("rm -f /usr/bin/airtime-import");
exec("rm -f /usr/bin/airtime-clean-storage");
}
}

View File

@ -0,0 +1,54 @@
#!/usr/bin/php
<?php
/**
* Repeatedly receive messages from queue until it receives a message with
* 'quit' as the body.
*
* @author Sean Murphy<sean@iamseanmurphy.com>
*/
require_once('../amqp.inc');
$HOST = 'localhost';
$PORT = 5672;
$USER = 'guest';
$PASS = 'guest';
$VHOST = '/';
$EXCHANGE = 'airtime-schedule';
$QUEUE = 'airtime-schedule-msgs';
$CONSUMER_TAG = 'airtime-consumer';
$conn = new AMQPConnection($HOST, $PORT, $USER, $PASS);
$ch = $conn->channel();
$ch->access_request($VHOST, false, false, true, true);
$ch->queue_declare($QUEUE);
$ch->exchange_declare($EXCHANGE, 'direct', false, false, false);
$ch->queue_bind($QUEUE, $EXCHANGE);
function process_message($msg) {
global $ch, $CONSUMER_TAG;
echo "\n--------\n";
echo $msg->body;
echo "\n--------\n";
$ch->basic_ack($msg->delivery_info['delivery_tag']);
// Cancel callback
if ($msg->body === 'quit') {
$ch->basic_cancel($CONSUMER_TAG);
}
}
$ch->basic_consume($QUEUE, $CONSUMER_TAG, false, false, false, false, 'process_message');
// Loop as long as the channel has callbacks registered
echo "Waiting for messages...\n";
while(count($ch->callbacks)) {
$ch->wait();
}
$ch->close();
$conn->close();
?>

267
plugins/jquery.showinfo.js Normal file
View File

@ -0,0 +1,267 @@
(function($){
$.fn.airtimeShowSchedule = function(options) {
var defaults = {
updatePeriod: 20, //seconds
sourceDomain: "http://localhost/", //where to get show status from
};
var options = $.extend(defaults, options);
return this.each(function() {
var obj = $(this);
var sd;
getServerData();
function updateWidget(){
var currentShow = sd.getCurrentShow();
var nextShows = sd.getNextShows();
var currentShowName = "";
var nextShowName = ""
if (currentShow.length > 0){
currentShowName = currentShow[0].getName();
}
if (nextShows.length > 0){
nextShowName = nextShows[0].getName();
}
tableString = "";
tableString += "<h3>On air today</h3>";
tableString += "<table width='100%' border='0' cellspacing='0' cellpadding='0' class='widget widget no-playing-list small'>"+
"<tbody>";
var shows=currentShow.concat(nextShows);
obj.empty();
for (var i=0; i<shows.length; i++){
tableString +=
"<tr>" +
"<td class='time'>"+shows[i].getRange()+"</td>" +
"<td><a href='#'>"+shows[i].getName()+"</a> <a href='#' class='listen'>Listen</a></td>" +
"</tr>";
}
tableString += "</tbody></table>";
obj.append(tableString);
}
function processData(data){
sd = new ScheduleData(data);
updateWidget();
}
function getServerData(){
$.ajax({ url: options.sourceDomain + "api/live-info/", dataType:"jsonp", success:function(data){
processData(data);
}, error:function(jqXHR, textStatus, errorThrown){}});
setTimeout(getServerData, defaults.updatePeriod*1000);
}
});
};
})(jQuery);
(function($){
$.fn.airtimeLiveInfo = function(options) {
var defaults = {
updatePeriod: 5, //seconds
sourceDomain: "http://localhost/", //where to get show status from
audioStreamSource: "" //where to get audio stream from
};
var options = $.extend(defaults, options);
return this.each(function() {
var obj = $(this);
var sd;
getServerData();
function updateWidget(){
var currentShow = sd.getCurrentShow();
var nextShows = sd.getNextShows();
var showStatus = "Offline";
var currentShowName = "";
var timeElapsed = "";
var timeRemaining = "";
var nextShowName = "";
var nextShowRange = "";
if (currentShow.length > 0){
showStatus = "On Air Now";
currentShowName = currentShow[0].getName();
timeElapsed = sd.getShowTimeElapsed(currentShow[0]);
timeRemaining = sd.getShowTimeRemaining(currentShow[0]);
}
if (nextShows.length > 0){
nextShowName = nextShows[0].getName();
nextShowRange = nextShows[0].getRange();
}
obj.empty();
obj.append("<a id='listenWadrLive'><span>Listen WADR Live</span></a>");
obj.append("<h4>"+showStatus+" &gt;&gt;</h4>");
obj.append("<ul class='widget no-playing-bar'>" +
"<li class='current'>Current: "+currentShowName+
"<span id='time-elapsed' class='time-elapsed'>"+timeElapsed+"</span>" +
"<span id='time-remaining' class='time-remaining'>"+timeRemaining+"</span>"+
"</li>" +
"<li class='next'>Next: "+nextShowName+"<span>"+nextShowRange+"</span></li>" +
"</ul>");
//refresh the UI to update the elapsed/remaining time
setTimeout(updateWidget, 1000);
}
function processData(data){
sd = new ScheduleData(data);
updateWidget();
}
function getServerData(){
$.ajax({ url: options.sourceDomain + "api/live-info/", dataType:"jsonp", success:function(data){
processData(data);
}, error:function(jqXHR, textStatus, errorThrown){}});
setTimeout(getServerData, defaults.updatePeriod*1000);
}
});
};
})(jQuery);
/* ScheduleData class BEGIN */
function ScheduleData(data){
this.data = data;
this.estimatedSchedulePosixTime;
this.currentShow = new Array();
for (var i=0; i< data.currentShow.length; i++){
this.currentShow[i] = new Show(data.currentShow[i]);
}
this.nextShows = new Array();
for (var i=0; i< data.nextShow.length; i++){
this.nextShows[i] = new Show(data.nextShow[i]);
}
this.schedulePosixTime = convertDateToPosixTime(data.schedulerTime);
this.schedulePosixTime += parseInt(data.timezoneOffset)*1000;
var date = new Date();
this.localRemoteTimeOffset = date.getTime() - this.schedulePosixTime;
}
ScheduleData.prototype.secondsTimer = function(){
var date = new Date();
this.estimatedSchedulePosixTime = date.getTime() - this.localRemoteTimeOffset;
}
ScheduleData.prototype.getCurrentShow = function(){
return this.currentShow;
}
ScheduleData.prototype.getNextShows = function() {
return this.nextShows;
}
ScheduleData.prototype.getShowTimeElapsed = function(show) {
this.secondsTimer();
var showStart = convertDateToPosixTime(show.getStartTimestamp());
return convertToHHMMSS(this.estimatedSchedulePosixTime - showStart);
};
ScheduleData.prototype.getShowTimeRemaining = function(show) {
this.secondsTimer();
var showEnd = convertDateToPosixTime(show.getEndTimestamp());
return convertToHHMMSS(showEnd - this.estimatedSchedulePosixTime);
};
/* ScheduleData class END */
/* Show class BEGIN */
function Show(showData){
this.showData = showData;
}
Show.prototype.getName = function(){
return this.showData.name;
}
Show.prototype.getRange = function(){
return getTime(this.showData.start_timestamp) + " - " + getTime(this.showData.end_timestamp);
}
Show.prototype.getStartTimestamp = function(){
return this.showData.start_timestamp;
}
Show.prototype.getEndTimestamp = function(){
return this.showData.end_timestamp;
}
/* Show class END */
function getTime(timestamp) {
var time = timestamp.split(" ")[1].split(":");
return time[0] + ":" + time[1];
};
/* Takes an input parameter of milliseconds and converts these into
* the format HH:MM:SS */
function convertToHHMMSS(timeInMS){
var time = parseInt(timeInMS);
var hours = parseInt(time / 3600000);
time -= 3600000*hours;
var minutes = parseInt(time / 60000);
time -= 60000*minutes;
var seconds = parseInt(time / 1000);
hours = hours.toString();
minutes = minutes.toString();
seconds = seconds.toString();
if (hours.length == 1)
hours = "0" + hours;
if (minutes.length == 1)
minutes = "0" + minutes;
if (seconds.length == 1)
seconds = "0" + seconds;
if (hours == "00")
return minutes + ":" + seconds;
else
return hours + ":" + minutes + ":" + seconds;
}
/* Takes in a string of format similar to 2011-02-07 02:59:57,
* and converts this to epoch/posix time. */
function convertDateToPosixTime(s){
var datetime = s.split(" ");
var date = datetime[0].split("-");
var time = datetime[1].split(":");
var year = date[0];
var month = date[1];
var day = date[2];
var hour = time[0];
var minute = time[1];
var sec = 0;
var msec = 0;
if (time[2].indexOf(".") != -1){
var temp = time[2].split(".");
sec = temp[0];
msec = temp[1];
} else
sec = time[2];
return Date.UTC(year, month, day, hour, minute, sec, msec);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -15,7 +15,11 @@
#spl_sortable > li,
#side_playlist > div,
#spl_editor,
.spl_artist {
.spl_artist,
.spl_cue_in,
.spl_fade_in,
.spl_cue_out,
.spl_fade_out {
clear: left;
}
@ -35,8 +39,8 @@
#spl_sortable {
list-style: none;
padding:0;
height: 400px;
overflow-y: scroll;
height: 300px;
overflow: auto;
width:100%;
margin-top:0;
}
@ -52,6 +56,10 @@
border: none;
}
#spl_name {
}
.ui-icon-closethick {
margin-top: 7px;
}
@ -72,10 +80,18 @@
font-size:12px;
}
/*#spl_editor {
height: 50px;
}*/
#spl_editor > div > span {
/* display: inline-block;
width: 150px;*/
}
.ui-icon-closethick,
.ui-icon-play,
.spl_fade_control,
.spl_playlength,
.spl_text_input {
cursor: pointer;
}
@ -239,13 +255,12 @@
margin: 0;
}
dd.edit-error {
.edit-error {
color:#b80000;
margin:0;
padding-bottom:0;
font-size:12px;
display:none;
clear: left;
}
/*.edit-error:last-child {
@ -276,3 +291,26 @@ dd.edit-error {
top: 3px;
z-index: 3;
}
#spl_sortable li .spl_cue {
background-color: transparent;
float:right;
font-size: 9px;
height: 15px;
right: 35px;
width: 33px;
margin-top:2px;
cursor:pointer;
}
#spl_sortable li .spl_cue.ui-state-default {
background: transparent url(images/cue_playlist.png) no-repeat 0 0;
border:none;
}
#spl_sortable li .spl_cue.ui-state-default:hover {
background: transparent url(images/cue_playlist.png) no-repeat 0 -15px;
border:none;
}
#spl_sortable li .spl_cue.ui-state-active, #spl_sortable li .spl_cue.ui-state-active:hover {
background: transparent url(images/cue_playlist.png) no-repeat 0 -30px;
border:none;
}

View File

@ -184,7 +184,7 @@ select {
.progressbar .progress-show-error {
background:#d40000 url(images/progressbar_show_error.png) repeat-x 0 0;
}
.now-playing-info .lenght {
.now-playing-info .show-length {
color:#c4c4c4;
padding-left:6px;
}
@ -196,6 +196,7 @@ select {
.time-info-block {
padding:0 14px 0 2px;
background:url(images/masterpanel_spacer.png) no-repeat right 0;
min-width:105px;
}
.time-info-block ul {
margin:0;
@ -825,7 +826,6 @@ div.ui-datepicker {
#schedule_playlist_chosen li > h3 > span.ui-icon.ui-icon-triangle-1-e,
#schedule_playlist_chosen li > h3 > span.ui-icon.ui-icon-triangle-1-s {
float:left;
margin-right: 8px;
}
#schedule_playlist_chosen li > h3 > span.ui-icon.ui-icon-close {
@ -1219,6 +1219,16 @@ ul.errors li {
margin-bottom:2px;
border:1px solid #c83f3f;
}
div.success{
color:#3B5323;
font-size:11px;
padding:2px 4px;
background:#93DB70;
margin-bottom:2px;
border:1px solid #488214;
}
.collapsible-header {
border: 1px solid #8f8f8f;
background-color: #cccccc;
@ -1474,9 +1484,35 @@ ul.errors li {
margin:-4px 3px -3px 0;
float:right;
}
.time-flow {
float:right;
margin-right:4px;
float:right;
margin-right:4px;
}
.small-icon {
display:block;
width:20px;
height:10px;
float:right;
margin-left:3px;
}
.small-icon.recording {
background:url(images/icon_record.png) no-repeat 0 0;
}
.small-icon.rebroadcast {
background:url(images/icon_rebroadcast.png) no-repeat 0 0;
}
.medium-icon {
display:block;
width:25px;
height:12px;
float:right;
margin-left:4px;
}
.medium-icon.recording {
background:url(images/icon_record_m.png) no-repeat 0 0;
}
.medium-icon.rebroadcast {
background:url(images/icon_rebroadcast_m.png) no-repeat 0 0;
}

View File

@ -205,13 +205,15 @@ function openFadeEditor(event) {
function openCueEditor(event) {
event.stopPropagation();
var pos, url, li;
var pos, url, li, icon;
li = $(this).parent().parent().parent();
icon = $(this);
pos = li.attr("id").split("_").pop();
if(li.hasClass("ui-state-active")) {
li.removeClass("ui-state-active");
icon.attr("class", "spl_cue ui-state-default");
$("#cues_"+pos)
.empty()
@ -220,6 +222,7 @@ function openCueEditor(event) {
return;
}
icon.attr("class", "spl_cue ui-state-default ui-state-active");
url = '/Playlist/set-cue';
highlightActive(li);
@ -253,7 +256,8 @@ function setSPLContent(json) {
$("#spl_sortable .ui-icon-closethick").click(deleteSPLItem);
$(".spl_fade_control").click(openFadeEditor);
$(".spl_playlength").click(openCueEditor);
//$(".spl_playlength").click(openCueEditor);
$(".spl_cue").click(openCueEditor);
return false;
}
@ -487,7 +491,8 @@ function setUpSPL() {
$("#spl_sortable .ui-icon-closethick").click(deleteSPLItem);
$(".spl_fade_control").click(openFadeEditor);
$(".spl_playlength").click(openCueEditor);
//$(".spl_playlength").click(openCueEditor);
$(".spl_cue").click(openCueEditor);
$("#spl_sortable").droppable();
$("#spl_sortable" ).bind( "drop", addSPLItem);

View File

@ -182,7 +182,6 @@ function setAddShowEvents() {
});
form.find("#add-show-submit")
.button()
.click(function(event){
event.preventDefault();

View File

@ -165,6 +165,25 @@ function eventRender(event, element, view) {
}
$(element).find(".fc-event-title").after(div);
}
//add the record/rebroadcast icons if needed.
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1) {
$(element).find(".fc-event-time").after('<span class="small-icon recording"></span>');
}
if(view.name === 'month' && event.record === 1) {
$(element).find(".fc-event-title").after('<span class="small-icon recording"></span>');
}
if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.rebroadcast === 1) {
$(element).find(".fc-event-time").after('<span class="small-icon rebroadcast"></span>');
}
if(view.name === 'month' && event.rebroadcast === 1) {
$(element).find(".fc-event-title").after('<span class="small-icon rebroadcast"></span>');
}
if(event.backgroundColor !== "") {

View File

@ -2,6 +2,7 @@ function populateForm(entries){
//$('#user_details').show();
$('.errors').remove();
$('.success').remove();
$('#user_id').val(entries.id);
$('#login').val(entries.login);

View File

@ -165,11 +165,12 @@ function updatePlaybar(){
/* Column 1 update */
$('#playlist').text("Current Show:");
var recElem = $('.recording-show');
if (currentShow.length > 0){
$('#playlist').text(currentShow[0].name);
var recElem = $('.recording-show');
currentShow[0].record ? recElem.show(): recElem.hide();
(currentShow[0].record == "1") ? recElem.show(): recElem.hide();
} else {
recElem.hide();
}
$('#show-length').empty();

View File

@ -1,9 +0,0 @@
import airtime_api_client
import obp_api_client
def create_api_client(config):
if config["api_client"] == "airtime":
return campcaster_api_client.AirtimeApiClient(config)
elif config["api_client"] == "obp":
return obp_api_client.ObpApiClient(config)

View File

View File

View File

@ -1,14 +0,0 @@
#!/bin/sh
pypo_user="pypo"
export HOME="/home/pypo/"
# Location of pypo_cli.py Python script
pypo_path="/opt/pypo/bin/"
pypo_script="pypo-cli.py"
echo "*** Daemontools: starting daemon"
cd ${pypo_path}
exec 2>&1
# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr
exec setuidgid ${pypo_user} \
python -u ${pypo_path}${pypo_script} \
-f
# EOF

View File

@ -1,23 +0,0 @@
#!/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 "Stopping daemontool script pypo-fetch"
os.system("svc -dx /etc/service/pypo-fetch 2>/dev/null")
print "Stopping daemontool script pypo-push"
os.system("svc -dx /etc/service/pypo-push 2>/dev/null")
print "Stopping daemontool script pypo-liquidsoap"
os.system("svc -dx /etc/service/pypo-liquidsoap 2>/dev/null")
os.system("killall liquidsoap")
except Exception, e:
print "exception:" + str(e)

View File

@ -1,60 +0,0 @@
[loggers]
keys=root
[handlers]
keys=consoleHandler,fileHandlerERROR,fileHandlerDEBUG,nullHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandlerERROR,fileHandlerDEBUG
[logger_libs]
handlers=nullHandler
level=DEBUG
qualname="process"
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandlerERROR]
class=FileHandler
level=WARNING
formatter=simpleFormatter
args=("./error.log",)
[handler_fileHandlerDEBUG]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=("./debug.log",)
[handler_nullHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=("/dev/null",)
[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
datefmt=
## multitail color sheme
## pyml / python
# colorscheme:pyml:www.obp.net
# cs_re:blue:\[[^ ]*\]
# cs_re:red:CRITICAL:*
# cs_re:red,black,blink:ERROR:*
# cs_re:blue:NOTICE:*
# cs_re:cyan:INFO:*
# cs_re:green:DEBUG:*

Binary file not shown.

View File

@ -13,6 +13,7 @@
import sys
import time
import urllib
import urllib2
import logging
import json
import os
@ -90,6 +91,12 @@ class ApiClientInterface:
# You will be able to use this data in update_start_playing
def get_liquidsoap_data(self, pkey, schedule):
pass
def get_shows_to_record(self):
pass
def upload_recorded_show(self):
pass
# Put here whatever tests you want to run to make sure your API is working
def test(self):
@ -189,30 +196,10 @@ class AirTimeApiClient(ApiClientInterface):
def get_schedule(self, start=None, end=None):
logger = logging.getLogger()
"""
calculate start/end time range (format: YYYY-DD-MM-hh-mm-ss,YYYY-DD-MM-hh-mm-ss)
(seconds are ignored, just here for consistency)
"""
tnow = time.localtime(time.time())
if (not start):
tstart = time.localtime(time.time() - 3600 * int(self.config["cache_for"]))
start = "%04d-%02d-%02d-%02d-%02d" % (tstart[0], tstart[1], tstart[2], tstart[3], tstart[4])
if (not end):
tend = time.localtime(time.time() + 3600 * int(self.config["prepare_ahead"]))
end = "%04d-%02d-%02d-%02d-%02d" % (tend[0], tend[1], tend[2], tend[3], tend[4])
range = {}
range['start'] = start
range['end'] = end
# Construct the URL
export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"]
# Insert the start and end times into the URL
export_url = export_url.replace('%%from%%', range['start'])
export_url = export_url.replace('%%to%%', range['end'])
logger.info("Fetching schedule from %s", export_url)
export_url = export_url.replace('%%api_key%%', self.config["api_key"])
@ -225,24 +212,6 @@ class AirTimeApiClient(ApiClientInterface):
except Exception, e:
print e
#schedule = response["playlists"]
#scheduleKeys = sorted(schedule.iterkeys())
#
## Remove all playlists that have passed current time
#try:
# tnow = time.localtime(time.time())
# str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
# toRemove = []
# for pkey in scheduleKeys:
# if (str_tnow_s > schedule[pkey]['end']):
# toRemove.append(pkey)
# else:
# break
# for index in toRemove:
# del schedule[index]
#except Exception, e:
#response["playlists"] = schedule
return status, response
@ -316,6 +285,41 @@ class AirTimeApiClient(ApiClientInterface):
except Exception, e:
data["schedule_id"] = 0
return data
def get_shows_to_record(self):
logger = logging.getLogger()
response = ''
try:
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"])
response = urllib.urlopen(url)
response = json.loads(response.read())
logger.info("shows %s", response)
except Exception, e:
logger.error("Exception: %s", e)
return response[u'shows']
def upload_recorded_show(self, data, headers):
logger = logging.getLogger()
response = ''
try:
url = self.config["base_url"] + self.config["api_base"] + self.config["upload_file_url"]
logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
request = urllib2.Request(url, data, headers)
response = urllib2.urlopen(request).read().strip()
logger.info("uploaded show result %s", response)
except Exception, e:
logger.error("Exception: %s", e)
return response

View File

@ -26,6 +26,13 @@ base_url = 'http://localhost/'
ls_host = '127.0.0.1'
ls_port = '1234'
############################################
# RabbitMQ settings #
############################################
rabbitmq_host = 'localhost'
rabbitmq_user = 'guest'
rabbitmq_password = 'guest'
############################################
# pypo preferences #
############################################
@ -34,15 +41,13 @@ cache_for = 24 #how long to hold the cache, in hours
# Poll interval in seconds.
#
# This will rarely need to be changed because any schedule changes are
# automatically sent to pypo immediately.
#
# This is how often the poll script downloads new schedules and files from the
# server.
# server in the event that no changes are made to the schedule.
#
# For production use, this number depends on whether you plan on making any
# last-minute changes to your schedule. This number should be set to half of
# the time you expect to "lock-in" your schedule. So if your schedule is set
# 24 hours in advance, this can be set to poll every 12 hours.
#
poll_interval = 5 # in seconds.
poll_interval = 3600 # in seconds.
# Push interval in seconds.
@ -52,7 +57,7 @@ poll_interval = 5 # in seconds.
#
# It's hard to imagine a situation where this should be more than 1 second.
#
push_interval = 2 # in seconds
push_interval = 1 # in seconds
# 'pre' or 'otf'. 'pre' cues while playlist preparation
# while 'otf' (on the fly) cues while loading into ls
@ -80,7 +85,7 @@ version_url = 'version/api_key/%%api_key%%'
# Schedule export path.
# %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm
# %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm
export_url = 'schedule/api_key/%%api_key%%/from/%%from%%/to/%%to%%'
export_url = 'schedule/api_key/%%api_key%%'
# Update whether a schedule group has begun playing.
update_item_url = 'notify-schedule-group-play/api_key/%%api_key%%/schedule_id/%%schedule_id%%'

View File

@ -1,10 +1,12 @@
#!/bin/sh
ls_user="pypo"
export HOME="/home/pypo/"
api_client_path="/opt/pypo/"
ls_path="/opt/pypo/bin/liquidsoap/liquidsoap"
ls_param="/opt/pypo/bin/scripts/ls_script.liq"
echo "*** Daemontools: starting liquidsoap"
exec 2>&1
echo "exec sudo -u ${ls_user} ${ls_path} ${ls_param} "
cd /opt/pypo/bin/scripts && sudo -u ${ls_user} ${ls_path} ${ls_param}
cd /opt/pypo/bin/scripts
sudo PYTHONPATH=${api_client_path} -u ${ls_user} ${ls_path} ${ls_param}
# EOF

View File

@ -3,12 +3,16 @@ pypo_user="pypo"
export HOME="/home/pypo/"
# Location of pypo_cli.py Python script
pypo_path="/opt/pypo/bin/"
api_client_path="/opt/pypo/"
pypo_script="pypo-cli.py"
echo "*** Daemontools: starting daemon"
cd ${pypo_path}
exec 2>&1
PYTHONPATH=${api_client_path}:$PYTHONPATH
export PYTHONPATH
# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr
exec setuidgid ${pypo_user} \
python -u ${pypo_path}${pypo_script} \
-p
python -u ${pypo_path}${pypo_script}
# EOF

View File

@ -36,14 +36,16 @@ def create_user(username):
os.system("adduser --system --quiet --group --shell /bin/bash "+username)
#set pypo password
p = os.popen('/usr/bin/passwd pypo', 'w')
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 2>&1 1>/dev/null")
os.system("adduser " + username + " audio 1>/dev/null 2>&1")
#add pypo to pulse-access group
#os.system("adduser " + username + " pulse-access 1>/dev/null 2>&1")
def copy_dir(src_dir, dest_dir):
if (os.path.exists(dest_dir)) and (dest_dir != "/"):
@ -63,7 +65,7 @@ def get_current_script_dir():
try:
current_script_dir = get_current_script_dir()
print "Checking and removing any existing pypo processes"
os.system("python %s/pypo-uninstall.py 2>&1 1>/dev/null"% current_script_dir)
os.system("python %s/pypo-uninstall.py 1>/dev/null 2>&1"% current_script_dir)
time.sleep(5)
# Create users
@ -79,14 +81,8 @@ try:
create_path(BASE_PATH+"cache")
create_path(BASE_PATH+"files")
create_path(BASE_PATH+"tmp")
create_path(BASE_PATH+"files/basic")
create_path(BASE_PATH+"files/fallback")
create_path(BASE_PATH+"files/jingles")
create_path(BASE_PATH+"archive")
print "Copying pypo files"
shutil.copy("%s/../scripts/silence.mp3"%current_script_dir, BASE_PATH+"files/basic")
if platform.architecture()[0] == '64bit':
print "Installing 64-bit liquidsoap binary"
shutil.copy("%s/../liquidsoap/liquidsoap64"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
@ -98,28 +94,21 @@ try:
sys.exit(1)
copy_dir("%s/.."%current_script_dir, BASE_PATH+"bin/")
copy_dir("%s/../../api_clients"%current_script_dir, BASE_PATH+"api_clients/")
print "Setting permissions"
os.system("chmod -R 755 "+BASE_PATH)
os.system("chown -R pypo:pypo "+BASE_PATH)
print "Installing daemontool script pypo-fetch"
create_path("/etc/service/pypo-fetch")
create_path("/etc/service/pypo-fetch/log")
shutil.copy("%s/pypo-daemontools-fetch.sh"%current_script_dir, "/etc/service/pypo-fetch/run")
shutil.copy("%s/pypo-daemontools-logger.sh"%current_script_dir, "/etc/service/pypo-fetch/log/run")
os.system("chmod -R 755 /etc/service/pypo-fetch")
os.system("chown -R pypo:pypo /etc/service/pypo-fetch")
print "Installing pypo daemon"
create_path("/etc/service/pypo")
create_path("/etc/service/pypo/log")
shutil.copy("%s/pypo-daemontools.sh"%current_script_dir, "/etc/service/pypo/run")
shutil.copy("%s/pypo-daemontools-logger.sh"%current_script_dir, "/etc/service/pypo/log/run")
os.system("chmod -R 755 /etc/service/pypo")
os.system("chown -R pypo:pypo /etc/service/pypo")
print "Installing daemontool script pypo-push"
create_path("/etc/service/pypo-push")
create_path("/etc/service/pypo-push/log")
shutil.copy("%s/pypo-daemontools-push.sh"%current_script_dir, "/etc/service/pypo-push/run")
shutil.copy("%s/pypo-daemontools-logger.sh"%current_script_dir, "/etc/service/pypo-push/log/run")
os.system("chmod -R 755 /etc/service/pypo-push")
os.system("chown -R pypo:pypo /etc/service/pypo-push")
print "Installing daemontool script pypo-liquidsoap"
print "Installing liquidsoap daemon"
create_path("/etc/service/pypo-liquidsoap")
create_path("/etc/service/pypo-liquidsoap/log")
shutil.copy("%s/pypo-daemontools-liquidsoap.sh"%current_script_dir, "/etc/service/pypo-liquidsoap/run")
@ -134,16 +123,12 @@ try:
found = True
p = Popen('svstat /etc/service/pypo-fetch', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
p = Popen('svstat /etc/service/pypo', 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
p = Popen('svstat /etc/service/pypo-push', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
output = p.stdout.read()
print output
p = Popen('svstat /etc/service/pypo-liquidsoap', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
output = p.stdout.read()
print output
@ -152,6 +137,7 @@ try:
print "Pypo 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

@ -9,12 +9,9 @@ if os.geteuid() != 0:
sys.exit(1)
try:
print "Starting daemontool script pypo-fetch"
os.system("svc -u /etc/service/pypo-fetch")
print "Starting daemontool script pypo-push"
os.system("svc -u /etc/service/pypo-push")
print "Starting daemontool script pypo"
os.system("svc -u /etc/service/pypo")
print "Starting daemontool script pypo-liquidsoap"
os.system("svc -u /etc/service/pypo-liquidsoap")

View File

@ -0,0 +1,25 @@
#!/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 "Stopping daemontool script pypo"
os.system("svc -dx /etc/service/pypo 1>/dev/null 2>&1")
if os.path.exists("/etc/service/pypo-fetch"):
os.system("svc -dx /etc/service/pypo-fetch 1>/dev/null 2>&1")
if os.path.exists("/etc/service/pypo-push"):
os.system("svc -dx /etc/service/pypo-push 1>/dev/null 2>&1")
print "Stopping daemontool script pypo-liquidsoap"
os.system("svc -dx /etc/service/pypo-liquidsoap 1>/dev/null 2>&1")
os.system("killall liquidsoap")
except Exception, e:
print "exception:" + str(e)

View File

@ -15,13 +15,13 @@ def remove_path(path):
os.system("rm -rf " + path)
def remove_user(username):
os.system("killall -u %s 2>&1 1>/dev/null" % 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")
os.system("deluser --remove-home " + username + " 1>/dev/null 2>&1")
def get_current_script_dir():
current_script_dir = os.path.realpath(__file__)
@ -37,16 +37,19 @@ try:
print "Removing pypo files"
remove_path(BASE_PATH)
print "Removing daemontool script pypo-fetch"
remove_path("rm -rf /etc/service/pypo-fetch")
print "Removing daemontool script pypo-push"
remove_path("rm -rf /etc/service/pypo-push")
print "Removing daemontool script pypo"
remove_path("/etc/service/pypo")
if os.path.exists("/etc/service/pypo-fetch"):
remove_path("/etc/service/pypo-fetch")
if os.path.exists("/etc/service/pypo-push"):
remove_path("/etc/service/pypo-push")
print "Removing daemontool script pypo-liquidsoap"
remove_path("rm -rf /etc/service/pypo-liquidsoap")
remove_path("/etc/service/pypo-liquidsoap")
remove_user("pypo")
print "Uninstall complete."
print "Pypo uninstall complete."
except Exception, e:
print "exception:" + str(e)

View File

@ -0,0 +1,34 @@
[loggers]
keys=root,fetch,push
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_fetch]
level=DEBUG
handlers=consoleHandler
qualname=fetch
propagate=0
[logger_push]
level=DEBUG
handlers=consoleHandler
qualname=push
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
datefmt=

View File

@ -7,14 +7,13 @@ Python part of radio playout (pypo)
The main functions are "fetch" (./pypo_cli.py -f) and "push" (./pypo_cli.py -p)
"""
# python defaults (debian default)
import time
#import calendar
#import traceback
from optparse import *
import sys
import os
import signal
#import datetime
import logging
import logging.config
@ -27,11 +26,11 @@ import logging.config
#import string
#import operator
#import inspect
from Queue import Queue
from pypopush import PypoPush
from pypofetch import PypoFetch
# additional modules (should be checked)
from configobj import ConfigObj
# custom imports
@ -53,7 +52,6 @@ parser.add_option("-v", "--compat", help="Check compatibility with server API ve
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")
@ -66,10 +64,6 @@ logging.config.fileConfig("logging.cfg")
# loading config file
try:
config = ConfigObj('config.cfg')
POLL_INTERVAL = float(config['poll_interval'])
PUSH_INTERVAL = float(config['push_interval'])
LS_HOST = config['ls_host']
LS_PORT = config['ls_port']
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
@ -121,55 +115,44 @@ class Global:
for media in playlist['medias']:
print media
def keyboardInterruptHandler(signum, frame):
print "\nKeyboard Interrupt\n"
sys.exit();
if __name__ == '__main__':
print '###########################################'
print '# *** pypo *** #'
print '# Liquidsoap + External Scheduler #'
print '# Playout System #'
print '# Liquidsoap Scheduled Playout System #'
print '###########################################'
signal.signal(signal.SIGINT, keyboardInterruptHandler)
# initialize
g = Global()
g.selfcheck()
logger = logging.getLogger()
loops = 0
if options.test:
g.test_api()
sys.exit()
if options.fetch_scheduler:
pf = PypoFetch()
while True:
try: pf.fetch('scheduler')
except Exception, e:
print e
sys.exit()
q = Queue()
if (loops%2 == 0):
logger.info("heartbeat")
loops += 1
time.sleep(POLL_INTERVAL)
pp = PypoPush(q)
pp.daemon = True
pp.start()
if options.push_scheduler:
pp = PypoPush()
while True:
try: pp.push('scheduler')
except Exception, e:
print 'PUSH ERROR!! WILL EXIT NOW:('
print e
sys.exit()
pf = PypoFetch(q)
pf.daemon = True
pf.start()
if (loops%60 == 0):
logger.info("heartbeat")
loops += 1
time.sleep(PUSH_INTERVAL)
while True: time.sleep(3600)
#pp.join()
#pf.join()
"""
if options.check:
try: g.check_schedule()
except Exception, e:
@ -179,4 +162,4 @@ if __name__ == '__main__':
try: pf.cleanup('scheduler')
except Exception, e:
print e
sys.exit()
"""

View File

@ -9,146 +9,151 @@ import random
import string
import json
import telnetlib
import math
from threading import Thread
from subprocess import Popen, PIPE
# For RabbitMQ
from kombu.connection import BrokerConnection
from kombu.messaging import Exchange, Queue, Consumer, Producer
from api_clients import api_client
from util import CueFile
from configobj import ConfigObj
# configure logging
logging.config.fileConfig("logging.cfg")
# loading config file
try:
config = ConfigObj('config.cfg')
POLL_INTERVAL = float(config['poll_interval'])
PUSH_INTERVAL = 0.5
#PUSH_INTERVAL = float(config['push_interval'])
LS_HOST = config['ls_host']
LS_PORT = config['ls_port']
POLL_INTERVAL = int(config['poll_interval'])
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
class PypoFetch:
def __init__(self):
# Yuk - using a global, i know!
SCHEDULE_PUSH_MSG = []
"""
Handle a message from RabbitMQ, put it into our yucky global var.
Hopefully there is a better way to do this.
"""
def handle_message(body, message):
logger = logging.getLogger('fetch')
global SCHEDULE_PUSH_MSG
logger.info("Received schedule from RabbitMQ: " + message.body)
SCHEDULE_PUSH_MSG = json.loads(message.body)
# ACK the message to take it off the queue
message.ack()
class PypoFetch(Thread):
def __init__(self, q):
Thread.__init__(self)
logger = logging.getLogger('fetch')
self.api_client = api_client.api_client_factory(config)
self.cue_file = CueFile()
self.set_export_source('scheduler')
self.queue = q
logger.info("Initializing RabbitMQ stuff")
schedule_exchange = Exchange("airtime-schedule", "direct", durable=True, auto_delete=True)
schedule_queue = Queue("pypo-fetch", exchange=schedule_exchange, key="foo")
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
channel = self.connection.channel()
consumer = Consumer(channel, schedule_queue)
consumer.register_callback(handle_message)
consumer.consume()
logger.info("PypoFetch: init complete")
def set_export_source(self, export_source):
self.export_source = export_source
self.cache_dir = config["cache_dir"] + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule.pickle'
self.schedule_tracker_file = self.cache_dir + "schedule_tracker.pickle"
def check_matching_timezones(self, server_timezone):
logger = logging.getLogger('fetch')
process = Popen(["date", "+%z"], stdout=PIPE)
pypo_timezone = (process.communicate()[0]).strip(' \r\n\t')
if server_timezone != pypo_timezone:
logger.error("Server and pypo timezone offsets do not match. Audio playback may not start when expected!")
logger.error("Server timezone offset: %s", server_timezone)
logger.error("Pypo timezone offset: %s", pypo_timezone)
"""
Fetching part of pypo
- Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for")
- Saves a serialized file of the schedule
- playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
- runs the cleanup routine, to get rid of unused cashed files
Process the schedule
- Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for")
- Saves a serialized file of the schedule
- playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
- runs the cleanup routine, to get rid of unused cashed files
"""
def fetch(self, export_source):
"""
wrapper script for fetching the whole schedule (in json)
"""
logger = logging.getLogger()
def process_schedule(self, schedule_data, export_source):
logger = logging.getLogger('fetch')
self.schedule = schedule_data["playlists"]
try: os.mkdir(self.cache_dir)
except Exception, e: pass
# get schedule
self.check_matching_timezones(schedule_data["server_timezone"])
# Push stream metadata to liquidsoap
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
stream_metadata = schedule_data['stream_metadata']
try:
while self.get_schedule() != 1:
logger.warning("failed to read from export url")
time.sleep(1)
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
#encode in latin-1 due to telnet protocol not supporting utf-8
tn.write(('vars.stream_metadata_type %s\n' % stream_metadata['format']).encode('latin-1'))
tn.write(('vars.station_name %s\n' % stream_metadata['station_name']).encode('latin-1'))
tn.write('exit\n')
tn.read_all()
except Exception, e:
logger.error("Exception %s", e)
status = 0
# Download all the media and put playlists in liquidsoap format
try:
playlists = self.prepare_playlists()
except Exception, e: logger.error("%s", e)
# prepare the playlists
if config["cue_style"] == 'pre':
try: self.prepare_playlists_cue()
except Exception, e: logger.error("%s", e)
elif config["cue_style"] == 'otf':
try: self.prepare_playlists(self.export_source)
except Exception, e: logger.error("%s", e)
# Send the data to pypo-push
scheduled_data = dict()
scheduled_data['playlists'] = playlists
scheduled_data['schedule'] = self.schedule
scheduled_data['stream_metadata'] = schedule_data["stream_metadata"]
self.queue.put(scheduled_data)
# cleanup
try: self.cleanup(self.export_source)
except Exception, e: logger.error("%s", e)
def get_schedule(self):
logger = logging.getLogger()
status, response = self.api_client.get_schedule()
if status == 1:
logger.info("dump serialized schedule to %s", self.schedule_file)
schedule = response['playlists']
stream_metadata = response['stream_metadata']
try:
schedule_file = open(self.schedule_file, "w")
pickle.dump(schedule, schedule_file)
schedule_file.close()
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
#encode in latin-1 due to telnet protocol not supporting utf-8
tn.write(('vars.stream_metadata_type %s\n' % stream_metadata['format']).encode('latin-1'))
tn.write(('vars.station_name %s\n' % stream_metadata['station_name']).encode('latin-1'))
tn.write('exit\n')
logger.debug(tn.read_all())
except Exception, e:
logger.error("Exception %s", e)
status = 0
return status
#TODO this is a duplicate function!!!
def load_schedule(self):
logger = logging.getLogger()
schedule = None
# create the file if it doesnt exist
if (not os.path.exists(self.schedule_file)):
logger.debug('creating file ' + self.schedule_file)
open(self.schedule_file, 'w').close()
else:
# load the schedule from cache
#logger.debug('loading schedule file '+self.schedule_file)
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
schedule_file.close()
except Exception, e:
logger.error('%s', e)
return schedule
"""
Alternative version of playout preparation. Every playlist entry is
pre-cued if neccessary (cue_in/cue_out != 0) and stored in the
playlist folder.
file is eg 2010-06-23-15-00-00/17_cue_10.132-123.321.mp3
In this function every audio file is cut as necessary (cue_in/cue_out != 0)
and stored in a playlist folder.
file is e.g. 2010-06-23-15-00-00/17_cue_10.132-123.321.mp3
"""
def prepare_playlists_cue(self):
logger = logging.getLogger()
def prepare_playlists(self):
logger = logging.getLogger('fetch')
# Load schedule from disk
schedule = self.load_schedule()
schedule = self.schedule
playlists = dict()
# Dont do anything if schedule is empty
if (not schedule):
if not schedule:
logger.debug("Schedule is empty.")
return
return playlists
scheduleKeys = sorted(schedule.iterkeys())
try:
for pkey in scheduleKeys:
logger.info("found playlist at %s", pkey)
logger.info("Playlist starting at %s", pkey)
playlist = schedule[pkey]
# create playlist directory
@ -157,15 +162,15 @@ class PypoFetch:
except Exception, e:
pass
logger.debug('*****************************************')
logger.debug('pkey: ' + str(pkey))
logger.debug('cached at : ' + self.cache_dir + str(pkey))
logger.debug('subtype: ' + str(playlist['subtype']))
logger.debug('played: ' + str(playlist['played']))
logger.debug('schedule id: ' + str(playlist['schedule_id']))
logger.debug('duration: ' + str(playlist['duration']))
logger.debug('source id: ' + str(playlist['x_ident']))
logger.debug('*****************************************')
#logger.debug('*****************************************')
#logger.debug('pkey: ' + str(pkey))
#logger.debug('cached at : ' + self.cache_dir + str(pkey))
#logger.debug('subtype: ' + str(playlist['subtype']))
#logger.debug('played: ' + str(playlist['played']))
#logger.debug('schedule id: ' + str(playlist['schedule_id']))
#logger.debug('duration: ' + str(playlist['duration']))
#logger.debug('source id: ' + str(playlist['x_ident']))
#logger.debug('*****************************************')
if int(playlist['played']) == 1:
logger.info("playlist %s already played / sent to liquidsoap, so will ignore it", pkey)
@ -173,34 +178,32 @@ class PypoFetch:
elif int(playlist['subtype']) > 0 and int(playlist['subtype']) < 5:
ls_playlist = self.handle_media_file(playlist, pkey)
# write playlist file
plfile = open(self.cache_dir + str(pkey) + '/list.lsp', "w")
plfile.write(json.dumps(ls_playlist))
plfile.close()
logger.info('ls playlist file written to %s', self.cache_dir + str(pkey) + '/list.lsp')
playlists[pkey] = ls_playlist
except Exception, e:
logger.info("%s", e)
return playlists
"""
Download and cache the media files.
This handles both remote and local files.
Returns an updated ls_playlist string.
"""
def handle_media_file(self, playlist, pkey):
"""
This handles both remote and local files.
Returns an updated ls_playlist string.
"""
ls_playlist = []
logger = logging.getLogger()
logger = logging.getLogger('fetch')
for media in playlist['medias']:
logger.debug("Processing track %s", media['uri'])
fileExt = os.path.splitext(media['uri'])[1]
try:
if str(media['cue_in']) == '0' and str(media['cue_out']) == '0':
logger.debug('No cue in/out detected for this file')
#logger.debug('No cue in/out detected for this file')
dst = "%s%s/%s%s" % (self.cache_dir, str(pkey), str(media['id']), str(fileExt))
do_cue = False
else:
logger.debug('Cue in/out detected')
#logger.debug('Cue in/out detected')
dst = "%s%s/%s_cue_%s-%s%s" % \
(self.cache_dir, str(pkey), str(media['id']), str(float(media['cue_in']) / 1000), str(float(media['cue_out']) / 1000), str(fileExt))
do_cue = True
@ -225,7 +228,7 @@ class PypoFetch:
% (str(media['export_source']), media['id'], 0, str(float(media['fade_in']) / 1000), \
str(float(media['fade_out']) / 1000), media['row_id'],dst)
logger.debug(pl_entry)
#logger.debug(pl_entry)
"""
Tracks are only added to the playlist if they are accessible
@ -239,7 +242,7 @@ class PypoFetch:
entry['show_name'] = playlist['show_name']
ls_playlist.append(entry)
logger.debug("everything ok, adding %s to playlist", pl_entry)
#logger.debug("everything ok, adding %s to playlist", pl_entry)
else:
print 'zero-file: ' + dst + ' from ' + media['uri']
logger.warning("zero-size file - skipping %s. will not add it to playlist", dst)
@ -251,11 +254,15 @@ class PypoFetch:
return ls_playlist
"""
Download a file from a remote server and store it in the cache.
"""
def handle_remote_file(self, media, dst, do_cue):
logger = logging.getLogger()
logger = logging.getLogger('fetch')
if do_cue == False:
if os.path.isfile(dst):
logger.debug("file already in cache: %s", dst)
pass
#logger.debug("file already in cache: %s", dst)
else:
logger.debug("try to download %s", media['uri'])
self.api_client.get_media(media['uri'], dst)
@ -296,12 +303,12 @@ class PypoFetch:
logger.error("%s", e)
"""
Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR"
and deletes them.
"""
def cleanup(self, export_source):
"""
Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR"
and deletes them.
"""
logger = logging.getLogger()
logger = logging.getLogger('fetch')
offset = 3600 * int(config["cache_for"])
now = time.time()
@ -323,3 +330,41 @@ class PypoFetch:
print e
logger.error("%s", e)
"""
Main loop of the thread:
Wait for schedule updates from RabbitMQ, but in case there arent any,
poll the server to get the upcoming schedule.
"""
def run(self):
logger = logging.getLogger('fetch')
try: os.mkdir(self.cache_dir)
except Exception, e: pass
# Bootstrap: since we are just starting up, we need to grab the
# most recent schedule. After that we can just wait for updates.
status, schedule_data = self.api_client.get_schedule()
if status == 1:
self.process_schedule(schedule_data, "scheduler")
logger.info("Bootstrap complete: got initial copy of the schedule")
loops = 1
while True:
logger.info("Loop #"+str(loops))
try:
# Wait for messages from RabbitMQ. Timeout if we
# dont get any after POLL_INTERVAL.
self.connection.drain_events(timeout=POLL_INTERVAL)
# Hooray for globals!
schedule_data = SCHEDULE_PUSH_MSG
status = 1
except:
# We didnt get a message for a while, so poll the server
# to get an updated schedule.
status, schedule_data = self.api_client.get_schedule()
if status == 1:
self.process_schedule(schedule_data, "scheduler")
loops += 1

View File

@ -7,29 +7,38 @@ import pickle
import telnetlib
import calendar
import json
import math
from threading import Thread
from api_clients import api_client
from util import CueFile
from configobj import ConfigObj
# configure logging
logging.config.fileConfig("logging.cfg")
# loading config file
try:
config = ConfigObj('config.cfg')
POLL_INTERVAL = float(config['poll_interval'])
PUSH_INTERVAL = 0.5
#PUSH_INTERVAL = float(config['push_interval'])
LS_HOST = config['ls_host']
LS_PORT = config['ls_port']
PUSH_INTERVAL = 2
except Exception, e:
print 'Error loading config file: ', e
logger.error('Error loading config file %s', e)
sys.exit()
class PypoPush:
def __init__(self):
class PypoPush(Thread):
def __init__(self, q):
Thread.__init__(self)
self.api_client = api_client.api_client_factory(config)
self.cue_file = CueFile()
self.set_export_source('scheduler')
self.queue = q
self.schedule = dict()
self.playlists = dict()
self.stream_metadata = dict()
"""
push_ahead2 MUST be < push_ahead. The difference in these two values
@ -42,51 +51,58 @@ class PypoPush:
def set_export_source(self, export_source):
self.export_source = export_source
self.cache_dir = config["cache_dir"] + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule.pickle'
self.schedule_tracker_file = self.cache_dir + "schedule_tracker.pickle"
"""
The Push Loop - the push loop periodically (minimal 1/2 of the playlist-grid)
checks if there is a playlist that should be scheduled at the current time.
If yes, the temporary liquidsoap playlist gets replaced with the corresponding one,
The Push Loop - the push loop periodically checks if there is a playlist
that should be scheduled at the current time.
If yes, the current liquidsoap playlist gets replaced with the corresponding one,
then liquidsoap is asked (via telnet) to reload and immediately play it.
"""
def push(self, export_source):
logger = logging.getLogger()
logger = logging.getLogger('push')
self.schedule = self.load_schedule()
playedItems = self.load_schedule_tracker()
# get a new schedule from pypo-fetch
if not self.queue.empty():
scheduled_data = self.queue.get()
logger.debug("Received data from pypo-fetch")
self.schedule = scheduled_data['schedule']
self.playlists = scheduled_data['playlists']
self.stream_metadata = scheduled_data['stream_metadata']
tcoming = time.localtime(time.time() + self.push_ahead)
tcoming2 = time.localtime(time.time() + self.push_ahead2)
schedule = self.schedule
playlists = self.playlists
str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5])
str_tcoming2_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming2[0], tcoming2[1], tcoming2[2], tcoming2[3], tcoming2[4], tcoming2[5])
currently_on_air = False
if self.schedule == None:
logger.warn('Unable to loop schedule - maybe write in progress?')
logger.warn('Will try again in next loop.')
if schedule:
playedItems = self.load_schedule_tracker()
else:
for pkey in self.schedule:
timenow = time.time()
tcoming = time.localtime(timenow + self.push_ahead)
str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5])
tcoming2 = time.localtime(timenow + self.push_ahead2)
str_tcoming2_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming2[0], tcoming2[1], tcoming2[2], tcoming2[3], tcoming2[4], tcoming2[5])
tnow = time.localtime(timenow)
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
for pkey in schedule:
plstart = pkey[0:19]
start = self.schedule[pkey]['start']
end = self.schedule[pkey]['end']
start = schedule[pkey]['start']
end = schedule[pkey]['end']
playedFlag = (pkey in playedItems) and playedItems[pkey].get("played", 0)
if plstart == str_tcoming_s or (plstart < str_tcoming_s and plstart > str_tcoming2_s and not playedFlag):
logger.debug('Preparing to push playlist scheduled at: %s', pkey)
playlist = self.schedule[pkey]
playlist = schedule[pkey]
ptype = playlist['subtype']
currently_on_air = True
# We have a match, replace the current playlist and
# force liquidsoap to refresh.
if (self.push_liquidsoap(pkey, self.schedule, ptype) == 1):
if (self.push_liquidsoap(pkey, schedule, playlists) == 1):
logger.debug("Pushed to liquidsoap, updating 'played' status.")
# Marked the current playlist as 'played' in the schedule tracker
# so it is not called again in the next push loop.
@ -100,39 +116,28 @@ class PypoPush:
# Call API to update schedule states
logger.debug("Doing callback to server to update 'played' status.")
self.api_client.notify_scheduled_item_start_playing(pkey, self.schedule)
if self.schedule != None:
tnow = time.localtime(time.time())
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
for pkey in self.schedule:
start = self.schedule[pkey]['start']
end = self.schedule[pkey]['end']
self.api_client.notify_scheduled_item_start_playing(pkey, schedule)
start = schedule[pkey]['start']
end = schedule[pkey]['end']
if start <= str_tnow_s and str_tnow_s < end:
currently_on_air = True
else:
pass
#logger.debug('Empty schedule')
if not currently_on_air:
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
tn.write('source.skip\n'.encode('latin-1'))
tn.write('source.skip\n')
tn.write('exit\n')
tn.read_all()
#logger.info('source.skip')
#logger.debug(tn.read_all())
def push_liquidsoap(self, pkey, schedule, ptype):
logger = logging.getLogger()
src = self.cache_dir + str(pkey) + '/list.lsp'
def push_liquidsoap(self, pkey, schedule, playlists):
logger = logging.getLogger('push')
try:
if True == os.access(src, os.R_OK):
logger.debug('OK - Can read playlist file')
pl_file = open(src, "r")
file_content = pl_file.read()
pl_file.close()
logger.debug('file content: %s' % (file_content))
playlist = json.loads(file_content)
playlist = playlists[pkey]
#strptime returns struct_time in local time
#mktime takes a time_struct and returns a floating point
@ -180,43 +185,24 @@ class PypoPush:
except Exception, e:
logger.error('%s', e)
status = 0
return status
def load_schedule(self):
logger = logging.getLogger()
schedule = None
# create the file if it doesnt exist
if (not os.path.exists(self.schedule_file)):
logger.debug('creating file ' + self.schedule_file)
open(self.schedule_file, 'w').close()
else:
# load the schedule from cache
#logger.debug('loading schedule file '+self.schedule_file)
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
schedule_file.close()
except Exception, e:
logger.error('%s', e)
return schedule
def load_schedule_tracker(self):
logger = logging.getLogger()
logger = logging.getLogger('push')
#logger.debug('load_schedule_tracker')
playedItems = dict()
# create the file if it doesnt exist
if (not os.path.exists(self.schedule_tracker_file)):
logger.debug('creating file ' + self.schedule_tracker_file)
schedule_tracker = open(self.schedule_tracker_file, 'w')
pickle.dump(playedItems, schedule_tracker)
schedule_tracker.close()
try:
logger.debug('creating file ' + self.schedule_tracker_file)
schedule_tracker = open(self.schedule_tracker_file, 'w')
pickle.dump(playedItems, schedule_tracker)
schedule_tracker.close()
except Exception, e:
logger.error('Error creating schedule tracker file: %s', e)
else:
#logger.debug('schedule tracker file exists, opening: ' + self.schedule_tracker_file)
try:
schedule_tracker = open(self.schedule_tracker_file, "r")
playedItems = pickle.load(schedule_tracker)
@ -226,3 +212,18 @@ class PypoPush:
return playedItems
def run(self):
loops = 0
heartbeat_period = math.floor(30/PUSH_INTERVAL)
logger = logging.getLogger('push')
while True:
if loops % heartbeat_period == 0:
logger.info("heartbeat")
loops = 0
try: self.push('scheduler')
except Exception, e:
logger.error('Pypo Push Error, exiting: %s', e)
sys.exit()
time.sleep(PUSH_INTERVAL)
loops += 1

Some files were not shown because too many files have changed in this diff Show More