More refactoring: Allow files to be imported from disk directly

This commit is contained in:
Albert Santoni 2015-02-20 16:36:36 -05:00
parent 2a89e4d5a0
commit 84f0f135ef
4 changed files with 137 additions and 134 deletions

View File

@ -940,19 +940,17 @@ SQL;
* The file is actually copied to "stor/organize", which is a staging directory where files go
* before they're processed by airtime_analyzer, which then moves them to "stor/imported" in the final
* step.
*
* TODO: Implement better error handling here...
*
* @param string $tempFilePath
* @param string $originalFilename
* @param bool $copyFile Copy the file instead of moving it.
* @throws Exception
* @return Ambigous <unknown, string>
*/
public static function copyFileToStor($tempFilePath, $originalFilename)
public static function moveFileToStor($tempFilePath, $originalFilename, $copyFile=false)
{
$audio_file = $tempFilePath;
Logging::info('copyFileToStor: moving file '.$audio_file);
$storDir = Application_Model_MusicDir::getStorDir();
$stor = $storDir->getDirectory();
// check if "organize" dir exists and if not create one
@ -966,55 +964,35 @@ SQL;
Logging::info("Warning: couldn't change permissions of $audio_file to 0644");
}
// Check if liquidsoap can play this file
// TODO: Move this to airtime_analyzer
/*
if (!self::liquidsoapFilePlayabilityTest($audio_file)) {
return array(
"code" => 110,
"message" => _("This file appears to be corrupted and will not "
."be added to media library."));
}*/
// Did all the checks for real, now trying to copy
$audio_stor = Application_Common_OsPath::join($stor, "organize",
$originalFilename);
$user = Application_Model_User::getCurrentUser();
if (is_null($user)) {
$uid = Application_Model_User::getFirstAdminId();
} else {
$uid = $user->getId();
}
/*
$id_file = "$audio_stor.identifier";
if (file_put_contents($id_file, $uid) === false) {
Logging::info("Could not write file to identify user: '$uid'");
Logging::info("Id file path: '$id_file'");
Logging::info("Defaulting to admin (no identification file was
written)");
} else {
Logging::info("Successfully written identification file for
uploaded '$audio_stor'");
}*/
//if the uploaded file is not UTF-8 encoded, let's encode it. Assuming source
//encoding is ISO-8859-1
$audio_stor = mb_detect_encoding($audio_stor, "UTF-8") == "UTF-8" ? $audio_stor : utf8_encode($audio_stor);
Logging::info("copyFileToStor: moving file $audio_file to $audio_stor");
// Martin K.: changed to rename: Much less load + quicker since this is
// an atomic operation
if (@rename($audio_file, $audio_stor) === false) {
//something went wrong likely there wasn't enough space in .
//the audio_stor to move the file too warn the user that .
//the file wasn't uploaded and they should check if there .
//is enough disk space .
unlink($audio_file); //remove the file after failed rename
//unlink($id_file); // Also remove the identifier file
throw new Exception("The file was not uploaded, this error can occur if the computer "
."hard drive does not have enough disk space or the stor "
."directory does not have correct write permissions.");
if ($copyFile) {
Logging::info("Copying file $audio_file to $audio_stor");
if (@copy($audio_file, $audio_stor) === false) {
throw new Exception("Failed to copy $audio_file to $audio_stor");
}
} else {
Logging::info("Moving file $audio_file to $audio_stor");
// Martin K.: changed to rename: Much less load + quicker since this is
// an atomic operation
if (@rename($audio_file, $audio_stor) === false) {
//something went wrong likely there wasn't enough space in .
//the audio_stor to move the file too warn the user that .
//the file wasn't uploaded and they should check if there .
//is enough disk space .
unlink($audio_file); //remove the file after failed rename
//unlink($id_file); // Also remove the identifier file
throw new Exception("The file was not uploaded, this error can occur if the computer "
. "hard drive does not have enough disk space or the stor "
. "directory does not have correct write permissions.");
}
}
return $audio_stor;
}

View File

