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

This commit is contained in:
Daniel 2012-03-01 15:35:21 -05:00
commit f8e7d6eae3
57 changed files with 2514 additions and 2373 deletions

View File

@ -27,6 +27,7 @@ class ApiController extends Zend_Controller_Action
->addActionContext('live-chat', 'json')
->addActionContext('update-file-system-mount', 'json')
->addActionContext('handle-watched-dir-missing', 'json')
->addActionContext('rabbitmq-do-push', 'json')
->initContext();
}
@ -276,17 +277,19 @@ class ApiController extends Zend_Controller_Action
$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;
}
* */
PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$result = Application_Model_Schedule::GetScheduledPlaylists();
echo json_encode($result);
$data = Application_Model_Schedule::GetScheduledPlaylists();
echo json_encode($data, JSON_FORCE_OBJECT);
}
public function notifyMediaItemStartPlayAction()
@ -316,6 +319,7 @@ class ApiController extends Zend_Controller_Action
}
}
/*
public function notifyScheduleGroupPlayAction()
{
global $CC_CONFIG;
@ -355,6 +359,7 @@ class ApiController extends Zend_Controller_Action
exit;
}
}
*/
public function recordedShowsAction()
{
@ -901,5 +906,26 @@ class ApiController extends Zend_Controller_Action
$dir = base64_decode($request->getParam('dir'));
Application_Model_MusicDir::removeWatchedDir($dir, false);
}
/* This action is for use by our dev scripts, that make
* a change to the database and we want rabbitmq to send
* out a message to pypo that a potential change has been made. */
public function rabbitmqDoPushAction(){
global $CC_CONFIG;
$request = $this->getRequest();
$api_key = $request->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;
}
Logging::log("Notifying RabbitMQ to send message to pypo");
Application_Model_RabbitMq::PushSchedule();
}
}

View File

@ -55,7 +55,9 @@ class LibraryController extends Zend_Controller_Action
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.FixedColumns.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.TableTools.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/library.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/main_library.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/media_library.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/jquery.contextMenu.css?'.$CC_CONFIG['airtime_version']);

View File

@ -197,7 +197,7 @@ class PlaylistController extends Zend_Controller_Action
public function addItemsAction()
{
$ids = $this->_getParam('ids');
$ids = $this->_getParam('ids', array());
$ids = (!is_array($ids)) ? array($ids) : $ids;
$afterItem = $this->_getParam('afterItem', null);
$addType = $this->_getParam('type', 'after');

View File

@ -59,6 +59,29 @@ class ScheduleController extends Zend_Controller_Action
$this->view->headLink()->appendStylesheet($baseUrl.'/css/add-show.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/jquery.contextMenu.css?'.$CC_CONFIG['airtime_version']);
//Start Show builder JS/CSS requirements
$this->view->headScript()->appendFile($baseUrl.'/js/contextmenu/jquery.contextMenu.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/js/jquery.dataTables.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.pluginAPI.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.fnSetFilteringDelay.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.ColVis.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.ColReorder.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.FixedColumns.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.TableTools.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($this->view->baseUrl('/js/airtime/library/events/library_showbuilder.js?'.$CC_CONFIG['airtime_version']),'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/library.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/showbuilder/builder.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/media_library.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/jquery.contextMenu.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/datatables/css/ColVis.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/datatables/css/ColReorder.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/TableTools.css?'.$CC_CONFIG['airtime_version']);
$this->view->headLink()->appendStylesheet($baseUrl.'/css/showbuilder.css?'.$CC_CONFIG['airtime_version']);
//End Show builder JS/CSS requirements
Application_Model_Schedule::createNewFormSections($this->view);
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
@ -78,10 +101,12 @@ class ScheduleController extends Zend_Controller_Action
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new Application_Model_User($userInfo->id);
if($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)))
if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
$editable = true;
else
}
else {
$editable = false;
}
$this->view->events = Application_Model_Show::getFullCalendarEvents($start, $end, $editable);
}
@ -95,19 +120,19 @@ class ScheduleController extends Zend_Controller_Action
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new Application_Model_User($userInfo->id);
if($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
try{
if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
try {
$showInstance = new Application_Model_ShowInstance($showInstanceId);
}catch(Exception $e){
} catch (Exception $e){
$this->view->show_error = true;
return false;
}
$error = $showInstance->moveShow($deltaDay, $deltaMin);
}
if(isset($error))
if (isset($error)) {
$this->view->error = $error;
}
}
public function resizeShowAction()
@ -200,7 +225,7 @@ class ScheduleController extends Zend_Controller_Action
&& !$instance->isRebroadcast()) {
$menu["schedule"] = array("name"=> "Add / Remove Content",
"url" => "/showbuilder/index/");
"url" => "/showbuilder/builder-dialog/");
$menu["clear"] = array("name"=> "Remove All Content", "icon" => "delete",
"url" => "/schedule/clear-show");

View File

