SAAS-1071 - more work on backend podcast implementation
This commit is contained in:
parent
dcb30b3aa7
commit
ca51dcf3ae
|
@ -38,7 +38,6 @@ class Podcast extends BasePodcast
|
|||
* @throws InvalidPodcastException
|
||||
* @throws PodcastLimitReachedException
|
||||
*/
|
||||
|
||||
public static function create($data)
|
||||
{
|
||||
if (Application_Service_PodcastService::podcastLimitReached()) {
|
||||
|
@ -98,24 +97,11 @@ class Podcast extends BasePodcast
|
|||
$podcast->setDbType(IMPORTED_PODCAST);
|
||||
$podcast->save();
|
||||
|
||||
$podcastArray = $podcast->toArray(BasePeer::TYPE_FIELDNAME);
|
||||
|
||||
$podcastArray["episodes"] = array();
|
||||
foreach ($rss->get_items() as $item) {
|
||||
array_push($podcastArray["episodes"], array(
|
||||
"title" => $item->get_title(),
|
||||
"author" => $item->get_author()->get_name(),
|
||||
"description" => $item->get_description(),
|
||||
"pubDate" => $item->get_date("Y-m-d H:i:s"),
|
||||
"link" => $item->get_enclosure()->get_link()
|
||||
));
|
||||
}
|
||||
return $podcastArray;
|
||||
return self::_generatePodcastArray($podcast, $rss);
|
||||
} catch(Exception $e) {
|
||||
$podcast->delete();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,6 +111,7 @@ class Podcast extends BasePodcast
|
|||
* @param $podcastId
|
||||
*
|
||||
* @throws PodcastNotFoundException
|
||||
* @throws InvalidPodcastException
|
||||
* @return array - Podcast Array with a full list of episodes
|
||||
*/
|
||||
public static function getPodcastById($podcastId)
|
||||
|
@ -136,10 +123,22 @@ class Podcast extends BasePodcast
|
|||
|
||||
$rss = Application_Service_PodcastService::getPodcastFeed($podcast->getDbUrl());
|
||||
if (!$rss) {
|
||||
throw new PodcastNotFoundException();
|
||||
throw new InvalidPodcastException();
|
||||
}
|
||||
|
||||
// FIXME: Get rid of this duplication and move into a new function (serializer/deserializer)
|
||||
return self::_generatePodcastArray($podcast, $rss);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a podcast object and a SimplePie feed object,
|
||||
* generate a data array to pass back to the front-end
|
||||
*
|
||||
* @param Podcast $podcast Podcast model object
|
||||
* @param SimplePie $rss SimplePie feed object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function _generatePodcastArray($podcast, $rss) {
|
||||
$podcastArray = $podcast->toArray(BasePeer::TYPE_FIELDNAME);
|
||||
|
||||
$podcastArray["episodes"] = array();
|
||||
|
@ -148,8 +147,9 @@ class Podcast extends BasePodcast
|
|||
"title" => $item->get_title(),
|
||||
"author" => $item->get_author()->get_name(),
|
||||
"description" => $item->get_description(),
|
||||
"pubDate" => $item->get_date("Y-m-d H:i:s"),
|
||||
"link" => $item->get_enclosure()->get_link()
|
||||
"pub_date" => $item->get_date("Y-m-d H:i:s"),
|
||||
"link" => $item->get_link(),
|
||||
"enclosure" => $item->get_enclosure()
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
class Rest_PodcastController extends Zend_Rest_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Application_Service_PodcastService
|
||||
*/
|
||||
protected $_service;
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->view->layout()->disableLayout();
|
||||
|
@ -9,6 +15,7 @@ class Rest_PodcastController extends Zend_Rest_Controller
|
|||
// Remove reliance on .phtml files to render requests
|
||||
$this->_helper->viewRenderer->setNoRender(true);
|
||||
$this->view->setScriptPath(APPLICATION_PATH . 'views/scripts/');
|
||||
$this->_service = new Application_Service_PodcastService();
|
||||
}
|
||||
|
||||
public function indexAction()
|
||||
|
@ -119,6 +126,7 @@ class Rest_PodcastController extends Zend_Rest_Controller
|
|||
try {
|
||||
$requestData = json_decode($this->getRequest()->getRawBody(), true);
|
||||
|
||||
$this->_service->downloadEpisodes($requestData["podcast"]["episodes"]);
|
||||
$podcast = Podcast::updateFromArray($id, $requestData);
|
||||
|
||||
$this->getResponse()
|
||||
|
|
|
@ -56,7 +56,7 @@ class Application_Service_PodcastService extends Application_Service_ThirdPartyC
|
|||
$feed->enable_cache(false);
|
||||
$feed->init();
|
||||
return $feed;
|
||||
} catch (FeedException $e) {
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -66,15 +66,28 @@ class Application_Service_PodcastService extends Application_Service_ThirdPartyC
|
|||
|
||||
}
|
||||
/**
|
||||
* Given an array of track identifiers, download RSS feed tracks
|
||||
* Given an array of episodes, extract the download URLs and send them to Celery
|
||||
*
|
||||
* @param array $trackIds array of track identifiers to download
|
||||
* @param array $episodes array of podcast episodes
|
||||
*/
|
||||
public function downloadEpisodes($episodes) {
|
||||
$episodeUrls = array();
|
||||
foreach($episodes as $episode) {
|
||||
array_push($episodeUrls, $episode["enclosure"]["link"]);
|
||||
}
|
||||
$this->_download($episodeUrls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of download URLs, download RSS feed tracks
|
||||
*
|
||||
* @param array $downloadUrls array of download URLs to send to Celery
|
||||
* TODO: do we need other parameters here...?
|
||||
*/
|
||||
public function download($trackIds) {
|
||||
private function _download($downloadUrls) {
|
||||
$CC_CONFIG = Config::getConfig();
|
||||
$data = array(
|
||||
// 'download_urls' => , TODO: get download urls to send to Celery
|
||||
'download_urls' => $downloadUrls,
|
||||
'callback_url' => Application_Common_HTTPHelper::getStationUrl() . '/rest/media',
|
||||
'api_key' => $apiKey = $CC_CONFIG["apiKey"][0],
|
||||
);
|
||||
|
@ -87,8 +100,8 @@ class Application_Service_PodcastService extends Application_Service_ThirdPartyC
|
|||
* Update a ThirdPartyTrackReferences object for a completed upload
|
||||
*
|
||||
* @param $task CeleryTasks the completed CeleryTasks object
|
||||
* @param $trackId int ThirdPartyTrackReferences identifier
|
||||
* @param $track object third-party service track object
|
||||
* @param $episodeId int PodcastEpisodes identifier
|
||||
* @param $episode object object containing Podcast episode information
|
||||
* @param $status string Celery task status
|
||||
*
|
||||
* @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object
|
||||
|
@ -96,14 +109,14 @@ class Application_Service_PodcastService extends Application_Service_ThirdPartyC
|
|||
* @throws Exception
|
||||
* @throws PropelException
|
||||
*/
|
||||
function updateTrackReference($task, $trackId, $track, $status) {
|
||||
$ref = parent::updateTrackReference($task, $trackId, $track, $status);
|
||||
public function updateTrackReference($task, $episodeId, $episode, $status) {
|
||||
$ref = parent::updateTrackReference($task, $episodeId, $episode, $status);
|
||||
|
||||
if ($status == CELERY_SUCCESS_STATUS) {
|
||||
// TODO: handle successful download
|
||||
// $ref->setDbForeignId();
|
||||
// FIXME: we need the file ID here, but 'track' is too arbitrary...
|
||||
$ref->setDbFileId($track->fileId);
|
||||
$ref->setDbFileId($episode->fileId);
|
||||
}
|
||||
|
||||
$ref->save();
|
||||
|
|
|
@ -6,14 +6,24 @@
|
|||
<div class="inner_editor_wrapper">
|
||||
<form class="podcast-metadata">
|
||||
<input ng-value="podcast.id" class="obj_id" type="hidden"/>
|
||||
<label>
|
||||
<p>
|
||||
<label for="podcast_name">
|
||||
<?php echo _("Podcast Name") ?>
|
||||
<input disabled ng-model="podcast.title" type="text"/>
|
||||
</label>
|
||||
<label>
|
||||
<input disabled name="podcast_name" ng-model="podcast.title" type="text"/>
|
||||
</p>
|
||||
<p>
|
||||
<label for="podcast_url">
|
||||
<?php echo _("Podcast URL") ?>
|
||||
<input disabled ng-model="podcast.url" type="text"/>
|
||||
</label>
|
||||
<input disabled name="podcast_url" ng-model="podcast.url" type="text"/>
|
||||
</p>
|
||||
<p>
|
||||
<label for="podcast_auto_ingest">
|
||||
<?php echo _("Automatically download latest episodes?") ?>
|
||||
</label>
|
||||
<input name="podcast_auto_ingest" ng-model="podcast.auto_ingest" type="checkbox"/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ var AIRTIME = (function (AIRTIME) {
|
|||
|
||||
//AngularJS app
|
||||
var podcastApp = angular.module('podcast', [])
|
||||
.controller('RestController', function($scope, $http, podcast, tab) {
|
||||
.controller('RestController', function($scope, $http, podcast, tab, episodeTable) {
|
||||
// We need to pass in the tab object and the episodes table object so we can reference them
|
||||
|
||||
//We take a podcast object in as a parameter rather fetching the podcast by ID here because
|
||||
//when you're creating a new podcast, we already have the object from the result of the POST. We're saving
|
||||
|
@ -20,7 +21,9 @@ var AIRTIME = (function (AIRTIME) {
|
|||
tab.setName($scope.podcast.title);
|
||||
|
||||
$scope.savePodcast = function() {
|
||||
$http.put(endpoint + $scope.podcast.id, { csrf_token: jQuery("#csrf").val(), podcast: $scope.podcast })
|
||||
var podcastData = $scope.podcast; // Copy the podcast in scope so we can modify it
|
||||
podcastData.episodes = episodeTable.getSelectedRows();
|
||||
$http.put(endpoint + $scope.podcast.id, { csrf_token: jQuery("#csrf").val(), podcast: podcastData })
|
||||
.success(function() {
|
||||
// TODO
|
||||
});
|
||||
|
@ -44,9 +47,10 @@ var AIRTIME = (function (AIRTIME) {
|
|||
$.post(endpoint + "bulk", { csrf_token: $("#csrf").val(), method: method, ids: ids }, callback);
|
||||
}
|
||||
|
||||
function _bootstrapAngularApp(podcast, tab) {
|
||||
function _bootstrapAngularApp(podcast, tab, table) {
|
||||
podcastApp.value('podcast', podcast);
|
||||
podcastApp.value('tab', tab);
|
||||
podcastApp.value('episodeTable', table);
|
||||
var wrapper = tab.contents.find(".editor_pane_wrapper");
|
||||
wrapper.attr("ng-controller", "RestController");
|
||||
angular.bootstrap(wrapper.get(0), ["podcast"]);
|
||||
|
@ -70,9 +74,9 @@ var AIRTIME = (function (AIRTIME) {
|
|||
var podcast = JSON.parse(json.podcast);
|
||||
var uid = AIRTIME.library.MediaTypeStringEnum.PODCAST+"_"+podcast.id,
|
||||
tab = AIRTIME.tabs.openTab(json, uid, null);
|
||||
_bootstrapAngularApp(podcast, tab);
|
||||
var table = mod.initPodcastEpisodeDatatable(podcast.episodes);
|
||||
_bootstrapAngularApp(podcast, tab, table);
|
||||
$("#podcast_url_dialog").dialog("close");
|
||||
mod.initPodcastEpisodeDatatable(podcast.episodes);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -82,8 +86,8 @@ var AIRTIME = (function (AIRTIME) {
|
|||
var podcast = JSON.parse(el.podcast);
|
||||
var uid = AIRTIME.library.MediaTypeStringEnum.PODCAST+"_"+podcast.id,
|
||||
tab = AIRTIME.tabs.openTab(el, uid, null);
|
||||
_bootstrapAngularApp(podcast, tab);
|
||||
mod.initPodcastEpisodeDatatable(podcast.episodes);
|
||||
var table = mod.initPodcastEpisodeDatatable(podcast.episodes);
|
||||
_bootstrapAngularApp(podcast, tab, table);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -102,13 +106,13 @@ var AIRTIME = (function (AIRTIME) {
|
|||
/* Author */ { "sTitle" : $.i18n._("Author") , "mDataProp" : "author" , "sClass" : "podcast_episodes_author" , "sWidth" : "170px" },
|
||||
/* Description */ { "sTitle" : $.i18n._("Description") , "mDataProp" : "description" , "sClass" : "podcast_episodes_description" , "sWidth" : "300px" },
|
||||
/* Link */ { "sTitle" : $.i18n._("Link") , "mDataProp" : "link" , "sClass" : "podcast_episodes_link" , "sWidth" : "170px" },
|
||||
/* Publication Date */ { "sTitle" : $.i18n._("Publication Date") , "mDataProp" : "pubDate" , "sClass" : "podcast_episodes_pub_date" , "sWidth" : "170px" }
|
||||
/* Publication Date */ { "sTitle" : $.i18n._("Publication Date") , "mDataProp" : "pub_date" , "sClass" : "podcast_episodes_pub_date" , "sWidth" : "170px" }
|
||||
];
|
||||
|
||||
var podcastToolbarButtons = AIRTIME.widgets.Table.getStandardToolbarButtons();
|
||||
|
||||
// Set up the div with id "podcast_table" as a datatable.
|
||||
mod.podcastEpisodesTableWidget = new AIRTIME.widgets.Table(
|
||||
var podcastEpisodesTableWidget = new AIRTIME.widgets.Table(
|
||||
AIRTIME.tabs.getActiveTab().contents.find('#podcast_episodes'), // DOM node to create the table inside.
|
||||
true, // Enable item selection
|
||||
podcastToolbarButtons, // Toolbar buttons
|
||||
|
@ -116,11 +120,17 @@ var AIRTIME = (function (AIRTIME) {
|
|||
'aoColumns' : aoColumns,
|
||||
'bServerSide': false,
|
||||
'sAjaxSource' : null,
|
||||
'aaData' : episodes
|
||||
'aaData' : episodes,
|
||||
"oColVis": {
|
||||
"sAlign": "right",
|
||||
"aiExclude": [0, 1],
|
||||
"buttonText": $.i18n._("Columns"),
|
||||
"iOverlayFade": 0
|
||||
}
|
||||
});
|
||||
|
||||
mod.podcastEpisodesDatatable = mod.podcastEpisodesTableWidget.getDatatable();
|
||||
mod.podcastEpisodesDatatable.textScroll("td");
|
||||
podcastEpisodesTableWidget.getDatatable().textScroll("td");
|
||||
return podcastEpisodesTableWidget;
|
||||
};
|
||||
|
||||
return AIRTIME;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "8d8a51740ad37127ff6618f80861ccfc",
|
||||
|
@ -231,7 +231,7 @@
|
|||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"predis/predis": "0.8.5",
|
||||
"predis/predis": ">=0.8.5",
|
||||
"videlalvaro/php-amqplib": ">=2.4.0"
|
||||
},
|
||||
"type": "library",
|
||||
|
@ -363,29 +363,32 @@
|
|||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v0.8.5",
|
||||
"version": "v1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrk/predis.git",
|
||||
"reference": "5f2eea628eb465d866ad2771927d83769c8f956c"
|
||||
"reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nrk/predis/zipball/5f2eea628eb465d866ad2771927d83769c8f956c",
|
||||
"reference": "5f2eea628eb465d866ad2771927d83769c8f956c",
|
||||
"url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04",
|
||||
"reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Allows access to Webdis when paired with phpiredis",
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Predis": "lib/"
|
||||
"psr-4": {
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
@ -406,7 +409,7 @@
|
|||
"predis",
|
||||
"redis"
|
||||
],
|
||||
"time": "2014-01-16 14:10:29"
|
||||
"time": "2015-07-30 18:34:15"
|
||||
},
|
||||
{
|
||||
"name": "propel/propel1",
|
||||
|
@ -700,6 +703,7 @@
|
|||
"simplepie/simplepie": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@ import json
|
|||
import urllib2
|
||||
import requests
|
||||
import soundcloud
|
||||
import cgi
|
||||
import urlparse
|
||||
import posixpath
|
||||
from celery import Celery
|
||||
from celery.utils.log import get_task_logger
|
||||
from contextlib import closing
|
||||
|
||||
celery = Celery()
|
||||
logger = get_task_logger(__name__)
|
||||
|
@ -93,10 +97,18 @@ def podcast_download(download_urls, callback_url, api_key):
|
|||
"""
|
||||
try:
|
||||
for url in download_urls:
|
||||
r = requests.get(url, stream=True)
|
||||
r.raise_for_status()
|
||||
with r as f:
|
||||
requests.post(callback_url, data=f, auth=requests.auth.HTTPBasicAuth(api_key, ''))
|
||||
with closing(requests.get(url, stream=True)) as r:
|
||||
# Try to get the filename from the content disposition
|
||||
d = r.headers.get('Content-Disposition')
|
||||
if d:
|
||||
_, params = cgi.parse_header(d)
|
||||
filename = params['filename']
|
||||
else:
|
||||
# Since we don't necessarily get the filename back in the response headers,
|
||||
# parse the URL and get the filename and extension
|
||||
path = urlparse.urlsplit(r.url).path
|
||||
filename = posixpath.basename(path)
|
||||
requests.post(callback_url, files={'file': (filename, r.content)}, auth=requests.auth.HTTPBasicAuth(api_key, ''))
|
||||
except Exception as e:
|
||||
logger.info('Error during file download: {0}'.format(e.message))
|
||||
logger.info(str(e))
|
||||
|
|
Loading…
Reference in New Issue