@ -29,6 +29,10 @@ class CcFiles extends BaseCcFiles {
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(
@ -78,57 +82,104 @@ class CcFiles extends BaseCcFiles {
throw new OverDiskQuotaException();
}
$file = new CcFiles();
/* 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.
*/
try{
$fileArray = self::removeBlacklistedFields($fileArray);
//Extract the relative path to the temporary uploaded file on disk.
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
$filePathRelativeToOrganize = substr($fullPath, strlen($basePath)-1);
$originalFilename = $filePathRelativeToOrganize;
} else {
//Extract the original filename, which we set as the temporary title for the track
//until it's finished being processed by the analyzer.
$originalFilename = $_FILES["file"]["name"];
}
/*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);
$tempFilePath = $_FILES['file']['tmp_name'];
/* 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);
try {
self::createAndImport($fileArray, $tempFilePath, $originalFilename);
} catch (Exception $e)
{
@unlink($tempFilePath);
throw $e;
}
}
/** Import a music file to the library from a local file on disk (something pre-existing).
* This function allows you to copy a file rather than move it, which is useful for importing
* static music files (like sample tracks).
* @param string $filePath The full path to the audio file to import.
* @param bool $copyFile True if you want to just copy the false, false if you want to move it (default false)
* @throws Exception
*/
public static function createFromLocalFile($filePath, $copyFile=false)
{
$fileArray = array();
$info = pathinfo($filePath);
$fileName = basename($filePath).'.'.$info['extension'];
self::createAndImport($fileArray, $filePath, $fileName, $copyFile);
}
/** Create a new CcFiles object/row and import a file for it.
* You shouldn't call this directly. Either use createFromUpload() or createFromLocalFile().
* @param array $fileArray Any metadata to pre-fill for the audio file
* @param string $filePath The full path to the audio file to import
* @param string $originalFilename
* @param bool $copyFile
* @return mixed
* @throws Exception
* @throws PropelException
*/
private static function createAndImport($fileArray, $filePath, $originalFilename, $copyFile=false)
{
$file = new CcFiles();
try
{
$fileArray = self::removeBlacklistedFields($fileArray);
self::validateFileArray($fileArray);
$file->fromArray($fileArray);
$file->setDbOwnerId(self::getOwnerId());
$now = new DateTime("now", new DateTimeZone("UTC"));
$file->setDbTrackTitle($originalFilename);
$file->setDbUtime($now);
$file->setDbHidden(true);
$file->save();
//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"))) {
throw new Exception("Bad file extension.");
}
$callbackUrl = Application_Common_HTTPHelper::getStationUrl() . "rest/media/" . $file->getPrimaryKey();
Application_Service_MediaService::importFileToLibrary($callbackUrl, $filePath,
$originalFilename, self::getOwnerId(), $copyFile);
return CcFiles::sanitizeResponse($file);
} catch (Exception $e) {
$file->setDbImportStatus(self::IMPORT_STATUS_FAILED);
$file->setDbHidden(true);
$file->save();
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.
* @param $fileId string The ID of the file to update in the DB.
* @param $fileArray array 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
@ -238,22 +289,6 @@ class CcFiles extends BaseCcFiles {
}
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
@ -330,7 +365,7 @@ class CcFiles extends BaseCcFiles {
/**
*
* Strips out the private fields we do not want to send back in API responses
* @param $file a CcFiles object
* @param $file string a CcFiles object
*/
//TODO: rename this function?
public static function sanitizeResponse($file)

View File

@ -2,10 +2,6 @@
class Rest_MediaController extends Zend_Rest_Controller
{
const IMPORT_STATUS_SUCCESS = 0;
const IMPORT_STATUS_PENDING = 1;
const IMPORT_STATUS_FAILED = 2;
public function init()
{

View File

@ -4,38 +4,30 @@ require_once('ProxyStorageBackend.php');
class Application_Service_MediaService
{
public static function processUploadedFile($callbackUrl, $originalFilename, $ownerId)
/** Move (or copy) a file to the stor/organize directory and send it off to the
analyzer to be processed.
* @param $callbackUrl
* @param $filePath string Path to the local file to import to the library
* @param $originalFilename string The original filename, if you want it to be preserved after import.
* @param $ownerId string The ID of the user that will own the file inside Airtime.
* @param $copyFile bool True if you want to copy the file to the "organize" directory, false if you want to move it (default)
* @return Ambigous
* @throws Exception
*/
public static function importFileToLibrary($callbackUrl, $filePath, $originalFilename, $ownerId, $copyFile)
{
$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...
$importedStorageDirectory = "";
if ($CC_CONFIG["current_backend"] == "file") {
$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;
}
//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::moveFileToStor($filePath, $originalFilename, $copyFile);
//Dispatch a message to airtime_analyzer through RabbitMQ,
//notifying it that there's a new upload to process!
@ -45,6 +37,8 @@ class Application_Service_MediaService
$callbackUrl, $apiKey,
$CC_CONFIG["current_backend"],
$storageBackend->getFilePrefix());
return $newTempFilePath;
}