From e84f765696117b73de5e58a993543b2b31bf0982 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Mon, 7 Jul 2014 15:53:25 -0400 Subject: [PATCH 1/3] Better FileMover permissions unit test (passes as root) --- python_apps/airtime_analyzer/tests/filemover_analyzer_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_apps/airtime_analyzer/tests/filemover_analyzer_tests.py b/python_apps/airtime_analyzer/tests/filemover_analyzer_tests.py index 7591442c0..dbdbb2feb 100644 --- a/python_apps/airtime_analyzer/tests/filemover_analyzer_tests.py +++ b/python_apps/airtime_analyzer/tests/filemover_analyzer_tests.py @@ -104,7 +104,7 @@ def test_double_duplicate_files(): @raises(OSError) def test_bad_permissions_destination_dir(): filename = os.path.basename(DEFAULT_AUDIO_FILE) - dest_dir = u'/var/foobar' + dest_dir = u'/sys/foobar' # /sys is using sysfs on Linux, which is unwritable FileMoverAnalyzer.move(DEFAULT_AUDIO_FILE, dest_dir, filename, dict()) #Move the file back shutil.move(os.path.join(dest_dir, filename), DEFAULT_AUDIO_FILE) From 7438ecd2b418387cd62a56d1251b07842de3c765 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Tue, 15 Jul 2014 16:32:48 -0400 Subject: [PATCH 2/3] Use track metadata from Airtime in playout engine * Resolves CC-5893: Metadata not updated on Airtime Pro * Report track metadata in the schedule API, and make pypo pass that along to Liquidsoap via annotations. * Move HTTP response sanitization for file metadata out of the REST module and into CcFiles * Slightly improved the terrible exception handling in pypo --- airtime_mvc/application/models/Schedule.php | 5 ++- .../application/models/airtime/CcFiles.php | 24 ++++++++++++ .../rest/controllers/MediaController.php | 37 ++++--------------- .../generate_liquidsoap_cfg.py | 3 ++ python_apps/pypo/telnetliquidsoap.py | 34 ++++++++++++----- 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 2641094a3..2552bf07e 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -738,13 +738,16 @@ SQL; $replay_gain = is_null($item["replay_gain"]) ? "0": $item["replay_gain"]; $replay_gain += Application_Model_Preference::getReplayGainModifier(); - if ( !Application_Model_Preference::GetEnableReplayGain() ) { + if (!Application_Model_Preference::GetEnableReplayGain() ) { $replay_gain = 0; } + $fileMetadata = CcFiles::sanitizeResponse(CcFilesQuery::create()->findPk($media_id)); + $schedule_item = array( 'id' => $media_id, 'type' => 'file', + 'metadata' => $fileMetadata, 'row_id' => $item["id"], 'uri' => $uri, 'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]), diff --git a/airtime_mvc/application/models/airtime/CcFiles.php b/airtime_mvc/application/models/airtime/CcFiles.php index f49c9154a..93ef491a8 100644 --- a/airtime_mvc/application/models/airtime/CcFiles.php +++ b/airtime_mvc/application/models/airtime/CcFiles.php @@ -13,6 +13,14 @@ */ class CcFiles extends BaseCcFiles { + //fields we should never expose through our RESTful API + private static $privateFields = array( + 'file_exists', + 'silan_check', + 'is_scheduled', + 'is_playlist' + ); + public function getCueLength() { $cuein = $this->getDbCuein(); @@ -46,4 +54,20 @@ class CcFiles extends BaseCcFiles { $this->save(); } + /** + * + * Strips out the private fields we do not want to send back in API responses + * @param $file a CcFiles object + */ + //TODO: rename this function? + public static function sanitizeResponse($file) + { + $response = $file->toArray(BasePeer::TYPE_FIELDNAME); + + foreach (self::$privateFields as $key) { + unset($response[$key]); + } + + return $response; + } } // CcFiles diff --git a/airtime_mvc/application/modules/rest/controllers/MediaController.php b/airtime_mvc/application/modules/rest/controllers/MediaController.php index 1604d1262..f03b3b7b2 100644 --- a/airtime_mvc/application/modules/rest/controllers/MediaController.php +++ b/airtime_mvc/application/modules/rest/controllers/MediaController.php @@ -4,7 +4,7 @@ class Rest_MediaController extends Zend_Rest_Controller { //fields that are not modifiable via our RESTful API - private $blackList = array( + private static $blackList = array( 'id', 'directory', 'filepath', @@ -18,14 +18,6 @@ class Rest_MediaController extends Zend_Rest_Controller 'is_playlist' ); - //fields we should never expose through our RESTful API - private $privateFields = array( - 'file_exists', - 'silan_check', - 'is_scheduled', - 'is_playlist' - ); - public function init() { $this->view->layout()->disableLayout(); @@ -41,7 +33,7 @@ class Rest_MediaController extends Zend_Rest_Controller $files_array = array(); foreach (CcFilesQuery::create()->find() as $file) { - array_push($files_array, $this->sanitizeResponse($file)); + array_push($files_array, CcFiles::sanitizeResponse($file)); } $this->getResponse() @@ -134,7 +126,7 @@ class Rest_MediaController extends Zend_Rest_Controller $this->getResponse() ->setHttpResponseCode(200) - ->appendBody(json_encode($this->sanitizeResponse($file))); + ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); } else { $this->fileNotFoundResponse(); } @@ -201,7 +193,7 @@ class Rest_MediaController extends Zend_Rest_Controller $this->getResponse() ->setHttpResponseCode(201) - ->appendBody(json_encode($this->sanitizeResponse($file))); + ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); } } @@ -265,7 +257,7 @@ class Rest_MediaController extends Zend_Rest_Controller $this->getResponse() ->setHttpResponseCode(200) - ->appendBody(json_encode($this->sanitizeResponse($file))); + ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); } else { $file->setDbImportStatus(2)->save(); $this->fileNotFoundResponse(); @@ -490,30 +482,15 @@ class Rest_MediaController extends Zend_Rest_Controller * from outside of Airtime * @param array $data */ - private function removeBlacklistedFieldsFromRequestData($data) + private static function removeBlacklistedFieldsFromRequestData($data) { - foreach ($this->blackList as $key) { + foreach (self::$blackList as $key) { unset($data[$key]); } return $data; } - /** - * - * Strips out the private fields we do not want to send back in API responses - */ - //TODO: rename this function? - public function sanitizeResponse($file) - { - $response = $file->toArray(BasePeer::TYPE_FIELDNAME); - - foreach ($this->privateFields as $key) { - unset($response[$key]); - } - - return $response; - } private function removeEmptySubFolders($path) { diff --git a/python_apps/pypo/liquidsoap_scripts/generate_liquidsoap_cfg.py b/python_apps/pypo/liquidsoap_scripts/generate_liquidsoap_cfg.py index e0160181b..45bdb46f4 100644 --- a/python_apps/pypo/liquidsoap_scripts/generate_liquidsoap_cfg.py +++ b/python_apps/pypo/liquidsoap_scripts/generate_liquidsoap_cfg.py @@ -44,5 +44,8 @@ while not successful: logging.error("traceback: %s", traceback.format_exc()) sys.exit(1) else: + logging.error(str(e)) + logging.error("traceback: %s", traceback.format_exc()) + logging.info("Retrying in 3 seconds...") time.sleep(3) attempts += 1 diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index 44d97a13f..2f572ff13 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -3,17 +3,31 @@ from timeout import ls_timeout def create_liquidsoap_annotation(media): # We need liq_start_next value in the annotate. That is the value that controls overlap duration of crossfade. - return ('annotate:media_id="%s",liq_start_next="0",liq_fade_in="%s",' + \ + + filename = media['dst'] + annotation = ('annotate:media_id="%s",liq_start_next="0",liq_fade_in="%s",' + \ 'liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",' + \ - 'schedule_table_id="%s",replay_gain="%s dB":%s') % \ - (media['id'], - float(media['fade_in']) / 1000, - float(media['fade_out']) / 1000, - float(media['cue_in']), - float(media['cue_out']), - media['row_id'], - media['replay_gain'], - media['dst']) + 'schedule_table_id="%s",replay_gain="%s dB"') % \ + (media['id'], + float(media['fade_in']) / 1000, + float(media['fade_out']) / 1000, + float(media['cue_in']), + float(media['cue_out']), + media['row_id'], + media['replay_gain']) + + # Override the the artist/title that Liquidsoap extracts from a file's metadata + # with the metadata we get from Airtime. (You can modify metadata in Airtime's library, + # which doesn't get saved back to the file.) + if 'metadata' in media: + if 'artist_name' in media['metadata']: + annotation += ',artist="%s"' % (media['metadata']['artist_name']) + if 'track_title' in media['metadata']: + annotation += ',title="%s"' % (media['metadata']['track_title']) + + annotation += ":" + filename + + return annotation class TelnetLiquidsoap: From aa8a26271cf7eef11c2884f73e097c525d75bfc1 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Fri, 1 Aug 2014 11:51:20 -0400 Subject: [PATCH 3/3] Fix zend routing on Trusty (weird error due to REST module) --- airtime_mvc/application/configs/application.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/application/configs/application.ini b/airtime_mvc/application/configs/application.ini index a9302c71d..8c54af91e 100644 --- a/airtime_mvc/application/configs/application.ini +++ b/airtime_mvc/application/configs/application.ini @@ -6,11 +6,11 @@ bootstrap.class = "Bootstrap" appnamespace = "Application" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.frontController.params.displayExceptions = 0 -resources.frontController.moduleDirectory = APPLICATION_PATH "/modules" +;resources.frontController.moduleDirectory = APPLICATION_PATH "/modules" resources.frontController.plugins.putHandler = "Zend_Controller_Plugin_PutHandler" ;load everything in the modules directory including models -resources.modules[] = "" resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts/" +resources.modules[] = "" resources.view[] = ; These are no longer needed. They are specified in /etc/airtime/airtime.conf: ;resources.db.adapter = "Pdo_Pgsql"