diff --git a/airtime_mvc/application/controllers/AudiopreviewController.php b/airtime_mvc/application/controllers/AudiopreviewController.php index e094a7450..302e8c545 100644 --- a/airtime_mvc/application/controllers/AudiopreviewController.php +++ b/airtime_mvc/application/controllers/AudiopreviewController.php @@ -60,8 +60,10 @@ class AudiopreviewController extends Zend_Controller_Action $this->view->uri = $uri; $this->view->mime = $mime; $this->view->audioFileID = $audioFileID; - $this->view->audioFileArtist = $audioFileArtist; - $this->view->audioFileTitle = $audioFileTitle; + // We need to decode artist and title because it gets + // encoded twice in js + $this->view->audioFileArtist = urldecode($audioFileArtist); + $this->view->audioFileTitle = urldecode($audioFileTitle); $this->view->type = $type; $this->_helper->viewRenderer->setRender('audio-preview'); diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index 2974d85a1..2d0177dbd 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -412,7 +412,7 @@ class LibraryController extends Zend_Controller_Action $formValues = $this->_getParam('data', null); $formdata = array(); foreach ($formValues as $val) { - $formdata[$val["name"]] = $val["value"]; + $formdata[$val["name"]] = htmlspecialchars($val["value"]); } $file->setDbColMetadata($formdata); diff --git a/airtime_mvc/application/controllers/LocaleController.php b/airtime_mvc/application/controllers/LocaleController.php index d689417be..efdecff80 100644 --- a/airtime_mvc/application/controllers/LocaleController.php +++ b/airtime_mvc/application/controllers/LocaleController.php @@ -4,10 +4,6 @@ class LocaleController extends Zend_Controller_Action { public function init() { - $ajaxContext = $this->_helper->getHelper("AjaxContext"); - $ajaxContext->addActionContext("general-translation-table", "json") - ->addActionContext("datatables-translation-table", "json") - ->initContext(); } public function datatablesTranslationTableAction() @@ -26,7 +22,7 @@ class LocaleController extends Zend_Controller_Action $locale.".txt") ); } - + public function generalTranslationTableAction() { $translations = array ( diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index af78f996a..33a021aa4 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -274,6 +274,7 @@ class PreferenceController extends Zend_Controller_Action Application_Model_Preference::setReplayGainModifier($values["replayGainModifier"]); $md = array('schedule' => Application_Model_Schedule::getSchedule()); Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md); + //Application_Model_RabbitMq::PushSchedule(); } if (!Application_Model_Preference::GetMasterDjConnectionUrlOverride()) { diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index 1a22506b4..3ce212513 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -9,6 +9,7 @@ class ScheduleController extends Zend_Controller_Action { $ajaxContext = $this->_helper->getHelper('AjaxContext'); $ajaxContext->addActionContext('event-feed', 'json') + ->addActionContext('event-feed-preload', 'json') ->addActionContext('make-context-menu', 'json') ->addActionContext('add-show-dialog', 'json') ->addActionContext('add-show', 'json') @@ -89,15 +90,23 @@ class ScheduleController extends Zend_Controller_Action $this->view->headLink()->appendStylesheet($baseUrl.'css/showbuilder.css?'.$CC_CONFIG['airtime_version']); //End Show builder JS/CSS requirements + Application_Model_Schedule::createNewFormSections($this->view); - $user = Application_Model_User::getCurrentUser(); - if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) { $this->view->preloadShowForm = true; } - $this->view->headScript()->appendScript("var weekStart = ".Application_Model_Preference::GetWeekStartDay().";"); + $this->view->headScript()->appendScript( + "var calendarPref = {};\n". + "calendarPref.weekStart = ".Application_Model_Preference::GetWeekStartDay().";\n". + "calendarPref.timestamp = ".time().";\n". + "calendarPref.timezoneOffset = ".date("Z").";\n". + "calendarPref.timeScale = '".Application_Model_Preference::GetCalendarTimeScale()."';\n". + "calendarPref.timeInterval = ".Application_Model_Preference::GetCalendarTimeInterval().";\n". + "calendarPref.weekStartDay = ".Application_Model_Preference::GetWeekStartDay().";\n". + "var calendarEvents = null;" + ); } public function eventFeedAction() @@ -109,10 +118,28 @@ 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))) { - $editable = true; + $editable = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)); + + $events = &Application_Model_Show::getFullCalendarEvents($start, $end, $editable); + $this->view->events = $events; + } + + public function eventFeedPreloadAction() + { + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + $user = new Application_Model_User($userInfo->id); + $editable = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)); + + $calendar_interval = Application_Model_Preference::GetCalendarTimeScale(); + Logging::info($calendar_interval); + if ($calendar_interval == "agendaDay") { + list($start, $end) = Application_Model_Show::getStartEndCurrentDayView(); + } else if ($calendar_interval == "agendaWeek") { + list($start, $end) = Application_Model_Show::getStartEndCurrentWeekView(); + } else if ($calendar_interval == "month") { + list($start, $end) = Application_Model_Show::getStartEndCurrentMonthView(); } else { - $editable = false; + Logging::error("Invalid Calendar Interval '$calendar_interval'"); } $events = &Application_Model_Show::getFullCalendarEvents($start, $end, $editable); diff --git a/airtime_mvc/application/controllers/plugins/Acl_plugin.php b/airtime_mvc/application/controllers/plugins/Acl_plugin.php index 4cadba9db..44555e533 100644 --- a/airtime_mvc/application/controllers/plugins/Acl_plugin.php +++ b/airtime_mvc/application/controllers/plugins/Acl_plugin.php @@ -110,7 +110,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract { $controller = strtolower($request->getControllerName()); - if (in_array($controller, array("api", "auth"))) { + if (in_array($controller, array("api", "auth", "locale"))) { $this->setRoleName("G"); } elseif (!Zend_Auth::getInstance()->hasIdentity()) { diff --git a/airtime_mvc/application/forms/RegisterAirtime.php b/airtime_mvc/application/forms/RegisterAirtime.php index d1557612a..2da7083c8 100644 --- a/airtime_mvc/application/forms/RegisterAirtime.php +++ b/airtime_mvc/application/forms/RegisterAirtime.php @@ -7,7 +7,7 @@ class Application_Form_RegisterAirtime extends Zend_Form public function init() { - $this->setAction(Application_Common_OsPath::getBaseDir().'/Showbuilder'); + $this->setAction(Application_Common_OsPath::getBaseDir().'Showbuilder'); $this->setMethod('post'); $country_list = Application_Model_Preference::GetCountryList(); diff --git a/airtime_mvc/application/layouts/scripts/layout.phtml b/airtime_mvc/application/layouts/scripts/layout.phtml index 29d04c0d6..dedda7c88 100644 --- a/airtime_mvc/application/layouts/scripts/layout.phtml +++ b/airtime_mvc/application/layouts/scripts/layout.phtml @@ -24,7 +24,7 @@
diff --git a/airtime_mvc/application/logging/Logging.php b/airtime_mvc/application/logging/Logging.php index dfe7e1fbe..65a8f6dc2 100644 --- a/airtime_mvc/application/logging/Logging.php +++ b/airtime_mvc/application/logging/Logging.php @@ -32,6 +32,8 @@ class Logging { { if (is_array($p_msg) || is_object($p_msg)) { return print_r($p_msg, true); + } else if (is_bool($p_msg)) { + return $p_msg ? "true" : "false"; } else { return $p_msg; } diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index ca31a4378..1aac2566e 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -731,6 +731,10 @@ SQL; 'replay_gain' => $replay_gain, 'independent_event' => $independent_event, ); + + if ($schedule_item['cue_in'] > $schedule_item['cue_out']) { + $schedule_item['cue_in'] = $schedule_item['cue_out']; + } self::appendScheduleItem($data, $start, $schedule_item); } @@ -941,7 +945,6 @@ SQL; self::createScheduledEvents($data, $range_start, $range_end); self::foldData($data["media"]); - return $data; } diff --git a/airtime_mvc/application/models/Scheduler.php b/airtime_mvc/application/models/Scheduler.php index 4aa14bfff..2f62af6aa 100644 --- a/airtime_mvc/application/models/Scheduler.php +++ b/airtime_mvc/application/models/Scheduler.php @@ -136,13 +136,17 @@ class Application_Model_Scheduler if ($type === "audioclip") { $file = CcFilesQuery::create()->findPK($id, $this->con); + $storedFile = new Application_Model_StoredFile($file->getDbId()); if (is_null($file) || !$file->visible()) { throw new Exception(_("A selected File does not exist!")); } else { $data = $this->fileInfo; $data["id"] = $id; - $data["cliplength"] = $file->getDbLength(); + $data["cliplength"] = $storedFile->getRealClipLength( + $file->getDbCuein(), + $file->getDbCueout()); + $data["cuein"] = $file->getDbCuein(); $data["cueout"] = $file->getDbCueout(); @@ -438,7 +442,6 @@ class Application_Model_Scheduler } foreach ($schedFiles as $file) { - $endTimeDT = $this->findEndTime($nextStartDT, $file['cliplength']); //item existed previously and is being moved. diff --git a/airtime_mvc/application/models/Show.php b/airtime_mvc/application/models/Show.php index 20e6e3e23..933c2c841 100644 --- a/airtime_mvc/application/models/Show.php +++ b/airtime_mvc/application/models/Show.php @@ -1750,14 +1750,15 @@ SQL; $interval = $p_start->diff($p_end); $days = $interval->format('%a'); $shows = Application_Model_Show::getShows($p_start, $p_end); - $nowEpoch = time(); $content_count = Application_Model_ShowInstance::getContentCount( $p_start, $p_end); $isFull = Application_Model_ShowInstance::getIsFull($p_start, $p_end); $timezone = date_default_timezone_get(); + $current_timezone = new DateTimeZone($timezone); $utc = new DateTimeZone("UTC"); + $now = new DateTime("now", $utc); - foreach ($shows as $show) { + foreach ($shows as &$show) { $options = array(); //only bother calculating percent for week or day view. @@ -1767,7 +1768,6 @@ SQL; if (isset($show["parent_starts"])) { $parentStartsDT = new DateTime($show["parent_starts"], $utc); - $parentStartsEpoch = intval($parentStartsDT->format("U")); } $startsDT = DateTime::createFromFormat("Y-m-d G:i:s", @@ -1775,35 +1775,53 @@ SQL; $endsDT = DateTime::createFromFormat("Y-m-d G:i:s", $show["ends"], $utc); - $startsEpochStr = $startsDT->format("U"); - $endsEpochStr = $endsDT->format("U"); - - $startsEpoch = intval($startsEpochStr); - $endsEpoch = intval($endsEpochStr); - - $startsDT->setTimezone(new DateTimeZone($timezone)); - $endsDT->setTimezone(new DateTimeZone($timezone)); - if( $p_editable ) { - if ($show["record"] && $nowEpoch > $startsEpoch) { + if ($show["record"] && $now > $startsDT) { $options["editable"] = false; } elseif ($show["rebroadcast"] && - $nowEpoch > $parentStartsEpoch) { + $now > $parentStartsDT) { $options["editable"] = false; - } elseif ($nowEpoch < $endsEpoch) { + } elseif ($now < $endsDT) { $options["editable"] = true; } } + $startsDT->setTimezone($current_timezone); + $endsDT->setTimezone($current_timezone); + $options["show_empty"] = (array_key_exists($show['instance_id'], $content_count)) ? 0 : 1; $options["show_partial_filled"] = !$isFull[$show['instance_id']]; - $events[] = &self::makeFullCalendarEvent($show, $options, - $startsDT, $endsDT, $startsEpochStr, $endsEpochStr); - } + $event = array(); + $event["id"] = intval($show["instance_id"]); + $event["title"] = $show["name"]; + $event["start"] = $startsDT->format("Y-m-d H:i:s"); + $event["end"] = $endsDT->format("Y-m-d H:i:s"); + $event["allDay"] = false; + $event["showId"] = intval($show["show_id"]); + $event["record"] = intval($show["record"]); + $event["rebroadcast"] = intval($show["rebroadcast"]); + $event["soundcloud_id"] = is_null($show["soundcloud_id"]) + ? -1 : $show["soundcloud_id"]; + + //event colouring + if ($show["color"] != "") { + $event["textColor"] = "#".$show["color"]; + } + + if ($show["background_color"] != "") { + $event["color"] = "#".$show["background_color"]; + } + + foreach ($options as $key => $value) { + $event[$key] = $value; + } + + $events[] = $event; + } return $events; } @@ -1820,7 +1838,7 @@ SQL; return $percent; } - private static function &makeFullCalendarEvent(&$show, $options=array(), $startDateTime, $endDateTime, $startsEpoch, $endsEpoch) +/* private static function &makeFullCalendarEvent(&$show, $options=array(), $startDateTime, $endDateTime, $startsEpoch, $endsEpoch) { $event = array(); @@ -1851,7 +1869,7 @@ SQL; } return $event; - } + }*/ /* Takes in a UTC DateTime object. * Converts this to local time, since cc_show days @@ -2158,4 +2176,42 @@ SQL; } return $assocArray; } + + public static function getStartEndCurrentMonthView() { + $first_day_of_calendar_month_view = mktime(0, 0, 0, date("n"), 1); + $weekStart = Application_Model_Preference::GetWeekStartDay(); + while (date('w', $first_day_of_calendar_month_view) != $weekStart) { + $first_day_of_calendar_month_view -= 60*60*24; + } + $last_day_of_calendar_view = $first_day_of_calendar_month_view + 3600*24*42; + + $start = new DateTime("@".$first_day_of_calendar_month_view); + $end = new DateTime("@".$last_day_of_calendar_view); + + return array($start, $end); + } + + public static function getStartEndCurrentWeekView() { + $first_day_of_calendar_week_view = mktime(0, 0, 0, date("n"), date("j")); + $weekStart = Application_Model_Preference::GetWeekStartDay(); + while (date('w', $first_day_of_calendar_week_view) != $weekStart) { + $first_day_of_calendar_week_view -= 60*60*24; + } + $last_day_of_calendar_view = $first_day_of_calendar_week_view + 3600*24*7; + + $start = new DateTime("@".$first_day_of_calendar_week_view); + $end = new DateTime("@".$last_day_of_calendar_view); + + return array($start, $end); + } + + public static function getStartEndCurrentDayView() { + $today = mktime(0, 0, 0, date("n"), date("j")); + $tomorrow = $today + 3600*24; + + $start = new DateTime("@".$today); + $end = new DateTime("@".$tomorrow); + + return array($start, $end); + } } diff --git a/airtime_mvc/application/models/ShowBuilder.php b/airtime_mvc/application/models/ShowBuilder.php index a1ef7c588..72a5f6ab0 100644 --- a/airtime_mvc/application/models/ShowBuilder.php +++ b/airtime_mvc/application/models/ShowBuilder.php @@ -227,7 +227,7 @@ class Application_Model_ShowBuilder $row["endDate"] = $showEndDT->format("Y-m-d"); $row["endTime"] = $showEndDT->format("H:i"); $row["duration"] = floatval($showEndDT->format("U.u")) - floatval($showStartDT->format("U.u")); - $row["title"] = $p_item["show_name"]; + $row["title"] = htmlspecialchars($p_item["show_name"]); $row["instance"] = intval($p_item["si_id"]); $row["image"] = ''; diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 98b1bfe6c..403ba5d53 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -1310,6 +1310,14 @@ SQL; return $updateIsScheduled; } + + public function getRealClipLength($p_cuein, $p_cueout) { + $sql = "SELECT :cueout::INTERVAL - :cuein::INTERVAL"; + + return Application_Common_Database::prepareAndExecute($sql, array( + ':cueout' => $p_cueout, + ':cuein' => $p_cuein), 'column'); + } } class DeleteScheduledFileException extends Exception {} diff --git a/airtime_mvc/application/models/Systemstatus.php b/airtime_mvc/application/models/Systemstatus.php index ef694ccf2..6f6a05fff 100644 --- a/airtime_mvc/application/models/Systemstatus.php +++ b/airtime_mvc/application/models/Systemstatus.php @@ -214,35 +214,22 @@ class Application_Model_Systemstatus { $partions = array(); - if (isset($_SERVER['AIRTIME_SRV'])) { - //connect to DB and find how much total space user has allocated. - $totalSpace = Application_Model_Preference::GetDiskQuota(); + /* First lets get all the watched directories. Then we can group them + * into the same partitions by comparing the partition sizes. */ + $musicDirs = Application_Model_MusicDir::getWatchedDirs(); + $musicDirs[] = Application_Model_MusicDir::getStorDir(); - $storPath = Application_Model_MusicDir::getStorDir()->getDirectory(); + foreach ($musicDirs as $md) { + $totalSpace = disk_total_space($md->getDirectory()); - list($usedSpace,) = preg_split("/[\s]+/", exec("du -bs $storPath")); + if (!isset($partitions[$totalSpace])) { + $partitions[$totalSpace] = new StdClass; + $partitions[$totalSpace]->totalSpace = $totalSpace; + $partitions[$totalSpace]->totalFreeSpace = disk_free_space($md->getDirectory()); - $partitions[$totalSpace]->totalSpace = $totalSpace; - $partitions[$totalSpace]->totalFreeSpace = $totalSpace - $usedSpace; - Logging::info($partitions[$totalSpace]->totalFreeSpace); - } else { - /* First lets get all the watched directories. Then we can group them - * into the same partitions by comparing the partition sizes. */ - $musicDirs = Application_Model_MusicDir::getWatchedDirs(); - $musicDirs[] = Application_Model_MusicDir::getStorDir(); - - foreach ($musicDirs as $md) { - $totalSpace = disk_total_space($md->getDirectory()); - - if (!isset($partitions[$totalSpace])) { - $partitions[$totalSpace] = new StdClass; - $partitions[$totalSpace]->totalSpace = $totalSpace; - $partitions[$totalSpace]->totalFreeSpace = disk_free_space($md->getDirectory()); - - } - - $partitions[$totalSpace]->dirs[] = $md->getDirectory(); } + + $partitions[$totalSpace]->dirs[] = $md->getDirectory(); } return array_values($partitions); diff --git a/airtime_mvc/application/models/User.php b/airtime_mvc/application/models/User.php index 63b82820a..97c9ca3ad 100644 --- a/airtime_mvc/application/models/User.php +++ b/airtime_mvc/application/models/User.php @@ -335,6 +335,8 @@ class Application_Model_User } else { $record['delete'] = ""; } + + $record = array_map('htmlspecialchars', $record); } return $res; diff --git a/airtime_mvc/application/views/scripts/form/edit-user.phtml b/airtime_mvc/application/views/scripts/form/edit-user.phtml index cd4b70bd9..79a0081fc 100644 --- a/airtime_mvc/application/views/scripts/form/edit-user.phtml +++ b/airtime_mvc/application/views/scripts/form/edit-user.phtml @@ -1,4 +1,4 @@ -

currentUser) ?>

+

escape($this->currentUser)) ?>

