diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 2ea6beb89..fbed8005d 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -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(); + } } diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index dd16c377c..64d411086 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -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']); diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index b40de9d35..efa4fb8b4 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -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'); diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index 86678e8ba..a97471759 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -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"); diff --git a/airtime_mvc/application/controllers/ShowbuilderController.php b/airtime_mvc/application/controllers/ShowbuilderController.php index ef8177f7c..1a9082988 100644 --- a/airtime_mvc/application/controllers/ShowbuilderController.php +++ b/airtime_mvc/application/controllers/ShowbuilderController.php @@ -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(); diff --git a/airtime_mvc/application/forms/customfilters/ImageSize.php b/airtime_mvc/application/forms/customfilters/ImageSize.php index 69db3cb82..17c40228e 100644 --- a/airtime_mvc/application/forms/customfilters/ImageSize.php +++ b/airtime_mvc/application/forms/customfilters/ImageSize.php @@ -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); } diff --git a/airtime_mvc/application/models/RabbitMq.php b/airtime_mvc/application/models/RabbitMq.php index 4d0e501a5..a01d5c9a9 100644 --- a/airtime_mvc/application/models/RabbitMq.php +++ b/airtime_mvc/application/models/RabbitMq.php @@ -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); diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 367278bc7..21306d43d 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -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"]) ); diff --git a/airtime_mvc/application/models/Scheduler.php b/airtime_mvc/application/models/Scheduler.php index 3df43b5ce..1c77774c9 100644 --- a/airtime_mvc/application/models/Scheduler.php +++ b/airtime_mvc/application/models/Scheduler.php @@ -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) diff --git a/airtime_mvc/application/models/Show.php b/airtime_mvc/application/models/Show.php index ec4cdbcfe..6fa4fd132 100644 --- a/airtime_mvc/application/models/Show.php +++ b/airtime_mvc/application/models/Show.php @@ -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; } diff --git a/airtime_mvc/application/models/ShowBuilder.php b/airtime_mvc/application/models/ShowBuilder.php index e3804e17f..8762a1638 100644 --- a/airtime_mvc/application/models/ShowBuilder.php +++ b/airtime_mvc/application/models/ShowBuilder.php @@ -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; } diff --git a/airtime_mvc/application/models/ShowInstance.php b/airtime_mvc/application/models/ShowInstance.php index 8088b3a04..176bf9135 100644 --- a/airtime_mvc/application/models/ShowInstance.php +++ b/airtime_mvc/application/models/ShowInstance.php @@ -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() diff --git a/airtime_mvc/application/models/airtime/CcPlaylistcontents.php b/airtime_mvc/application/models/airtime/CcPlaylistcontents.php index e533d9ada..441207585 100644 --- a/airtime_mvc/application/models/airtime/CcPlaylistcontents.php +++ b/airtime_mvc/application/models/airtime/CcPlaylistcontents.php @@ -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 diff --git a/airtime_mvc/application/models/airtime/CcSchedule.php b/airtime_mvc/application/models/airtime/CcSchedule.php index ff222d7a3..0a15b7a3c 100644 --- a/airtime_mvc/application/models/airtime/CcSchedule.php +++ b/airtime_mvc/application/models/airtime/CcSchedule.php @@ -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. * diff --git a/airtime_mvc/application/models/airtime/CcShowInstances.php b/airtime_mvc/application/models/airtime/CcShowInstances.php index d1c97f437..ecd34e10e 100644 --- a/airtime_mvc/application/models/airtime/CcShowInstances.php +++ b/airtime_mvc/application/models/airtime/CcShowInstances.php @@ -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 diff --git a/airtime_mvc/application/models/airtime/map/CcPlaylistcontentsTableMap.php b/airtime_mvc/application/models/airtime/map/CcPlaylistcontentsTableMap.php index d654fb799..c8a01608a 100644 --- a/airtime_mvc/application/models/airtime/map/CcPlaylistcontentsTableMap.php +++ b/airtime_mvc/application/models/airtime/map/CcPlaylistcontentsTableMap.php @@ -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 diff --git a/airtime_mvc/application/models/airtime/map/CcScheduleTableMap.php b/airtime_mvc/application/models/airtime/map/CcScheduleTableMap.php index ebbe397c8..e2a988421 100644 --- a/airtime_mvc/application/models/airtime/map/CcScheduleTableMap.php +++ b/airtime_mvc/application/models/airtime/map/CcScheduleTableMap.php @@ -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() diff --git a/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontents.php b/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontents.php index 457a9b958..8f823a25e 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontents.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontents.php @@ -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() diff --git a/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontentsQuery.php b/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontentsQuery.php index 85262c7bd..8769dfe60 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontentsQuery.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcPlaylistcontentsQuery.php @@ -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); diff --git a/airtime_mvc/application/models/airtime/om/BaseCcSchedule.php b/airtime_mvc/application/models/airtime/om/BaseCcSchedule.php index 751babc2d..977298da2 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcSchedule.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcSchedule.php @@ -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(); diff --git a/airtime_mvc/application/models/airtime/om/BaseCcSchedulePeer.php b/airtime_mvc/application/models/airtime/om/BaseCcSchedulePeer.php index e11d284a7..2aa1199f7 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcSchedulePeer.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcSchedulePeer.php @@ -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'); } } diff --git a/airtime_mvc/application/models/airtime/om/BaseCcScheduleQuery.php b/airtime_mvc/application/models/airtime/om/BaseCcScheduleQuery.php index 4c661ad56..dc26a7311 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcScheduleQuery.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcScheduleQuery.php @@ -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 * diff --git a/airtime_mvc/application/models/formatters/TimeFilledFormatter.php b/airtime_mvc/application/models/formatters/TimeFilledFormatter.php new file mode 100644 index 000000000..6a894d194 --- /dev/null +++ b/airtime_mvc/application/models/formatters/TimeFilledFormatter.php @@ -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; + } +} \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/library/contents.phtml b/airtime_mvc/application/views/scripts/library/contents.phtml deleted file mode 100644 index e69de29bb..000000000 diff --git a/airtime_mvc/application/views/scripts/library/context-menu.phtml b/airtime_mvc/application/views/scripts/library/context-menu.phtml deleted file mode 100644 index e69de29bb..000000000 diff --git a/airtime_mvc/application/views/scripts/library/delete.phtml b/airtime_mvc/application/views/scripts/library/delete.phtml deleted file mode 100644 index 5cbb9a545..000000000 --- a/airtime_mvc/application/views/scripts/library/delete.phtml +++ /dev/null @@ -1 +0,0 @@ -<br /><br /><center>View script for controller <b>Library</b> and script/action name <b>delete</b></center> \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/library/libraryTablePartial.phtml b/airtime_mvc/application/views/scripts/library/libraryTablePartial.phtml deleted file mode 100644 index 52074462d..000000000 --- a/airtime_mvc/application/views/scripts/library/libraryTablePartial.phtml +++ /dev/null @@ -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> diff --git a/airtime_mvc/application/views/scripts/library/search.phtml b/airtime_mvc/application/views/scripts/library/search.phtml deleted file mode 100644 index 5fb9621a7..000000000 --- a/airtime_mvc/application/views/scripts/library/search.phtml +++ /dev/null @@ -1 +0,0 @@ -<br /><br /><center>View script for controller <b>Library</b> and script/action name <b>search</b></center> \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/library/update.phtml b/airtime_mvc/application/views/scripts/library/update.phtml deleted file mode 100644 index 52eb9608b..000000000 --- a/airtime_mvc/application/views/scripts/library/update.phtml +++ /dev/null @@ -1,3 +0,0 @@ -<?php - - echo $this->partialLoop('library/libraryTablePartial.phtml', $this->files); diff --git a/airtime_mvc/application/views/scripts/playlist/update.phtml b/airtime_mvc/application/views/scripts/playlist/update.phtml index bd55a9f94..24ddd0882 100644 --- a/airtime_mvc/application/views/scripts/playlist/update.phtml +++ b/airtime_mvc/application/views/scripts/playlist/update.phtml @@ -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> diff --git a/airtime_mvc/application/views/scripts/showbuilder/builderDialog.phtml b/airtime_mvc/application/views/scripts/showbuilder/builderDialog.phtml new file mode 100644 index 000000000..8c8bee2b5 --- /dev/null +++ b/airtime_mvc/application/views/scripts/showbuilder/builderDialog.phtml @@ -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> \ No newline at end of file diff --git a/airtime_mvc/build/schema.xml b/airtime_mvc/build/schema.xml index 85aca369f..3060cbd19 100644 --- a/airtime_mvc/build/schema.xml +++ b/airtime_mvc/build/schema.xml @@ -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. \ diff --git a/airtime_mvc/build/sql/schema.sql b/airtime_mvc/build/sql/schema.sql index 455ccbf5c..cc85b26f0 100644 --- a/airtime_mvc/build/sql/schema.sql +++ b/airtime_mvc/build/sql/schema.sql @@ -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") ); diff --git a/airtime_mvc/public/css/datatables/css/ColVis.css b/airtime_mvc/public/css/datatables/css/ColVis.css index 95987b876..f4be403c1 100644 --- a/airtime_mvc/public/css/datatables/css/ColVis.css +++ b/airtime_mvc/public/css/datatables/css/ColVis.css @@ -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 { diff --git a/airtime_mvc/public/css/media_library.css b/airtime_mvc/public/css/media_library.css index 20b60b4bc..97de2e91c 100644 --- a/airtime_mvc/public/css/media_library.css +++ b/airtime_mvc/public/css/media_library.css @@ -79,3 +79,8 @@ .library_year { text-align: center; } + +.library_sr, +.library_bitrate { + text-align: right; +} diff --git a/airtime_mvc/public/css/playlist_builder.css b/airtime_mvc/public/css/playlist_builder.css index 30947f291..cef0a7eba 100644 --- a/airtime_mvc/public/css/playlist_builder.css +++ b/airtime_mvc/public/css/playlist_builder.css @@ -457,7 +457,5 @@ div.helper li { } li.spl_empty { - text-align: center; height: 56px; - border:2px dashed black; } \ No newline at end of file diff --git a/airtime_mvc/public/css/showbuilder.css b/airtime_mvc/public/css/showbuilder.css index 9854c67b4..1afa1e7b4 100644 --- a/airtime_mvc/public/css/showbuilder.css +++ b/airtime_mvc/public/css/showbuilder.css @@ -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; } \ No newline at end of file diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index 9845cd260..5215c675e 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -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; diff --git a/airtime_mvc/public/js/airtime/buttons/buttons.js b/airtime_mvc/public/js/airtime/buttons/buttons.js new file mode 100644 index 000000000..e11814aae --- /dev/null +++ b/airtime_mvc/public/js/airtime/buttons/buttons.js @@ -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 || {})); \ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/library/events/library_playlistbuilder.js b/airtime_mvc/public/js/airtime/library/events/library_playlistbuilder.js index 676bac86c..0178d5688 100644 --- a/airtime_mvc/public/js/airtime/library/events/library_playlistbuilder.js +++ b/airtime_mvc/public/js/airtime/library/events/library_playlistbuilder.js @@ -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); }; diff --git a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js index 822b2f82f..c18d36b9e 100644 --- a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js +++ b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js @@ -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); }; diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 626de54c0..42aa267ef 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -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 - }; - } - }); -}); +} \ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/library/main_library.js b/airtime_mvc/public/js/airtime/library/main_library.js new file mode 100644 index 000000000..d50811e7e --- /dev/null +++ b/airtime_mvc/public/js/airtime/library/main_library.js @@ -0,0 +1 @@ +$(document).ready(AIRTIME.library.libraryInit); \ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 4ca90ee9a..e90aeaa33 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -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(); }; diff --git a/airtime_mvc/public/js/airtime/schedule/schedule.js b/airtime_mvc/public/js/airtime/schedule/schedule.js index 8cd072b2c..e93be7df3 100644 --- a/airtime_mvc/public/js/airtime/schedule/schedule.js +++ b/airtime_mvc/public/js/airtime/schedule/schedule.js @@ -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; } diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index 084fda9ff..f64d0914e 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -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 || {})); \ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/showbuilder/main_builder.js b/airtime_mvc/public/js/airtime/showbuilder/main_builder.js new file mode 100644 index 000000000..e1cc1f1c0 --- /dev/null +++ b/airtime_mvc/public/js/airtime/showbuilder/main_builder.js @@ -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(); +}); \ No newline at end of file diff --git a/dev_tools/auto_schedule_show.php b/dev_tools/auto_schedule_show.php new file mode 100644 index 000000000..dee0aa7d1 --- /dev/null +++ b/dev_tools/auto_schedule_show.php @@ -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; diff --git a/dev_tools/toggle-pypo-debug.sh b/dev_tools/toggle-pypo-debug.sh new file mode 100755 index 000000000..982c7435c --- /dev/null +++ b/dev_tools/toggle-pypo-debug.sh @@ -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 diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index c0b64ea01..1148dcdc1 100755 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -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 diff --git a/python_apps/pypo/logging.cfg b/python_apps/pypo/logging.cfg index acff7007d..6dae7d9c5 100644 --- a/python_apps/pypo/logging.cfg +++ b/python_apps/pypo/logging.cfg @@ -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= diff --git a/python_apps/pypo/pypo-cli.py b/python_apps/pypo/pypo-cli.py index d82d0b782..300f57f9c 100644 --- a/python_apps/pypo/pypo-cli.py +++ b/python_apps/pypo/pypo-cli.py @@ -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() """ diff --git a/python_apps/pypo/pypo-notify.py b/python_apps/pypo/pypo-notify.py index 42fb523fc..ef480691f 100644 --- a/python_apps/pypo/pypo-notify.py +++ b/python_apps/pypo/pypo-notify.py @@ -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 diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 99c742d15..fbdf1ea2a 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -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() diff --git a/python_apps/pypo/pypomessagehandler.py b/python_apps/pypo/pypomessagehandler.py new file mode 100644 index 000000000..75b0407ea --- /dev/null +++ b/python_apps/pypo/pypomessagehandler.py @@ -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() + diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 24f48c7cb..db153cd36 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -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 diff --git a/python_apps/pypo/recorder.py b/python_apps/pypo/recorder.py index 6347c4f76..89a95b001 100644 --- a/python_apps/pypo/recorder.py +++ b/python_apps/pypo/recorder.py @@ -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()