@ -9,6 +9,7 @@ class ShowbuilderController extends Zend_Controller_Action
$ajaxContext->addActionContext('schedule-move', 'json')
->addActionContext('schedule-add', 'json')
->addActionContext('schedule-remove', 'json')
->addActionContext('builder-dialog', 'json')
->addActionContext('builder-feed', 'json')
->initContext();
}
@ -53,11 +54,40 @@ class ShowbuilderController extends Zend_Controller_Action
$this->view->headScript()->appendScript("var serverTimezoneOffset = {$offset}; //in seconds");
$this->view->headScript()->appendFile($baseUrl.'/js/timepicker/jquery.ui.timepicker.js','text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/showbuilder/builder.js','text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/showbuilder/main_builder.js','text/javascript');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/jquery.ui.timepicker.css');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/showbuilder.css');
}
public function builderDialogAction() {
$request = $this->getRequest();
$id = $request->getParam("id");
$instance = CcShowInstancesQuery::create()->findPK($id);
if (is_null($instance)) {
$this->view->error = "show does not exist";
return;
}
$start = $instance->getDbStarts(null);
$start->setTimezone(new DateTimeZone(date_default_timezone_get()));
$end = $instance->getDbEnds(null);
$end->setTimezone(new DateTimeZone(date_default_timezone_get()));
$show_name = $instance->getCcShow()->getDbName();
$start_time = $start->format("Y-m-d H:i:s");
$end_time = $end->format("Y-m-d H:i:s");
$this->view->title = "{$show_name}: {$start_time} - {$end_time}";
$this->view->start = $instance->getDbStarts("U");
$this->view->end = $instance->getDbEnds("U");
$this->view->dialog = $this->view->render('showbuilder/builderDialog.phtml');
}
public function builderFeedAction() {
$request = $this->getRequest();

View File

@ -6,7 +6,7 @@ class Zend_Filter_ImageSize implements Zend_Filter_Interface {
throw new Zend_Filter_Exception('Image does not exist: ' . $value);
}
$image = imageCreateFromString(file_get_contents($value));
$image = imagecreatefromstring(file_get_contents($value));
if (false === $image) {
throw new Zend_Filter_Exception('Can\'t load image: ' . $value);
}

View File

@ -29,7 +29,7 @@ class Application_Model_RabbitMq
$EXCHANGE = 'airtime-pypo';
$channel->exchange_declare($EXCHANGE, 'direct', false, true);
$data = json_encode($md);
$data = json_encode($md, JSON_FORCE_OBJECT);
$msg = new AMQPMessage($data, array('content_type' => 'text/plain'));
$channel->basic_publish($msg, $EXCHANGE);

View File

@ -46,87 +46,6 @@ class Application_Model_Schedule {
}
/**
* Returns array indexed by:
* "playlistId"/"playlist_id" (aliases to the same thing)
* "start"/"starts" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn
* "end"/"ends" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn
* "group_id"/"id" (aliases to the same thing)
* "clip_length" (for audio clips this is the length of the audio clip,
* for playlists this is the length of the entire playlist)
* "name" (playlist only)
* "creator" (playlist only)
* "file_id" (audioclip only)
* "count" (number of items in the playlist, always 1 for audioclips.
* Note that playlists with one item will also have count = 1.
*
* @param string $p_fromDateTime
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
* @param string $p_toDateTime
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
* @param boolean $p_playlistsOnly
* Retrieve playlists as a single item.
* @return array
* Returns empty array if nothing found
*/
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_currentDateTime') "
." AND (ends <= TIMESTAMP '$p_toDateTime')";
$rows = $CC_DBC->GetAll($sql);
foreach ($rows as &$row) {
$row["count"] = "1";
$row["playlistId"] = $row["playlist_id"];
$row["start"] = $row["starts"];
$row["end"] = $row["ends"];
$row["id"] = $row["group_id"];
}
} else {
$sql = "SELECT MIN(pt.creator) AS creator,"
." st.group_id,"
." SUM(st.clip_length) AS clip_length,"
." MIN(st.file_id) AS file_id,"
." COUNT(*) as count,"
." MIN(st.playlist_id) AS playlist_id,"
." MIN(st.starts) AS starts,"
." MAX(st.ends) AS ends,"
." MIN(sh.name) AS show_name,"
." MIN(si.starts) AS show_start,"
." MAX(si.ends) AS show_end"
." FROM $CC_CONFIG[scheduleTable] as st"
." LEFT JOIN $CC_CONFIG[playListTable] as pt"
." ON st.playlist_id = pt.id"
." LEFT JOIN $CC_CONFIG[showInstances] as si"
." ON st.instance_id = si.id"
." LEFT JOIN $CC_CONFIG[showTable] as sh"
." ON si.show_id = sh.id"
//The next line ensures we only get songs that haven't ended yet
." 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)"
." GROUP BY st.group_id"
." ORDER BY starts";
$rows = $CC_DBC->GetAll($sql);
if (!PEAR::isError($rows)) {
foreach ($rows as &$row) {
$row["playlistId"] = $row["playlist_id"];
$row["start"] = $row["starts"];
$row["end"] = $row["ends"];
$row["id"] = $row["group_id"];
}
}
}
return $rows;
}
/**
* Returns data related to the scheduled items.
*
@ -318,6 +237,7 @@ class Application_Model_Schedule {
sched.starts AS sched_starts, sched.ends AS sched_ends, sched.id AS sched_id,
sched.cue_in AS cue_in, sched.cue_out AS cue_out,
sched.fade_in AS fade_in, sched.fade_out AS fade_out,
sched.status AS sched_status,
ft.track_title AS file_track_title, ft.artist_name AS file_artist_name,
ft.album_title AS file_album_title, ft.length AS file_length
@ -476,6 +396,120 @@ class Application_Model_Schedule {
return $diff;
}
/**
* Returns array indexed by:
* "playlistId"/"playlist_id" (aliases to the same thing)
* "start"/"starts" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn
* "end"/"ends" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn
* "group_id"/"id" (aliases to the same thing)
* "clip_length" (for audio clips this is the length of the audio clip,
* for playlists this is the length of the entire playlist)
* "name" (playlist only)
* "creator" (playlist only)
* "file_id" (audioclip only)
* "count" (number of items in the playlist, always 1 for audioclips.
* Note that playlists with one item will also have count = 1.
*
* @param string $p_fromDateTime
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
* @param string $p_toDateTime
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
* @param boolean $p_playlistsOnly
* Retrieve playlists as a single item.
* @return array
* Returns null if nothing found
*/
public static function GetItems($p_currentDateTime, $p_toDateTime) {
global $CC_CONFIG, $CC_DBC;
$rows = array();
$sql = "SELECT st.file_id AS file_id,"
." st.id as id,"
." st.starts AS start,"
." st.ends AS end,"
." st.cue_in AS cue_in,"
." st.cue_out AS cue_out,"
." st.fade_in AS fade_in,"
." st.fade_out AS fade_out,"
." si.starts as show_start,"
." si.ends as show_end"
." FROM $CC_CONFIG[scheduleTable] as st"
." LEFT JOIN $CC_CONFIG[showInstances] as si"
." ON st.instance_id = si.id"
." ORDER BY start";
Logging::log($sql);
$rows = $CC_DBC->GetAll($sql);
if (PEAR::isError($rows)) {
return null;
}
return $rows;
}
public static function GetScheduledPlaylists($p_fromDateTime = null, $p_toDateTime = null){
global $CC_CONFIG, $CC_DBC;
/* if $p_fromDateTime and $p_toDateTime function parameters are null, then set range
* from "now" to "now + 24 hours". */
if (is_null($p_fromDateTime)) {
$t1 = new DateTime("@".time());
$range_start = $t1->format("Y-m-d H:i:s");
} else {
$range_start = Application_Model_Schedule::PypoTimeToAirtimeTime($p_fromDateTime);
}
if (is_null($p_fromDateTime)) {
$t2 = new DateTime("@".time());
$t2->add(new DateInterval("PT24H"));
$range_end = $t2->format("Y-m-d H:i:s");
} else {
$range_end = Application_Model_Schedule::PypoTimeToAirtimeTime($p_toDateTime);
}
// Scheduler wants everything in a playlist
$items = Application_Model_Schedule::GetItems($range_start, $range_end);
$data = array();
$utcTimeZone = new DateTimeZone("UTC");
$data["status"] = array();
$data["media"] = array();
foreach ($items as $item){
$storedFile = Application_Model_StoredFile::Recall($item["file_id"]);
$uri = $storedFile->getFileUrlUsingConfigAddress();
$showEndDateTime = new DateTime($item["show_end"], $utcTimeZone);
$trackEndDateTime = new DateTime($item["end"], $utcTimeZone);
/* Note: cue_out and end are always the same. */
/* TODO: Not all tracks will have "show_end" */
if ($trackEndDateTime->getTimestamp() > $showEndDateTime->getTimestamp()){
$diff = $trackEndDateTime->getTimestamp() - $showEndDateTime->getTimestamp();
//assuming ends takes cue_out into assumption
$item["cue_out"] = $item["cue_out"] - $diff;
}
$start = Application_Model_Schedule::AirtimeTimeToPypoTime($item["start"]);
$data["media"][$start] = array(
'id' => $storedFile->getGunid(),
'row_id' => $item["id"],
'uri' => $uri,
'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]),
'fade_out' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_out"]),
'cue_in' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_in"]),
'cue_out' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_out"]),
'start' => $start,
'end' => Application_Model_Schedule::AirtimeTimeToPypoTime($item["end"])
);
}
return $data;
}
/**
* Export the schedule in json formatted for pypo (the liquidsoap scheduler)
@ -485,7 +519,7 @@ class Application_Model_Schedule {
* @param string $p_toDateTime
* In the format "YYYY-MM-DD-HH-mm-SS"
*/
public static function GetScheduledPlaylists($p_fromDateTime = null, $p_toDateTime = null)
public static function GetScheduledPlaylistsOld($p_fromDateTime = null, $p_toDateTime = null)
{
global $CC_CONFIG, $CC_DBC;
@ -546,7 +580,6 @@ class Application_Model_Schedule {
$starts = Application_Model_Schedule::AirtimeTimeToPypoTime($item["starts"]);
$medias[$starts] = array(
'row_id' => $item["id"],
'id' => $storedFile->getGunid(),
'uri' => $uri,
'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]),
@ -554,7 +587,6 @@ class Application_Model_Schedule {
'fade_cross' => 0,
'cue_in' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_in"]),
'cue_out' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_out"]),
'export_source' => 'scheduler',
'start' => $starts,
'end' => Application_Model_Schedule::AirtimeTimeToPypoTime($item["ends"])
);

View File

@ -229,6 +229,15 @@ class Application_Model_Scheduler {
}
}
//update the status flag in cc_schedule.
$instances = CcShowInstancesQuery::create()
->filterByPrimaryKeys($affectedShowInstances)
->find($this->con);
foreach ($instances as $instance) {
$instance->updateScheduleStatus($this->con);
}
//update the last scheduled timestamp.
CcShowInstancesQuery::create()
->filterByPrimaryKeys($affectedShowInstances)
@ -383,11 +392,20 @@ class Application_Model_Scheduler {
}
}
foreach($showInstances as $instance) {
foreach ($showInstances as $instance) {
$this->removeGaps($instance);
}
}
//update the status flag in cc_schedule.
$instances = CcShowInstancesQuery::create()
->filterByPrimaryKeys($showInstances)
->find($this->con);
foreach ($instances as $instance) {
$instance->updateScheduleStatus($this->con);
}
//update the last scheduled timestamp.
CcShowInstancesQuery::create()
->filterByPrimaryKeys($showInstances)

View File

@ -125,10 +125,12 @@ class Application_Model_Show {
}
$hours = $deltaMin/60;
if($hours > 0)
if ($hours > 0) {
$hours = floor($hours);
else
}
else {
$hours = ceil($hours);
}
$mins = abs($deltaMin%60);
@ -149,6 +151,28 @@ class Application_Model_Show {
//do both the queries at once.
$CC_DBC->query($sql);
$con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);
$con->beginTransaction();
try {
//update the status flag in cc_schedule.
$instances = CcShowInstancesQuery::create()
->filterByDbStarts($current_timestamp, Criteria::GREATER_EQUAL)
->filterByDbShowId($this->_showId)
->find($con);
foreach ($instances as $instance) {
$instance->updateScheduleStatus();
}
$con->commit();
}
catch (Exception $e) {
$con->rollback();
Logging::log("Couldn't update schedule status.");
Logging::log($e->getMessage());
}
Application_Model_RabbitMq::PushSchedule();
}
@ -1043,6 +1067,33 @@ class Application_Model_Show {
}
}
if ($data['add_show_id'] != -1) {
$con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);
$con->beginTransaction();
//current timesamp in UTC.
$current_timestamp = gmdate("Y-m-d H:i:s");
try {
//update the status flag in cc_schedule.
$instances = CcShowInstancesQuery::create()
->filterByDbStarts($current_timestamp, Criteria::GREATER_EQUAL)
->filterByDbShowId($data['add_show_id'])
->find($con);
foreach ($instances as $instance) {
$instance->updateScheduleStatus();
}
$con->commit();
}
catch (Exception $e) {
$con->rollback();
Logging::log("Couldn't update schedule status.");
Logging::log($e->getMessage());
}
}
Application_Model_Show::populateShowUntil($showId);
Application_Model_RabbitMq::PushSchedule();
return $showId;
@ -1491,7 +1542,7 @@ class Application_Model_Show {
$events = array();
$interval = $start->diff($end);
$days = $interval->format('%a');
$days = $interval->format('%a');
$shows = Application_Model_Show::getShows($start, $end);
@ -1508,10 +1559,9 @@ class Application_Model_Show {
if ($editable && (strtotime($today_timestamp) < strtotime($show["starts"]))) {
$options["editable"] = true;
$events[] = Application_Model_Show::makeFullCalendarEvent($show, $options);
} else {
$events[] = Application_Model_Show::makeFullCalendarEvent($show, $options);
}
$events[] = Application_Model_Show::makeFullCalendarEvent($show, $options);
}
return $events;
@ -1521,10 +1571,6 @@ class Application_Model_Show {
{
$event = array();
if($show["rebroadcast"]) {
$event["disableResizing"] = true;
}
$startDateTime = new DateTime($show["starts"], new DateTimeZone("UTC"));
$startDateTime->setTimezone(new DateTimeZone(date_default_timezone_get()));
@ -1538,29 +1584,27 @@ class Application_Model_Show {
$event["end"] = $endDateTime->format("Y-m-d H:i:s");
$event["endUnix"] = $endDateTime->format("U");
$event["allDay"] = false;
//$event["description"] = $show["description"];
$event["showId"] = intval($show["show_id"]);
$event["record"] = intval($show["record"]);
$event["rebroadcast"] = intval($show["rebroadcast"]);
// get soundcloud_id
if(!is_null($show["file_id"])){
if (!is_null($show["file_id"])){
$file = Application_Model_StoredFile::Recall($show["file_id"]);
$soundcloud_id = $file->getSoundCloudId();
}else{
$soundcloud_id = null;
}
$event["soundcloud_id"] = (is_null($soundcloud_id) ? -1 : $soundcloud_id);
$event["soundcloud_id"] = isset($soundcloud_id) ? $soundcloud_id : -1;
//event colouring
if($show["color"] != "") {
if ($show["color"] != "") {
$event["textColor"] = "#".$show["color"];
}
if($show["background_color"] != "") {
if ($show["background_color"] != "") {
$event["color"] = "#".$show["background_color"];
}
foreach($options as $key=>$value) {
foreach ($options as $key => $value) {
$event[$key] = $value;
}

View File

@ -1,6 +1,7 @@
<?php
require_once 'formatters/LengthFormatter.php';
require_once 'formatters/TimeFilledFormatter.php';
class Application_Model_ShowBuilder {
@ -30,7 +31,8 @@ class Application_Model_ShowBuilder {
"cuein" => "",
"cueout" => "",
"fadein" => "",
"fadeout" => ""
"fadeout" => "",
"current" => false,
);
/*
@ -47,37 +49,14 @@ class Application_Model_ShowBuilder {
$this->epoch_now = time();
}
private function formatTimeFilled($p_sec) {
$formatted = "";
$sign = ($p_sec < 0) ? "-" : "+";
$time = Application_Model_Playlist::secondsToPlaylistTime(abs($p_sec));
Logging::log("time is: ".$time);
$info = explode(":", $time);
$formatted .= $sign;
if (intval($info[0]) > 0) {
$info[0] = ltrim($info[0], "0");
$formatted .= " {$info[0]}h";
}
if (intval($info[1]) > 0) {
$info[1] = ltrim($info[1], "0");
$formatted .= " {$info[1]}m";
}
if (intval($info[2]) > 0) {
$sec = round($info[2], 0);
$formatted .= " {$sec}s";
}
return $formatted;
}
//check to see if this row should be editable.
private function isAllowed($p_item, &$row) {
//cannot schedule in a recorded show.
if (intval($p_item["si_record"]) === 1) {
return;
}
$showStartDT = new DateTime($p_item["si_starts"], new DateTimeZone("UTC"));
//can only schedule the show if it hasn't started and you are allowed.
@ -86,27 +65,10 @@ class Application_Model_ShowBuilder {
}
}
//information about whether a track is inside|boundary|outside a show.
private function getItemStatus($p_item, &$row) {
$showEndDT = new DateTime($p_item["si_ends"]);
$schedStartDT = new DateTime($p_item["sched_starts"]);
$schedEndDT = new DateTime($p_item["sched_ends"]);
$showEndEpoch = intval($showEndDT->format("U"));
$schedStartEpoch = intval($schedStartDT->format("U"));
$schedEndEpoch = intval($schedEndDT->format("U"));
if ($schedEndEpoch < $showEndEpoch) {
$status = 0; //item will playout in full
}
else if ($schedStartEpoch < $showEndEpoch && $schedEndEpoch > $showEndEpoch) {
$status = 1; //item is on boundry
}
else {
$status = 2; //item is overscheduled won't play.
}
$row["status"] = $status;
$row["status"] = intval($p_item["sched_status"]);
}
private function getRowTimestamp($p_item, &$row) {
@ -121,6 +83,16 @@ class Application_Model_ShowBuilder {
$row["timestamp"] = $ts;
}
private function isCurrent($p_epochItemStart, $p_epochItemEnd) {
$current = false;
if ($this->epoch_now >= $p_epochItemStart && $this->epoch_now < $p_epochItemEnd) {
$current = true;
}
return $current;
}
private function makeHeaderRow($p_item) {
$row = $this->defaultRowArray;
@ -148,8 +120,8 @@ class Application_Model_ShowBuilder {
private function makeScheduledItemRow($p_item) {
$row = $this->defaultRowArray;
$this->isAllowed($p_item, $row);
$this->getRowTimestamp($p_item, $row);
$this->isAllowed($p_item, $row);
if (isset($p_item["sched_starts"])) {
@ -157,9 +129,19 @@ class Application_Model_ShowBuilder {
$schedStartDT->setTimezone(new DateTimeZone($this->timezone));
$schedEndDT = new DateTime($p_item["sched_ends"], new DateTimeZone("UTC"));
$schedEndDT->setTimezone(new DateTimeZone($this->timezone));
$showEndDT = new DateTime($p_item["si_ends"], new DateTimeZone("UTC"));
$this->getItemStatus($p_item, $row);
$startsEpoch = intval($schedStartDT->format("U"));
$endsEpoch = intval($schedEndDT->format("U"));
$showEndEpoch = intval($showEndDT->format("U"));
//don't want an overbooked item to stay marked as current.
if ($this->isCurrent($startsEpoch, min($endsEpoch, $showEndEpoch))) {
$row["current"] = true;
}
$row["id"] = intval($p_item["sched_id"]);
$row["instance"] = intval($p_item["si_id"]);
$row["starts"] = $schedStartDT->format("H:i:s");
@ -179,7 +161,10 @@ class Application_Model_ShowBuilder {
$this->contentDT = $schedEndDT;
}
//show is empty
//show is empty or is a special kind of show (recording etc)
else if (intval($p_item["si_record"]) === 1) {
$row["record"] = true;
}
else {
$row["empty"] = true;
@ -193,7 +178,6 @@ class Application_Model_ShowBuilder {
private function makeFooterRow($p_item) {
$row = $this->defaultRowArray;
$this->isAllowed($p_item, $row);
$row["footer"] = true;
$showEndDT = new DateTime($p_item["si_ends"], new DateTimeZone("UTC"));
@ -201,7 +185,9 @@ class Application_Model_ShowBuilder {
$runtime = bcsub($contentDT->format("U.u"), $showEndDT->format("U.u"), 6);
$row["runtime"] = $runtime;
$row["fRuntime"] = $this->formatTimeFilled($runtime);
$timeFilled = new TimeFilledFormatter($runtime);
$row["fRuntime"] = $timeFilled->format();
return $row;
}

View File

@ -617,14 +617,14 @@ class Application_Model_ShowInstance {
public function getTimeScheduledSecs()
{
$time_filled = $this->getTimeScheduled();
return Application_Model_Schedule::WallTimeToMillisecs($time_filled) / 1000;
return Application_Model_Playlist::playlistTimeToSeconds($time_filled);
}
public function getDurationSecs()
{
$ends = $this->getShowInstanceEnd(null);
$starts = $this->getShowInstanceStart(null);
return $ends->format('U') - $starts->format('U');
return intval($ends->format('U')) - intval($starts->format('U'));
}
public function getPercentScheduled()

View File

@ -35,39 +35,6 @@ class CcPlaylistcontents extends BaseCcPlaylistcontents {
return parent::getDbFadeout($format);
}
/**
* Just changing the default format to return subseconds
*
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
*/
public function getDbCuein($format = 'H:i:s.u')
{
return parent::getDbCuein($format);
}
/**
* Just changing the default format to return subseconds
*
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
*/
public function getDbCueout($format = 'H:i:s.u')
{
return parent::getDbCueout($format);
}
/**
* Just changing the default format to return subseconds
*
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
*/
public function getDbCliplength($format = 'H:i:s.u')
{
return parent::getDbCliplength($format);
}
/**
*
* @param String in format SS.uuuuuu, Datetime, or DateTime accepted string.
@ -124,88 +91,4 @@ class CcPlaylistcontents extends BaseCcPlaylistcontents {
return $this;
} // setDbFadeout()
/**
* Sets the value of [cuein] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCuein($v)
{
if ($v instanceof DateTime) {
$dt = $v;
}
else {
try {
$dt = new DateTime($v);
}
catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
}
$this->cuein = $dt->format('H:i:s.u');
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEIN;
return $this;
} // setDbCuein()
/**
* Sets the value of [cueout] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCueout($v)
{
if ($v instanceof DateTime) {
$dt = $v;
}
else {
try {
$dt = new DateTime($v);
}
catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
}
$this->cueout = $dt->format('H:i:s.u');
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEOUT;
return $this;
} // setDbCueout()
/**
* Sets the value of [cliplength] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCliplength($v)
{
if ($v instanceof DateTime) {
$dt = $v;
}
else {
try {
$dt = new DateTime($v);
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
}
$this->cliplength = $dt->format('H:i:s.u');
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CLIPLENGTH;
return $this;
} // setDbCliplength()
} // CcPlaylistcontents

View File

@ -15,11 +15,6 @@
*/
class CcSchedule extends BaseCcSchedule {
public function getDbClipLength($format = 'H:i:s.u')
{
return parent::getDbClipLength($format);
}
/**
* Get the [optionally formatted] temporal [starts] column value.
*
@ -104,28 +99,6 @@ class CcSchedule extends BaseCcSchedule {
return parent::getDbFadeout($format);
}
/**
* Just changing the default format to return subseconds
*
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
*/
public function getDbCueIn($format = 'H:i:s.u')
{
return parent::getDbCuein($format);
}
/**
* Just changing the default format to return subseconds
*
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
*/
public function getDbCueOut($format = 'H:i:s.u')
{
return parent::getDbCueout($format);
}
/**
*
* @param String in format SS.uuuuuu, Datetime, or DateTime accepted string.
@ -182,89 +155,6 @@ class CcSchedule extends BaseCcSchedule {
return $this;
} // setDbFadeout()
/**
* Sets the value of [cuein] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCueIn($v)
{
if ($v instanceof DateTime) {
$dt = $v;
}
else {
try {
$dt = new DateTime($v);
}
catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
}
$this->cue_in = $dt->format('H:i:s.u');
$this->modifiedColumns[] = CcSchedulePeer::CUE_IN;
return $this;
} // setDbCuein()
/**
* Sets the value of [cueout] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCueout($v)
{
if ($v instanceof DateTime) {
$dt = $v;
}
else {
try {
$dt = new DateTime($v);
}
catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
}
$this->cue_out = $dt->format('H:i:s.u');
$this->modifiedColumns[] = CcSchedulePeer::CUE_OUT;
return $this;
} // setDbCueout()
/**
* Sets the value of [cliplength] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbClipLength($v)
{
if ($v instanceof DateTime) {
$dt = $v;
}
else {
try {
$dt = new DateTime($v);
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
}
$this->clip_length = $dt->format('H:i:s.u');
$this->modifiedColumns[] = CcSchedulePeer::CLIP_LENGTH;
return $this;
} // setDbCliplength()
/**
* Sets the value of [starts] column to a normalized version of the date/time value specified.
*

View File

@ -107,4 +107,33 @@ class CcShowInstances extends BaseCcShowInstances {
return $dt->format($format);
}
}
//post save hook to update the cc_schedule status column for the tracks in the show.
public function updateScheduleStatus(PropelPDO $con) {
Logging::log("in post save for showinstances");
//scheduled track is in the show
CcScheduleQuery::create()
->filterByDbInstanceId($this->id)
->filterByDbEnds($this->ends, Criteria::LESS_EQUAL)
->update(array('DbStatus' => 1), $con);
Logging::log("updating status for in show items.");
//scheduled track is a boundary track
CcScheduleQuery::create()
->filterByDbInstanceId($this->id)
->filterByDbStarts($this->ends, Criteria::LESS_THAN)
->filterByDbEnds($this->ends, Criteria::GREATER_THAN)
->update(array('DbStatus' => 2), $con);
//scheduled track is overbooked.
CcScheduleQuery::create()
->filterByDbInstanceId($this->id)
->filterByDbStarts($this->ends, Criteria::GREATER_THAN)
->update(array('DbStatus' => 0), $con);
}
} // CcShowInstances

View File

@ -42,9 +42,9 @@ class CcPlaylistcontentsTableMap extends TableMap {
$this->addForeignKey('PLAYLIST_ID', 'DbPlaylistId', 'INTEGER', 'cc_playlist', 'ID', false, null, null);
$this->addForeignKey('FILE_ID', 'DbFileId', 'INTEGER', 'cc_files', 'ID', false, null, null);
$this->addColumn('POSITION', 'DbPosition', 'INTEGER', false, null, null);
$this->addColumn('CLIPLENGTH', 'DbCliplength', 'TIME', false, null, '00:00:00');
$this->addColumn('CUEIN', 'DbCuein', 'TIME', false, null, '00:00:00');
$this->addColumn('CUEOUT', 'DbCueout', 'TIME', false, null, '00:00:00');
$this->addColumn('CLIPLENGTH', 'DbCliplength', 'VARCHAR', false, null, '00:00:00');
$this->addColumn('CUEIN', 'DbCuein', 'VARCHAR', false, null, '00:00:00');
$this->addColumn('CUEOUT', 'DbCueout', 'VARCHAR', false, null, '00:00:00');
$this->addColumn('FADEIN', 'DbFadein', 'TIME', false, null, '00:00:00');
$this->addColumn('FADEOUT', 'DbFadeout', 'TIME', false, null, '00:00:00');
// validators

View File

@ -42,13 +42,14 @@ class CcScheduleTableMap extends TableMap {
$this->addColumn('STARTS', 'DbStarts', 'TIMESTAMP', true, null, null);
$this->addColumn('ENDS', 'DbEnds', 'TIMESTAMP', true, null, null);
$this->addForeignKey('FILE_ID', 'DbFileId', 'INTEGER', 'cc_files', 'ID', false, null, null);
$this->addColumn('CLIP_LENGTH', 'DbClipLength', 'TIME', false, null, '00:00:00');
$this->addColumn('CLIP_LENGTH', 'DbClipLength', 'VARCHAR', false, null, '00:00:00');
$this->addColumn('FADE_IN', 'DbFadeIn', 'TIME', false, null, '00:00:00');
$this->addColumn('FADE_OUT', 'DbFadeOut', 'TIME', false, null, '00:00:00');
$this->addColumn('CUE_IN', 'DbCueIn', 'TIME', false, null, '00:00:00');
$this->addColumn('CUE_OUT', 'DbCueOut', 'TIME', false, null, '00:00:00');
$this->addColumn('CUE_IN', 'DbCueIn', 'VARCHAR', false, null, '00:00:00');
$this->addColumn('CUE_OUT', 'DbCueOut', 'VARCHAR', false, null, '00:00:00');
$this->addColumn('MEDIA_ITEM_PLAYED', 'DbMediaItemPlayed', 'BOOLEAN', false, null, false);
$this->addForeignKey('INSTANCE_ID', 'DbInstanceId', 'INTEGER', 'cc_show_instances', 'ID', true, null, null);
$this->addColumn('STATUS', 'DbStatus', 'SMALLINT', true, null, 1);
// validators
} // initialize()

View File

@ -176,102 +176,33 @@ abstract class BaseCcPlaylistcontents extends BaseObject implements Persistent
}
/**
* Get the [optionally formatted] temporal [cliplength] column value.
* Get the [cliplength] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is NULL, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
* @return string
*/
public function getDbCliplength($format = '%X')
public function getDbCliplength()
{
if ($this->cliplength === null) {
return null;
}
try {
$dt = new DateTime($this->cliplength);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cliplength, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is TRUE, we return a DateTime object.
return $dt;
} elseif (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
} else {
return $dt->format($format);
}
return $this->cliplength;
}
/**
* Get the [optionally formatted] temporal [cuein] column value.
* Get the [cuein] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is NULL, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
* @return string
*/
public function getDbCuein($format = '%X')
public function getDbCuein()
{
if ($this->cuein === null) {
return null;
}
try {
$dt = new DateTime($this->cuein);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cuein, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is TRUE, we return a DateTime object.
return $dt;
} elseif (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
} else {
return $dt->format($format);
}
return $this->cuein;
}
/**
* Get the [optionally formatted] temporal [cueout] column value.
* Get the [cueout] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is NULL, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
* @return string
*/
public function getDbCueout($format = '%X')
public function getDbCueout()
{
if ($this->cueout === null) {
return null;
}
try {
$dt = new DateTime($this->cueout);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cueout, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is TRUE, we return a DateTime object.
return $dt;
} elseif (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
} else {
return $dt->format($format);
}
return $this->cueout;
}
/**
@ -429,151 +360,61 @@ abstract class BaseCcPlaylistcontents extends BaseObject implements Persistent
} // setDbPosition()
/**
* Sets the value of [cliplength] column to a normalized version of the date/time value specified.
* Set the value of [cliplength] column.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @param string $v new value
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCliplength($v)
{
// we treat '' as NULL for temporal objects because DateTime('') == DateTime('now')
// -- which is unexpected, to say the least.
if ($v === null || $v === '') {
$dt = null;
} elseif ($v instanceof DateTime) {
$dt = $v;
} else {
// some string/numeric value passed; we normalize that so that we can
// validate it.
try {
if (is_numeric($v)) { // if it's a unix timestamp
$dt = new DateTime('@'.$v, new DateTimeZone('UTC'));
// We have to explicitly specify and then change the time zone because of a
// DateTime bug: http://bugs.php.net/bug.php?id=43003
$dt->setTimeZone(new DateTimeZone(date_default_timezone_get()));
} else {
$dt = new DateTime($v);
}
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
if ($v !== null) {
$v = (string) $v;
}
if ( $this->cliplength !== null || $dt !== null ) {
// (nested ifs are a little easier to read in this case)
$currNorm = ($this->cliplength !== null && $tmpDt = new DateTime($this->cliplength)) ? $tmpDt->format('H:i:s') : null;
$newNorm = ($dt !== null) ? $dt->format('H:i:s') : null;
if ( ($currNorm !== $newNorm) // normalized values don't match
|| ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default
)
{
$this->cliplength = ($dt ? $dt->format('H:i:s') : null);
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CLIPLENGTH;
}
} // if either are not null
if ($this->cliplength !== $v || $this->isNew()) {
$this->cliplength = $v;
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CLIPLENGTH;
}
return $this;
} // setDbCliplength()
/**
* Sets the value of [cuein] column to a normalized version of the date/time value specified.
* Set the value of [cuein] column.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @param string $v new value
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCuein($v)
{
// we treat '' as NULL for temporal objects because DateTime('') == DateTime('now')
// -- which is unexpected, to say the least.
if ($v === null || $v === '') {
$dt = null;
} elseif ($v instanceof DateTime) {
$dt = $v;
} else {
// some string/numeric value passed; we normalize that so that we can
// validate it.
try {
if (is_numeric($v)) { // if it's a unix timestamp
$dt = new DateTime('@'.$v, new DateTimeZone('UTC'));
// We have to explicitly specify and then change the time zone because of a
// DateTime bug: http://bugs.php.net/bug.php?id=43003
$dt->setTimeZone(new DateTimeZone(date_default_timezone_get()));
} else {
$dt = new DateTime($v);
}
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
if ($v !== null) {
$v = (string) $v;
}
if ( $this->cuein !== null || $dt !== null ) {
// (nested ifs are a little easier to read in this case)
$currNorm = ($this->cuein !== null && $tmpDt = new DateTime($this->cuein)) ? $tmpDt->format('H:i:s') : null;
$newNorm = ($dt !== null) ? $dt->format('H:i:s') : null;
if ( ($currNorm !== $newNorm) // normalized values don't match
|| ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default
)
{
$this->cuein = ($dt ? $dt->format('H:i:s') : null);
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEIN;
}
} // if either are not null
if ($this->cuein !== $v || $this->isNew()) {
$this->cuein = $v;
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEIN;
}
return $this;
} // setDbCuein()
/**
* Sets the value of [cueout] column to a normalized version of the date/time value specified.
* Set the value of [cueout] column.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @param string $v new value
* @return CcPlaylistcontents The current object (for fluent API support)
*/
public function setDbCueout($v)
{
// we treat '' as NULL for temporal objects because DateTime('') == DateTime('now')
// -- which is unexpected, to say the least.
if ($v === null || $v === '') {
$dt = null;
} elseif ($v instanceof DateTime) {
$dt = $v;
} else {
// some string/numeric value passed; we normalize that so that we can
// validate it.
try {
if (is_numeric($v)) { // if it's a unix timestamp
$dt = new DateTime('@'.$v, new DateTimeZone('UTC'));
// We have to explicitly specify and then change the time zone because of a
// DateTime bug: http://bugs.php.net/bug.php?id=43003
$dt->setTimeZone(new DateTimeZone(date_default_timezone_get()));
} else {
$dt = new DateTime($v);
}
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
if ($v !== null) {
$v = (string) $v;
}
if ( $this->cueout !== null || $dt !== null ) {
// (nested ifs are a little easier to read in this case)
$currNorm = ($this->cueout !== null && $tmpDt = new DateTime($this->cueout)) ? $tmpDt->format('H:i:s') : null;
$newNorm = ($dt !== null) ? $dt->format('H:i:s') : null;
if ( ($currNorm !== $newNorm) // normalized values don't match
|| ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default
)
{
$this->cueout = ($dt ? $dt->format('H:i:s') : null);
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEOUT;
}
} // if either are not null
if ($this->cueout !== $v || $this->isNew()) {
$this->cueout = $v;
$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEOUT;
}
return $this;
} // setDbCueout()

View File

@ -282,29 +282,20 @@ abstract class BaseCcPlaylistcontentsQuery extends ModelCriteria
/**
* Filter the query on the cliplength column
*
* @param string|array $dbCliplength The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $dbCliplength The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcPlaylistcontentsQuery The current query, for fluid interface
*/
public function filterByDbCliplength($dbCliplength = null, $comparison = null)
{
if (is_array($dbCliplength)) {
$useMinMax = false;
if (isset($dbCliplength['min'])) {
$this->addUsingAlias(CcPlaylistcontentsPeer::CLIPLENGTH, $dbCliplength['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbCliplength['max'])) {
$this->addUsingAlias(CcPlaylistcontentsPeer::CLIPLENGTH, $dbCliplength['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
if (null === $comparison) {
if (is_array($dbCliplength)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $dbCliplength)) {
$dbCliplength = str_replace('*', '%', $dbCliplength);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(CcPlaylistcontentsPeer::CLIPLENGTH, $dbCliplength, $comparison);
@ -313,29 +304,20 @@ abstract class BaseCcPlaylistcontentsQuery extends ModelCriteria
/**
* Filter the query on the cuein column
*
* @param string|array $dbCuein The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $dbCuein The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcPlaylistcontentsQuery The current query, for fluid interface
*/
public function filterByDbCuein($dbCuein = null, $comparison = null)
{
if (is_array($dbCuein)) {
$useMinMax = false;
if (isset($dbCuein['min'])) {
$this->addUsingAlias(CcPlaylistcontentsPeer::CUEIN, $dbCuein['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbCuein['max'])) {
$this->addUsingAlias(CcPlaylistcontentsPeer::CUEIN, $dbCuein['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
if (null === $comparison) {
if (is_array($dbCuein)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $dbCuein)) {
$dbCuein = str_replace('*', '%', $dbCuein);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(CcPlaylistcontentsPeer::CUEIN, $dbCuein, $comparison);
@ -344,29 +326,20 @@ abstract class BaseCcPlaylistcontentsQuery extends ModelCriteria
/**
* Filter the query on the cueout column
*
* @param string|array $dbCueout The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $dbCueout The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcPlaylistcontentsQuery The current query, for fluid interface
*/
public function filterByDbCueout($dbCueout = null, $comparison = null)
{
if (is_array($dbCueout)) {
$useMinMax = false;
if (isset($dbCueout['min'])) {
$this->addUsingAlias(CcPlaylistcontentsPeer::CUEOUT, $dbCueout['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbCueout['max'])) {
$this->addUsingAlias(CcPlaylistcontentsPeer::CUEOUT, $dbCueout['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
if (null === $comparison) {
if (is_array($dbCueout)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $dbCueout)) {
$dbCueout = str_replace('*', '%', $dbCueout);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(CcPlaylistcontentsPeer::CUEOUT, $dbCueout, $comparison);

View File

@ -96,6 +96,13 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
*/
protected $instance_id;
/**
* The value for the status field.
* Note: this column has a database default value of: 1
* @var int
*/
protected $status;
/**
* @var CcShowInstances
*/
@ -137,6 +144,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
$this->cue_in = '00:00:00';
$this->cue_out = '00:00:00';
$this->media_item_played = false;
$this->status = 1;
}
/**
@ -236,36 +244,13 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
}
/**
* Get the [optionally formatted] temporal [clip_length] column value.
* Get the [clip_length] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is NULL, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
* @return string
*/
public function getDbClipLength($format = '%X')
public function getDbClipLength()
{
if ($this->clip_length === null) {
return null;
}
try {
$dt = new DateTime($this->clip_length);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->clip_length, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is TRUE, we return a DateTime object.
return $dt;
} elseif (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
} else {
return $dt->format($format);
}
return $this->clip_length;
}
/**
@ -335,69 +320,23 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
}
/**
* Get the [optionally formatted] temporal [cue_in] column value.
* Get the [cue_in] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is NULL, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
* @return string
*/
public function getDbCueIn($format = '%X')
public function getDbCueIn()
{
if ($this->cue_in === null) {
return null;
}
try {
$dt = new DateTime($this->cue_in);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cue_in, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is TRUE, we return a DateTime object.
return $dt;
} elseif (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
} else {
return $dt->format($format);
}
return $this->cue_in;
}
/**
* Get the [optionally formatted] temporal [cue_out] column value.
* Get the [cue_out] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is NULL, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL
* @throws PropelException - if unable to parse/validate the date/time value.
* @return string
*/
public function getDbCueOut($format = '%X')
public function getDbCueOut()
{
if ($this->cue_out === null) {
return null;
}
try {
$dt = new DateTime($this->cue_out);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cue_out, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is TRUE, we return a DateTime object.
return $dt;
} elseif (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
} else {
return $dt->format($format);
}
return $this->cue_out;
}
/**
@ -420,6 +359,16 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
return $this->instance_id;
}
/**
* Get the [status] column value.
*
* @return int
*/
public function getDbStatus()
{
return $this->status;
}
/**
* Set the value of [id] column.
*
@ -563,51 +512,21 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
} // setDbFileId()
/**
* Sets the value of [clip_length] column to a normalized version of the date/time value specified.
* Set the value of [clip_length] column.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @param string $v new value
* @return CcSchedule The current object (for fluent API support)
*/
public function setDbClipLength($v)
{
// we treat '' as NULL for temporal objects because DateTime('') == DateTime('now')
// -- which is unexpected, to say the least.
if ($v === null || $v === '') {
$dt = null;
} elseif ($v instanceof DateTime) {
$dt = $v;
} else {
// some string/numeric value passed; we normalize that so that we can
// validate it.
try {
if (is_numeric($v)) { // if it's a unix timestamp
$dt = new DateTime('@'.$v, new DateTimeZone('UTC'));
// We have to explicitly specify and then change the time zone because of a
// DateTime bug: http://bugs.php.net/bug.php?id=43003
$dt->setTimeZone(new DateTimeZone(date_default_timezone_get()));
} else {
$dt = new DateTime($v);
}
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
if ($v !== null) {
$v = (string) $v;
}
if ( $this->clip_length !== null || $dt !== null ) {
// (nested ifs are a little easier to read in this case)
$currNorm = ($this->clip_length !== null && $tmpDt = new DateTime($this->clip_length)) ? $tmpDt->format('H:i:s') : null;
$newNorm = ($dt !== null) ? $dt->format('H:i:s') : null;
if ( ($currNorm !== $newNorm) // normalized values don't match
|| ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default
)
{
$this->clip_length = ($dt ? $dt->format('H:i:s') : null);
$this->modifiedColumns[] = CcSchedulePeer::CLIP_LENGTH;
}
} // if either are not null
if ($this->clip_length !== $v || $this->isNew()) {
$this->clip_length = $v;
$this->modifiedColumns[] = CcSchedulePeer::CLIP_LENGTH;
}
return $this;
} // setDbClipLength()
@ -713,101 +632,41 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
} // setDbFadeOut()
/**
* Sets the value of [cue_in] column to a normalized version of the date/time value specified.
* Set the value of [cue_in] column.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @param string $v new value
* @return CcSchedule The current object (for fluent API support)
*/
public function setDbCueIn($v)
{
// we treat '' as NULL for temporal objects because DateTime('') == DateTime('now')
// -- which is unexpected, to say the least.
if ($v === null || $v === '') {
$dt = null;
} elseif ($v instanceof DateTime) {
$dt = $v;
} else {
// some string/numeric value passed; we normalize that so that we can
// validate it.
try {
if (is_numeric($v)) { // if it's a unix timestamp
$dt = new DateTime('@'.$v, new DateTimeZone('UTC'));
// We have to explicitly specify and then change the time zone because of a
// DateTime bug: http://bugs.php.net/bug.php?id=43003
$dt->setTimeZone(new DateTimeZone(date_default_timezone_get()));
} else {
$dt = new DateTime($v);
}
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
if ($v !== null) {
$v = (string) $v;
}
if ( $this->cue_in !== null || $dt !== null ) {
// (nested ifs are a little easier to read in this case)
$currNorm = ($this->cue_in !== null && $tmpDt = new DateTime($this->cue_in)) ? $tmpDt->format('H:i:s') : null;
$newNorm = ($dt !== null) ? $dt->format('H:i:s') : null;
if ( ($currNorm !== $newNorm) // normalized values don't match
|| ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default
)
{
$this->cue_in = ($dt ? $dt->format('H:i:s') : null);
$this->modifiedColumns[] = CcSchedulePeer::CUE_IN;
}
} // if either are not null
if ($this->cue_in !== $v || $this->isNew()) {
$this->cue_in = $v;
$this->modifiedColumns[] = CcSchedulePeer::CUE_IN;
}
return $this;
} // setDbCueIn()
/**
* Sets the value of [cue_out] column to a normalized version of the date/time value specified.
* Set the value of [cue_out] column.
*
* @param mixed $v string, integer (timestamp), or DateTime value. Empty string will
* be treated as NULL for temporal objects.
* @param string $v new value
* @return CcSchedule The current object (for fluent API support)
*/
public function setDbCueOut($v)
{
// we treat '' as NULL for temporal objects because DateTime('') == DateTime('now')
// -- which is unexpected, to say the least.
if ($v === null || $v === '') {
$dt = null;
} elseif ($v instanceof DateTime) {
$dt = $v;
} else {
// some string/numeric value passed; we normalize that so that we can
// validate it.
try {
if (is_numeric($v)) { // if it's a unix timestamp
$dt = new DateTime('@'.$v, new DateTimeZone('UTC'));
// We have to explicitly specify and then change the time zone because of a
// DateTime bug: http://bugs.php.net/bug.php?id=43003
$dt->setTimeZone(new DateTimeZone(date_default_timezone_get()));
} else {
$dt = new DateTime($v);
}
} catch (Exception $x) {
throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x);
}
if ($v !== null) {
$v = (string) $v;
}
if ( $this->cue_out !== null || $dt !== null ) {
// (nested ifs are a little easier to read in this case)
$currNorm = ($this->cue_out !== null && $tmpDt = new DateTime($this->cue_out)) ? $tmpDt->format('H:i:s') : null;
$newNorm = ($dt !== null) ? $dt->format('H:i:s') : null;
if ( ($currNorm !== $newNorm) // normalized values don't match
|| ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default
)
{
$this->cue_out = ($dt ? $dt->format('H:i:s') : null);
$this->modifiedColumns[] = CcSchedulePeer::CUE_OUT;
}
} // if either are not null
if ($this->cue_out !== $v || $this->isNew()) {
$this->cue_out = $v;
$this->modifiedColumns[] = CcSchedulePeer::CUE_OUT;
}
return $this;
} // setDbCueOut()
@ -856,6 +715,26 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
return $this;
} // setDbInstanceId()
/**
* Set the value of [status] column.
*
* @param int $v new value
* @return CcSchedule The current object (for fluent API support)
*/
public function setDbStatus($v)
{
if ($v !== null) {
$v = (int) $v;
}
if ($this->status !== $v || $this->isNew()) {
$this->status = $v;
$this->modifiedColumns[] = CcSchedulePeer::STATUS;
}
return $this;
} // setDbStatus()
/**
* Indicates whether the columns in this object are only set to default values.
*
@ -890,6 +769,10 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
return false;
}
if ($this->status !== 1) {
return false;
}
// otherwise, everything was equal, so return TRUE
return true;
} // hasOnlyDefaultValues()
@ -923,6 +806,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
$this->cue_out = ($row[$startcol + 8] !== null) ? (string) $row[$startcol + 8] : null;
$this->media_item_played = ($row[$startcol + 9] !== null) ? (boolean) $row[$startcol + 9] : null;
$this->instance_id = ($row[$startcol + 10] !== null) ? (int) $row[$startcol + 10] : null;
$this->status = ($row[$startcol + 11] !== null) ? (int) $row[$startcol + 11] : null;
$this->resetModified();
$this->setNew(false);
@ -931,7 +815,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
$this->ensureConsistency();
}
return $startcol + 11; // 11 = CcSchedulePeer::NUM_COLUMNS - CcSchedulePeer::NUM_LAZY_LOAD_COLUMNS).
return $startcol + 12; // 12 = CcSchedulePeer::NUM_COLUMNS - CcSchedulePeer::NUM_LAZY_LOAD_COLUMNS).
} catch (Exception $e) {
throw new PropelException("Error populating CcSchedule object", $e);
@ -1310,6 +1194,9 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
case 10:
return $this->getDbInstanceId();
break;
case 11:
return $this->getDbStatus();
break;
default:
return null;
break;
@ -1345,6 +1232,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
$keys[8] => $this->getDbCueOut(),
$keys[9] => $this->getDbMediaItemPlayed(),
$keys[10] => $this->getDbInstanceId(),
$keys[11] => $this->getDbStatus(),
);
if ($includeForeignObjects) {
if (null !== $this->aCcShowInstances) {
@ -1417,6 +1305,9 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
case 10:
$this->setDbInstanceId($value);
break;
case 11:
$this->setDbStatus($value);
break;
} // switch()
}
@ -1452,6 +1343,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
if (array_key_exists($keys[8], $arr)) $this->setDbCueOut($arr[$keys[8]]);
if (array_key_exists($keys[9], $arr)) $this->setDbMediaItemPlayed($arr[$keys[9]]);
if (array_key_exists($keys[10], $arr)) $this->setDbInstanceId($arr[$keys[10]]);
if (array_key_exists($keys[11], $arr)) $this->setDbStatus($arr[$keys[11]]);
}
/**
@ -1474,6 +1366,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
if ($this->isColumnModified(CcSchedulePeer::CUE_OUT)) $criteria->add(CcSchedulePeer::CUE_OUT, $this->cue_out);
if ($this->isColumnModified(CcSchedulePeer::MEDIA_ITEM_PLAYED)) $criteria->add(CcSchedulePeer::MEDIA_ITEM_PLAYED, $this->media_item_played);
if ($this->isColumnModified(CcSchedulePeer::INSTANCE_ID)) $criteria->add(CcSchedulePeer::INSTANCE_ID, $this->instance_id);
if ($this->isColumnModified(CcSchedulePeer::STATUS)) $criteria->add(CcSchedulePeer::STATUS, $this->status);
return $criteria;
}
@ -1545,6 +1438,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
$copyObj->setDbCueOut($this->cue_out);
$copyObj->setDbMediaItemPlayed($this->media_item_played);
$copyObj->setDbInstanceId($this->instance_id);
$copyObj->setDbStatus($this->status);
$copyObj->setNew(true);
$copyObj->setDbId(NULL); // this is a auto-increment column, so set to default value
@ -1706,6 +1600,7 @@ abstract class BaseCcSchedule extends BaseObject implements Persistent
$this->cue_out = null;
$this->media_item_played = null;
$this->instance_id = null;
$this->status = null;
$this->alreadyInSave = false;
$this->alreadyInValidation = false;
$this->clearAllReferences();

View File

@ -26,7 +26,7 @@ abstract class BaseCcSchedulePeer {
const TM_CLASS = 'CcScheduleTableMap';
/** The total number of columns. */
const NUM_COLUMNS = 11;
const NUM_COLUMNS = 12;
/** The number of lazy-loaded columns. */
const NUM_LAZY_LOAD_COLUMNS = 0;
@ -64,6 +64,9 @@ abstract class BaseCcSchedulePeer {
/** the column name for the INSTANCE_ID field */
const INSTANCE_ID = 'cc_schedule.INSTANCE_ID';
/** the column name for the STATUS field */
const STATUS = 'cc_schedule.STATUS';
/**
* An identiy map to hold any loaded instances of CcSchedule objects.
* This must be public so that other peer classes can access this when hydrating from JOIN
@ -80,12 +83,12 @@ abstract class BaseCcSchedulePeer {
* e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id'
*/
private static $fieldNames = array (
BasePeer::TYPE_PHPNAME => array ('DbId', 'DbStarts', 'DbEnds', 'DbFileId', 'DbClipLength', 'DbFadeIn', 'DbFadeOut', 'DbCueIn', 'DbCueOut', 'DbMediaItemPlayed', 'DbInstanceId', ),
BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbStarts', 'dbEnds', 'dbFileId', 'dbClipLength', 'dbFadeIn', 'dbFadeOut', 'dbCueIn', 'dbCueOut', 'dbMediaItemPlayed', 'dbInstanceId', ),
BasePeer::TYPE_COLNAME => array (self::ID, self::STARTS, self::ENDS, self::FILE_ID, self::CLIP_LENGTH, self::FADE_IN, self::FADE_OUT, self::CUE_IN, self::CUE_OUT, self::MEDIA_ITEM_PLAYED, self::INSTANCE_ID, ),
BasePeer::TYPE_RAW_COLNAME => array ('ID', 'STARTS', 'ENDS', 'FILE_ID', 'CLIP_LENGTH', 'FADE_IN', 'FADE_OUT', 'CUE_IN', 'CUE_OUT', 'MEDIA_ITEM_PLAYED', 'INSTANCE_ID', ),
BasePeer::TYPE_FIELDNAME => array ('id', 'starts', 'ends', 'file_id', 'clip_length', 'fade_in', 'fade_out', 'cue_in', 'cue_out', 'media_item_played', 'instance_id', ),
BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, )
BasePeer::TYPE_PHPNAME => array ('DbId', 'DbStarts', 'DbEnds', 'DbFileId', 'DbClipLength', 'DbFadeIn', 'DbFadeOut', 'DbCueIn', 'DbCueOut', 'DbMediaItemPlayed', 'DbInstanceId', 'DbStatus', ),
BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbStarts', 'dbEnds', 'dbFileId', 'dbClipLength', 'dbFadeIn', 'dbFadeOut', 'dbCueIn', 'dbCueOut', 'dbMediaItemPlayed', 'dbInstanceId', 'dbStatus', ),
BasePeer::TYPE_COLNAME => array (self::ID, self::STARTS, self::ENDS, self::FILE_ID, self::CLIP_LENGTH, self::FADE_IN, self::FADE_OUT, self::CUE_IN, self::CUE_OUT, self::MEDIA_ITEM_PLAYED, self::INSTANCE_ID, self::STATUS, ),
BasePeer::TYPE_RAW_COLNAME => array ('ID', 'STARTS', 'ENDS', 'FILE_ID', 'CLIP_LENGTH', 'FADE_IN', 'FADE_OUT', 'CUE_IN', 'CUE_OUT', 'MEDIA_ITEM_PLAYED', 'INSTANCE_ID', 'STATUS', ),
BasePeer::TYPE_FIELDNAME => array ('id', 'starts', 'ends', 'file_id', 'clip_length', 'fade_in', 'fade_out', 'cue_in', 'cue_out', 'media_item_played', 'instance_id', 'status', ),
BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, )
);
/**
@ -95,12 +98,12 @@ abstract class BaseCcSchedulePeer {
* e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0
*/
private static $fieldKeys = array (
BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbStarts' => 1, 'DbEnds' => 2, 'DbFileId' => 3, 'DbClipLength' => 4, 'DbFadeIn' => 5, 'DbFadeOut' => 6, 'DbCueIn' => 7, 'DbCueOut' => 8, 'DbMediaItemPlayed' => 9, 'DbInstanceId' => 10, ),
BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbStarts' => 1, 'dbEnds' => 2, 'dbFileId' => 3, 'dbClipLength' => 4, 'dbFadeIn' => 5, 'dbFadeOut' => 6, 'dbCueIn' => 7, 'dbCueOut' => 8, 'dbMediaItemPlayed' => 9, 'dbInstanceId' => 10, ),
BasePeer::TYPE_COLNAME => array (self::ID => 0, self::STARTS => 1, self::ENDS => 2, self::FILE_ID => 3, self::CLIP_LENGTH => 4, self::FADE_IN => 5, self::FADE_OUT => 6, self::CUE_IN => 7, self::CUE_OUT => 8, self::MEDIA_ITEM_PLAYED => 9, self::INSTANCE_ID => 10, ),
BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'STARTS' => 1, 'ENDS' => 2, 'FILE_ID' => 3, 'CLIP_LENGTH' => 4, 'FADE_IN' => 5, 'FADE_OUT' => 6, 'CUE_IN' => 7, 'CUE_OUT' => 8, 'MEDIA_ITEM_PLAYED' => 9, 'INSTANCE_ID' => 10, ),
BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'starts' => 1, 'ends' => 2, 'file_id' => 3, 'clip_length' => 4, 'fade_in' => 5, 'fade_out' => 6, 'cue_in' => 7, 'cue_out' => 8, 'media_item_played' => 9, 'instance_id' => 10, ),
BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, )
BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbStarts' => 1, 'DbEnds' => 2, 'DbFileId' => 3, 'DbClipLength' => 4, 'DbFadeIn' => 5, 'DbFadeOut' => 6, 'DbCueIn' => 7, 'DbCueOut' => 8, 'DbMediaItemPlayed' => 9, 'DbInstanceId' => 10, 'DbStatus' => 11, ),
BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbStarts' => 1, 'dbEnds' => 2, 'dbFileId' => 3, 'dbClipLength' => 4, 'dbFadeIn' => 5, 'dbFadeOut' => 6, 'dbCueIn' => 7, 'dbCueOut' => 8, 'dbMediaItemPlayed' => 9, 'dbInstanceId' => 10, 'dbStatus' => 11, ),
BasePeer::TYPE_COLNAME => array (self::ID => 0, self::STARTS => 1, self::ENDS => 2, self::FILE_ID => 3, self::CLIP_LENGTH => 4, self::FADE_IN => 5, self::FADE_OUT => 6, self::CUE_IN => 7, self::CUE_OUT => 8, self::MEDIA_ITEM_PLAYED => 9, self::INSTANCE_ID => 10, self::STATUS => 11, ),
BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'STARTS' => 1, 'ENDS' => 2, 'FILE_ID' => 3, 'CLIP_LENGTH' => 4, 'FADE_IN' => 5, 'FADE_OUT' => 6, 'CUE_IN' => 7, 'CUE_OUT' => 8, 'MEDIA_ITEM_PLAYED' => 9, 'INSTANCE_ID' => 10, 'STATUS' => 11, ),
BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'starts' => 1, 'ends' => 2, 'file_id' => 3, 'clip_length' => 4, 'fade_in' => 5, 'fade_out' => 6, 'cue_in' => 7, 'cue_out' => 8, 'media_item_played' => 9, 'instance_id' => 10, 'status' => 11, ),
BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, )
);
/**
@ -183,6 +186,7 @@ abstract class BaseCcSchedulePeer {
$criteria->addSelectColumn(CcSchedulePeer::CUE_OUT);
$criteria->addSelectColumn(CcSchedulePeer::MEDIA_ITEM_PLAYED);
$criteria->addSelectColumn(CcSchedulePeer::INSTANCE_ID);
$criteria->addSelectColumn(CcSchedulePeer::STATUS);
} else {
$criteria->addSelectColumn($alias . '.ID');
$criteria->addSelectColumn($alias . '.STARTS');
@ -195,6 +199,7 @@ abstract class BaseCcSchedulePeer {
$criteria->addSelectColumn($alias . '.CUE_OUT');
$criteria->addSelectColumn($alias . '.MEDIA_ITEM_PLAYED');
$criteria->addSelectColumn($alias . '.INSTANCE_ID');
$criteria->addSelectColumn($alias . '.STATUS');
}
}

View File

@ -17,6 +17,7 @@
* @method CcScheduleQuery orderByDbCueOut($order = Criteria::ASC) Order by the cue_out column
* @method CcScheduleQuery orderByDbMediaItemPlayed($order = Criteria::ASC) Order by the media_item_played column
* @method CcScheduleQuery orderByDbInstanceId($order = Criteria::ASC) Order by the instance_id column
* @method CcScheduleQuery orderByDbStatus($order = Criteria::ASC) Order by the status column
*
* @method CcScheduleQuery groupByDbId() Group by the id column
* @method CcScheduleQuery groupByDbStarts() Group by the starts column
@ -29,6 +30,7 @@
* @method CcScheduleQuery groupByDbCueOut() Group by the cue_out column
* @method CcScheduleQuery groupByDbMediaItemPlayed() Group by the media_item_played column
* @method CcScheduleQuery groupByDbInstanceId() Group by the instance_id column
* @method CcScheduleQuery groupByDbStatus() Group by the status column
*
* @method CcScheduleQuery leftJoin($relation) Adds a LEFT JOIN clause to the query
* @method CcScheduleQuery rightJoin($relation) Adds a RIGHT JOIN clause to the query
@ -56,6 +58,7 @@
* @method CcSchedule findOneByDbCueOut(string $cue_out) Return the first CcSchedule filtered by the cue_out column
* @method CcSchedule findOneByDbMediaItemPlayed(boolean $media_item_played) Return the first CcSchedule filtered by the media_item_played column
* @method CcSchedule findOneByDbInstanceId(int $instance_id) Return the first CcSchedule filtered by the instance_id column
* @method CcSchedule findOneByDbStatus(int $status) Return the first CcSchedule filtered by the status column
*
* @method array findByDbId(int $id) Return CcSchedule objects filtered by the id column
* @method array findByDbStarts(string $starts) Return CcSchedule objects filtered by the starts column
@ -68,6 +71,7 @@
* @method array findByDbCueOut(string $cue_out) Return CcSchedule objects filtered by the cue_out column
* @method array findByDbMediaItemPlayed(boolean $media_item_played) Return CcSchedule objects filtered by the media_item_played column
* @method array findByDbInstanceId(int $instance_id) Return CcSchedule objects filtered by the instance_id column
* @method array findByDbStatus(int $status) Return CcSchedule objects filtered by the status column
*
* @package propel.generator.airtime.om
*/
@ -290,29 +294,20 @@ abstract class BaseCcScheduleQuery extends ModelCriteria
/**
* Filter the query on the clip_length column
*
* @param string|array $dbClipLength The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $dbClipLength The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcScheduleQuery The current query, for fluid interface
*/
public function filterByDbClipLength($dbClipLength = null, $comparison = null)
{
if (is_array($dbClipLength)) {
$useMinMax = false;
if (isset($dbClipLength['min'])) {
$this->addUsingAlias(CcSchedulePeer::CLIP_LENGTH, $dbClipLength['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbClipLength['max'])) {
$this->addUsingAlias(CcSchedulePeer::CLIP_LENGTH, $dbClipLength['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
if (null === $comparison) {
if (is_array($dbClipLength)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $dbClipLength)) {
$dbClipLength = str_replace('*', '%', $dbClipLength);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(CcSchedulePeer::CLIP_LENGTH, $dbClipLength, $comparison);
@ -383,29 +378,20 @@ abstract class BaseCcScheduleQuery extends ModelCriteria
/**
* Filter the query on the cue_in column
*
* @param string|array $dbCueIn The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $dbCueIn The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcScheduleQuery The current query, for fluid interface
*/
public function filterByDbCueIn($dbCueIn = null, $comparison = null)
{
if (is_array($dbCueIn)) {
$useMinMax = false;
if (isset($dbCueIn['min'])) {
$this->addUsingAlias(CcSchedulePeer::CUE_IN, $dbCueIn['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbCueIn['max'])) {
$this->addUsingAlias(CcSchedulePeer::CUE_IN, $dbCueIn['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
if (null === $comparison) {
if (is_array($dbCueIn)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $dbCueIn)) {
$dbCueIn = str_replace('*', '%', $dbCueIn);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(CcSchedulePeer::CUE_IN, $dbCueIn, $comparison);
@ -414,29 +400,20 @@ abstract class BaseCcScheduleQuery extends ModelCriteria
/**
* Filter the query on the cue_out column
*
* @param string|array $dbCueOut The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $dbCueOut The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcScheduleQuery The current query, for fluid interface
*/
public function filterByDbCueOut($dbCueOut = null, $comparison = null)
{
if (is_array($dbCueOut)) {
$useMinMax = false;
if (isset($dbCueOut['min'])) {
$this->addUsingAlias(CcSchedulePeer::CUE_OUT, $dbCueOut['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbCueOut['max'])) {
$this->addUsingAlias(CcSchedulePeer::CUE_OUT, $dbCueOut['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
if (null === $comparison) {
if (is_array($dbCueOut)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $dbCueOut)) {
$dbCueOut = str_replace('*', '%', $dbCueOut);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(CcSchedulePeer::CUE_OUT, $dbCueOut, $comparison);
@ -490,6 +467,37 @@ abstract class BaseCcScheduleQuery extends ModelCriteria
return $this->addUsingAlias(CcSchedulePeer::INSTANCE_ID, $dbInstanceId, $comparison);
}
/**
* Filter the query on the status column
*
* @param int|array $dbStatus The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return CcScheduleQuery The current query, for fluid interface
*/
public function filterByDbStatus($dbStatus = null, $comparison = null)
{
if (is_array($dbStatus)) {
$useMinMax = false;
if (isset($dbStatus['min'])) {
$this->addUsingAlias(CcSchedulePeer::STATUS, $dbStatus['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbStatus['max'])) {
$this->addUsingAlias(CcSchedulePeer::STATUS, $dbStatus['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
$comparison = Criteria::IN;
}
}
return $this->addUsingAlias(CcSchedulePeer::STATUS, $dbStatus, $comparison);
}
/**
* Filter the query by a related CcShowInstances object
*

View File

@ -0,0 +1,46 @@
<?php
class TimeFilledFormatter {
/**
* @string seconds
*/
private $_seconds;
/*
* @param string $seconds
*/
public function __construct($seconds)
{
$this->_seconds = $seconds;
}
public function format()
{
$formatted = "";
$sign = ($this->_seconds < 0) ? "-" : "+";
$time = Application_Model_Playlist::secondsToPlaylistTime(abs($this->_seconds));
Logging::log("time is: ".$time);
$info = explode(":", $time);
$formatted .= $sign;
if (intval($info[0]) > 0) {
$info[0] = ltrim($info[0], "0");
$formatted .= " {$info[0]}h";
}
if (intval($info[1]) > 0) {
$info[1] = ltrim($info[1], "0");
$formatted .= " {$info[1]}m";
}
if (intval($info[2]) > 0) {
$sec = round($info[2], 0);
$formatted .= " {$sec}s";
}
return $formatted;
}
}

View File

@ -1 +0,0 @@
<br /><br /><center>View script for controller <b>Library</b> and script/action name <b>delete</b></center>

View File

@ -1,8 +0,0 @@
<?php // libraryTablePartial.phtml ?>
<tr id="<?php echo substr($this->ftype, 0, 2) ?>_<?php echo $this->id ?>">
<td><?php echo $this->track_title ?></td>
<td><?php echo $this->artist_name ?></td>
<td><?php echo $this->album_title ?></td>
<td><?php echo $this->track_number ?></td>
<td><?php echo $this->length ?></td>
</tr>

View File

@ -1 +0,0 @@
<br /><br /><center>View script for controller <b>Library</b> and script/action name <b>search</b></center>

View File

@ -1,3 +0,0 @@
<?php
echo $this->partialLoop('library/libraryTablePartial.phtml', $this->files);

View File

@ -4,11 +4,11 @@ if (count($items)) : ?>
<?php $i = 0; ?>
<?php foreach($items as $item) : ?>
<li class="ui-state-default" id="spl_<?php echo $item["id"] ?>" unqid="<?php echo $item["CcFiles"]["gunid"]."_".$i; ?>">
<li class="ui-state-default" id="spl_<?php echo $item["id"] ?>" unqid="<?php echo $item["CcFiles"]["gunid"]."_".$item["id"]; ?>">
<div class="list-item-container">
<a href="javascript:void(0);" class="big_play"
onclick="audioPreview('<?php echo $item["CcFiles"]["gunid"].".".pathinfo($item["CcFiles"]["filepath"], PATHINFO_EXTENSION);?>',
'spl_<?php echo $i ?>')"><span class="ui-icon ui-icon-play"></span></a>
'spl_<?php echo $item["id"] ?>')"><span class="ui-icon ui-icon-play"></span></a>
<div class="text-row top">
<span class="spl_playlength"><?php echo $item["cliplength"] ?></span>

View File

@ -0,0 +1,9 @@
<div class="wrapper">
<div id="library_content" class="tabs ui-widget ui-widget-content block-shadow alpha-block padded">
<div id="import_status" style="display:none">File import in progress...</div>
<table id="library_display" cellpadding="0" cellspacing="0" class="datatable"></table>
</div>
<div id="show_builder" class="ui-widget ui-widget-content block-shadow omega-block padded">
<table id="show_builder_table" cellpadding="0" cellspacing="0" class="datatable"></table>
</div>
</div>

View File

@ -237,9 +237,9 @@
<column name="playlist_id" phpName="DbPlaylistId" type="INTEGER" required="false"/>
<column name="file_id" phpName="DbFileId" type="INTEGER" required="false"/>
<column name="position" phpName="DbPosition" type="INTEGER" required="false"/>
<column name="cliplength" phpName="DbCliplength" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cuein" phpName="DbCuein" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cueout" phpName="DbCueout" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cliplength" phpName="DbCliplength" type="VARCHAR" sqlType="interval" required="false" defaultValue="00:00:00"/>
<column name="cuein" phpName="DbCuein" type="VARCHAR" sqlType="interval" required="false" defaultValue="00:00:00"/>
<column name="cueout" phpName="DbCueout" type="VARCHAR" sqlType="interval" required="false" defaultValue="00:00:00"/>
<column name="fadein" phpName="DbFadein" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="fadeout" phpName="DbFadeout" type="TIME" required="false" defaultValue="00:00:00"/>
<foreign-key foreignTable="cc_files" name="cc_playlistcontents_file_id_fkey" onDelete="CASCADE">
@ -273,13 +273,14 @@
<column name="starts" phpName="DbStarts" type="TIMESTAMP" required="true"/>
<column name="ends" phpName="DbEnds" type="TIMESTAMP" required="true"/>
<column name="file_id" phpName="DbFileId" type="INTEGER" required="false"/>
<column name="clip_length" phpName="DbClipLength" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="clip_length" phpName="DbClipLength" type="VARCHAR" sqlType="interval" required="false" defaultValue="00:00:00"/>
<column name="fade_in" phpName="DbFadeIn" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="fade_out" phpName="DbFadeOut" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cue_in" phpName="DbCueIn" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cue_out" phpName="DbCueOut" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cue_in" phpName="DbCueIn" type="VARCHAR" sqlType="interval" required="false" defaultValue="00:00:00"/>
<column name="cue_out" phpName="DbCueOut" type="VARCHAR" sqlType="interval" required="false" defaultValue="00:00:00"/>
<column name="media_item_played" phpName="DbMediaItemPlayed" type="BOOLEAN" required="false" defaultValue="0"/>
<column name="instance_id" phpName="DbInstanceId" type="INTEGER" required="true"/>
<column name="status" phpName="DbStatus" type="SMALLINT" required="true" defaultValue="1"/>
<!-- This foreign key is still useful even though it may seem we don't ever delete cc_show_instances anymore.
We will do delete them in some cases (when editing a show and changing the repeating days of the week
for example. \

View File

@ -315,9 +315,9 @@ CREATE TABLE "cc_playlistcontents"
"playlist_id" INTEGER,
"file_id" INTEGER,
"position" INTEGER,
"cliplength" TIME default '00:00:00',
"cuein" TIME default '00:00:00',
"cueout" TIME default '00:00:00',
"cliplength" interval default '00:00:00',
"cuein" interval default '00:00:00',
"cueout" interval default '00:00:00',
"fadein" TIME default '00:00:00',
"fadeout" TIME default '00:00:00',
PRIMARY KEY ("id")
@ -364,13 +364,14 @@ CREATE TABLE "cc_schedule"
"starts" TIMESTAMP NOT NULL,
"ends" TIMESTAMP NOT NULL,
"file_id" INTEGER,
"clip_length" TIME default '00:00:00',
"clip_length" interval default '00:00:00',
"fade_in" TIME default '00:00:00',
"fade_out" TIME default '00:00:00',
"cue_in" TIME default '00:00:00',
"cue_out" TIME default '00:00:00',
"cue_in" interval default '00:00:00',
"cue_out" interval default '00:00:00',
"media_item_played" BOOLEAN default 'f',
"instance_id" INTEGER NOT NULL,
"status" INT2 default 1 NOT NULL,
PRIMARY KEY ("id")
);

View File

@ -30,7 +30,7 @@ button.ColVis_Button::-moz-focus-inner {
div.ColVis_collectionBackground {
background-color: black;
z-index: 996;
z-index: 1003;
}
div.ColVis_collection {
@ -39,7 +39,7 @@ div.ColVis_collection {
background-color: #999;
padding: 3px;
border: 1px solid #ccc;
z-index: 998;
z-index: 1005;
}
div.ColVis_collection button.ColVis_Button {
@ -51,7 +51,7 @@ div.ColVis_collection button.ColVis_Button {
div.ColVis_catcher {
position: absolute;
z-index: 997;
z-index: 1004;
}
.disabled {

View File

@ -79,3 +79,8 @@
.library_year {
text-align: center;
}
.library_sr,
.library_bitrate {
text-align: right;
}

View File

@ -457,7 +457,5 @@ div.helper li {
}
li.spl_empty {
text-align: center;
height: 56px;
border:2px dashed black;
}

View File

@ -34,4 +34,17 @@ tr.cursor-selected-row .marker {
.sb-over {
background-color:#ff3030;
}
.sb-now-playing {
background-color:#17eb25 !important;
}
.ui-dialog .wrapper {
margin: 0;
padding: 10px 0 0 0;
}
.ui-dialog .ui-buttonset {
margin-right: 0 !important;
}

View File

@ -601,6 +601,7 @@ dl.inline-list dd {
}
.dataTables_info {
float: left;
padding: 8px 0 0 8px;
font-size:12px;
color:#555555;
@ -608,6 +609,7 @@ dl.inline-list dd {
}
.dataTables_paginate {
float: right;
padding: 8px 0 8px 8px;
}
.dataTables_paginate .ui-button {
@ -618,7 +620,7 @@ dl.inline-list dd {
}
.dataTables_filter input {
background: url("images/search_auto_bg.png") no-repeat scroll 0 0 #DDDDDD;
width: 60%;
width: 55%;
border: 1px solid #5B5B5B;
margin-left: -8px;
padding: 4px 3px 4px 25px;

View File

@ -0,0 +1,29 @@
var AIRTIME = (function(AIRTIME){
var mod,
DEFAULT_CLASS = 'ui-button ui-state-default',
DISABLED_CLASS = 'ui-state-disabled';
if (AIRTIME.button === undefined) {
AIRTIME.button = {};
}
mod = AIRTIME.button;
mod.enableButton = function(c) {
var button = $("."+c).find("button");
if (button.hasClass(DISABLED_CLASS)) {
button.removeClass(DISABLED_CLASS);
}
};
mod.disableButton = function(c) {
var button = $("."+c).find("button");
if (!button.hasClass(DISABLED_CLASS)) {
button.addClass(DISABLED_CLASS);
}
};
return AIRTIME;
}(AIRTIME || {}));

View File

@ -7,6 +7,24 @@ var AIRTIME = (function(AIRTIME){
AIRTIME.library.events = {};
mod = AIRTIME.library.events;
mod.enableAddButtonCheck = function() {
var selected = $('#library_display tr[id ^= "au"] input[type=checkbox]').filter(":checked"),
sortable = $('#spl_sortable'),
check = false;
//make sure audioclips are selected and a playlist is currently open.
if (selected.length !== 0 && sortable.length !== 0) {
check = true;
}
if (check === true) {
AIRTIME.button.enableButton("library_group_add");
}
else {
AIRTIME.button.disableButton("library_group_add");
}
};
mod.fnRowCallback = function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var $nRow = $(nRow);
@ -63,22 +81,22 @@ var AIRTIME = (function(AIRTIME){
*/
mod.setupLibraryToolbar = function( oLibTable ) {
var aButtons,
fnResetCol,
fnAddSelectedItems;
fnAddSelectedItems = function() {
var oLibTT = TableTools.fnGetInstance('library_display'),
aData = oLibTT.fnGetSelectedData(),
item,
i,
temp,
length,
aMediaIds = [];
//process selected files/playlists.
for (item in aData) {
temp = aData[item];
if (temp !== null && temp.hasOwnProperty('id') && temp.ftype === "audioclip") {
for (i = 0, length = aData.length; i < length; i++) {
temp = aData[i];
if (temp.ftype === "audioclip") {
aMediaIds.push(temp.id);
}
}
}
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
@ -88,8 +106,8 @@ var AIRTIME = (function(AIRTIME){
//[1] = id
//[2] = enabled
//[3] = click event
aButtons = [["Delete", "library_group_delete", true, AIRTIME.library.fnDeleteSelectedItems],
["Add", "library_group_add", true, fnAddSelectedItems]];
aButtons = [["Delete", "library_group_delete", false, AIRTIME.library.fnDeleteSelectedItems],
["Add", "library_group_add", false, fnAddSelectedItems]];
addToolBarButtonsLibrary(aButtons);
};

View File

@ -8,6 +8,24 @@ var AIRTIME = (function(AIRTIME){
AIRTIME.library.events = {};
mod = AIRTIME.library.events;
mod.enableAddButtonCheck = function() {
var selected = $('#library_display tr input[type=checkbox]').filter(":checked"),
cursor = $('tr.cursor-selected-row'),
check = false;
//make sure library items are selected and a cursor is selected.
if (selected.length !== 0 && cursor.length !== 0) {
check = true;
}
if (check === true) {
AIRTIME.button.enableButton("library_group_add");
}
else {
AIRTIME.button.disableButton("library_group_add");
}
};
mod.fnRowCallback = function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var $nRow = $(nRow);
@ -21,7 +39,6 @@ var AIRTIME = (function(AIRTIME){
$('#library_display tr:not(:first)').draggable({
helper: function(){
var selected = $('#library_display tr:not(:first) input:checked').parents('tr'),
aItems = [],
container,
thead = $("#show_builder_table thead"),
colspan = thead.find("th").length,
@ -34,10 +51,10 @@ var AIRTIME = (function(AIRTIME){
}
if (selected.length === 1) {
message = "Moving "+selected.length+" Item."
message = "Moving "+selected.length+" Item.";
}
else {
message = "Moving "+selected.length+" Items."
message = "Moving "+selected.length+" Items.";
}
container = $('<div/>').attr('id', 'draggingContainer')
@ -61,8 +78,6 @@ var AIRTIME = (function(AIRTIME){
mod.setupLibraryToolbar = function(oLibTable) {
var aButtons,
fnTest,
fnResetCol,
fnAddSelectedItems,
fnAddSelectedItems = function() {
@ -75,7 +90,7 @@ var AIRTIME = (function(AIRTIME){
aSchedIds = [];
//process selected files/playlists.
for (i=0, length = aData.length; i < length; i++) {
for (i = 0, length = aData.length; i < length; i++) {
temp = aData[i];
aMediaIds.push({"id": temp.id, "type": temp.ftype});
}
@ -93,12 +108,13 @@ var AIRTIME = (function(AIRTIME){
AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds);
};
//[0] = button text
//[1] = id
//[2] = enabled
//[3] = click event
aButtons = [["Delete", "library_group_delete", true, AIRTIME.library.fnDeleteSelectedItems],
["Add", "library_group_add", true, fnAddSelectedItems]];
aButtons = [["Delete", "library_group_delete", false, AIRTIME.library.fnDeleteSelectedItems],
["Add", "library_group_add", false, fnAddSelectedItems]];
addToolBarButtonsLibrary(aButtons);
};

View File

@ -1,5 +1,6 @@
var AIRTIME = (function(AIRTIME){
var mod;
var mod,
libraryInit;
if (AIRTIME.library === undefined) {
AIRTIME.library = {};
@ -13,6 +14,10 @@ var AIRTIME = (function(AIRTIME){
$.post("/library/delete",
{"format": "json", "media": aMedia},
function(json){
if (json.message !== undefined) {
alert(json.message);
}
oLibTT.fnSelectNone();
oLibTable.fnDraw();
});
@ -36,6 +41,408 @@ var AIRTIME = (function(AIRTIME){
AIRTIME.library.fnDeleteItems(aMedia);
};
libraryInit = function() {
var oTable;
oTable = $('#library_display').dataTable( {
"aoColumns": [
/* Checkbox */ {"sTitle": "<input type='checkbox' name='pl_cb_all'>", "mDataProp": "checkbox", "bSortable": false, "bSearchable": false, "sWidth": "25px", "sClass": "library_checkbox"},
/* Type */ {"sTitle": "", "mDataProp": "image", "bSearchable": false, "sWidth": "25px", "sClass": "library_type", "iDataSort": 2},
/* ftype */ {"sTitle": "", "mDataProp": "ftype", "bSearchable": false, "bVisible": false},
/* Title */ {"sTitle": "Title", "mDataProp": "track_title", "sClass": "library_title"},
/* Creator */ {"sTitle": "Creator", "mDataProp": "artist_name", "sClass": "library_creator"},
/* Album */ {"sTitle": "Album", "mDataProp": "album_title", "sClass": "library_album"},
/* Genre */ {"sTitle": "Genre", "mDataProp": "genre", "sClass": "library_genre"},
/* Year */ {"sTitle": "Year", "mDataProp": "year", "sClass": "library_year", "sWidth": "60px"},
/* Length */ {"sTitle": "Length", "mDataProp": "length", "sClass": "library_length", "sWidth": "80px"},
/* Upload Time */ {"sTitle": "Uploaded", "mDataProp": "utime", "sClass": "library_upload_time"},
/* Last Modified */ {"sTitle": "Last Modified", "mDataProp": "mtime", "bVisible": false, "sClass": "library_modified_time"},
/* Track Number */ {"sTitle": "Track", "mDataProp": "track_number", "bSearchable": false, "bVisible": false, "sClass": "library_track"},
/* Mood */ {"sTitle": "Mood", "mDataProp": "mood", "bSearchable": false, "bVisible": false, "sClass": "library_mood"},
/* BPM */ {"sTitle": "BPM", "mDataProp": "bpm", "bSearchable": false, "bVisible": false, "sClass": "library_bpm"},
/* Composer */ {"sTitle": "Composer", "mDataProp": "composer", "bSearchable": false, "bVisible": false, "sClass": "library_composer"},
/* Website */ {"sTitle": "Website", "mDataProp": "info_url", "bSearchable": false, "bVisible": false, "sClass": "library_url"},
/* Bit Rate */ {"sTitle": "Bit Rate", "mDataProp": "bit_rate", "bSearchable": false, "bVisible": false, "sClass": "library_bitrate", "sWidth": "80px"},
/* Sample Rate */ {"sTitle": "Sample", "mDataProp": "sample_rate", "bSearchable": false, "bVisible": false, "sClass": "library_sr", "sWidth": "80px"},
/* ISRC Number */ {"sTitle": "ISRC", "mDataProp": "isrc_number", "bSearchable": false, "bVisible": false, "sClass": "library_isrc"},
/* Encoded */ {"sTitle": "Encoded", "mDataProp": "encoded_by", "bSearchable": false, "bVisible": false, "sClass": "library_encoded"},
/* Label */ {"sTitle": "Label", "mDataProp": "label", "bSearchable": false, "bVisible": false, "sClass": "library_label"},
/* Copyright */ {"sTitle": "Copyright", "mDataProp": "copyright", "bSearchable": false, "bVisible": false, "sClass": "library_copyright"},
/* Mime */ {"sTitle": "Mime", "mDataProp": "mime", "bSearchable": false, "bVisible": false, "sClass": "library_mime"},
/* Language */ {"sTitle": "Language", "mDataProp": "language", "bSearchable": false, "bVisible": false, "sClass": "library_language"}
],
"bProcessing": true,
"bServerSide": true,
"bStateSave": true,
"fnStateSaveParams": function (oSettings, oData) {
//remove oData components we don't want to save.
delete oData.oSearch;
delete oData.aoSearchCols;
},
"fnStateSave": function (oSettings, oData) {
$.ajax({
url: "/usersettings/set-library-datatable",
type: "POST",
data: {settings : oData, format: "json"},
dataType: "json",
success: function(){},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
},
"fnStateLoad": function (oSettings) {
var o;
$.ajax({
url: "/usersettings/get-library-datatable",
type: "GET",
data: {format: "json"},
dataType: "json",
async: false,
success: function(json){
o = json.settings;
},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
return o;
},
"fnStateLoadParams": function (oSettings, oData) {
var i,
length,
a = oData.abVisCols;
//putting serialized data back into the correct js type to make
//sure everything works properly.
for (i = 0, length = a.length; i < length; i++) {
a[i] = (a[i] === "true") ? true : false;
}
a = oData.ColReorder;
for (i = 0, length = a.length; i < length; i++) {
a[i] = parseInt(a[i], 10);
}
oData.iEnd = parseInt(oData.iEnd, 10);
oData.iLength = parseInt(oData.iLength, 10);
oData.iStart = parseInt(oData.iStart, 10);
oData.iCreate = parseInt(oData.iCreate, 10);
},
"sAjaxSource": "/Library/contents",
"fnServerData": function ( sSource, aoData, fnCallback ) {
var type;
aoData.push( { name: "format", value: "json"} );
//push whether to search files/playlists or all.
type = $("#library_display_type").find("select").val();
type = (type === undefined) ? 0 : type;
aoData.push( { name: "type", value: type} );
$.ajax( {
"dataType": 'json',
"type": "GET",
"url": sSource,
"data": aoData,
"success": fnCallback
} );
},
"fnRowCallback": AIRTIME.library.events.fnRowCallback,
"fnCreatedRow": function( nRow, aData, iDataIndex ) {
//call the context menu so we can prevent the event from propagating.
$(nRow).find('td:not(.library_checkbox)').click(function(e){
$(this).contextMenu({x: e.pageX, y: e.pageY});
return false;
});
//add a tool tip to appear when the user clicks on the type icon.
$(nRow).find("td:not(:first, td>img)").qtip({
content: {
text: "Loading...",
title: {
text: aData.track_title
},
ajax: {
url: "/Library/get-file-meta-data",
type: "get",
data: ({format: "html", id : aData.id, type: aData.ftype}),
success: function(data, status) {
this.set('content.text', data);
}
}
},
position: {
target: 'event',
adjust: {
resize: true,
method: "flip flip"
},
my: 'left center',
at: 'right center',
viewport: $(window), // Keep the tooltip on-screen at all times
effect: false // Disable positioning animation
},
style: {
classes: "ui-tooltip-dark"
},
show: 'mousedown',
events: {
show: function(event, api) {
// Only show the tooltip if it was a right-click
if(event.originalEvent.button !== 2) {
event.preventDefault();
}
}
},
hide: 'mouseout'
});
},
"fnDrawCallback": AIRTIME.library.events.fnDrawCallback,
"fnHeaderCallback": function(nHead) {
$(nHead).find("input[type=checkbox]").attr("checked", false);
},
"aaSorting": [[3, 'asc']],
"sPaginationType": "full_numbers",
"bJQueryUI": true,
"bAutoWidth": false,
"oLanguage": {
"sSearch": ""
},
// R = ColReorder, C = ColVis, T = TableTools
"sDom": 'Rl<"#library_display_type">fr<"H"T<"library_toolbar"C>>t<"F"ip>',
"oTableTools": {
"sRowSelect": "multi",
"aButtons": [],
"fnRowSelected": function ( node ) {
var selected;
//seems to happen if everything is selected
if ( node === null) {
selected = oTable.find("input[type=checkbox]");
selected.attr("checked", true);
}
else {
$(node).find("input[type=checkbox]").attr("checked", true);
selected = oTable.find("input[type=checkbox]").filter(":checked");
}
//checking to enable buttons
AIRTIME.button.enableButton("library_group_delete");
AIRTIME.library.events.enableAddButtonCheck();
},
"fnRowDeselected": function ( node ) {
var selected;
//seems to happen if everything is deselected
if ( node === null) {
oTable.find("input[type=checkbox]").attr("checked", false);
selected = [];
}
else {
$(node).find("input[type=checkbox]").attr("checked", false);
selected = oTable.find("input[type=checkbox]").filter(":checked");
}
//checking to disable buttons
if (selected.length === 0) {
AIRTIME.button.disableButton("library_group_delete");
}
AIRTIME.library.events.enableAddButtonCheck();
}
},
"oColVis": {
"buttonText": "Show/Hide Columns",
"sAlign": "right",
"aiExclude": [0, 1, 2],
"sSize": "css"
},
"oColReorder": {
"iFixedColumns": 2
}
});
oTable.fnSetFilteringDelay(350);
AIRTIME.library.events.setupLibraryToolbar(oTable);
$("#library_display_type")
.addClass("dataTables_type")
.append('<select name="library_display_type" />')
.find("select")
.append('<option value="0">All</option>')
.append('<option value="1">Files</option>')
.append('<option value="2">Playlists</option>')
.end()
.change(function(ev){
oTable.fnDraw();
});
$('[name="pl_cb_all"]').click(function(){
var oTT = TableTools.fnGetInstance('library_display');
if ($(this).is(":checked")) {
oTT.fnSelectAll();
}
else {
oTT.fnSelectNone();
}
});
checkImportStatus();
setInterval( checkImportStatus, 5000 );
setInterval( checkSCUploadStatus, 5000 );
addQtipToSCIcons();
$.contextMenu({
selector: '#library_display td:not(.library_checkbox)',
trigger: "left",
ignoreRightClick: true,
build: function($el, e) {
var data, screen, items, callback, $tr;
$tr = $el.parent();
data = $tr.data("aData");
screen = $tr.data("screen");
function processMenuItems(oItems) {
//define an add to playlist callback.
if (oItems.pl_add !== undefined) {
callback = function() {
AIRTIME.playlist.fnAddItems([data.id], undefined, 'after');
};
oItems.pl_add.callback = callback;
}
//define an edit callback.
if (oItems.edit !== undefined) {
if (data.ftype === "audioclip") {
callback = function() {
document.location.href = oItems.edit.url;
};
}
else {
callback = function() {
AIRTIME.playlist.fnEdit(data.id);
};
}
oItems.edit.callback = callback;
}
//define a delete callback.
if (oItems.del !== undefined) {
//delete through the playlist controller, will reset
//playlist screen if this is the currently edited playlist.
if (data.ftype === "playlist" && screen === "playlist") {
callback = function() {
if (confirm('Are you sure you want to delete the selected item?')) {
AIRTIME.playlist.fnDelete(data.id);
}
};
}
else {
callback = function() {
var media = [];
if (confirm('Are you sure you want to delete the selected item?')) {
media.push({"id": data.id, "type": data.ftype});
$.post(oItems.del.url, {format: "json", media: media }, function(json){
var oTable;
if (json.message) {
alert(json.message);
}
oTable = $("#library_display").dataTable();
oTable.fnDeleteRow( $tr[0] );
});
}
};
}
oItems.del.callback = callback;
}
//define a download callback.
if (oItems.download !== undefined) {
callback = function() {
document.location.href = oItems.download.url;
};
oItems.download.callback = callback;
}
//add callbacks for Soundcloud menu items.
if (oItems.soundcloud !== undefined) {
var soundcloud = oItems.soundcloud.items;
//define an upload to soundcloud callback.
if (soundcloud.upload !== undefined) {
callback = function() {
$.post(soundcloud.upload.url, function(){
addProgressIcon(data.id);
});
};
soundcloud.upload.callback = callback;
}
//define a view on soundcloud callback
if (soundcloud.view !== undefined) {
callback = function() {
window.open(soundcloud.view.url);
};
soundcloud.view.callback = callback;
}
}
items = oItems;
}
request = $.ajax({
url: "/library/context-menu",
type: "GET",
data: {id : data.id, type: data.ftype, format: "json", "screen": screen},
dataType: "json",
async: false,
success: function(json){
processMenuItems(json.items);
}
});
return {
items: items
};
}
});
};
mod.libraryInit = libraryInit;
return AIRTIME;
}(AIRTIME || {}));
@ -43,42 +450,45 @@ var AIRTIME = (function(AIRTIME){
function addToolBarButtonsLibrary(aButtons) {
var i,
length = aButtons.length,
libToolBar,
libToolBar = $(".library_toolbar"),
html,
buttonClass = '',
DEFAULT_CLASS = 'ui-button ui-state-default',
DISABLED_CLASS = 'ui-state-disabled';
DISABLED_CLASS = 'ui-state-disabled',
fn;
libToolBar = $(".library_toolbar");
for ( i=0; i < length; i+=1 ) {
for ( i = 0; i < length; i += 1 ) {
buttonClass = '';
//add disabled class if not enabled.
if (aButtons[i][2] === false) {
buttonClass+=DISABLED_CLASS;
buttonClass += DISABLED_CLASS;
}
html = '<div id="'+aButtons[i][1]+'" class="ColVis TableTools"><button class="'+DEFAULT_CLASS+' '+buttonClass+'"><span>'+aButtons[i][0]+'</span></button></div>';
html = '<div class="ColVis TableTools '+aButtons[i][1]+'"><button class="'+DEFAULT_CLASS+' '+buttonClass+'"><span>'+aButtons[i][0]+'</span></button></div>';
libToolBar.append(html);
libToolBar.find("#"+aButtons[i][1]).click(aButtons[i][3]);
//create a closure to preserve the state of i.
(function(index){
libToolBar.find("."+aButtons[index][1]).click(function(){
fn = function() {
var $button = $(this).find("button");
//only call the passed function if the button is enabled.
if (!$button.hasClass(DISABLED_CLASS)) {
aButtons[index][3]();
}
};
fn.call(this);
});
}(i));
}
}
function enableGroupBtn(btnId, func) {
btnId = '#' + btnId;
if ($(btnId).hasClass('ui-state-disabled')) {
$(btnId).removeClass('ui-state-disabled');
}
}
function disableGroupBtn(btnId) {
btnId = '#' + btnId;
if (!$(btnId).hasClass('ui-state-disabled')) {
$(btnId).addClass('ui-state-disabled');
}
}
function checkImportStatus(){
$.getJSON('/Preference/is-import-in-progress', function(data){
var div = $('#import_status');
@ -210,389 +620,4 @@ function addQtipToSCIcons(){
});
}
});
}
$(document).ready(function() {
var oTable;
oTable = $('#library_display').dataTable( {
"aoColumns": [
/* Checkbox */ {"sTitle": "<input type='checkbox' name='pl_cb_all'>", "mDataProp": "checkbox", "bSortable": false, "bSearchable": false, "sWidth": "25px", "sClass": "library_checkbox"},
/* Type */ {"sTitle": "", "mDataProp": "image", "bSearchable": false, "sWidth": "25px", "sClass": "library_type", "iDataSort": 2},
/* ftype */ {"sTitle": "", "mDataProp": "ftype", "bSearchable": false, "bVisible": false},
/* Title */ {"sTitle": "Title", "mDataProp": "track_title", "sClass": "library_title"},
/* Creator */ {"sTitle": "Creator", "mDataProp": "artist_name", "sClass": "library_creator"},
/* Album */ {"sTitle": "Album", "mDataProp": "album_title", "sClass": "library_album"},
/* Genre */ {"sTitle": "Genre", "mDataProp": "genre", "sClass": "library_genre"},
/* Year */ {"sTitle": "Year", "mDataProp": "year", "sClass": "library_year", "sWidth": "60px"},
/* Length */ {"sTitle": "Length", "mDataProp": "length", "sClass": "library_length", "sWidth": "80px"},
/* Upload Time */ {"sTitle": "Uploaded", "mDataProp": "utime", "sClass": "library_upload_time"},
/* Last Modified */ {"sTitle": "Last Modified", "mDataProp": "mtime", "bVisible": false, "sClass": "library_modified_time"},
/* Track Number */ {"sTitle": "Track", "mDataProp": "track_number", "bSearchable": false, "bVisible": false, "sClass": "library_track"},
/* Mood */ {"sTitle": "Mood", "mDataProp": "mood", "bSearchable": false, "bVisible": false, "sClass": "library_mood"},
/* BPM */ {"sTitle": "BPM", "mDataProp": "bpm", "bSearchable": false, "bVisible": false, "sClass": "library_bpm"},
/* Composer */ {"sTitle": "Composer", "mDataProp": "composer", "bSearchable": false, "bVisible": false, "sClass": "library_composer"},
/* Website */ {"sTitle": "Website", "mDataProp": "info_url", "bSearchable": false, "bVisible": false, "sClass": "library_url"},
/* Bit Rate */ {"sTitle": "Bit Rate", "mDataProp": "bit_rate", "bSearchable": false, "bVisible": false, "sClass": "library_bitrate"},
/* Sameple Rate */ {"sTitle": "Sample Rate", "mDataProp": "sample_rate", "bSearchable": false, "bVisible": false, "sClass": "library_sr"},
/* ISRC Number */ {"sTitle": "ISRC", "mDataProp": "isrc_number", "bSearchable": false, "bVisible": false, "sClass": "library_isrc"},
/* Encoded */ {"sTitle": "Encoded", "mDataProp": "encoded_by", "bSearchable": false, "bVisible": false, "sClass": "library_encoded"},
/* Label */ {"sTitle": "Label", "mDataProp": "label", "bSearchable": false, "bVisible": false, "sClass": "library_label"},
/* Copyright */ {"sTitle": "Copyright", "mDataProp": "copyright", "bSearchable": false, "bVisible": false, "sClass": "library_copyright"},
/* Mime */ {"sTitle": "Mime", "mDataProp": "mime", "bSearchable": false, "bVisible": false, "sClass": "library_mime"},
/* Language */ {"sTitle": "Language", "mDataProp": "language", "bSearchable": false, "bVisible": false, "sClass": "library_language"}
],
"bProcessing": true,
"bServerSide": true,
"bStateSave": true,
"fnStateSaveParams": function (oSettings, oData) {
//remove oData components we don't want to save.
delete oData.oSearch;
delete oData.aoSearchCols;
},
"fnStateSave": function (oSettings, oData) {
$.ajax({
url: "/usersettings/set-library-datatable",
type: "POST",
data: {settings : oData, format: "json"},
dataType: "json",
success: function(){},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
},
"fnStateLoad": function (oSettings) {
var o;
$.ajax({
url: "/usersettings/get-library-datatable",
type: "GET",
data: {format: "json"},
dataType: "json",
async: false,
success: function(json){
o = json.settings;
},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
return o;
},
"fnStateLoadParams": function (oSettings, oData) {
var i,
length,
a = oData.abVisCols;
//putting serialized data back into the correct js type to make
//sure everything works properly.
for (i = 0, length = a.length; i < length; i++) {
a[i] = (a[i] === "true") ? true : false;
}
a = oData.ColReorder;
for (i = 0, length = a.length; i < length; i++) {
a[i] = parseInt(a[i], 10);
}
oData.iEnd = parseInt(oData.iEnd, 10);
oData.iLength = parseInt(oData.iLength, 10);
oData.iStart = parseInt(oData.iStart, 10);
oData.iCreate = parseInt(oData.iCreate, 10);
},
"sAjaxSource": "/Library/contents",
"fnServerData": function ( sSource, aoData, fnCallback ) {
var type;
aoData.push( { name: "format", value: "json"} );
//push whether to search files/playlists or all.
type = $("#library_display_type").find("select").val();
type = (type === undefined) ? 0 : type;
aoData.push( { name: "type", value: type} );
$.ajax( {
"dataType": 'json',
"type": "GET",
"url": sSource,
"data": aoData,
"success": fnCallback
} );
},
"fnRowCallback": AIRTIME.library.events.fnRowCallback,
"fnCreatedRow": function( nRow, aData, iDataIndex ) {
//call the context menu so we can prevent the event from propagating.
$(nRow).find('td:not(.library_checkbox)').click(function(e){
$(this).contextMenu({x: e.pageX, y: e.pageY});
return false;
});
//add a tool tip to appear when the user clicks on the type icon.
$(nRow).find("td:not(:first, td>img)").qtip({
content: {
text: "Loading...",
title: {
text: aData.track_title
},
ajax: {
url: "/Library/get-file-meta-data",
type: "get",
data: ({format: "html", id : aData.id, type: aData.ftype}),
success: function(data, status) {
this.set('content.text', data);
}
}
},
position: {
target: 'event',
adjust: {
resize: true,
method: "flip flip"
},
my: 'left center',
at: 'right center',
viewport: $(window), // Keep the tooltip on-screen at all times
effect: false // Disable positioning animation
},
style: {
classes: "ui-tooltip-dark"
},
show: 'mousedown',
events: {
show: function(event, api) {
// Only show the tooltip if it was a right-click
if(event.originalEvent.button !== 2) {
event.preventDefault();
}
}
},
hide: 'mouseout'
});
},
"fnDrawCallback": AIRTIME.library.events.fnDrawCallback,
"fnHeaderCallback": function(nHead) {
$(nHead).find("input[type=checkbox]").attr("checked", false);
},
"aaSorting": [[3, 'asc']],
"sPaginationType": "full_numbers",
"bJQueryUI": true,
"bAutoWidth": false,
"oLanguage": {
"sSearch": ""
},
// R = ColReorder, C = ColVis, T = TableTools
"sDom": 'Rl<"#library_display_type">fr<"H"T<"library_toolbar"C>>t<"F"ip>',
"oTableTools": {
"sRowSelect": "multi",
"aButtons": [],
"fnRowSelected": function ( node ) {
//seems to happen if everything is selected
if ( node === null) {
oTable.find("input[type=checkbox]").attr("checked", true);
}
else {
$(node).find("input[type=checkbox]").attr("checked", true);
}
},
"fnRowDeselected": function ( node ) {
//seems to happen if everything is deselected
if ( node === null) {
oTable.find("input[type=checkbox]").attr("checked", false);
}
else {
$(node).find("input[type=checkbox]").attr("checked", false);
}
}
},
"oColVis": {
"buttonText": "Show/Hide Columns",
"sAlign": "right",
"aiExclude": [0, 1, 2],
"sSize": "css"
},
"oColReorder": {
"iFixedColumns": 2
}
});
oTable.fnSetFilteringDelay(350);
AIRTIME.library.events.setupLibraryToolbar(oTable);
$("#library_display_type")
.addClass("dataTables_type")
.append('<select name="library_display_type" />')
.find("select")
.append('<option value="0">All</option>')
.append('<option value="1">Files</option>')
.append('<option value="2">Playlists</option>')
.end()
.change(function(ev){
oTable.fnDraw();
});
$('[name="pl_cb_all"]').click(function(){
var oTT = TableTools.fnGetInstance('library_display');
if ($(this).is(":checked")) {
oTT.fnSelectAll();
}
else {
oTT.fnSelectNone();
}
});
checkImportStatus();
setInterval( checkImportStatus, 5000 );
setInterval( checkSCUploadStatus, 5000 );
addQtipToSCIcons();
$.contextMenu({
selector: '#library_display td:not(.library_checkbox)',
trigger: "left",
ignoreRightClick: true,
build: function($el, e) {
var x, request, data, screen, items, callback, $tr;
$tr = $el.parent();
data = $tr.data("aData");
screen = $tr.data("screen");
function processMenuItems(oItems) {
//define an add to playlist callback.
if (oItems.pl_add !== undefined) {
callback = function() {
AIRTIME.playlist.fnAddItems([data.id], undefined, 'after');
};
oItems.pl_add.callback = callback;
}
//define an edit callback.
if (oItems.edit !== undefined) {
if (data.ftype === "audioclip") {
callback = function() {
document.location.href = oItems.edit.url;
};
}
else {
callback = function() {
AIRTIME.playlist.fnEdit(data.id);
};
}
oItems.edit.callback = callback;
}
//define a delete callback.
if (oItems.del !== undefined) {
//delete through the playlist controller, will reset
//playlist screen if this is the currently edited playlist.
if (data.ftype === "playlist" && screen === "playlist") {
callback = function() {
if (confirm('Are you sure you want to delete the selected item?')) {
AIRTIME.playlist.fnDelete(data.id);
}
};
}
else {
callback = function() {
var media = [];
if (confirm('Are you sure you want to delete the selected item?')) {
media.push({"id": data.id, "type": data.ftype});
$.post(oItems.del.url, {format: "json", media: media }, function(json){
var oTable, tr;
if (json.message) {
alert(json.message);
}
oTable = $("#library_display").dataTable();
oTable.fnDeleteRow( $tr[0] );
});
}
};
}
oItems.del.callback = callback;
}
//define a download callback.
if (oItems.download !== undefined) {
callback = function() {
document.location.href = oItems.download.url;
};
oItems.download.callback = callback;
}
//add callbacks for Soundcloud menu items.
if (oItems.soundcloud !== undefined) {
var soundcloud = oItems.soundcloud.items;
//define an upload to soundcloud callback.
if (soundcloud.upload !== undefined) {
callback = function() {
$.post(soundcloud.upload.url, function(){
addProgressIcon(data.id);
});
};
soundcloud.upload.callback = callback;
}
//define a view on soundcloud callback
if (soundcloud.view !== undefined) {
callback = function() {
window.open(soundcloud.view.url);
};
soundcloud.view.callback = callback;
}
}
items = oItems;
}
request = $.ajax({
url: "/library/context-menu",
type: "GET",
data: {id : data.id, type: data.ftype, format: "json", "screen": screen},
dataType: "json",
async: false,
success: function(json){
processMenuItems(json.items);
}
});
return {
items: items
};
}
});
});
}

View File

@ -0,0 +1 @@
$(document).ready(AIRTIME.library.libraryInit);

View File

@ -506,18 +506,25 @@ var AIRTIME = (function(AIRTIME){
fnUpdate;
fnReceive = function(event, ui) {
var selected = $('#library_display tr[id^="au"] input:checked').parents('tr'),
aItems = [];
var aItems = [],
aSelected,
oLibTT = TableTools.fnGetInstance('library_display'),
i,
length;
//filter out anything that isn't an audiofile.
aSelected = oLibTT.fnGetSelectedData();
//if nothing is checked select the dragged item.
if (selected.length === 0) {
selected = ui.item;
if (aSelected.length === 0) {
aSelected.push(ui.item.data("aData"));
}
selected.each(function(i, el) {
aItems.push($(el).data("aData").id);
});
for (i = 0, length = aSelected.length; i < length; i++) {
if (aSelected[i].ftype === "audioclip") {
aItems.push(aSelected[i].id);
}
}
aReceiveItems = aItems;
html = ui.helper.html();
};

View File

@ -67,21 +67,10 @@ function uploadToSoundCloud(show_instance_id){
}
}
function buildContentDialog (json){
var dialog = $(json.dialog),
viewportwidth,
viewportheight,
height,
width;
function findViewportDimensions() {
var viewportwidth,
viewportheight;
if (json.show_error == true){
alertShowErrorAndReload();
}
dialog.find("#show_progressbar").progressbar({
value: json.percentFilled
});
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') {
@ -101,9 +90,57 @@ function buildContentDialog (json){
viewportheight = document.getElementsByTagName('body')[0].clientHeight;
}
height = viewportheight * 2/3;
width = viewportwidth * 4/5;
return {
width: viewportwidth,
height: viewportheight
};
}
function buildScheduleDialog (json) {
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = viewport.height * 0.96,
width = viewport.width * 0.96,
fnServer = AIRTIME.showbuilder.fnServerData;
dialog.dialog({
autoOpen: false,
title: json.title,
width: width,
height: height,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {
dialog.remove();
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
}}
});
//set the start end times so the builder datatables knows its time range.
fnServer.start = json.start;
fnServer.end = json.end;
AIRTIME.library.libraryInit();
AIRTIME.showbuilder.builderDataTable();
dialog.dialog('open');
}
function buildContentDialog (json){
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = viewport.height * 2/3,
width = viewport.width * 4/5;
if (json.show_error == true){
alertShowErrorAndReload();
}
dialog.find("#show_progressbar").progressbar({
value: json.percentFilled
});
dialog.dialog({
autoOpen: false,
title: 'Show Contents',
@ -201,8 +238,12 @@ $(document).ready(function() {
if (oItems.schedule !== undefined) {
callback = function() {
document.location = oItems.schedule.url + "from/" + data.startUnix + "/to/" + data.endUnix;
};
$.post(oItems.schedule.url, {format: "json", id: data.id}, function(json){
buildScheduleDialog(json);
});
};
oItems.schedule.callback = callback;
}

View File

@ -1,6 +1,7 @@
var AIRTIME = (function(AIRTIME){
var mod,
oSchedTable;
oSchedTable,
fnServerData;
if (AIRTIME.showbuilder === undefined) {
AIRTIME.showbuilder = {};
@ -45,111 +46,6 @@ var AIRTIME = (function(AIRTIME){
});
};
mod.init = function(oTable) {
oSchedTable = oTable;
};
return AIRTIME;
}(AIRTIME || {}));
$(document).ready(function() {
var tableDiv = $('#show_builder_table'),
oTable,
oBaseDatePickerSettings,
oBaseTimePickerSettings,
fnAddSelectedItems,
fnRemoveSelectedItems,
oRange,
fnServerData;
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
onSelect: function(sDate, oDatePicker) {
var oDate,
dInput;
dInput = $(this);
oDate = dInput.datepicker( "setDate", sDate );
}
};
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
showLeadingZero: false,
defaultTime: '0:00'
};
/*
* Get the schedule range start in unix timestamp form (in seconds).
* defaults to NOW if nothing is selected.
*
* @param String sDatePickerId
*
* @param String sTimePickerId
*
* @return Number iTime
*/
function fnGetTimestamp(sDatePickerId, sTimePickerId) {
var date,
time,
iTime,
iServerOffset,
iClientOffset;
if ($(sDatePickerId).val() === "") {
return 0;
}
date = $(sDatePickerId).val();
time = $(sTimePickerId).val();
date = date.split("-");
time = time.split(":");
//0 based month in js.
oDate = new Date(date[0], date[1]-1, date[2], time[0], time[1]);
iTime = oDate.getTime(); //value is in millisec.
iTime = Math.round(iTime / 1000);
iServerOffset = serverTimezoneOffset;
iClientOffset = oDate.getTimezoneOffset() * -60;//function returns minutes
//adjust for the fact the the Date object is in client time.
iTime = iTime + iClientOffset + iServerOffset;
return iTime;
}
/*
* Returns an object containing a unix timestamp in seconds for the start/end range
*
* @return Object {"start", "end", "range"}
*/
function fnGetScheduleRange() {
var iStart,
iEnd,
iRange,
DEFAULT_RANGE = 60*60*24;
iStart = fnGetTimestamp("#sb_date_start", "#sb_time_start");
iEnd = fnGetTimestamp("#sb_date_end", "#sb_time_end");
iRange = iEnd - iStart;
if (iRange === 0 || iEnd < iStart) {
iEnd = iStart + DEFAULT_RANGE;
iRange = DEFAULT_RANGE;
}
return {
start: iStart,
end: iEnd,
range: iRange
};
}
fnServerData = function ( sSource, aoData, fnCallback ) {
aoData.push( { name: "format", value: "json"} );
@ -173,428 +69,435 @@ $(document).ready(function() {
} );
};
oRange = fnGetScheduleRange();
fnServerData.start = oRange.start;
fnServerData.end = oRange.end;
fnRemoveSelectedItems = function() {
var oTT = TableTools.fnGetInstance('show_builder_table'),
aData = oTT.fnGetSelectedData(),
i,
length,
temp,
aItems = [];
mod.fnServerData = fnServerData;
for (i=0, length = aData.length; i < length; i++) {
temp = aData[i];
aItems.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp});
}
AIRTIME.showbuilder.fnRemove(aItems);
};
oTable = tableDiv.dataTable( {
"aoColumns": [
/* checkbox */ {"mDataProp": "allowed", "sTitle": "<input type='checkbox' name='sb_cb_all'>", "sWidth": "15px"},
/* starts */{"mDataProp": "starts", "sTitle": "Start"},
/* ends */{"mDataProp": "ends", "sTitle": "End"},
/* runtime */{"mDataProp": "runtime", "sTitle": "Duration", "sClass": "library_length"},
/* title */{"mDataProp": "title", "sTitle": "Title"},
/* creator */{"mDataProp": "creator", "sTitle": "Creator"},
/* album */{"mDataProp": "album", "sTitle": "Album"},
/* cue in */{"mDataProp": "cuein", "sTitle": "Cue In", "bVisible": false},
/* cue out */{"mDataProp": "cueout", "sTitle": "Cue Out", "bVisible": false},
/* fade in */{"mDataProp": "fadein", "sTitle": "Fade In", "bVisible": false},
/* fade out */{"mDataProp": "fadeout", "sTitle": "Fade Out", "bVisible": false}
],
"bJQueryUI": true,
"bSort": false,
"bFilter": false,
"bProcessing": true,
"bServerSide": true,
"bInfo": false,
"bAutoWidth": false,
"bStateSave": true,
"fnStateSaveParams": function (oSettings, oData) {
//remove oData components we don't want to save.
delete oData.oSearch;
delete oData.aoSearchCols;
},
"fnStateSave": function (oSettings, oData) {
$.ajax({
url: "/usersettings/set-timeline-datatable",
type: "POST",
data: {settings : oData, format: "json"},
dataType: "json",
success: function(){},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
},
"fnStateLoad": function (oSettings) {
var o;
mod.builderDataTable = function() {
var tableDiv = $('#show_builder_table'),
oTable,
fnRemoveSelectedItems;
$.ajax({
url: "/usersettings/get-timeline-datatable",
type: "GET",
data: {format: "json"},
dataType: "json",
async: false,
success: function(json){
o = json.settings;
},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
return o;
},
"fnStateLoadParams": function (oSettings, oData) {
var i,
fnRemoveSelectedItems = function() {
var oTT = TableTools.fnGetInstance('show_builder_table'),
aData = oTT.fnGetSelectedData(),
i,
length,
a = oData.abVisCols;
temp,
aItems = [];
//putting serialized data back into the correct js type to make
//sure everything works properly.
for (i = 0, length = a.length; i < length; i++) {
a[i] = (a[i] === "true") ? true : false;
}
for (i=0, length = aData.length; i < length; i++) {
temp = aData[i];
aItems.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp});
}
AIRTIME.showbuilder.fnRemove(aItems);
};
oTable = tableDiv.dataTable( {
"aoColumns": [
/* checkbox */ {"mDataProp": "allowed", "sTitle": "<input type='checkbox' name='sb_cb_all'>", "sWidth": "15px"},
/* starts */{"mDataProp": "starts", "sTitle": "Start"},
/* ends */{"mDataProp": "ends", "sTitle": "End"},
/* runtime */{"mDataProp": "runtime", "sTitle": "Duration", "sClass": "library_length"},
/* title */{"mDataProp": "title", "sTitle": "Title"},
/* creator */{"mDataProp": "creator", "sTitle": "Creator"},
/* album */{"mDataProp": "album", "sTitle": "Album"},
/* cue in */{"mDataProp": "cuein", "sTitle": "Cue In", "bVisible": false},
/* cue out */{"mDataProp": "cueout", "sTitle": "Cue Out", "bVisible": false},
/* fade in */{"mDataProp": "fadein", "sTitle": "Fade In", "bVisible": false},
/* fade out */{"mDataProp": "fadeout", "sTitle": "Fade Out", "bVisible": false}
],
a = oData.ColReorder;
for (i = 0, length = a.length; i < length; i++) {
a[i] = parseInt(a[i], 10);
}
oData.iCreate = parseInt(oData.iCreate, 10);
},
"fnServerData": fnServerData,
"fnRowCallback": function ( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var i,
sSeparatorHTML,
fnPrepareSeparatorRow,
node,
cl="";
"bJQueryUI": true,
"bSort": false,
"bFilter": false,
"bProcessing": true,
"bServerSide": true,
"bInfo": false,
"bAutoWidth": false,
//save some info for reordering purposes.
$(nRow).data({"aData": aData});
"bStateSave": true,
"fnStateSaveParams": function (oSettings, oData) {
//remove oData components we don't want to save.
delete oData.oSearch;
delete oData.aoSearchCols;
},
"fnStateSave": function (oSettings, oData) {
$.ajax({
url: "/usersettings/set-timeline-datatable",
type: "POST",
data: {settings : oData, format: "json"},
dataType: "json",
success: function(){},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
},
"fnStateLoad": function (oSettings) {
var o;
$.ajax({
url: "/usersettings/get-timeline-datatable",
type: "GET",
data: {format: "json"},
dataType: "json",
async: false,
success: function(json){
o = json.settings;
},
error: function (jqXHR, textStatus, errorThrown) {
var x;
}
});
return o;
},
"fnStateLoadParams": function (oSettings, oData) {
var i,
length,
a = oData.abVisCols;
if (aData.allowed !== true) {
$(nRow).addClass("sb-not-allowed");
}
//status used to colour tracks.
if (aData.status === 1) {
$(nRow).addClass("sb-boundry");
}
else if (aData.status === 2) {
$(nRow).addClass("sb-over");
}
fnPrepareSeparatorRow = function(sRowContent, sClass, iNodeIndex) {
//putting serialized data back into the correct js type to make
//sure everything works properly.
for (i = 0, length = a.length; i < length; i++) {
a[i] = (a[i] === "true") ? true : false;
}
a = oData.ColReorder;
for (i = 0, length = a.length; i < length; i++) {
a[i] = parseInt(a[i], 10);
}
oData.iCreate = parseInt(oData.iCreate, 10);
},
"fnServerData": AIRTIME.showbuilder.fnServerData,
"fnRowCallback": function ( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var i,
sSeparatorHTML,
fnPrepareSeparatorRow,
node,
cl="";
node = nRow.children[iNodeIndex];
node.innerHTML = sRowContent;
node.setAttribute('colspan',100);
for (i = iNodeIndex + 1; i < nRow.children.length; i = i+1) {
node = nRow.children[i];
node.innerHTML = "";
node.setAttribute("style", "display : none");
//save some info for reordering purposes.
$(nRow).data({"aData": aData});
if (aData.current === true) {
$(nRow).addClass("sb-now-playing");
}
$(nRow).addClass(sClass);
if (aData.allowed !== true) {
$(nRow).addClass("sb-not-allowed");
}
else {
$(nRow).addClass("sb-allowed");
}
//status used to colour tracks.
if (aData.status === 2) {
$(nRow).addClass("sb-boundry");
}
else if (aData.status === 0) {
$(nRow).addClass("sb-over");
}
fnPrepareSeparatorRow = function(sRowContent, sClass, iNodeIndex) {
node = nRow.children[iNodeIndex];
node.innerHTML = sRowContent;
node.setAttribute('colspan',100);
for (i = iNodeIndex + 1; i < nRow.children.length; i = i+1) {
node = nRow.children[i];
node.innerHTML = "";
node.setAttribute("style", "display : none");
}
$(nRow).addClass(sClass);
};
if (aData.header === true) {
cl = 'sb-header';
sSeparatorHTML = '<span>'+aData.title+'</span><span>'+aData.starts+'</span><span>'+aData.ends+'</span>';
fnPrepareSeparatorRow(sSeparatorHTML, cl, 0);
}
else if (aData.footer === true) {
node = nRow.children[0];
cl = 'sb-footer';
//check the show's content status.
if (aData.runtime > 0) {
node.innerHTML = '<span class="ui-icon ui-icon-check"></span>';
cl = cl + ' ui-state-highlight';
}
else {
node.innerHTML = '<span class="ui-icon ui-icon-notice"></span>';
cl = cl + ' ui-state-error';
}
sSeparatorHTML = '<span>'+aData.fRuntime+'</span>';
fnPrepareSeparatorRow(sSeparatorHTML, cl, 1);
}
else if (aData.empty === true) {
sSeparatorHTML = '<span>Show Empty</span>';
cl = cl + " sb-empty odd";
fnPrepareSeparatorRow(sSeparatorHTML, cl, 0);
}
else if (aData.record === true) {
sSeparatorHTML = '<span>Recording From Line In</span>';
cl = cl + " sb-record odd";
fnPrepareSeparatorRow(sSeparatorHTML, cl, 0);
}
else {
node = nRow.children[0];
if (aData.allowed === true) {
node.innerHTML = '<input type="checkbox" name="'+aData.id+'"></input>';
}
else {
node.innerHTML = '';
}
}
},
"fnDrawCallback": function(oSettings, json) {
var wrapperDiv,
markerDiv,
td;
//create cursor arrows.
tableDiv.find("tr:not(:first, .sb-footer, .sb-empty, .sb-not-allowed)").each(function(i, el) {
td = $(el).find("td:first");
if (td.hasClass("dataTables_empty")) {
return false;
}
wrapperDiv = $("<div />", {
"class": "innerWrapper",
"css": {
"height": td.height()
}
});
markerDiv = $("<div />", {
"class": "marker"
});
td.append(markerDiv).wrapInner(wrapperDiv);
});
},
"fnHeaderCallback": function(nHead) {
$(nHead).find("input[type=checkbox]").attr("checked", false);
},
//remove any selected nodes before the draw.
"fnPreDrawCallback": function( oSettings ) {
var oTT = TableTools.fnGetInstance('show_builder_table');
oTT.fnSelectNone();
},
"oColVis": {
"aiExclude": [ 0, 1 ]
},
"oColReorder": {
"iFixedColumns": 2
},
"oTableTools": {
"sRowSelect": "multi",
"aButtons": [],
"fnPreRowSelect": function ( e ) {
var node = e.currentTarget;
//don't select separating rows, or shows without privileges.
if ($(node).hasClass("sb-header")
|| $(node).hasClass("sb-footer")
|| $(node).hasClass("sb-empty")
|| $(node).hasClass("sb-not-allowed")) {
return false;
}
return true;
},
"fnRowSelected": function ( node ) {
//seems to happen if everything is selected
if ( node === null) {
oTable.find("input[type=checkbox]").attr("checked", true);
}
else {
$(node).find("input[type=checkbox]").attr("checked", true);
}
//checking to enable buttons
AIRTIME.button.enableButton("sb_delete");
},
"fnRowDeselected": function ( node ) {
var selected;
//seems to happen if everything is deselected
if ( node === null) {
tableDiv.find("input[type=checkbox]").attr("checked", false);
selected = [];
}
else {
$(node).find("input[type=checkbox]").attr("checked", false);
selected = tableDiv.find("input[type=checkbox]").filter(":checked");
}
//checking to disable buttons
if (selected.length === 0) {
AIRTIME.button.disableButton("sb_delete");
}
}
},
// R = ColReorderResize, C = ColVis, T = TableTools
"sDom": 'Rr<"H"CT>t<"F">',
"sAjaxDataProp": "schedule",
"sAjaxSource": "/showbuilder/builder-feed"
});
$('[name="sb_cb_all"]').click(function(){
var oTT = TableTools.fnGetInstance('show_builder_table');
if ($(this).is(":checked")) {
var allowedNodes;
allowedNodes = oTable.find('tr:not(:first, .sb-header, .sb-empty, .sb-footer, .sb-not-allowed)');
allowedNodes.each(function(i, el){
oTT.fnSelect(el);
});
}
else {
oTT.fnSelectNone();
}
});
var sortableConf = (function(){
var origTrs,
aItemData = [],
oPrevData,
fnAdd,
fnMove,
fnReceive,
fnUpdate,
i,
html;
fnAdd = function() {
var aMediaIds = [],
aSchedIds = [];
for(i = 0; i < aItemData.length; i++) {
aMediaIds.push({"id": aItemData[i].id, "type": aItemData[i].ftype});
}
aSchedIds.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp});
AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds);
};
if (aData.header === true) {
cl = 'sb-header';
fnMove = function() {
var aSelect = [],
aAfter = [];
aSelect.push({"id": aItemData[0].id, "instance": aItemData[0].instance, "timestamp": aItemData[0].timestamp});
aAfter.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp});
AIRTIME.showbuilder.fnMove(aSelect, aAfter);
};
fnReceive = function(event, ui) {
var aItems = [],
oLibTT = TableTools.fnGetInstance('library_display');
aItems = oLibTT.fnGetSelectedData();
sSeparatorHTML = '<span>'+aData.title+'</span><span>'+aData.starts+'</span><span>'+aData.ends+'</span>';
fnPrepareSeparatorRow(sSeparatorHTML, cl, 0);
}
else if (aData.footer === true) {
node = nRow.children[0];
cl = 'sb-footer';
//if nothing is checked select the dragged item.
if (aItems.length === 0) {
aItems.push(ui.item.data("aData"));
}
origTrs = aItems;
html = ui.helper.html();
};
fnUpdate = function(event, ui) {
var prev = ui.item.prev();
//check the show's content status.
if (aData.runtime > 0) {
node.innerHTML = '<span class="ui-icon ui-icon-check"></span>';
cl = cl + ' ui-state-highlight';
}
else {
node.innerHTML = '<span class="ui-icon ui-icon-notice"></span>';
cl = cl + ' ui-state-error';
//can't add items outside of shows.
if (!prev.hasClass("sb-allowed")) {
alert("Cannot schedule outside a show.");
ui.item.remove();
return;
}
aItemData = [];
oPrevData = prev.data("aData");
//item was dragged in
if (origTrs !== undefined) {
sSeparatorHTML = '<span>'+aData.fRuntime+'</span>';
fnPrepareSeparatorRow(sSeparatorHTML, cl, 1);
}
else if (aData.empty === true) {
sSeparatorHTML = '<span>Show Empty</span>';
cl = cl + " sb-empty odd";
fnPrepareSeparatorRow(sSeparatorHTML, cl, 0);
}
else {
node = nRow.children[0];
if (aData.allowed === true) {
node.innerHTML = '<input type="checkbox" name="'+aData.id+'"></input>';
$("#show_builder_table tr.ui-draggable")
.empty()
.after(html);
aItemData = origTrs;
origTrs = undefined;
fnAdd();
}
//item was reordered.
else {
node.innerHTML = '';
aItemData.push(ui.item.data("aData"));
fnMove();
}
};
return {
placeholder: "placeholder show-builder-placeholder ui-state-highlight",
forcePlaceholderSize: true,
items: 'tr:not(:first, :last, .sb-header, .sb-footer, .sb-not-allowed)',
receive: fnReceive,
update: fnUpdate
};
}());
tableDiv.sortable(sortableConf);
$("#show_builder .fg-toolbar")
.append('<div class="ColVis TableTools sb_delete"><button class="ui-button ui-state-default ui-state-disabled"><span>Delete</span></button></div>')
.click(fnRemoveSelectedItems);
//set things like a reference to the table.
AIRTIME.showbuilder.init(oTable);
//add event to cursors.
tableDiv.find("tbody").on("click", "div.marker", function(event){
var tr = $(this).parents("tr"),
cursorSelClass = "cursor-selected-row";
if (tr.hasClass(cursorSelClass)) {
tr.removeClass(cursorSelClass);
}
},
"fnDrawCallback": function(oSettings, json) {
var wrapperDiv,
markerDiv,
td;
//create cursor arrows.
tableDiv.find("tr:not(:first, .sb-footer, .sb-empty, .sb-not-allowed)").each(function(i, el) {
td = $(el).find("td:first");
if (td.hasClass("dataTables_empty")) {
return false;
}
wrapperDiv = $("<div />", {
"class": "innerWrapper",
"css": {
"height": td.height()
}
});
markerDiv = $("<div />", {
"class": "marker"
});
td.append(markerDiv).wrapInner(wrapperDiv);
});
},
"fnHeaderCallback": function(nHead) {
$(nHead).find("input[type=checkbox]").attr("checked", false);
},
//remove any selected nodes before the draw.
"fnPreDrawCallback": function( oSettings ) {
var oTT = TableTools.fnGetInstance('show_builder_table');
oTT.fnSelectNone();
},
"oColVis": {
"aiExclude": [ 0, 1 ]
},
"oColReorder": {
"iFixedColumns": 2
},
"oTableTools": {
"sRowSelect": "multi",
"aButtons": [],
"fnPreRowSelect": function ( e ) {
var node = e.currentTarget;
//don't select separating rows, or shows without privileges.
if ($(node).hasClass("sb-header")
|| $(node).hasClass("sb-footer")
|| $(node).hasClass("sb-empty")
|| $(node).hasClass("sb-not-allowed")) {
return false;
}
return true;
},
"fnRowSelected": function ( node ) {
//seems to happen if everything is selected
if ( node === null) {
oTable.find("input[type=checkbox]").attr("checked", true);
}
else {
$(node).find("input[type=checkbox]").attr("checked", true);
}
},
"fnRowDeselected": function ( node ) {
//seems to happen if everything is deselected
if ( node === null) {
var oTable = $("#show_builder_table").dataTable();
oTable.find("input[type=checkbox]").attr("checked", false);
}
else {
$(node).find("input[type=checkbox]").attr("checked", false);
}
}
},
// R = ColReorderResize, C = ColVis, T = TableTools
"sDom": 'Rr<"H"CT>t<"F">',
"sAjaxDataProp": "schedule",
"sAjaxSource": "/showbuilder/builder-feed"
});
$('[name="sb_cb_all"]').click(function(){
var oTT = TableTools.fnGetInstance('show_builder_table');
if ($(this).is(":checked")) {
var allowedNodes;
allowedNodes = oTable.find('tr:not(:first, .sb-header, .sb-empty, .sb-footer, .sb-not-allowed)');
allowedNodes.each(function(i, el){
oTT.fnSelect(el);
});
}
else {
oTT.fnSelectNone();
}
});
$("#sb_date_start").datepicker(oBaseDatePickerSettings);
$("#sb_time_start").timepicker(oBaseTimePickerSettings);
$("#sb_date_end").datepicker(oBaseDatePickerSettings);
$("#sb_time_end").timepicker(oBaseTimePickerSettings);
$("#sb_submit").click(function(ev){
var fn,
oRange,
op;
oRange = fnGetScheduleRange();
fn = oTable.fnSettings().fnServerData;
fn.start = oRange.start;
fn.end = oRange.end;
op = $("div.sb-advanced-options");
if (op.is(":visible")) {
if (fn.ops === undefined) {
fn.ops = {};
}
fn.ops.showFilter = op.find("#sb_show_filter").val();
fn.ops.myShows = op.find("#sb_my_shows").is(":checked") ? 1 : 0;
}
oTable.fnDraw();
});
var sortableConf = (function(){
var origTrs,
aItemData = [],
oPrevData,
fnAdd,
fnMove,
fnReceive,
fnUpdate,
i,
html;
fnAdd = function() {
var aMediaIds = [],
aSchedIds = [],
oLibTT = TableTools.fnGetInstance('library_display');
for(i=0; i < aItemData.length; i++) {
aMediaIds.push({"id": aItemData[i].id, "type": aItemData[i].ftype});
}
aSchedIds.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp});
AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds);
};
fnMove = function() {
var aSelect = [],
aAfter = [];
aSelect.push({"id": aItemData[0].id, "instance": aItemData[0].instance, "timestamp": aItemData[0].timestamp});
aAfter.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp});
AIRTIME.showbuilder.fnMove(aSelect, aAfter);
};
fnReceive = function(event, ui) {
var selected = $('#library_display tr:not(:first) input:checked').parents('tr'),
aItems = [];
//if nothing is checked select the dragged item.
if (selected.length === 0) {
selected = ui.item;
}
selected.each(function(i, el) {
aItems.push($(el).data("aData"));
});
origTrs = aItems;
html = ui.helper.html();
};
fnUpdate = function(event, ui) {
var prev = ui.item.prev();
//can't add items outside of shows.
if (prev.hasClass("sb-footer")) {
alert("Cannot add an item outside a show.");
ui.item.remove();
return;
}
aItemData = [];
oPrevData = prev.data("aData");
//item was dragged in
if (origTrs !== undefined) {
$("#show_builder_table tr.ui-draggable")
.empty()
.after(html);
aItemData = origTrs;
origTrs = undefined;
fnAdd();
}
//item was reordered.
else {
aItemData.push(ui.item.data("aData"));
fnMove();
tr.addClass(cursorSelClass);
}
};
//check if add button can still be enabled.
AIRTIME.library.events.enableAddButtonCheck();
return false;
});
return {
placeholder: "placeholder show-builder-placeholder ui-state-highlight",
forcePlaceholderSize: true,
items: 'tr:not(:first, :last, .sb-header, .sb-footer, .sb-not-allowed)',
receive: fnReceive,
update: fnUpdate
};
}());
};
tableDiv.sortable(sortableConf);
mod.init = function(oTable) {
oSchedTable = oTable;
};
$("#show_builder .fg-toolbar")
.append('<div class="ColVis TableTools"><button class="ui-button ui-state-default"><span>Delete</span></button></div>')
.click(fnRemoveSelectedItems);
return AIRTIME;
//set things like a reference to the table.
AIRTIME.showbuilder.init(oTable);
//add event to cursors.
tableDiv.find("tbody").on("click", "div.marker", function(event){
var tr = $(this).parents("tr");
if (tr.hasClass("cursor-selected-row")) {
tr.removeClass("cursor-selected-row");
}
else {
tr.addClass("cursor-selected-row");
}
return false;
});
});
}(AIRTIME || {}));

View File

@ -0,0 +1,128 @@
$(document).ready(function(){
var oBaseDatePickerSettings,
oBaseTimePickerSettings,
oRange;
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
onSelect: function(sDate, oDatePicker) {
var oDate,
dInput;
dInput = $(this);
oDate = dInput.datepicker( "setDate", sDate );
}
};
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
showLeadingZero: false,
defaultTime: '0:00'
};
/*
* Get the schedule range start in unix timestamp form (in seconds).
* defaults to NOW if nothing is selected.
*
* @param String sDatePickerId
*
* @param String sTimePickerId
*
* @return Number iTime
*/
function fnGetTimestamp(sDatePickerId, sTimePickerId) {
var date,
time,
iTime,
iServerOffset,
iClientOffset;
if ($(sDatePickerId).val() === "") {
return 0;
}
date = $(sDatePickerId).val();
time = $(sTimePickerId).val();
date = date.split("-");
time = time.split(":");
//0 based month in js.
oDate = new Date(date[0], date[1]-1, date[2], time[0], time[1]);
iTime = oDate.getTime(); //value is in millisec.
iTime = Math.round(iTime / 1000);
iServerOffset = serverTimezoneOffset;
iClientOffset = oDate.getTimezoneOffset() * -60;//function returns minutes
//adjust for the fact the the Date object is in client time.
iTime = iTime + iClientOffset + iServerOffset;
return iTime;
}
/*
* Returns an object containing a unix timestamp in seconds for the start/end range
*
* @return Object {"start", "end", "range"}
*/
function fnGetScheduleRange() {
var iStart,
iEnd,
iRange,
DEFAULT_RANGE = 60*60*24;
iStart = fnGetTimestamp("#sb_date_start", "#sb_time_start");
iEnd = fnGetTimestamp("#sb_date_end", "#sb_time_end");
iRange = iEnd - iStart;
if (iRange === 0 || iEnd < iStart) {
iEnd = iStart + DEFAULT_RANGE;
iRange = DEFAULT_RANGE;
}
return {
start: iStart,
end: iEnd,
range: iRange
};
}
$("#sb_date_start").datepicker(oBaseDatePickerSettings);
$("#sb_time_start").timepicker(oBaseTimePickerSettings);
$("#sb_date_end").datepicker(oBaseDatePickerSettings);
$("#sb_time_end").timepicker(oBaseTimePickerSettings);
$("#sb_submit").click(function(ev){
var fn,
oRange,
op,
oTable = $('#show_builder_table').dataTable();
oRange = fnGetScheduleRange();
fn = oTable.fnSettings().fnServerData;
fn.start = oRange.start;
fn.end = oRange.end;
op = $("div.sb-advanced-options");
if (op.is(":visible")) {
if (fn.ops === undefined) {
fn.ops = {};
}
fn.ops.showFilter = op.find("#sb_show_filter").val();
fn.ops.myShows = op.find("#sb_my_shows").is(":checked") ? 1 : 0;
}
oTable.fnDraw();
});
oRange = fnGetScheduleRange();
AIRTIME.showbuilder.fnServerData.start = oRange.start;
AIRTIME.showbuilder.fnServerData.end = oRange.end;
AIRTIME.showbuilder.builderDataTable();
});

View File

@ -0,0 +1,163 @@
<?PHP
/*
The purpose of this script is to take a file from cc_files table, and insert it into
the schedule table. DB columns at the time of writing are
starts | ends | file_id | clip_length | fade_in | fade_out | cue_in | cue_out | media_item_played | instance_id
an example of data in this row is:
"9" | "2012-02-29 17:10:00" | "2012-02-29 17:15:05.037166" | 1 | "00:05:05.037166" | "00:00:00" | "00:00:00" | "00:00:00" | "00:05:05.037166" | FALSE | 5
*/
function query($conn, $query){
$result = pg_query($conn, $query);
if (!$result) {
echo "Error executing query $query.\n";
exit(1);
}
return $result;
}
function getFileFromCcFiles($conn){
$query = "SELECT * from cc_files LIMIT 1";
$result = query($conn, $query);
$file = null;
while ($row = pg_fetch_array($result)) {
$file = $row;
}
if (is_null($file)){
echo "Library is empty. Could not choose random file.";
exit(1);
}
return $file;
}
function insertIntoCcShow($conn){
/* Step 1:
* Create a show
* */
$query = "INSERT INTO cc_show (name, url, genre, description, color, background_color) VALUES ('test', '', '', '', '', '')";
echo $query.PHP_EOL;
$result = query($conn, $query);
$query = "SELECT currval('cc_show_id_seq');";
$result = pg_query($conn, $query);
if (!$result) {
echo "Error executing query $query.\n";
exit(1);
}
while ($row = pg_fetch_array($result)) {
$show_id = $row["currval"];
}
return $show_id;
}
function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $file){
/* Step 2:
* Create a show instance.
* Column values:
* starts | ends | show_id | record | rebroadcast | instance_id | file_id | time_filled | last_scheduled | modified_instance
* */
$nowDateTime = new DateTime("now", new DateTimeZone("UTC"));
$now = $nowDateTime->format("Y-m-d H:i:s");
$columns = "(starts, ends, show_id, record, rebroadcast, instance_id, file_id, time_filled, last_scheduled, modified_instance)";
$values = "('$starts', '$ends', $show_id, 0, 0, NULL, NULL, '$file[length]', '$now', 'f')";
$query = "INSERT INTO cc_show_instances $columns values $values ";
echo $query.PHP_EOL;
$result = query($conn, $query);
$query = "SELECT currval('cc_show_instances_id_seq');";
$result = pg_query($conn, $query);
if (!$result) {
echo "Error executing query $query.\n";
exit(1);
}
while ($row = pg_fetch_array($result)) {
$show_instance_id = $row["currval"];
}
return $show_instance_id;
}
/*
* id | starts | ends | file_id | clip_length| fade_in | fade_out | cue_in | cue_out | media_item_played | instance_id
* 1 | 2012-02-29 23:25:00 | 2012-02-29 23:30:05.037166 | 1 | 00:05:05.037166 | 00:00:00 | 00:00:00 | 00:00:00 | 00:05:05.037166 | f | 5
*/
function insertIntoCcSchedule($conn, $file, $show_instance_id, $starts, $ends){
$columns = "(starts, ends, file_id, clip_length, fade_in, fade_out, cue_in, cue_out, media_item_played, instance_id)";
$values = "('$starts', '$ends', $file[id], '$file[length]', '00:00:00', '00:00:00', '00:00:00', '$file[length]', 'f', $show_instance_id)";
$query = "INSERT INTO cc_schedule $columns VALUES $values";
echo $query.PHP_EOL;
$result = query($conn, $query);
}
function rabbitMqNotify(){
$ini_file = parse_ini_file("/etc/airtime/airtime.conf", true);
$url = "http://localhost/api/rabbitmq-do-push/format/json/api_key/".$ini_file["general"]["api_key"];
echo "Contacting $url".PHP_EOL;
$ch = curl_init($url);
curl_exec($ch);
curl_close($ch);
}
$conn = pg_connect("host=localhost port=5432 dbname=airtime user=airtime password=airtime");
if (!$conn) {
echo "Couldn't connect to Airtime DB.\n";
exit(1);
}
if (count($argv) > 1){
if ($argv[1] == "--clean"){
$tables = array("cc_schedule", "cc_show_instances", "cc_show");
foreach($tables as $table){
$query = "DELETE FROM $table";
echo $query.PHP_EOL;
query($conn, $query);
}
rabbitMqNotify();
exit(0);
} else {
$str = <<<EOD
This script schedules a file to play 30 seconds in the future. It
modifies the database tables cc_schedule, cc_show_instances and cc_show.
You can clean up these tables using the --clean option.
EOD;
echo $str.PHP_EOL;
exit(0);
}
}
$startDateTime = new DateTime("now + 30sec", new DateTimeZone("UTC"));
$endDateTime = new DateTime("now + 1min 30sec", new DateTimeZone("UTC"));
$starts = $startDateTime->format("Y-m-d H:i:s");
$ends = $endDateTime->format("Y-m-d H:i:s");
$file = getFileFromCcFiles($conn);
$show_id = insertIntoCcShow($conn);
$show_instance_id = insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $file);
insertIntoCcSchedule($conn, $file, $show_instance_id, $starts, $ends);
rabbitMqNotify();
echo PHP_EOL."Show scheduled for $starts (UTC)".PHP_EOL;

39
dev_tools/toggle-pypo-debug.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/bash
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root." 1>&2
exit 1
fi
usage () {
echo "Use --enable <user> or --disable flag. Enable is to set up environment"
echo "for specified user. --disable is to reset it back to pypo user"
}
if [ "$1" = "--enable" ]; then
/etc/init.d/airtime-playout stop
/etc/init.d/airtime-playout start-liquidsoap
user=$2
echo "Changing ownership to user $1"
chown -Rv $user:$user /var/log/airtime/pypo
chown -v $user:$user /etc/airtime/pypo.cfg
chown -Rv $user:$user /var/tmp/airtime/pypo/
chmod -v a+r /etc/airtime/api_client.cfg
elif [ "$1" = "--disable" ]; then
user="pypo"
echo "Changing ownership to user $1"
chown -Rv $user:$user /var/log/airtime/pypo
chown -v $user:$user /etc/airtime/pypo.cfg
chown -Rv $user:$user /var/tmp/airtime/pypo/
chmod -v a+r /etc/airtime/api_client.cfg
/etc/init.d/airtime-playout stop-liquidsoap
/etc/init.d/airtime-playout start
else
usage
fi

View File

@ -81,14 +81,6 @@ class ApiClientInterface:
def get_media(self, src, dst):
pass
# Implementation: optional
#
# Called from: push loop
#
# Tell server that the scheduled *playlist* has started.
def notify_scheduled_item_start_playing(self, pkey, schedule):
pass
# Implementation: optional
# You dont actually have to implement this function for the liquidsoap playout to work.
#
@ -261,15 +253,15 @@ class AirTimeApiClient(ApiClientInterface):
export_url = export_url.replace('%%api_key%%', self.config["api_key"])
response = ""
status = 0
try:
response_json = self.get_response_from_server(export_url)
response = json.loads(response_json)
status = response['check']
success = True
except Exception, e:
logger.error(e)
success = False
return status, response
return success, response
def get_media(self, uri, dst):
@ -285,32 +277,6 @@ class AirTimeApiClient(ApiClientInterface):
except Exception, e:
logger.error("%s", e)
"""
Tell server that the scheduled *playlist* has started.
"""
def notify_scheduled_item_start_playing(self, pkey, schedule):
logger = self.logger
playlist = schedule[pkey]
schedule_id = playlist["schedule_id"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_item_url"])
url = url.replace("%%schedule_id%%", str(schedule_id))
logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
try:
response = urllib.urlopen(url)
response = json.loads(response.read())
logger.info("API-Status %s", response['status'])
logger.info("API-Message %s", response['message'])
except Exception, e:
logger.error("Unable to connect - %s", e)
return response
"""
This is a callback from liquidsoap, we use this to notify about the
currently playing *song*. We get passed a JSON string which we handed to

View File

@ -1,8 +1,8 @@
[loggers]
keys=root,fetch,push,recorder
keys=root,fetch,push,recorder,message_h
[handlers]
keys=pypo,recorder
keys=pypo,recorder,message_h
[formatters]
keys=simpleFormatter
@ -29,6 +29,12 @@ handlers=recorder
qualname=recorder
propagate=0
[logger_message_h]
level=DEBUG
handlers=message_h
qualname=message_h
propagate=0
[handler_pypo]
class=logging.handlers.RotatingFileHandler
level=DEBUG
@ -41,6 +47,12 @@ level=DEBUG
formatter=simpleFormatter
args=("/var/log/airtime/pypo/show-recorder.log", 'a', 1000000, 5,)
[handler_message_h]
class=logging.handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=("/var/log/airtime/pypo/message-handler.log", 'a', 1000000, 5,)
[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
datefmt=

View File

@ -16,6 +16,7 @@ from Queue import Queue
from pypopush import PypoPush
from pypofetch import PypoFetch
from recorder import Recorder
from pypomessagehandler import PypoMessageHandler
from configobj import ConfigObj
@ -55,23 +56,16 @@ except Exception, e:
class Global:
def __init__(self):
self.api_client = api_client.api_client_factory(config)
self.set_export_source('scheduler')
def selfcheck(self):
self.api_client = api_client.api_client_factory(config)
return self.api_client.is_server_compatible()
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 test_api(self):
self.api_client.test()
"""
def check_schedule(self, export_source):
def check_schedule(self):
logger = logging.getLogger()
try:
@ -127,11 +121,19 @@ if __name__ == '__main__':
api_client = api_client.api_client_factory(config)
api_client.register_component("pypo")
q = Queue()
pypoFetch_q = Queue()
recorder_q = Queue()
pp = PypoPush(q)
pypoPush_q = Queue()
pmh = PypoMessageHandler(pypoFetch_q, recorder_q)
pmh.daemon = True
pmh.start()
pf = PypoFetch(pypoFetch_q, pypoPush_q)
pf.daemon = True
pf.start()
pp = PypoPush(pypoPush_q)
pp.daemon = True
pp.start()
@ -139,12 +141,11 @@ if __name__ == '__main__':
recorder.daemon = True
recorder.start()
pf = PypoFetch(q, recorder_q)
pf.daemon = True
pf.start()
#pp.join()
pmh.join()
pp.join()
pf.join()
recorder.join()
logger.info("pypo fetch exit")
sys.exit()
"""

View File

@ -34,7 +34,7 @@ import json
from configobj import ConfigObj
# custom imports
from util import *
#from util import *
from api_clients import *
# Set up command-line options

View File

@ -16,12 +16,6 @@ from datetime import datetime
from datetime import timedelta
import filecmp
# For RabbitMQ
from kombu.connection import BrokerConnection
from kombu.messaging import Exchange, Queue, Consumer, Producer
from kombu.exceptions import MessageStateError
from kombu.simple import SimpleQueue
from api_clients import api_client
from configobj import ConfigObj
@ -42,30 +36,31 @@ except Exception, e:
sys.exit()
class PypoFetch(Thread):
def __init__(self, q, recorder_q):
def __init__(self, pypoFetch_q, pypoPush_q):
Thread.__init__(self)
self.api_client = api_client.api_client_factory(config)
self.set_export_source('scheduler')
self.queue = q
self.recorder_queue = recorder_q
self.schedule_data = []
logger = logging.getLogger('fetch')
logger.info("PypoFetch: init complete")
self.fetch_queue = pypoFetch_q
self.push_queue = pypoPush_q
self.logger = logging.getLogger();
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
self.logger.debug("Cache dir %s", self.cache_dir)
def init_rabbit_mq(self):
logger = logging.getLogger('fetch')
logger.info("Initializing RabbitMQ stuff")
try:
schedule_exchange = Exchange("airtime-pypo", "direct", durable=True, auto_delete=True)
schedule_queue = Queue("pypo-fetch", exchange=schedule_exchange, key="foo")
connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], config["rabbitmq_vhost"])
channel = connection.channel()
self.simple_queue = SimpleQueue(channel, schedule_queue)
if not os.path.isdir(dir):
"""
We get here if path does not exist, or path does exist but
is a file. We are not handling the second case, but don't
think we actually care about handling it.
"""
self.logger.debug("Cache dir does not exist. Creating...")
os.makedirs(dir)
except Exception, e:
logger.error(e)
return False
return True
pass
self.schedule_data = []
self.logger.info("PypoFetch: init complete")
"""
Handle a message from RabbitMQ, put it into our yucky global var.
@ -73,55 +68,51 @@ class PypoFetch(Thread):
"""
def handle_message(self, message):
try:
logger = logging.getLogger('fetch')
logger.info("Received event from RabbitMQ: %s" % message)
self.logger.info("Received event from Pypo Message Handler: %s" % message)
m = json.loads(message)
command = m['event_type']
logger.info("Handling command: " + command)
self.logger.info("Handling command: " + command)
if command == 'update_schedule':
self.schedule_data = m['schedule']
self.process_schedule(self.schedule_data, "scheduler", False)
self.process_schedule(self.schedule_data, False)
elif command == 'update_stream_setting':
logger.info("Updating stream setting...")
self.logger.info("Updating stream setting...")
self.regenerateLiquidsoapConf(m['setting'])
elif command == 'update_stream_format':
logger.info("Updating stream format...")
self.logger.info("Updating stream format...")
self.update_liquidsoap_stream_format(m['stream_format'])
elif command == 'update_station_name':
logger.info("Updating station name...")
self.logger.info("Updating station name...")
self.update_liquidsoap_station_name(m['station_name'])
elif command == 'cancel_current_show':
logger.info("Cancel current show command received...")
self.logger.info("Cancel current show command received...")
self.stop_current_show()
elif command == 'update_recorder_schedule':
temp = m
if temp is not None:
self.parse_shows(temp)
elif command == 'cancel_recording':
self.recorder_queue.put('cancel_recording')
except Exception, e:
logger.error("Exception in handling RabbitMQ message: %s", e)
import traceback
top = traceback.format_exc()
self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top)
self.logger.error("Exception in handling Message Handler message: %s", e)
def stop_current_show(self):
logger = logging.getLogger('fetch')
logger.debug('Notifying Liquidsoap to stop playback.')
self.logger.debug('Notifying Liquidsoap to stop playback.')
try:
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
tn.write('source.skip\n')
tn.write('exit\n')
tn.read_all()
except Exception, e:
logger.debug(e)
logger.debug('Could not connect to liquidsoap')
self.logger.debug(e)
self.logger.debug('Could not connect to liquidsoap')
def regenerateLiquidsoapConf(self, setting):
logger = logging.getLogger('fetch')
existing = {}
# create a temp file
fh = open('/etc/airtime/liquidsoap.cfg', 'r')
logger.info("Reading existing config...")
self.logger.info("Reading existing config...")
# read existing conf file and build dict
while 1:
line = fh.readline()
@ -151,7 +142,7 @@ class PypoFetch(Thread):
#restart flag
restart = False
logger.info("Looking for changes...")
self.logger.info("Looking for changes...")
# look for changes
for s in setting:
if "output_sound_device" in s[u'keyname'] or "icecast_vorbis_metadata" in s[u'keyname']:
@ -159,13 +150,13 @@ class PypoFetch(Thread):
state_change_restart[stream] = False
# This is the case where restart is required no matter what
if (existing[s[u'keyname']] != s[u'value']):
logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
restart = True;
else:
stream, dump = s[u'keyname'].split('_',1)
if "_output" in s[u'keyname']:
if (existing[s[u'keyname']] != s[u'value']):
logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
restart = True;
state_change_restart[stream] = True
elif ( s[u'value'] != 'disabled'):
@ -177,22 +168,22 @@ class PypoFetch(Thread):
if stream not in change:
change[stream] = False
if not (s[u'value'] == existing[s[u'keyname']]):
logger.info("Keyname: %s, Curent value: %s, New Value: %s", s[u'keyname'], existing[s[u'keyname']], s[u'value'])
self.logger.info("Keyname: %s, Curent value: %s, New Value: %s", s[u'keyname'], existing[s[u'keyname']], s[u'value'])
change[stream] = True
# set flag change for sound_device alway True
logger.info("Change:%s, State_Change:%s...", change, state_change_restart)
self.logger.info("Change:%s, State_Change:%s...", change, state_change_restart)
for k, v in state_change_restart.items():
if k == "sound_device" and v:
restart = True
elif v and change[k]:
logger.info("'Need-to-restart' state detected for %s...", k)
self.logger.info("'Need-to-restart' state detected for %s...", k)
restart = True
# rewrite
if restart:
fh = open('/etc/airtime/liquidsoap.cfg', 'w')
logger.info("Rewriting liquidsoap.cfg...")
self.logger.info("Rewriting liquidsoap.cfg...")
fh.write("################################################\n")
fh.write("# THIS FILE IS AUTO GENERATED. DO NOT CHANGE!! #\n")
fh.write("################################################\n")
@ -214,17 +205,18 @@ class PypoFetch(Thread):
fh.close()
# restarting pypo.
# we could just restart liquidsoap but it take more time somehow.
logger.info("Restarting pypo...")
self.logger.info("Restarting pypo...")
sys.exit(0)
else:
logger.info("No change detected in setting...")
self.logger.info("No change detected in setting...")
self.update_liquidsoap_connection_status()
"""
def update_liquidsoap_connection_status(self):
"""
updates the status of liquidsoap connection to the streaming server
This fucntion updates the bootup time variable in liquidsoap script
"""
def update_liquidsoap_connection_status(self):
logger = logging.getLogger('fetch')
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
# update the boot up time of liquidsoap. Since liquidsoap is not restarting,
# we are manually adjusting the bootup time variable so the status msg will get
@ -242,7 +234,7 @@ class PypoFetch(Thread):
# streamin info is in the form of:
# eg. s1:true,2:true,3:false
streams = stream_info.split(",")
logger.info(streams)
self.logger.info(streams)
fake_time = current_time + 1
for s in streams:
@ -252,46 +244,35 @@ class PypoFetch(Thread):
if(status == "true"):
self.api_client.notify_liquidsoap_status("OK", stream_id, str(fake_time))
def set_export_source(self, export_source):
logger = logging.getLogger('fetch')
self.export_source = export_source
self.cache_dir = config["cache_dir"] + self.export_source + '/'
logger.info("Creating cache directory at %s", self.cache_dir)
def update_liquidsoap_stream_format(self, stream_format):
# Push stream metadata to liquidsoap
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
try:
logger = logging.getLogger('fetch')
logger.info(LS_HOST)
logger.info(LS_PORT)
self.logger.info(LS_HOST)
self.logger.info(LS_PORT)
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8')
logger.info(command)
self.logger.info(command)
tn.write(command)
tn.write('exit\n')
tn.read_all()
except Exception, e:
logger.error("Exception %s", e)
self.logger.error("Exception %s", e)
def update_liquidsoap_station_name(self, station_name):
# Push stream metadata to liquidsoap
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
try:
logger = logging.getLogger('fetch')
logger.info(LS_HOST)
logger.info(LS_PORT)
self.logger.info(LS_HOST)
self.logger.info(LS_PORT)
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
command = ('vars.station_name %s\n' % station_name).encode('utf-8')
logger.info(command)
self.logger.info(command)
tn.write(command)
tn.write('exit\n')
tn.read_all()
except Exception, e:
logger.error("Exception %s", e)
self.logger.error("Exception %s", e)
"""
Process the schedule
@ -301,191 +282,157 @@ class PypoFetch(Thread):
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
- runs the cleanup routine, to get rid of unused cached files
"""
def process_schedule(self, schedule_data, export_source, bootstrapping):
logger = logging.getLogger('fetch')
playlists = schedule_data["playlists"]
def process_schedule(self, schedule_data, bootstrapping):
self.logger.debug(schedule_data)
media = schedule_data["media"]
# Download all the media and put playlists in liquidsoap "annotate" format
try:
liquidsoap_playlists = self.prepare_playlists(playlists, bootstrapping)
except Exception, e: logger.error("%s", e)
media = self.prepare_media(media, bootstrapping)
except Exception, e: self.logger.error("%s", e)
# Send the data to pypo-push
scheduled_data = dict()
scheduled_data['liquidsoap_playlists'] = liquidsoap_playlists
scheduled_data['schedule'] = playlists
self.queue.put(scheduled_data)
self.logger.debug("Pushing to pypo-push: "+ str(media))
self.push_queue.put(media)
"""
TODO
# cleanup
try: self.cleanup(self.export_source)
except Exception, e: logger.error("%s", e)
def getDateTimeObj(self,time):
timeinfo = time.split(" ")
date = timeinfo[0].split("-")
time = timeinfo[1].split(":")
date = map(int, date)
time = map(int, time)
return datetime(date[0], date[1], date[2], time[0], time[1], time[2], 0, None)
try: self.cleanup()
except Exception, e: self.logger.error("%s", e)
"""
def parse_shows(self, m):
logger = logging.getLogger('fetch')
logger.info("Parsing recording show schedules...")
shows_to_record = {}
shows = m['shows']
for show in shows:
show_starts = self.getDateTimeObj(show[u'starts'])
show_end = self.getDateTimeObj(show[u'ends'])
time_delta = show_end - show_starts
shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name'], m['server_timezone']]
self.recorder_queue.put(shows_to_record)
logger.info(shows_to_record)
"""
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(self, playlists, bootstrapping):
logger = logging.getLogger('fetch')
liquidsoap_playlists = dict()
# Dont do anything if playlists is empty
if not playlists:
logger.debug("Schedule is empty.")
return liquidsoap_playlists
scheduleKeys = sorted(playlists.iterkeys())
def prepare_media(self, media, bootstrapping):
"""
Iterate through the list of media items in "media" and
download them.
"""
try:
for pkey in scheduleKeys:
logger.info("Playlist starting at %s", pkey)
playlist = playlists[pkey]
mediaKeys = sorted(media.iterkeys())
for mkey in mediaKeys:
self.logger.debug("Media item starting at %s", mkey)
media_item = media[mkey]
if bootstrapping:
self.check_for_previous_crash(media_item)
# create playlist directory
try:
os.mkdir(self.cache_dir + str(pkey))
"""
Extract year, month, date from mkey
"""
y_m_d = mkey[0:10]
download_dir = os.path.join(self.cache_dir, y_m_d)
try:
os.makedirs(os.path.join(self.cache_dir, y_m_d))
except Exception, e:
pass
fileExt = os.path.splitext(media_item['uri'])[1]
dst = os.path.join(download_dir, media_item['id']+fileExt)
except Exception, e:
logger.warning(e)
self.logger.warning(e)
if self.handle_media_file(media_item, dst):
entry = self.create_liquidsoap_annotation(media_item, dst)
media_item['show_name'] = "TODO"
media_item["annotation"] = entry
ls_playlist = self.handle_media_file(playlist, pkey, bootstrapping)
liquidsoap_playlists[pkey] = ls_playlist
except Exception, e:
logger.error("%s", e)
return liquidsoap_playlists
self.logger.error("%s", e)
return media
def create_liquidsoap_annotation(self, media, dst):
entry = \
'annotate:media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \
% (media['id'], 0, \
float(media['fade_in']) / 1000, \
float(media['fade_out']) / 1000, \
float(media['cue_in']), \
float(media['cue_out']), \
media['row_id'], dst)
"""
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, bootstrapping):
logger = logging.getLogger('fetch')
return entry
ls_playlist = []
def check_for_previous_crash(self, media_item):
start = media_item['start']
end = media_item['end']
dtnow = datetime.utcnow()
str_tnow_s = dtnow.strftime('%Y-%m-%d-%H-%M-%S')
sortedKeys = sorted(playlist['medias'].iterkeys())
for key in sortedKeys:
media = playlist['medias'][key]
logger.debug("Processing track %s", media['uri'])
if start <= str_tnow_s and str_tnow_s < end:
#song is currently playing and we just started pypo. Maybe there
#was a power outage? Let's restart playback of this song.
start_split = map(int, start.split('-'))
media_start = datetime(start_split[0], start_split[1], start_split[2], start_split[3], start_split[4], start_split[5], 0, None)
self.logger.debug("Found media item that started at %s.", media_start)
if bootstrapping:
start = media['start']
end = media['end']
if end <= str_tnow_s:
continue
elif start <= str_tnow_s and str_tnow_s < end:
#song is currently playing and we just started pypo. Maybe there
#was a power outage? Let's restart playback of this song.
start_split = map(int, start.split('-'))
media_start = datetime(start_split[0], start_split[1], start_split[2], start_split[3], start_split[4], start_split[5], 0, None)
logger.debug("Found media item that started at %s.", media_start)
delta = dtnow - media_start #we get a TimeDelta object from this operation
logger.info("Starting media item at %d second point", delta.seconds)
media['cue_in'] = delta.seconds + 10
td = timedelta(seconds=10)
playlist['start'] = (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S')
logger.info("Crash detected, setting playlist to restart at %s", (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S'))
delta = dtnow - media_start #we get a TimeDelta object from this operation
self.logger.info("Starting media item at %d second point", delta.seconds)
"""
Set the cue_in. This is used by Liquidsoap to determine at what point in the media
item it should start playing. If the cue_in happens to be > cue_out, then make cue_in = cue_out
"""
media_item['cue_in'] = delta.seconds + 10 if delta.seconds + 10 < media_item['cue_out'] else media_item['cue_out']
"""
Set the start time, which is used by pypo-push to determine when a media item is scheduled.
Pushing the start time into the future will ensure pypo-push will push this to Liquidsoap.
"""
td = timedelta(seconds=10)
media_item['start'] = (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S')
self.logger.info("Crash detected, setting playlist to restart at %s", (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S'))
def handle_media_file(self, media_item, dst):
"""
Download and cache the media item.
"""
self.logger.debug("Processing track %s", media_item['uri'])
fileExt = os.path.splitext(media['uri'])[1]
try:
dst = "%s%s/%s%s" % (self.cache_dir, pkey, media['id'], fileExt)
# download media file
self.handle_remote_file(media, dst)
if True == os.access(dst, os.R_OK):
# check filesize (avoid zero-byte files)
try: fsize = os.path.getsize(dst)
except Exception, e:
logger.error("%s", e)
fsize = 0
try:
#blocking function to download the media item
self.download_file(media_item, dst)
if os.access(dst, os.R_OK):
# check filesize (avoid zero-byte files)
try:
fsize = os.path.getsize(dst)
if fsize > 0:
pl_entry = \
'annotate:export_source="%s",media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \
% (media['export_source'], media['id'], 0, \
float(media['fade_in']) / 1000, \
float(media['fade_out']) / 1000, \
float(media['cue_in']), \
float(media['cue_out']), \
media['row_id'], dst)
return True
except Exception, e:
self.logger.error("%s", e)
fsize = 0
else:
self.logger.warning("Cannot read file %s.", dst)
"""
Tracks are only added to the playlist if they are accessible
on the file system and larger than 0 bytes.
So this can lead to playlists shorter than expectet.
(there is a hardware silence detector for this cases...)
"""
entry = dict()
entry['type'] = 'file'
entry['annotate'] = pl_entry
entry['show_name'] = playlist['show_name']
ls_playlist.append(entry)
else:
logger.warning("zero-size file - skipping %s. will not add it to playlist at %s", media['uri'], dst)
else:
logger.warning("something went wrong. file %s not available. will not add it to playlist", dst)
except Exception, e: logger.info("%s", e)
return ls_playlist
except Exception, e:
self.logger.info("%s", e)
return False
"""
Download a file from a remote server and store it in the cache.
"""
def handle_remote_file(self, media, dst):
logger = logging.getLogger('fetch')
def download_file(self, media_item, dst):
if os.path.isfile(dst):
pass
#logger.debug("file already in cache: %s", dst)
#self.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)
self.logger.debug("try to download %s", media_item['uri'])
self.api_client.get_media(media_item['uri'], dst)
"""
Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR"
and deletes them.
"""
def cleanup(self, export_source):
logger = logging.getLogger('fetch')
def cleanup(self):
offset = 3600 * int(config["cache_for"])
now = time.time()
@ -495,86 +442,42 @@ class PypoFetch(Thread):
timestamp = calendar.timegm(time.strptime(dir, "%Y-%m-%d-%H-%M-%S"))
if (now - timestamp) > offset:
try:
logger.debug('trying to remove %s - timestamp: %s', os.path.join(r, dir), timestamp)
self.logger.debug('trying to remove %s - timestamp: %s', os.path.join(r, dir), timestamp)
shutil.rmtree(os.path.join(r, dir))
except Exception, e:
logger.error("%s", e)
self.logger.error("%s", e)
pass
else:
logger.info('sucessfully removed %s', os.path.join(r, dir))
self.logger.info('sucessfully removed %s', os.path.join(r, dir))
except Exception, e:
logger.error(e)
self.logger.error(e)
def main(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, self.schedule_data = self.api_client.get_schedule()
if status == 1:
logger.info("Bootstrap schedule received: %s", self.schedule_data)
self.process_schedule(self.schedule_data, "scheduler", True)
# Bootstrap: since we are just starting up, we need to grab the
# most recent schedule. After that we can just wait for updates.
try:
temp = self.api_client.get_shows_to_record()
if temp is not None:
self.parse_shows(temp)
logger.info("Bootstrap recorder schedule received: %s", temp)
except Exception, e:
logger.error(e)
logger.info("Bootstrap complete: got initial copy of the schedule")
while not self.init_rabbit_mq():
logger.error("Error connecting to RabbitMQ Server. Trying again in few seconds")
time.sleep(5)
success, self.schedule_data = self.api_client.get_schedule()
if success:
self.logger.info("Bootstrap schedule received: %s", self.schedule_data)
self.process_schedule(self.schedule_data, True)
loops = 1
while True:
logger.info("Loop #%s", loops)
self.logger.info("Loop #%s", loops)
try:
try:
message = self.simple_queue.get(block=True)
self.handle_message(message.payload)
# ACK the message to take it off the queue
message.ack()
except MessageStateError, m:
logger.error("Message ACK error: %s", m)
message = self.fetch_queue.get(block=True, timeout=3600)
self.handle_message(message)
except Exception, e:
"""
There is a problem with the RabbitMq messenger service. Let's
log the error and get the schedule via HTTP polling
"""
logger.error("Exception, %s", e)
self.logger.error("Exception, %s", e)
status, self.schedule_data = self.api_client.get_schedule()
if status == 1:
self.process_schedule(self.schedule_data, "scheduler", False)
"""
Fetch recorder schedule
"""
try:
temp = self.api_client.get_shows_to_record()
if temp is not None:
self.parse_shows(temp)
logger.info("updated recorder schedule received: %s", temp)
except Exception, e:
logger.error(e)
success, self.schedule_data = self.api_client.get_schedule()
if success:
self.process_schedule(self.schedule_data, False)
loops += 1
"""
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):
while True:
self.main()
"""
Entry point of the thread
"""
self.main()

View File

@ -0,0 +1,120 @@
import logging
import logging.config
import sys
from configobj import ConfigObj
from threading import Thread
import time
# For RabbitMQ
from kombu.connection import BrokerConnection
from kombu.messaging import Exchange, Queue, Consumer, Producer
from kombu.exceptions import MessageStateError
from kombu.simple import SimpleQueue
import json
# configure logging
logging.config.fileConfig("logging.cfg")
# loading config file
try:
config = ConfigObj('/etc/airtime/pypo.cfg')
LS_HOST = config['ls_host']
LS_PORT = config['ls_port']
POLL_INTERVAL = int(config['poll_interval'])
except Exception, e:
logger = logging.getLogger('message_h')
logger.error('Error loading config file: %s', e)
sys.exit()
class PypoMessageHandler(Thread):
def __init__(self, pq, rq):
Thread.__init__(self)
self.logger = logging.getLogger('message_h')
self.pypo_queue = pq
self.recorder_queue = rq
def init_rabbit_mq(self):
self.logger.info("Initializing RabbitMQ stuff")
try:
schedule_exchange = Exchange("airtime-pypo", "direct", durable=True, auto_delete=True)
schedule_queue = Queue("pypo-fetch", exchange=schedule_exchange, key="foo")
connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], config["rabbitmq_vhost"])
channel = connection.channel()
self.simple_queue = SimpleQueue(channel, schedule_queue)
except Exception, e:
self.logger.error(e)
return False
return True
"""
Handle a message from RabbitMQ, put it into our yucky global var.
Hopefully there is a better way to do this.
"""
def handle_message(self, message):
try:
self.logger.info("Received event from RabbitMQ: %s" % message)
m = json.loads(message)
command = m['event_type']
self.logger.info("Handling command: " + command)
if command == 'update_schedule':
self.logger.info("Updating schdule...")
self.pypo_queue.put(message)
elif command == 'update_stream_setting':
self.logger.info("Updating stream setting...")
self.pypo_queue.put(message)
elif command == 'update_stream_format':
self.logger.info("Updating stream format...")
self.pypo_queue.put(message)
elif command == 'update_station_name':
self.logger.info("Updating station name...")
self.pypo_queue.put(message)
elif command == 'cancel_current_show':
self.logger.info("Cancel current show command received...")
self.pypo_queue.put(message)
elif command == 'update_recorder_schedule':
self.recorder_queue.put(message)
elif command == 'cancel_recording':
self.recorder_queue.put(message)
except Exception, e:
self.logger.error("Exception in handling RabbitMQ message: %s", e)
def main(self):
while not self.init_rabbit_mq():
self.logger.error("Error connecting to RabbitMQ Server. Trying again in few seconds")
time.sleep(5)
loops = 1
while True:
self.logger.info("Loop #%s", loops)
try:
message = self.simple_queue.get(block=True)
self.handle_message(message.payload)
# ACK the message to take it off the queue
message.ack()
except Exception, e:
"""
sleep 5 seconds so that we don't spin inside this
while loop and eat all the CPU
"""
time.sleep(5)
"""
There is a problem with the RabbitMq messenger service. Let's
log the error and get the schedule via HTTP polling
"""
self.logger.error("Exception, %s", e)
loops += 1
"""
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):
while True:
self.main()

View File

@ -34,166 +34,147 @@ class PypoPush(Thread):
def __init__(self, q):
Thread.__init__(self)
self.api_client = api_client.api_client_factory(config)
self.set_export_source('scheduler')
self.queue = q
self.schedule = dict()
self.playlists = dict()
self.media = dict()
self.liquidsoap_state_play = True
self.push_ahead = 10
def set_export_source(self, export_source):
self.export_source = export_source
self.cache_dir = config["cache_dir"] + self.export_source + '/'
self.schedule_tracker_file = self.cache_dir + "schedule_tracker.pickle"
self.last_end_time = 0
"""
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('push')
self.logger = logging.getLogger('push')
def push(self):
"""
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.
"""
timenow = time.time()
# get a new schedule from pypo-fetch
if not self.queue.empty():
# make sure we get the latest schedule
while 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['liquidsoap_playlists']
logger.debug('schedule %s' % json.dumps(self.schedule))
logger.debug('playlists %s' % json.dumps(self.playlists))
self.media = self.queue.get()
self.logger.debug("Received data from pypo-fetch")
self.logger.debug('media %s' % json.dumps(self.media))
schedule = self.schedule
playlists = self.playlists
media = self.media
currently_on_air = False
if schedule:
if media:
tnow = time.gmtime(timenow)
tcoming = time.gmtime(timenow + self.push_ahead)
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5])
for pkey in schedule:
plstart = schedule[pkey]['start'][0:19]
if str_tnow_s <= plstart and plstart < str_tcoming_s:
logger.debug('Preparing to push playlist scheduled at: %s', pkey)
playlist = schedule[pkey]
# We have a match, replace the current playlist and
# force liquidsoap to refresh.
if (self.push_liquidsoap(pkey, schedule, playlists) == 1):
logger.debug("Pushed to liquidsoap, updating 'played' status.")
for key in media:
media_item = media[key]
item_start = media_item['start'][0:19]
if str_tnow_s <= item_start and item_start < str_tcoming_s:
"""
If the media item starts in the next 30 seconds, push it to the queue.
"""
self.logger.debug('Preparing to push media item scheduled at: %s', key)
if self.push_to_liquidsoap(media_item):
self.logger.debug("Pushed to liquidsoap, updating 'played' status.")
currently_on_air = True
self.liquidsoap_state_play = True
# 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, schedule)
show_start = schedule[pkey]['show_start']
show_end = schedule[pkey]['show_end']
if show_start <= str_tnow_s and str_tnow_s < show_end:
currently_on_air = True
"""
If currently_on_air = False but liquidsoap_state_play = True then it means that Liquidsoap may
still be playing audio even though the show has ended ('currently_on_air = False' means no show is scheduled)
See CC-3231.
This is a temporary solution for Airtime 2.0
"""
if not currently_on_air and self.liquidsoap_state_play:
logger.debug('Notifying Liquidsoap to stop playback.')
try:
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
tn.write('source.skip\n')
tn.write('exit\n')
tn.read_all()
except Exception, e:
logger.debug(e)
logger.debug('Could not connect to liquidsoap')
self.liquidsoap_state_play = False
def push_liquidsoap(self, pkey, schedule, playlists):
logger = logging.getLogger('push')
def push_to_liquidsoap(self, media_item):
"""
This function looks at the media item, and either pushes it to the Liquidsoap
queue immediately, or if the queue is empty - waits until the start time of the
media item before pushing it.
"""
try:
playlist = playlists[pkey]
plstart = schedule[pkey]['start'][0:19]
#strptime returns struct_time in local time
#mktime takes a time_struct and returns a floating point
#gmtime Convert a time expressed in seconds since the epoch to a struct_time in UTC
#mktime: expresses the time in local time, not UTC. It returns a floating point number, for compatibility with time().
epoch_start = calendar.timegm(time.strptime(plstart, '%Y-%m-%d-%H-%M-%S'))
#Return the time as a floating point number expressed in seconds since the epoch, in UTC.
epoch_now = time.time()
logger.debug("Epoch start: %s" % epoch_start)
logger.debug("Epoch now: %s" % epoch_now)
sleep_time = epoch_start - epoch_now;
if sleep_time < 0:
sleep_time = 0
logger.debug('sleeping for %s s' % (sleep_time))
time.sleep(sleep_time)
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
#skip the currently playing song if any.
logger.debug("source.skip\n")
tn.write("source.skip\n")
# Get any extra information for liquidsoap (which will be sent back to us)
liquidsoap_data = self.api_client.get_liquidsoap_data(pkey, schedule)
#Sending schedule table row id string.
logger.debug("vars.pypo_data %s\n"%(liquidsoap_data["schedule_id"]))
tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8'))
logger.debug('Preparing to push playlist %s' % pkey)
for item in playlist:
annotate = item['annotate']
tn.write(str('queue.push %s\n' % annotate.encode('utf-8')))
show_name = item['show_name']
tn.write(str('vars.show_name %s\n' % show_name.encode('utf-8')))
tn.write("exit\n")
logger.debug(tn.read_all())
status = 1
if media_item["start"] == self.last_end_time:
"""
this media item is attached to the end of the last
track, so let's push it now so that Liquidsoap can start playing
it immediately after (and prepare crossfades if need be).
"""
telnet_to_liquidsoap(media_item)
self.last_end_time = media_item["end"]
else:
"""
this media item does not start right after a current playing track.
We need to sleep, and then wake up when this track starts.
"""
self.sleep_until_start(media_item)
self.telnet_to_liquidsoap(media_item)
self.last_end_time = media_item["end"]
except Exception, e:
logger.error('%s', e)
status = 0
return status
return False
return True
def sleep_until_start(self, media_item):
"""
The purpose of this function is to look at the difference between
"now" and when the media_item starts, and sleep for that period of time.
After waking from sleep, this function returns.
"""
mi_start = media_item['start'][0:19]
#strptime returns struct_time in local time
epoch_start = calendar.timegm(time.strptime(mi_start, '%Y-%m-%d-%H-%M-%S'))
#Return the time as a floating point number expressed in seconds since the epoch, in UTC.
epoch_now = time.time()
self.logger.debug("Epoch start: %s" % epoch_start)
self.logger.debug("Epoch now: %s" % epoch_now)
sleep_time = epoch_start - epoch_now
if sleep_time < 0:
sleep_time = 0
self.logger.debug('sleeping for %s s' % (sleep_time))
time.sleep(sleep_time)
def telnet_to_liquidsoap(self, media_item):
"""
telnets to liquidsoap and pushes the media_item to its queue. Push the
show name of every media_item as well, just to keep Liquidsoap up-to-date
about which show is playing.
"""
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
#tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8'))
annotation = media_item['annotation']
msg = 'queue.push %s\n' % annotation.encode('utf-8')
tn.write(msg)
self.logger.debug(msg)
show_name = media_item['show_name']
msg = 'vars.show_name %s\n' % show_name.encode('utf-8')
tn.write(msg)
self.logger.debug(msg)
tn.write("exit\n")
self.logger.debug(tn.read_all())
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")
self.logger.info("heartbeat")
loops = 0
try: self.push('scheduler')
try: self.push()
except Exception, e:
logger.error('Pypo Push Exception: %s', e)
self.logger.error('Pypo Push Exception: %s', e)
time.sleep(PUSH_INTERVAL)
loops += 1

View File

@ -169,26 +169,42 @@ class Recorder(Thread):
def __init__(self, q):
Thread.__init__(self)
self.logger = logging.getLogger('recorder')
self.api_client = api_client.api_client_factory(config)
self.api_client = api_client.api_client_factory(config, self.logger)
self.api_client.register_component("show-recorder")
self.sr = None
self.shows_to_record = {}
self.server_timezone = ''
self.queue = q
self.logger.info("RecorderFetch: init complete")
self.loops = 0
def handle_message(self):
if not self.queue.empty():
msg = self.queue.get()
self.logger.info("Receivied msg from Pypo Fetch: %s", msg)
if msg == 'cancel_recording':
message = self.queue.get()
msg = json.loads(message)
command = msg["event_type"]
self.logger.info("Received msg from Pypo Message Handler: %s", msg)
if command == 'cancel_recording':
if self.sr is not None and self.sr.is_recording():
self.sr.cancel_recording()
else:
self.shows_to_record = msg
self.process_recorder_schedule(msg)
self.loops = 0
if self.shows_to_record:
self.start_record()
def process_recorder_schedule(self, m):
self.logger.info("Parsing recording show schedules...")
temp_shows_to_record = {}
shows = m['shows']
for show in shows:
show_starts = getDateTimeObj(show[u'starts'])
show_end = getDateTimeObj(show[u'ends'])
time_delta = show_end - show_starts
temp_shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name'], m['server_timezone']]
self.shows_to_record = temp_shows_to_record
def get_time_till_next_show(self):
if len(self.shows_to_record) != 0:
@ -247,21 +263,43 @@ class Recorder(Thread):
def run(self):
try:
self.logger.info("Started...")
# Bootstrap: since we are just starting up, we need to grab the
# most recent schedule. After that we can just wait for updates.
try:
temp = self.api_client.get_shows_to_record()
if temp is not None:
self.process_recorder_schedule(temp)
self.logger.info("Bootstrap recorder schedule received: %s", temp)
except Exception, e:
self.logger.error(e)
self.logger.info("Bootstrap complete: got initial copy of the schedule")
recording = False
loops = 0
self.loops = 0
heartbeat_period = math.floor(30/PUSH_INTERVAL)
while True:
if loops % heartbeat_period == 0:
if self.loops % heartbeat_period == 0:
self.logger.info("heartbeat")
loops = 0
if self.loops * PUSH_INTERVAL > 3600:
self.loops = 0
"""
Fetch recorder schedule
"""
try:
temp = self.api_client.get_shows_to_record()
if temp is not None:
self.process_recorder_schedule(temp)
self.logger.info("updated recorder schedule received: %s", temp)
except Exception, e:
self.logger.error(e)
try: self.handle_message()
except Exception, e:
self.logger.error('Pypo Recorder Exception: %s', e)
time.sleep(PUSH_INTERVAL)
loops += 1
self.loops += 1
except Exception,e :
import traceback
top = traceback.format_exc()