diff --git a/airtime_mvc/application/common/HTTPHelper.php b/airtime_mvc/application/common/HTTPHelper.php index db314bb0b..505befd7e 100644 --- a/airtime_mvc/application/common/HTTPHelper.php +++ b/airtime_mvc/application/common/HTTPHelper.php @@ -17,4 +17,22 @@ class Application_Common_HTTPHelper $request->getParam("timezone", null) ); } + + public static function getStationUrl() + { + $scheme = "http"; + if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + $scheme = "https"; + } + $CC_CONFIG = Config::getConfig(); + $baseUrl = $CC_CONFIG['baseUrl']; + $baseDir = $CC_CONFIG['baseDir']; + $basePort = $CC_CONFIG['basePort']; + if (empty($baseDir)) { + $baseDir = "/"; + } + $stationUrl = "$scheme://${baseUrl}:${basePort}${baseDir}"; + + return $stationUrl; + } } diff --git a/airtime_mvc/application/models/airtime/CcFiles.php b/airtime_mvc/application/models/airtime/CcFiles.php index 93ef491a8..554139e40 100644 --- a/airtime_mvc/application/models/airtime/CcFiles.php +++ b/airtime_mvc/application/models/airtime/CcFiles.php @@ -11,17 +11,288 @@ * * @package propel.generator.campcaster */ + +class InvalidMetadataException extends Exception +{ +} + +class FileNotFoundException extends Exception +{ +} + +class OverDiskQuotaException extends Exception +{ + +} + class CcFiles extends BaseCcFiles { - + + const MUSIC_DIRS_STOR_PK = 1; + + + //fields that are not modifiable via our RESTful API + private static $blackList = array( + 'id', + 'directory', + 'filepath', + 'file_exists', + 'mtime', + 'utime', + 'lptime', + 'silan_check', + 'soundcloud_id', + 'is_scheduled', + 'is_playlist' + ); + //fields we should never expose through our RESTful API private static $privateFields = array( - 'file_exists', - 'silan_check', - 'is_scheduled', - 'is_playlist' + 'file_exists', + 'silan_check', + 'is_scheduled', + 'is_playlist' ); - - public function getCueLength() + + /** + * Retrieve a sanitized version of the file metadata, suitable for public access. + * @param $fileId + */ + public static function getSantiziedFileById($fileId) + { + $file = CcFilesQuery::create()->findPk($fileId); + if ($file) { + return CcFiles::sanitizeResponse($file); + } else { + throw new FileNotFoundException(); + } + } + + /** Used to create a CcFiles object from an array containing metadata and a file uploaded by POST. + * This is used by our Media REST API! + * @param $fileArray An array containing metadata for a CcFiles object. + * @throws Exception + */ + public static function createFromUpload($fileArray) + { + if (Application_Model_Systemstatus::isDiskOverQuota()) { + throw new OverDiskQuotaException(); + } + + $file = new CcFiles(); + + try{ + $fileArray = self::removeBlacklistedFields($fileArray); + + /*if (!self::validateFileArray($fileArray)) + { + $file->setDbTrackTitle($_FILES["file"]["name"]); + $file->setDbUtime(new DateTime("now", new DateTimeZone("UTC"))); + $file->save(); + return CcFiles::sanitizeResponse($file);*/ + self::validateFileArray($fileArray); + + /* If full_path is set, the post request came from ftp. + * Users are allowed to upload folders via ftp. If this is the case + * we need to include the folder name with the file name, otherwise + * files won't get removed from the organize folder. + */ + if (isset($fileArray["full_path"])) { + $fullPath = $fileArray["full_path"]; + $basePath = isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."/srv/airtime/stor/organize/" : "/srv/airtime/stor/organize/"; + //$relativePath is the folder name(if one) + track name, that was uploaded via ftp + $relativePath = substr($fullPath, strlen($basePath)-1); + } else { + $relativePath = $_FILES["file"]["name"]; + } + + + $file->fromArray($fileArray); + $file->setDbOwnerId(self::getOwnerId()); + $now = new DateTime("now", new DateTimeZone("UTC")); + $file->setDbTrackTitle($_FILES["file"]["name"]); + $file->setDbUtime($now); + $file->setDbHidden(true); + $file->save(); + + $callbackUrl = Application_Common_HTTPHelper::getStationUrl() . "/rest/media/" . $file->getPrimaryKey(); + + Application_Service_MediaService::processUploadedFile($callbackUrl, $relativePath, self::getOwnerId()); + return CcFiles::sanitizeResponse($file); + + } catch (Exception $e) { + $file->setDbImportStatus(2); + $file->setDbHidden(true); + throw $e; + } + } + + /** Update a file with metadata specified in an array. + * @param $fileId The ID of the file to update in the DB. + * @param $fileArray An associative array containing metadata. Replaces those fields if they exist. + * @return array A sanitized version of the file metadata array. + * @throws Exception + * @throws FileNotFoundException + * @throws PropelException + */ + public static function updateFromArray($fileId, $fileArray) + { + $file = CcFilesQuery::create()->findPk($fileId); + + // Since we check for this value when deleting files, set it first + $file->setDbDirectory(self::MUSIC_DIRS_STOR_PK); + + $fileArray = self::removeBlacklistedFields($fileArray); + $fileArray = self::stripTimeStampFromYearTag($fileArray); + + self::validateFileArray($fileArray); + if ($file && isset($requestData["resource_id"])) { + + $file->fromArray($fileArray, BasePeer::TYPE_FIELDNAME); + + //store the original filename + $file->setDbFilepath($fileArray["filename"]); + + $fileSizeBytes = $fileArray["filesize"]; + if (!isset($fileSizeBytes) || $fileSizeBytes === false) + { + $file->setDbImportStatus(2)->save(); + throw new FileNotFoundException(); + } + $cloudFile = new CloudFile(); + $cloudFile->setStorageBackend($fileArray["storage_backend"]); + $cloudFile->setResourceId($fileArray["resource_id"]); + $cloudFile->setCcFiles($file); + $cloudFile->save(); + + Application_Model_Preference::updateDiskUsage($fileSizeBytes); + + $now = new DateTime("now", new DateTimeZone("UTC")); + $file->setDbMtime($now); + $file->save(); + + } else if ($file) { + + $file->fromArray($fileArray, BasePeer::TYPE_FIELDNAME); + + //Our RESTful API takes "full_path" as a field, which we then split and translate to match + //our internal schema. Internally, file path is stored relative to a directory, with the directory + //as a foreign key to cc_music_dirs. + if (isset($fileArray["full_path"])) { + $fileSizeBytes = filesize($fileArray["full_path"]); + if (!isset($fileSizeBytes) || $fileSizeBytes === false) + { + $file->setDbImportStatus(self::IMPORT_STATUS_FAILED)->save(); + throw new FileNotFoundException(); + } + Application_Model_Preference::updateDiskUsage($fileSizeBytes); + + $fullPath = $fileArray["full_path"]; + $storDir = Application_Model_MusicDir::getStorDir()->getDirectory(); + $pos = strpos($fullPath, $storDir); + + if ($pos !== FALSE) + { + assert($pos == 0); //Path must start with the stor directory path + + $filePathRelativeToStor = substr($fullPath, strlen($storDir)); + $file->setDbFilepath($filePathRelativeToStor); + } + } + + $now = new DateTime("now", new DateTimeZone("UTC")); + $file->setDbMtime($now); + $file->save(); + + /* $this->removeEmptySubFolders( + isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."/srv/airtime/stor/organize/" : "/srv/airtime/stor/organize/"); */ + } else { + $file->setDbImportStatus(self::IMPORT_STATUS_FAILED)->save(); + throw new FileNotFoundException(); + } + + return CcFiles::sanitizeResponse($file); + } + + /** Delete a file from the database and disk (or cloud). + * @param $id The file ID + * @throws DeleteScheduledFileException + * @throws Exception + * @throws FileNoPermissionException + * @throws FileNotFoundException + * @throws PropelException + */ + public static function deleteById($id) + { + $file = CcFilesQuery::create()->findPk($id); + if ($file) { + $con = Propel::getConnection(); + $storedFile = new Application_Model_StoredFile($file, $con); + if ($storedFile->existsOnDisk()) { + $storedFile->delete(); //TODO: This checks your session permissions... Make it work without a session? + } + $file->delete(); + } else { + throw new FileNotFoundException(); + } + + } + + public static function getDownloadUrl($id) + { + $file = CcFilesQuery::create()->findPk($id); + if ($file) { + $con = Propel::getConnection(); + $storedFile = new Application_Model_StoredFile($file, $con); + $baseDir = Application_Common_OsPath::getBaseDir(); + + return $storedFile->getRelativeFileUrl($baseDir) . '/download/true'; + } + else { + throw new FileNotFoundException(); + } + } + + private static function validateFileArray(&$fileArray) + { + // Sanitize any wildly incorrect metadata before it goes to be validated + FileDataHelper::sanitizeData($fileArray); + + // EditAudioMD form is used here for validation + $fileForm = new Application_Form_EditAudioMD(); + $fileForm->startForm(0); //The file ID doesn't matter here + $fileForm->populate($fileArray); + + /* + * Here we are truncating metadata of any characters greater than the + * max string length set in the database. In the rare case a track's + * genre is more than 64 chars, for example, we don't want to reject + * tracks for that reason + */ + foreach($fileArray as $tag => &$value) { + if ($fileForm->getElement($tag)) { + $stringLengthValidator = $fileForm->getElement($tag)->getValidator('StringLength'); + //$stringLengthValidator will be false if the StringLength validator doesn't exist on the current element + //in which case we don't have to truncate the extra characters + if ($stringLengthValidator) { + $value = substr($value, 0, $stringLengthValidator->getMax()); + } + + $value = self::stripInvalidUtf8Characters($value); + } + } + + if (!$fileForm->isValidPartial($fileArray)) { + $errors = $fileForm->getErrors(); + $messages = $fileForm->getMessages(); + Logging::error($messages); + throw new Exception("Data validation failed: $errors - $messages"); + } + + return true; + } + + + public function getCueLength() { $cuein = $this->getDbCuein(); $cueout = $this->getDbCueout(); @@ -70,4 +341,83 @@ class CcFiles extends BaseCcFiles { return $response; } -} // CcFiles + + /** + * + * Strips out fields from incoming request data that should never be modified + * from outside of Airtime + * @param array $data + */ + private static function removeBlacklistedFields($data) + { + foreach (self::$blackList as $key) { + unset($data[$key]); + } + + return $data; + } + + + private static function getOwnerId() + { + try { + if (Zend_Auth::getInstance()->hasIdentity()) { + $service_user = new Application_Service_UserService(); + return $service_user->getCurrentUser()->getDbId(); + } else { + $defaultOwner = CcSubjsQuery::create() + ->filterByDbType('A') + ->orderByDbId() + ->findOne(); + if (!$defaultOwner) { + // what to do if there is no admin user? + // should we handle this case? + return null; + } + return $defaultOwner->getDbId(); + } + } catch(Exception $e) { + Logging::info($e->getMessage()); + } + } + /* + * It's possible that the year tag will be a timestamp but Airtime doesn't support this. + * The year field in cc_files can only be 16 chars max. + * + * This functions strips the year field of it's timestamp, if one, and leaves just the year + */ + private static function stripTimeStampFromYearTag($metadata) + { + if (isset($metadata["year"])) { + if (preg_match("/^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2}):(\d{2}))?$/", $metadata["year"])) { + $metadata["year"] = substr($metadata["year"], 0, 4); + } + } + return $metadata; + } + + private static function stripInvalidUtf8Characters($string) + { + //Remove invalid UTF-8 characters + //reject overly long 2 byte sequences, as well as characters above U+10000 and replace with ? + $string = preg_replace('/[\x00-\x08\x10\x0B\x0C\x0E-\x19\x7F]'. + '|[\x00-\x7F][\x80-\xBF]+'. + '|([\xC0\xC1]|[\xF0-\xFF])[\x80-\xBF]*'. + '|[\xC2-\xDF]((?![\x80-\xBF])|[\x80-\xBF]{2,})'. + '|[\xE0-\xEF](([\x80-\xBF](?![\x80-\xBF]))|(?![\x80-\xBF]{2})|[\x80-\xBF]{3,})/S', + '?', $string ); + + //reject overly long 3 byte sequences and UTF-16 surrogates and replace with ? + $string = preg_replace('/\xE0[\x80-\x9F][\x80-\xBF]'. + '|\xED[\xA0-\xBF][\x80-\xBF]/S','?', $string ); + + //Do a final encoding conversion to + $string = mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + return $string; + } + + private function removeEmptySubFolders($path) + { + exec("find $path -empty -type d -delete"); + } +} diff --git a/airtime_mvc/application/modules/rest/controllers/MediaController.php b/airtime_mvc/application/modules/rest/controllers/MediaController.php index 90587d480..08240dc80 100644 --- a/airtime_mvc/application/modules/rest/controllers/MediaController.php +++ b/airtime_mvc/application/modules/rest/controllers/MediaController.php @@ -2,26 +2,10 @@ class Rest_MediaController extends Zend_Rest_Controller { - const MUSIC_DIRS_STOR_PK = 1; - const IMPORT_STATUS_SUCCESS = 0; const IMPORT_STATUS_PENDING = 1; const IMPORT_STATUS_FAILED = 2; - //fields that are not modifiable via our RESTful API - private static $blackList = array( - 'id', - 'directory', - 'filepath', - 'file_exists', - 'mtime', - 'utime', - 'lptime', - 'silan_check', - 'soundcloud_id', - 'is_scheduled', - 'is_playlist' - ); public function init() { @@ -54,17 +38,19 @@ class Rest_MediaController extends Zend_Rest_Controller return; } - $file = CcFilesQuery::create()->findPk($id); - if ($file) { - $con = Propel::getConnection(); - $storedFile = new Application_Model_StoredFile($file, $con); - $baseUrl = Application_Common_OsPath::getBaseDir(); - + try + { $this->getResponse() ->setHttpResponseCode(200) - ->appendBody($this->_redirect($storedFile->getRelativeFileUrl($baseUrl).'/download/true')); - } else { + ->appendBody($this->_redirect(CcFiles::getDownloadUrl($id))); + } + catch (FileNotFoundException $e) { $this->fileNotFoundResponse(); + Logging::error($e->getMessage()); + } + catch (Exception $e) { + $this->unknownErrorResponse(); + Logging::error($e->getMessage()); } } @@ -75,14 +61,18 @@ class Rest_MediaController extends Zend_Rest_Controller return; } - $file = CcFilesQuery::create()->findPk($id); - if ($file) { - + try { $this->getResponse() ->setHttpResponseCode(200) - ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); - } else { + ->appendBody(json_encode(CcFiles::getSantiziedFileById($id))); + } + catch (FileNotFoundException $e) { $this->fileNotFoundResponse(); + Logging::error($e->getMessage()); + } + catch (Exception $e) { + $this->unknownErrorResponse(); + Logging::error($e->getMessage()); } } @@ -97,52 +87,24 @@ class Rest_MediaController extends Zend_Rest_Controller return; } - if (Application_Model_Systemstatus::isDiskOverQuota()) { + try { + $sanitizedFile = CcFiles::createFromUpload($this->getRequest()->getPost()); + $this->getResponse() + ->setHttpResponseCode(201) + ->appendBody(json_encode($sanitizedFile)); + } + catch (InvalidMetadataException $e) { + $this->invalidDataResponse(); + Logging::error($e->getMessage()); + } + catch (OverDiskQuotaException $e) { $this->getResponse() ->setHttpResponseCode(400) ->appendBody("ERROR: Disk Quota reached."); - return; } - - $file = new CcFiles(); - $whiteList = $this->removeBlacklistedFieldsFromRequestData($this->getRequest()->getPost()); - - if (!$this->validateRequestData($file, $whiteList)) { - $file->setDbTrackTitle($_FILES["file"]["name"]); - $file->setDbUtime(new DateTime("now", new DateTimeZone("UTC"))); - $file->save(); - return; - } else { - /* If full_path is set, the post request came from ftp. - * Users are allowed to upload folders via ftp. If this is the case - * we need to include the folder name with the file name, otherwise - * files won't get removed from the organize folder. - */ - if (isset($whiteList["full_path"])) { - $fullPath = $whiteList["full_path"]; - $basePath = isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."/srv/airtime/stor/organize/" : "/srv/airtime/stor/organize/"; - //$relativePath is the folder name(if one) + track name, that was uploaded via ftp - $relativePath = substr($fullPath, strlen($basePath)-1); - } else { - $relativePath = $_FILES["file"]["name"]; - } - - - $file->fromArray($whiteList); - $file->setDbOwnerId($this->getOwnerId()); - $now = new DateTime("now", new DateTimeZone("UTC")); - $file->setDbTrackTitle($_FILES["file"]["name"]); - $file->setDbUtime($now); - $file->setDbHidden(true); - $file->save(); - - $callbackUrl = $this->getRequest()->getScheme() . '://' . $this->getRequest()->getHttpHost() . $this->getRequest()->getRequestUri() . "/" . $file->getPrimaryKey(); - - $this->processUploadedFile($callbackUrl, $relativePath, $this->getOwnerId()); - - $this->getResponse() - ->setHttpResponseCode(201) - ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); + catch (Exception $e) { + $this->unknownErrorResponse(); + Logging::error($e->getMessage()); } } @@ -152,90 +114,25 @@ class Rest_MediaController extends Zend_Rest_Controller if (!$id) { return; } - - $file = CcFilesQuery::create()->findPk($id); - // Since we check for this value when deleting files, set it first - $file->setDbDirectory(self::MUSIC_DIRS_STOR_PK); - $requestData = json_decode($this->getRequest()->getRawBody(), true); - $whiteList = $this->removeBlacklistedFieldsFromRequestData($requestData); - $whiteList = $this->stripTimeStampFromYearTag($whiteList); - - if (!$this->validateRequestData($file, $whiteList)) { - $file->save(); - return; - } else if ($file && isset($requestData["resource_id"])) { - - $file->fromArray($whiteList, BasePeer::TYPE_FIELDNAME); - - //store the original filename - $file->setDbFilepath($requestData["filename"]); - - $fileSizeBytes = $requestData["filesize"]; - if (!isset($fileSizeBytes) || $fileSizeBytes === false) - { - $file->setDbImportStatus(2)->save(); - $this->fileNotFoundResponse(); - return; - } - $cloudFile = new CloudFile(); - $cloudFile->setStorageBackend($requestData["storage_backend"]); - $cloudFile->setResourceId($requestData["resource_id"]); - $cloudFile->setCcFiles($file); - $cloudFile->save(); - - Application_Model_Preference::updateDiskUsage($fileSizeBytes); - - $now = new DateTime("now", new DateTimeZone("UTC")); - $file->setDbMtime($now); - $file->save(); - + try { + $requestData = json_decode($this->getRequest()->getRawBody(), true); + $sanitizedFile = CcFiles::updateFromArray($id, $requestData); $this->getResponse() - ->setHttpResponseCode(200) - ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); - } else if ($file) { - - $file->fromArray($whiteList, BasePeer::TYPE_FIELDNAME); - - //Our RESTful API takes "full_path" as a field, which we then split and translate to match - //our internal schema. Internally, file path is stored relative to a directory, with the directory - //as a foreign key to cc_music_dirs. - if (isset($requestData["full_path"])) { - $fileSizeBytes = filesize($requestData["full_path"]); - if (!isset($fileSizeBytes) || $fileSizeBytes === false) - { - $file->setDbImportStatus(self::IMPORT_STATUS_FAILED)->save(); - $this->fileNotFoundResponse(); - return; - } - Application_Model_Preference::updateDiskUsage($fileSizeBytes); - - $fullPath = $requestData["full_path"]; - $storDir = Application_Model_MusicDir::getStorDir()->getDirectory(); - $pos = strpos($fullPath, $storDir); - - if ($pos !== FALSE) - { - assert($pos == 0); //Path must start with the stor directory path - - $filePathRelativeToStor = substr($fullPath, strlen($storDir)); - $file->setDbFilepath($filePathRelativeToStor); - } - } - - $now = new DateTime("now", new DateTimeZone("UTC")); - $file->setDbMtime($now); - $file->save(); - - /* $this->removeEmptySubFolders( - isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."/srv/airtime/stor/organize/" : "/srv/airtime/stor/organize/"); */ - - $this->getResponse() - ->setHttpResponseCode(200) - ->appendBody(json_encode(CcFiles::sanitizeResponse($file))); - } else { - $file->setDbImportStatus(self::IMPORT_STATUS_FAILED)->save(); + ->setHttpResponseCode(201) + ->appendBody(json_encode($sanitizedFile)); + } + catch (InvalidMetadataException $e) { + $this->invalidDataResponse(); + Logging::error($e->getMessage()); + } + catch (FileNotFoundException $e) { $this->fileNotFoundResponse(); + Logging::error($e->getMessage()); + } + catch (Exception $e) { + $this->unknownErrorResponse(); + Logging::error($e->getMessage()); } } @@ -245,18 +142,19 @@ class Rest_MediaController extends Zend_Rest_Controller if (!$id) { return; } - $file = CcFilesQuery::create()->findPk($id); - if ($file) { - $con = Propel::getConnection(); - $storedFile = new Application_Model_StoredFile($file, $con); - if ($storedFile->existsOnDisk()) { - $storedFile->delete(); //TODO: This checks your session permissions... Make it work without a session? - } - $file->delete(); + + try { + CcFiles::deleteById($id); $this->getResponse() ->setHttpResponseCode(204); - } else { + } + catch (FileNotFoundException $e) { $this->fileNotFoundResponse(); + Logging::error($e->getMessage()); + } + catch (Exception $e) { + $this->unknownErrorResponse(); + Logging::error($e->getMessage()); } } @@ -278,174 +176,11 @@ class Rest_MediaController extends Zend_Rest_Controller $resp->appendBody("ERROR: Media not found."); } - private function invalidDataResponse() + private function unknownErrorResponse() { $resp = $this->getResponse(); - $resp->setHttpResponseCode(422); - $resp->appendBody("ERROR: Invalid data"); - } - - private function validateRequestData($file, &$whiteList) - { - // Sanitize any wildly incorrect metadata before it goes to be validated - FileDataHelper::sanitizeData($whiteList); - - try { - // EditAudioMD form is used here for validation - $fileForm = new Application_Form_EditAudioMD(); - $fileForm->startForm($file->getDbId()); - $fileForm->populate($whiteList); - - /* - * Here we are truncating metadata of any characters greater than the - * max string length set in the database. In the rare case a track's - * genre is more than 64 chars, for example, we don't want to reject - * tracks for that reason - */ - foreach($whiteList as $tag => &$value) { - if ($fileForm->getElement($tag)) { - $stringLengthValidator = $fileForm->getElement($tag)->getValidator('StringLength'); - //$stringLengthValidator will be false if the StringLength validator doesn't exist on the current element - //in which case we don't have to truncate the extra characters - if ($stringLengthValidator) { - $value = substr($value, 0, $stringLengthValidator->getMax()); - } - - $value = $this->stripInvalidUtf8Characters($value); - } - } - - if (!$fileForm->isValidPartial($whiteList)) { - throw new Exception("Data validation failed"); - } - } catch (Exception $e) { - $errors = $fileForm->getErrors(); - $messages = $fileForm->getMessages(); - Logging::error($messages); - $file->setDbImportStatus(2); - $file->setDbHidden(true); - $this->invalidDataResponse(); - return false; - } - return true; - } - - private function processUploadedFile($callbackUrl, $originalFilename, $ownerId) - { - $CC_CONFIG = Config::getConfig(); - $apiKey = $CC_CONFIG["apiKey"][0]; - - $tempFilePath = $_FILES['file']['tmp_name']; - $tempFileName = basename($tempFilePath); - - //Only accept files with a file extension that we support. - $fileExtension = pathinfo($originalFilename, PATHINFO_EXTENSION); - if (!in_array(strtolower($fileExtension), explode(",", "ogg,mp3,oga,flac,wav,m4a,mp4,opus"))) - { - @unlink($tempFilePath); - throw new Exception("Bad file extension."); - } - - //TODO: Remove uploadFileAction from ApiController.php **IMPORTANT** - It's used by the recorder daemon... - - $storDir = Application_Model_MusicDir::getStorDir(); - $importedStorageDirectory = $storDir->getDirectory() . "/imported/" . $ownerId; - - try { - //Copy the temporary file over to the "organize" folder so that it's off our webserver - //and accessible by airtime_analyzer which could be running on a different machine. - $newTempFilePath = Application_Model_StoredFile::copyFileToStor($tempFilePath, $originalFilename); - } catch (Exception $e) { - @unlink($tempFilePath); - Logging::error($e->getMessage()); - return; - } - - //Dispatch a message to airtime_analyzer through RabbitMQ, - //notifying it that there's a new upload to process! - Application_Model_RabbitMq::SendMessageToAnalyzer($newTempFilePath, - $importedStorageDirectory, basename($originalFilename), - $callbackUrl, $apiKey); - } - - private function getOwnerId() - { - try { - if (Zend_Auth::getInstance()->hasIdentity()) { - $service_user = new Application_Service_UserService(); - return $service_user->getCurrentUser()->getDbId(); - } else { - $defaultOwner = CcSubjsQuery::create() - ->filterByDbType('A') - ->orderByDbId() - ->findOne(); - if (!$defaultOwner) { - // what to do if there is no admin user? - // should we handle this case? - return null; - } - return $defaultOwner->getDbId(); - } - } catch(Exception $e) { - Logging::info($e->getMessage()); - } - } - - /** - * - * Strips out fields from incoming request data that should never be modified - * from outside of Airtime - * @param array $data - */ - private static function removeBlacklistedFieldsFromRequestData($data) - { - foreach (self::$blackList as $key) { - unset($data[$key]); - } - - return $data; - } - - - private function removeEmptySubFolders($path) - { - exec("find $path -empty -type d -delete"); - } - - /* - * It's possible that the year tag will be a timestamp but Airtime doesn't support this. - * The year field in cc_files can only be 16 chars max. - * - * This functions strips the year field of it's timestamp, if one, and leaves just the year - */ - private function stripTimeStampFromYearTag($metadata) - { - if (isset($metadata["year"])) { - if (preg_match("/^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2}):(\d{2}))?$/", $metadata["year"])) { - $metadata["year"] = substr($metadata["year"], 0, 4); - } - } - return $metadata; - } - - private function stripInvalidUtf8Characters($string) - { - //Remove invalid UTF-8 characters - //reject overly long 2 byte sequences, as well as characters above U+10000 and replace with ? - $string = preg_replace('/[\x00-\x08\x10\x0B\x0C\x0E-\x19\x7F]'. - '|[\x00-\x7F][\x80-\xBF]+'. - '|([\xC0\xC1]|[\xF0-\xFF])[\x80-\xBF]*'. - '|[\xC2-\xDF]((?![\x80-\xBF])|[\x80-\xBF]{2,})'. - '|[\xE0-\xEF](([\x80-\xBF](?![\x80-\xBF]))|(?![\x80-\xBF]{2})|[\x80-\xBF]{3,})/S', - '?', $string ); - - //reject overly long 3 byte sequences and UTF-16 surrogates and replace with ? - $string = preg_replace('/\xE0[\x80-\x9F][\x80-\xBF]'. - '|\xED[\xA0-\xBF][\x80-\xBF]/S','?', $string ); - - //Do a final encoding conversion to - $string = mb_convert_encoding($string, 'UTF-8', 'UTF-8'); - return $string; + $resp->setHttpResponseCode(400); + $resp->appendBody("An unknown error occurred."); } } diff --git a/airtime_mvc/application/services/MediaService.php b/airtime_mvc/application/services/MediaService.php new file mode 100644 index 000000000..74b2609d0 --- /dev/null +++ b/airtime_mvc/application/services/MediaService.php @@ -0,0 +1,41 @@ +getDirectory() . "/imported/" . $ownerId; + + try { + //Copy the temporary file over to the "organize" folder so that it's off our webserver + //and accessible by airtime_analyzer which could be running on a different machine. + $newTempFilePath = Application_Model_StoredFile::copyFileToStor($tempFilePath, $originalFilename); + } catch (Exception $e) { + @unlink($tempFilePath); + Logging::error($e->getMessage()); + return; + } + + //Dispatch a message to airtime_analyzer through RabbitMQ, + //notifying it that there's a new upload to process! + Application_Model_RabbitMq::SendMessageToAnalyzer($newTempFilePath, + $importedStorageDirectory, basename($originalFilename), + $callbackUrl, $apiKey); + } +} \ No newline at end of file diff --git a/airtime_mvc/public/index.php b/airtime_mvc/public/index.php index f9c691f51..c45ac3b1d 100644 --- a/airtime_mvc/public/index.php +++ b/airtime_mvc/public/index.php @@ -49,6 +49,9 @@ set_include_path(APPLICATION_PATH . '/models' . PATH_SEPARATOR . get_include_pat //Controller plugins. set_include_path(APPLICATION_PATH . '/controllers/plugins' . PATH_SEPARATOR . get_include_path()); +//Services. +set_include_path(APPLICATION_PATH . '/services/' . PATH_SEPARATOR . get_include_path()); + //Zend framework if (file_exists('/usr/share/php/libzend-framework-php')) { set_include_path('/usr/share/php/libzend-framework-php' . PATH_SEPARATOR . get_include_path());