@@ -160,4 +160,4 @@
-
\ No newline at end of file + diff --git a/airtime_mvc/application/views/scripts/form/preferences_watched_dirs.phtml b/airtime_mvc/application/views/scripts/form/preferences_watched_dirs.phtml index 4889892dd..ad8e77797 100644 --- a/airtime_mvc/application/views/scripts/form/preferences_watched_dirs.phtml +++ b/airtime_mvc/application/views/scripts/form/preferences_watched_dirs.phtml @@ -11,7 +11,7 @@ element->getElement('storageFolder')->hasErrors()) : ?> @@ -29,7 +29,7 @@ element->getElement('watchedFolder')->hasErrors()) : ?> diff --git a/airtime_mvc/application/views/scripts/playlist/playlist.phtml b/airtime_mvc/application/views/scripts/playlist/playlist.phtml index 3d388bf36..5a31ba2b5 100644 --- a/airtime_mvc/application/views/scripts/playlist/playlist.phtml +++ b/airtime_mvc/application/views/scripts/playlist/playlist.phtml @@ -42,7 +42,7 @@ if (isset($this->obj)) {

- obj->getName(); ?> + escape($this->obj->getName()); ?>

length; ?>

diff --git a/airtime_mvc/public/js/airtime/login/login.js b/airtime_mvc/public/js/airtime/login/login.js index a37210b35..f21043c31 100644 --- a/airtime_mvc/public/js/airtime/login/login.js +++ b/airtime_mvc/public/js/airtime/login/login.js @@ -1,6 +1,6 @@ $(window).load(function(){ $("#username").focus(); - $("#locale").val($.cookie("airtime_locale")!== null?$.cookie("airtime_locale"):'en_CA'); + $("#locale").val($.cookie("airtime_locale")!== null?$.cookie("airtime_locale"):$.cookie("default_airtime_locale")); }); $(document).ready(function() { diff --git a/airtime_mvc/public/js/airtime/preferences/preferences.js b/airtime_mvc/public/js/airtime/preferences/preferences.js index 1d529617d..eec5e1c7e 100644 --- a/airtime_mvc/public/js/airtime/preferences/preferences.js +++ b/airtime_mvc/public/js/airtime/preferences/preferences.js @@ -111,6 +111,7 @@ $(document).ready(function() { $.post(url, {format: "json", data: data}, function(data){ var json = $.parseJSON(data); $('#content').empty().append(json.html); + $.cookie("default_airtime_locale", $('#locale').val(), {path: '/'}); setTimeout(removeSuccessMsg, 5000); showErrorSections(); }); diff --git a/airtime_mvc/public/js/airtime/preferences/streamsetting.js b/airtime_mvc/public/js/airtime/preferences/streamsetting.js index 54bb986ca..6e76b693c 100644 --- a/airtime_mvc/public/js/airtime/preferences/streamsetting.js +++ b/airtime_mvc/public/js/airtime/preferences/streamsetting.js @@ -28,7 +28,7 @@ function rebuildStreamURL(ele){ }else{ streamurl = "http://"+host+":"+port+"/" } - div.find("#stream_url").html(streamurl) + div.find("#stream_url").text(streamurl) } function restrictOggBitrate(ele, on){ var div = ele.closest("div") diff --git a/airtime_mvc/public/js/airtime/schedule/add-show.js b/airtime_mvc/public/js/airtime/schedule/add-show.js index 2242f3c66..4f33ccdc7 100644 --- a/airtime_mvc/public/js/airtime/schedule/add-show.js +++ b/airtime_mvc/public/js/airtime/schedule/add-show.js @@ -37,7 +37,7 @@ function createDateInput(el, onSelect) { dayNamesMin: i18n_days_short, closeText: $.i18n._('Close'), //showButtonPanel: true, - firstDay: weekStart + firstDay: calendarPref.weekStart }); } @@ -324,7 +324,7 @@ function setAddShowEvents() { dayNamesMin: i18n_days_short, closeText: 'Close', showButtonPanel: true, - firstDay: weekStart + firstDay: calendarPref.weekStart }); form.find('input[name^="add_show_rebroadcast_time"]').timepicker({ amPmText: ['', ''], diff --git a/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js b/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js index 08dd31116..810ce0d88 100644 --- a/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js +++ b/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js @@ -326,21 +326,36 @@ function eventResize( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, vie }); } -function getFullCalendarEvents(start, end, callback) { - var url, start_date, end_date; - - start_date = makeTimeStamp(start); - end_date = makeTimeStamp(end); - - url = baseUrl+'Schedule/event-feed'; - +function preloadEventFeed () { + var url = baseUrl+'Schedule/event-feed-preload'; var d = new Date(); - - $.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){ - callback(json.events); + + $.post(url, {format: "json", cachep: d.getTime()}, function(json){ + calendarEvents = json.events; + createFullCalendar({calendarInit: calendarPref}); }); } +var initialLoad = true; +function getFullCalendarEvents(start, end, callback) { + + if (initialLoad) { + initialLoad = false; + callback(calendarEvents); + } else { + var url, start_date, end_date; + + start_date = makeTimeStamp(start); + end_date = makeTimeStamp(end); + url = baseUrl+'Schedule/event-feed'; + + var d = new Date(); + $.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){ + callback(json.events); + }); + } +} + function checkSCUploadStatus(){ var url = baseUrl+'Library/get-upload-to-soundcloud-status/format/json'; $("span[class*=progress]").each(function(){ @@ -541,6 +556,7 @@ function alertShowErrorAndReload(){ window.location.reload(); } +preloadEventFeed(); $(document).ready(function(){ setInterval( "checkSCUploadStatus()", 5000 ); setInterval( "getCurrentShow()", 5000 ); diff --git a/airtime_mvc/public/js/airtime/schedule/schedule.js b/airtime_mvc/public/js/airtime/schedule/schedule.js index 2de53cdb5..3c4f9fd3b 100644 --- a/airtime_mvc/public/js/airtime/schedule/schedule.js +++ b/airtime_mvc/public/js/airtime/schedule/schedule.js @@ -328,9 +328,6 @@ function alertShowErrorAndReload(){ } $(document).ready(function() { - $.ajax({ url: baseUrl+"Api/calendar-init/format/json", dataType:"json", success:createFullCalendar - , error:function(jqXHR, textStatus, errorThrown){}}); - setInterval(checkCalendarSCUploadStatus, 5000); $.contextMenu({ diff --git a/changelog b/changelog index 9aa22c634..aff42ec52 100644 --- a/changelog +++ b/changelog @@ -1,6 +1,7 @@ 2.3.0 - Jan 21st, 2013 * New features - * Localization (Brazilian, Chinese, Czech, English, French, German, Italian, Korean, Portugese, Russian, Spanish) + * Localization (Chinese, Czech, English, French, German, Italian, Korean, + Portuguese, Russian, Spanish) * User management page for non-admin users * Listener statistics (Icecast/Shoutcast) * Airtime no longer requires Apache document root diff --git a/install_minimal/upgrades/airtime-2.3.0/data/upgrade.sql b/install_minimal/upgrades/airtime-2.3.0/data/upgrade.sql index c2d176b23..cd34a28bb 100644 --- a/install_minimal/upgrades/airtime-2.3.0/data/upgrade.sql +++ b/install_minimal/upgrades/airtime-2.3.0/data/upgrade.sql @@ -15,6 +15,8 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_admin_pas UPDATE cc_music_dirs SET directory = directory || '/' where id in (select id from cc_music_dirs where substr(directory, length(directory)) != '/'); UPDATE cc_files SET filepath = substring(filepath from 2) where id in (select id from cc_files where substring(filepath from 1 for 1) = '/'); +UPDATE cc_files SET cueout = length where cueout = '00:00:00'; + INSERT INTO cc_pref("keystr", "valstr") VALUES('locale', 'en_CA'); INSERT INTO cc_pref("subjid", "keystr", "valstr") VALUES(1, 'user_locale', 'en_CA'); diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index 048da729c..b28a4ca5a 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -73,17 +73,26 @@ class ApcUrl(object): else: return self.base_url class ApiRequest(object): - def __init__(self, name, url): + def __init__(self, name, url, logger=None): self.name = name self.url = url self.__req = None + if logger is None: self.logger = logging + else: self.logger = logger def __call__(self,_post_data=None, **kwargs): # TODO : get rid of god damn urllib and replace everything with # grequests or requests at least final_url = self.url.params(**kwargs).url() if _post_data is not None: _post_data = urllib.urlencode(_post_data) - req = urllib2.Request(final_url, _post_data) - response = urllib2.urlopen(req).read() + try: + req = urllib2.Request(final_url, _post_data) + response = urllib2.urlopen(req).read() + except Exception, e: + self.logger.error('Exception: %s', e) + import traceback + top = traceback.format_exc() + self.logger.error("traceback: %s", top) + response = "" # Ghetto hack for now because we don't the content type we are getting # (Pointless to look at mime since it's not being set correctly always) try: return json.loads(response) @@ -168,9 +177,9 @@ class AirtimeApiClient(object): def get_schedule(self): # TODO : properly refactor this routine - # For now thre return type is a little fucked for compatibility reasons + # For now the return type is a little fucked for compatibility reasons try: return (True, self.services.export_url()) - except: (False, "") + except: return (False, None) def notify_liquidsoap_started(self): return self.services.notify_liquidsoap_started() @@ -356,7 +365,7 @@ class AirtimeApiClient(object): """ #http://localhost/api/get-files-without-replay-gain/dir_id/1 return self.services.get_files_without_replay_gain(dir_id=dir_id) - + def get_files_without_silan_value(self): """ Download a list of files that need to have their cue in/out value @@ -372,7 +381,7 @@ class AirtimeApiClient(object): """ self.logger.debug(self.services.update_replay_gain_value( _post_data={'data': json.dumps(pairs)})) - + def update_cue_values_by_silan(self, pairs): """ 'pairs' is a list of pairs in (x, y), where x is the file's database diff --git a/python_apps/pypo/airtime-liquidsoap-init-d b/python_apps/pypo/airtime-liquidsoap-init-d index 4180d5c67..7096bc59c 100755 --- a/python_apps/pypo/airtime-liquidsoap-init-d +++ b/python_apps/pypo/airtime-liquidsoap-init-d @@ -28,10 +28,10 @@ start () { stop () { monit unmonitor airtime-liquidsoap >/dev/null 2>&1 - /usr/lib/airtime/airtime_virtualenv/bin/python /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_prepare_terminate.py - + #send term signal after 10 seconds + timeout 10 /usr/lib/airtime/airtime_virtualenv/bin/python /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_prepare_terminate.py # Send TERM after 5 seconds, wait at most 30 seconds. - start-stop-daemon --stop --oknodo --retry 5 --quiet --pidfile $PIDFILE + start-stop-daemon --stop --oknodo --retry=TERM/10/KILL/5 --quiet --pidfile $PIDFILE rm -f $PIDFILE } diff --git a/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py b/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py index e1dac82b6..2f632d9c7 100644 --- a/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py +++ b/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py @@ -6,14 +6,14 @@ try: config = ConfigObj('/etc/airtime/pypo.cfg') LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] - + tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn.write("master_harbor.stop\n") tn.write("live_dj_harbor.stop\n") tn.write('exit\n') tn.read_all() - + except Exception, e: print('Error loading config file: %s', e) sys.exit() - \ No newline at end of file + diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index cd01ad453..489d494a3 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -195,28 +195,81 @@ def check_dj_client(user,password) = hd == "True" end -def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point, dj_harbor_input_port, dj_harbor_input_mount_point, s) = - if master_harbor_input_port != 0 and master_harbor_input_mount_point != "" and dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then - master_dj = mksafe(audio_to_stereo(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, - max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect))) - dj_live = mksafe(audio_to_stereo(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, - max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect))) +def append_dj_inputs(master_harbor_input_port, + master_harbor_input_mount_point, + dj_harbor_input_port, + dj_harbor_input_mount_point, + s) = + if master_harbor_input_port != 0 + and master_harbor_input_mount_point != "" + and dj_harbor_input_port != 0 + and dj_harbor_input_mount_point != "" then + + master_dj = mksafe( + audio_to_stereo( + input.harbor(id="master_harbor", + master_harbor_input_mount_point, + port=master_harbor_input_port, + auth=check_master_dj_client, + max=40., + on_connect=master_dj_connect, + on_disconnect=master_dj_disconnect))) + + dj_live = mksafe( + audio_to_stereo( + input.harbor(id="live_dj_harbor", + dj_harbor_input_mount_point, + port=dj_harbor_input_port, + auth=check_dj_client, + max=40., + on_connect=live_dj_connect, + on_disconnect=live_dj_disconnect))) ignore(output.dummy(master_dj, fallible=true)) ignore(output.dummy(dj_live, fallible=true)) - switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition, transition], [({!master_dj_enabled},master_dj), ({!live_dj_enabled},dj_live), ({true}, s)]) + + switch(id="master_dj_switch", + track_sensitive=false, + transitions=[transition, transition, transition], + [({!master_dj_enabled},master_dj), + ({!live_dj_enabled},dj_live), + ({true}, s)]) + elsif master_harbor_input_port != 0 and master_harbor_input_mount_point != "" then - master_dj = mksafe(audio_to_stereo(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, - max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect))) + master_dj = mksafe( + audio_to_stereo( + input.harbor(id="master_harbor", + master_harbor_input_mount_point, + port=master_harbor_input_port, + auth=check_master_dj_client, + max=40., + on_connect=master_dj_connect, + on_disconnect=master_dj_disconnect))) + ignore(output.dummy(master_dj, fallible=true)) - switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!master_dj_enabled},master_dj), ({true}, s)]) + switch(id="master_dj_switch", + track_sensitive=false, + transitions=[transition, transition], + [({!master_dj_enabled},master_dj), ({true}, s)]) + elsif dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then - dj_live = mksafe(audio_to_stereo(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, - max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect))) + dj_live = mksafe( + audio_to_stereo( + input.harbor(id="live_dj_harbor", + dj_harbor_input_mount_point, + port=dj_harbor_input_port, + auth=check_dj_client, + max=40., + on_connect=live_dj_connect, + on_disconnect=live_dj_disconnect))) ignore(output.dummy(dj_live, fallible=true)) - switch(id="live_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!live_dj_enabled},dj_live), ({true}, s)]) + + switch(id="live_dj_switch", + track_sensitive=false, + transitions=[transition, transition], + [({!live_dj_enabled},dj_live), ({true}, s)]) else s end diff --git a/python_apps/pypo/monit-airtime-playout.cfg b/python_apps/pypo/monit-airtime-playout.cfg index 5b096c72a..453f4efec 100644 --- a/python_apps/pypo/monit-airtime-playout.cfg +++ b/python_apps/pypo/monit-airtime-playout.cfg @@ -5,5 +5,5 @@ check process airtime-playout with pidfile "/var/run/airtime-playout.pid" - start program = "/etc/init.d/airtime-playout monit-restart" with timeout 5 seconds + start program = "/etc/init.d/airtime-playout start" with timeout 5 seconds stop program = "/etc/init.d/airtime-playout stop" diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index ea8950d41..93a1718aa 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -176,7 +176,7 @@ if __name__ == '__main__': sys.exit() api_client = api_client.AirtimeApiClient() - + ReplayGainUpdater.start_reply_gain(api_client) api_client.register_component("pypo") diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index ec3ef11ce..2d6ad0fb5 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -18,7 +18,8 @@ from std_err_override import LogWriter from configobj import ConfigObj # configure logging -logging.config.fileConfig("logging.cfg") +logging_cfg = os.path.join(os.path.dirname(__file__), "logging.cfg") +logging.config.fileConfig(logging_cfg) logger = logging.getLogger() LogWriter.override_std_err(logger) @@ -132,9 +133,10 @@ class PypoFetch(Thread): elif(sourcename == "live_dj"): command += "live_dj_harbor.kick\n" - lock.acquire() try: + lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) + logger.info(command) tn.write(command) tn.write('exit\n') tn.read_all() @@ -143,6 +145,24 @@ class PypoFetch(Thread): finally: lock.release() + @staticmethod + def telnet_send(logger, lock, commands): + try: + lock.acquire() + + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + for i in commands: + logger.info(i) + tn.write(i) + + tn.write('exit\n') + tn.read_all() + except Exception, e: + logger.error(str(e)) + finally: + lock.release() + + @staticmethod def switch_source(logger, lock, sourcename, status): logger.debug('Switching source: %s to "%s" status', sourcename, status) @@ -159,16 +179,26 @@ class PypoFetch(Thread): else: command += "stop\n" - lock.acquire() - try: - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - tn.write(command) - tn.write('exit\n') - tn.read_all() - except Exception, e: - logger.error(str(e)) - finally: - lock.release() + PypoFetch.telnet_send(logger, lock, [command]) + + + #TODO: Merge this with switch_source + def switch_source_temp(self, sourcename, status): + self.logger.debug('Switching source: %s to "%s" status', sourcename, status) + command = "streams." + if sourcename == "master_dj": + command += "master_dj_" + elif sourcename == "live_dj": + command += "live_dj_" + elif sourcename == "scheduled_play": + command += "scheduled_play_" + + if status == "on": + command += "start\n" + else: + command += "stop\n" + + return command """ grabs some information that are needed to be set on bootstrap time @@ -179,19 +209,25 @@ class PypoFetch(Thread): info = self.api_client.get_bootstrap_info() if info is None: self.logger.error('Unable to get bootstrap info.. Exiting pypo...') - sys.exit(1) else: self.logger.debug('info:%s', info) + commands = [] for k, v in info['switch_status'].iteritems(): - self.switch_source(self.logger, self.telnet_lock, k, v) - self.update_liquidsoap_stream_format(info['stream_label']) - self.update_liquidsoap_station_name(info['station_name']) - self.update_liquidsoap_transition_fade(info['transition_fade']) + commands.append(self.switch_source_temp(k, v)) + + stream_format = info['stream_label'] + station_name = info['station_name'] + fade = info['transition_fade'] + + commands.append(('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8')) + commands.append(('vars.station_name %s\n' % station_name).encode('utf-8')) + commands.append(('vars.default_dj_fade %s\n' % fade).encode('utf-8')) + PypoFetch.telnet_send(self.logger, self.telnet_lock, commands) def restart_liquidsoap(self): - self.telnet_lock.acquire() try: + self.telnet_lock.acquire() self.logger.info("Restarting Liquidsoap") subprocess.call('/etc/init.d/airtime-liquidsoap restart', shell=True) @@ -217,7 +253,7 @@ class PypoFetch(Thread): self.set_bootstrap_variables() #get the most up to date schedule, which will #initiate the process #of making sure Liquidsoap is playing the schedule - self.manual_schedule_fetch() + self.persistent_manual_schedule_fetch(max_attempts=5) except Exception, e: self.logger.error(str(e)) @@ -322,16 +358,21 @@ class PypoFetch(Thread): This function updates the bootup time variable in Liquidsoap script """ - self.telnet_lock.acquire() try: + self.telnet_lock.acquire() 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 # updated. current_time = time.time() boot_up_time_command = "vars.bootup_time " + str(current_time) + "\n" + self.logger.info(boot_up_time_command) tn.write(boot_up_time_command) - tn.write("streams.connection_status\n") + + connection_status = "streams.connection_status\n" + self.logger.info(connection_status) + tn.write(connection_status) + tn.write('exit\n') output = tn.read_all() @@ -356,6 +397,7 @@ class PypoFetch(Thread): if(status == "true"): self.api_client.notify_liquidsoap_status("OK", stream_id, str(fake_time)) + def update_liquidsoap_stream_format(self, stream_format): # Push stream metadata to liquidsoap # TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!! @@ -395,8 +437,8 @@ class PypoFetch(Thread): self.logger.info(LS_HOST) self.logger.info(LS_PORT) - self.telnet_lock.acquire() try: + self.telnet_lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) command = ('vars.station_name %s\n' % station_name).encode('utf-8') self.logger.info(command) @@ -488,10 +530,20 @@ class PypoFetch(Thread): self.process_schedule(self.schedule_data) return success + def persistent_manual_schedule_fetch(self, max_attempts=1): + success = False + num_attempts = 0 + while not success and num_attempts < max_attempts: + success = self.manual_schedule_fetch() + num_attempts += 1 + + return success + + def main(self): # Bootstrap: since we are just starting up, we need to grab the # most recent schedule. After that we can just wait for updates. - success = self.manual_schedule_fetch() + success = self.persistent_manual_schedule_fetch(max_attempts=5) if success: self.logger.info("Bootstrap schedule received: %s", self.schedule_data) self.set_bootstrap_variables() @@ -519,7 +571,7 @@ class PypoFetch(Thread): self.handle_message(message) except Empty, e: self.logger.info("Queue timeout. Fetching schedule manually") - self.manual_schedule_fetch() + self.persistent_manual_schedule_fetch(max_attempts=5) except Exception, e: import traceback top = traceback.format_exc() diff --git a/python_apps/pypo/pyponotify.py b/python_apps/pypo/pyponotify.py index 9c2f1688c..797d1ce9b 100644 --- a/python_apps/pypo/pyponotify.py +++ b/python_apps/pypo/pyponotify.py @@ -43,7 +43,7 @@ parser.add_option("-t", "--time", help="Liquidsoap boot up time", action="store" parser.add_option("-x", "--source-name", help="source connection name", metavar="source_name") parser.add_option("-y", "--source-status", help="source connection status", metavar="source_status") parser.add_option("-w", "--webstream", help="JSON metadata associated with webstream", metavar="json_data") -parser.add_option("-n", "--liquidsoap-started", help="notify liquidsoap started", metavar="json_data", action="store_true", default=True) +parser.add_option("-n", "--liquidsoap-started", help="notify liquidsoap started", metavar="json_data", action="store_true", default=False) # parse options diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 6fedfab1b..f438b3bb1 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -9,6 +9,7 @@ import logging.config import telnetlib import calendar import math +import os from pypofetch import PypoFetch from Queue import Empty @@ -21,7 +22,8 @@ from configobj import ConfigObj # configure logging -logging.config.fileConfig("logging.cfg") +logging_cfg = os.path.join(os.path.dirname(__file__), "logging.cfg") +logging.config.fileConfig(logging_cfg) logger = logging.getLogger() LogWriter.override_std_err(logger) @@ -249,7 +251,7 @@ class PypoPush(Thread): self.start_web_stream_buffer(current_item) self.start_web_stream(current_item) if is_file(current_item): - self.modify_cue_point(file_chain[0]) + file_chain = self.modify_first_link_cue_point(file_chain) self.push_to_liquidsoap(file_chain) #we've changed the queue, so let's refetch it liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() @@ -279,7 +281,7 @@ class PypoPush(Thread): chain_to_push = file_chain[problem_at_iteration:] if len(chain_to_push) > 0: - self.modify_cue_point(chain_to_push[0]) + chain_to_push = self.modify_first_link_cue_point(chain_to_push) self.push_to_liquidsoap(chain_to_push) @@ -363,6 +365,18 @@ class PypoPush(Thread): original_cue_in_td = timedelta(seconds=float(link['cue_in'])) link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec + def modify_first_link_cue_point(self, chain): + if not len(chain): + return [] + + first_link = chain[0] + + self.modify_cue_point(first_link) + if float(first_link['cue_in']) >= float(first_link['cue_out']): + chain = chain [1:] + + return chain + """ Returns two chains, original chain and current_chain. current_chain is a subset of original_chain but can also be equal to original chain. diff --git a/python_apps/pypo/tests/run_tests.sh b/python_apps/pypo/tests/run_tests.sh new file mode 100755 index 000000000..830a9bb85 --- /dev/null +++ b/python_apps/pypo/tests/run_tests.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +which py.test +pytest_exist=$? + +if [ "$pytest_exist" != "0" ]; then + echo "Need to have py.test installed. Exiting..." + exit 1 +fi + +SCRIPT=`readlink -f $0` +# Absolute directory this script is in +SCRIPTPATH=`dirname $SCRIPT` + +export PYTHONPATH=$PYTHONPATH:$SCRIPTPATH/..:$SCRIPTPATH/../.. + +py.test + diff --git a/python_apps/pypo/tests/test_modify_cue_in.py b/python_apps/pypo/tests/test_modify_cue_in.py new file mode 100644 index 000000000..da17fd53f --- /dev/null +++ b/python_apps/pypo/tests/test_modify_cue_in.py @@ -0,0 +1,26 @@ +from pypopush import PypoPush +from threading import Lock +from Queue import Queue + +import datetime + +pypoPush_q = Queue() +telnet_lock = Lock() + +pp = PypoPush(pypoPush_q, telnet_lock) + +def test_modify_cue_in(): + link = pp.modify_first_link_cue_point([]) + assert len(link) == 0 + + min_ago = datetime.datetime.utcnow() - datetime.timedelta(minutes = 1) + link = [{"start":min_ago.strftime("%Y-%m-%d-%H-%M-%S"), + "cue_in":"0", "cue_out":"30"}] + link = pp.modify_first_link_cue_point(link) + assert len(link) == 0 + + link = [{"start":min_ago.strftime("%Y-%m-%d-%H-%M-%S"), + "cue_in":"0", "cue_out":"70"}] + link = pp.modify_first_link_cue_point(link) + assert len(link) == 1 + diff --git a/utils/airtime-import/airtime-import.py b/utils/airtime-import/airtime-import.py index 1e55c732c..2b3ce03cd 100644 --- a/utils/airtime-import/airtime-import.py +++ b/utils/airtime-import/airtime-import.py @@ -170,6 +170,8 @@ def WatchAddAction(option, opt, value, parser): print "%s added to watched folder list successfully" % path else: print "Adding a watched folder failed: %s" % res['msg']['error'] + print "This error most likely caused by wrong permissions" + print "Try fixing this error by chmodding the parent directory(ies)" else: print "Given path is not a directory: %s" % path