Refactored file storage code slightly to allow multiple download URLs

This commit is contained in:
Albert Santoni 2015-03-30 11:31:07 -04:00
parent 271dc266fa
commit d31de0937f
11 changed files with 78 additions and 35 deletions

View File

@ -41,15 +41,19 @@ class Amazon_S3StorageBackend extends StorageBackend
return $this->s3Client->getObjectUrl($this->getBucket(), $resourceId);
}
public function getSignedURL($resourceId)
/** Returns a signed download URL from Amazon S3, expiring in 60 minutes */
public function getDownloadURLs($resourceId)
{
$url = $this->s3Client->getObjectUrl($this->getBucket(), $resourceId, '+60 minutes');
$urls = array();
$signedS3Url = $this->s3Client->getObjectUrl($this->getBucket(), $resourceId, '+60 minutes');
//If we're using the proxy cache, we need to modify the request URL after it has
//been generated by the above. (The request signature must be for the amazonaws.com,
//not our proxy, since the proxy translates the host back to amazonaws.com)
if ($this->proxyHost) {
$p = parse_url($url);
$p = parse_url($signedS3Url);
$p["host"] = $this->getBucket() . "." . $this->proxyHost;
$p["scheme"] = "http";
//If the path contains the bucket name (which is the case with HTTPS requests to Amazon),
@ -60,13 +64,19 @@ class Amazon_S3StorageBackend extends StorageBackend
if (strpos($p["path"], $this->getBucket()) == 1) {
$p["path"] = substr($p["path"], 1 + strlen($this->getBucket()));
}
$url = $p["scheme"] . "://" . $p["host"] . $p["path"] . "?" . $p["query"];
$proxyUrl = $p["scheme"] . "://" . $p["host"] . $p["path"] . "?" . $p["query"];
//Add this proxy cache URL to the list of download URLs.
array_push($urls, $proxyUrl);
}
//Add the direct S3 URL to the list (as a fallback)
array_push($urls, $signedS3Url);
//http_build_url() would be nice to use but it requires pecl_http :-(
Logging::info($url);
//Logging::info($url);
return $url;
return $urls;
}
public function deletePhysicalFile($resourceId)

View File

@ -13,7 +13,7 @@ class FileStorageBackend extends StorageBackend
return $resourceId;
}
public function getSignedURL($resourceId)
public function getDownloadURLs($resourceId)
{
return "";
}

View File

@ -38,9 +38,9 @@ class ProxyStorageBackend extends StorageBackend
return $this->storageBackend->getAbsoluteFilePath($resourceId);
}
public function getSignedURL($resourceId)
public function getDownloadURLs($resourceId)
{
return $this->storageBackend->getSignedURL($resourceId);
return $this->storageBackend->getDownloadURLs($resourceId);
}
public function deletePhysicalFile($resourceId)

View File

@ -15,7 +15,7 @@ abstract class StorageBackend
/** Returns the file object's signed URL. The URL must be signed since they
* privately stored on the storage backend. */
abstract public function getSignedURL($resourceId);
abstract public function getDownloadURLs($resourceId);
/** Deletes the file from the storage backend. */
abstract public function deletePhysicalFile($resourceId);

View File

@ -10,7 +10,7 @@ class Application_Common_FileIO
*
* This HTTP_RANGE compatible read file function is necessary for allowing streaming media to be skipped around in.
*
* @param string $filePath - the full filepath pointing to the location of the file
* @param string $filePath - the full filepath or URL pointing to the location of the file
* @param string $mimeType - the file's mime type. Defaults to 'audio/mp3'
* @param integer $size - the file size, in bytes
* @return void
@ -22,8 +22,7 @@ class Application_Common_FileIO
{
$fm = @fopen($filePath, 'rb');
if (!$fm) {
header ("HTTP/1.1 505 Internal server error");
return;
throw new FileNotFoundException($filePath);
}
//Note that $size is allowed to be zero. If that's the case, it means we don't

View File

@ -1073,7 +1073,9 @@ class ApiController extends Zend_Controller_Action
$dir->getId(),$all=false);
foreach ($files as $f) {
// if the file is from this mount
if (substr($f->getFilePath(), 0, strlen($rd)) === $rd) {
$filePaths = $f->getFilePaths();
$filePath = $filePaths[0];
if (substr($filePath, 0, strlen($rd)) === $rd) {
$f->delete();
}
}

View File

@ -138,8 +138,11 @@ SQL;
if (isset($file_id)) {
$file = Application_Model_StoredFile::RecallById($file_id);
if (isset($file) && file_exists($file->getFilePath())) {
return $file;
if (isset($file)) {
$filePaths = $file->getFilePaths();
if (file_exists($filePaths[0])) {
return $file;
}
}
}

View File

@ -362,8 +362,9 @@ SQL;
{
$exists = false;
try {
$filePath = $this->getFilePath();
$exists = (file_exists($this->getFilePath()) && !is_dir($filePath));
$filePaths = $this->getFilePaths();
$filePath = $filePaths[0];
$exists = (file_exists($filePath) && !is_dir($filePath));
} catch (Exception $e) {
return false;
}
@ -444,8 +445,6 @@ SQL;
*/
public function deleteByMediaMonitor($deleteFromPlaylist=false)
{
$filepath = $this->getFilePath();
if ($deleteFromPlaylist) {
Application_Model_Playlist::DeleteFileFromAllPlaylists($this->getId());
}
@ -499,13 +498,13 @@ SQL;
/**
* Get the absolute filepath
*
* @return string
* @return array of strings
*/
public function getFilePath()
public function getFilePaths()
{
assert($this->_file);
return $this->_file->getURLForTrackPreviewOrDownload();
return $this->_file->getURLsForTrackPreviewOrDownload();
}
/**
@ -1238,9 +1237,11 @@ SQL;
$genre = $file->getDbGenre();
$release = $file->getDbUtime();
try {
$filePaths = $this->getFilePaths();
$filePath = $filePaths[0];
$soundcloud = new Application_Model_Soundcloud();
$soundcloud_res = $soundcloud->uploadTrack(
$this->getFilePath(), $this->getName(), $description,
$filePath, $this->getName(), $description,
$tag, $release, $genre);
$this->setSoundCloudFileId($soundcloud_res['id']);
$this->setSoundCloudLinkToFile($soundcloud_res['permalink_url']);

View File

@ -386,9 +386,9 @@ class CcFiles extends BaseCcFiles {
/**
* Returns the file's absolute file path stored on disk.
*/
public function getURLForTrackPreviewOrDownload()
public function getURLsForTrackPreviewOrDownload()
{
return $this->getAbsoluteFilePath();
return array($this->getAbsoluteFilePath());
}
/**

View File

@ -27,12 +27,12 @@ class CloudFile extends BaseCloudFile
* requesting the file's object via this URL, it needs to be signed because
* all objects stored on Amazon S3 are private.
*/
public function getURLForTrackPreviewOrDownload()
public function getURLsForTrackPreviewOrDownload()
{
if ($this->proxyStorageBackend == null) {
$this->proxyStorageBackend = new ProxyStorageBackend($this->getStorageBackend());
}
return $this->proxyStorageBackend->getSignedURL($this->getResourceId());
return $this->proxyStorageBackend->getDownloadURLs($this->getResourceId());
}
/**

View File

@ -55,10 +55,11 @@ class Application_Service_MediaService
if ($media == null) {
throw new FileNotFoundException();
}
$filepath = $media->getFilePath();
// Make sure we don't have some wrong result beecause of caching
// Make sure we don't have some wrong result because of caching
clearstatcache();
$filePath = "";
if ($media->getPropelOrm()->isValidPhysicalFile()) {
$filename = $media->getPropelOrm()->getFilename();
//Download user left clicks a track and selects Download.
@ -71,13 +72,40 @@ class Application_Service_MediaService
header('Content-Disposition: inline; filename="' . $filename . '"');
}
$filepath = $media->getFilePath();
$size= $media->getFileSize();
$mimeType = $media->getPropelOrm()->getDbMime();
Application_Common_FileIO::smartReadFile($filepath, $size, $mimeType);
/*
In this block of code below, we're getting the list of download URLs for a track
and then streaming the file as the response. A file can be stored in more than one location,
with the alternate locations used as a fallback, so that's why we're looping until we
are able to actually send the file.
This mechanism is used to try fetching our file from our internal S3 caching proxy server first.
If the file isn't found there (or the cache is down), then we attempt to download the file
directly from Amazon S3. We do this to save bandwidth costs!
*/
$filePaths = $media->getFilePaths();
assert(is_array($filePaths));
do {
//Read from $filePath and stream it to the browser.
$filePath = array_shift($filePaths);
try {
$size= $media->getFileSize();
$mimeType = $media->getPropelOrm()->getDbMime();
Application_Common_FileIO::smartReadFile($filePath, $size, $mimeType);
} catch (FileNotFoundException $e) {
//If we have no alternate filepaths left, then let the exception bubble up.
if (sizeof($filePaths) == 0) {
throw $e;
}
}
//Retry with the next alternate filepath in the list
} while (sizeof($filePaths) > 0);
exit;
} else {
throw new FileNotFoundException();
throw new FileNotFoundException($filePath);
}
}