2015-09-16 20:22:13 +02:00
|
|
|
<?php
|
|
|
|
|
2015-10-22 20:51:39 +02:00
|
|
|
class PodcastEpisodeNotFoundException extends Exception {}
|
|
|
|
|
2015-10-21 01:03:34 +02:00
|
|
|
class Application_Service_PodcastEpisodeService extends Application_Service_ThirdPartyCeleryService implements Publish
|
2015-09-16 20:22:13 +02:00
|
|
|
{
|
2015-09-23 02:22:06 +02:00
|
|
|
/**
|
|
|
|
* Arbitrary constant identifiers for the internal tasks array
|
|
|
|
*/
|
|
|
|
|
|
|
|
const DOWNLOAD = 'download';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string service name to store in ThirdPartyTrackReferences database
|
|
|
|
*/
|
|
|
|
protected static $_SERVICE_NAME = PODCAST_SERVICE_NAME; // Service name constant from constants.php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string exchange name for Podcast tasks
|
|
|
|
*/
|
|
|
|
protected static $_CELERY_EXCHANGE_NAME = 'podcast';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array map of constant identifiers to Celery task names
|
|
|
|
*/
|
|
|
|
protected static $_CELERY_TASKS = [
|
2015-10-21 01:03:34 +02:00
|
|
|
self::DOWNLOAD => 'podcast-download'
|
2015-09-23 02:22:06 +02:00
|
|
|
];
|
|
|
|
|
2015-10-22 20:51:39 +02:00
|
|
|
private static $privateFields = array(
|
|
|
|
"id"
|
|
|
|
);
|
|
|
|
|
2015-10-22 18:12:41 +02:00
|
|
|
/**
|
|
|
|
* Utility function to import and download a single episode
|
|
|
|
*
|
|
|
|
* @param int $podcastId ID of the podcast the episode should belong to
|
|
|
|
* @param array $episode array of episode data to store
|
|
|
|
*
|
|
|
|
* @return PodcastEpisodes the stored PodcastEpisodes object
|
|
|
|
*/
|
|
|
|
public function importEpisode($podcastId, $episode) {
|
|
|
|
$e = $this->addPlaceholder($podcastId, $episode);
|
|
|
|
$this->_download($e->getDbId(), $e->getDbDownloadUrl());
|
|
|
|
return $e;
|
|
|
|
}
|
|
|
|
|
2015-09-23 02:22:06 +02:00
|
|
|
/**
|
2015-10-15 20:44:17 +02:00
|
|
|
* Given an array of episodes, store them in the database as placeholder objects until
|
|
|
|
* they can be processed by Celery
|
2015-09-23 02:22:06 +02:00
|
|
|
*
|
2015-09-24 21:57:38 +02:00
|
|
|
* @param int $podcastId Podcast object identifier
|
|
|
|
* @param array $episodes array of podcast episodes
|
|
|
|
*
|
|
|
|
* @return array the stored PodcastEpisodes objects
|
|
|
|
*/
|
|
|
|
public function addPodcastEpisodePlaceholders($podcastId, $episodes) {
|
|
|
|
$storedEpisodes = array();
|
|
|
|
foreach ($episodes as $episode) {
|
2015-10-21 01:03:34 +02:00
|
|
|
$e = $this->addPlaceholder($podcastId, $episode);
|
2015-09-24 21:57:38 +02:00
|
|
|
array_push($storedEpisodes, $e);
|
|
|
|
}
|
|
|
|
return $storedEpisodes;
|
|
|
|
}
|
|
|
|
|
2015-10-15 20:44:17 +02:00
|
|
|
/**
|
|
|
|
* Given an episode, store it in the database as a placeholder object until
|
|
|
|
* it can be processed by Celery
|
|
|
|
*
|
|
|
|
* @param int $podcastId Podcast object identifier
|
|
|
|
* @param array $episode array of podcast episode data
|
|
|
|
*
|
|
|
|
* @return PodcastEpisodes the stored PodcastEpisodes object
|
|
|
|
*/
|
2015-10-21 01:03:34 +02:00
|
|
|
public function addPlaceholder($podcastId, $episode) {
|
2015-10-15 20:44:17 +02:00
|
|
|
// We need to check whether the array is parsed directly from the SimplePie
|
|
|
|
// feed object, or whether it's passed in as json
|
2015-10-21 01:03:34 +02:00
|
|
|
$enclosure = $episode["enclosure"];
|
|
|
|
$url = $enclosure instanceof SimplePie_Enclosure ? $enclosure->get_link() : $enclosure["link"];
|
|
|
|
return $this->_buildEpisode($podcastId, $url, $episode["guid"], $episode["pub_date"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given episode parameters, construct and store a basic PodcastEpisodes object
|
|
|
|
*
|
|
|
|
* @param int $podcastId the podcast the episode belongs to
|
|
|
|
* @param string $url the download URL for the episode
|
|
|
|
* @param string $guid the unique id for the episode. Often the same as the download URL
|
|
|
|
* @param string $publicationDate the publication date of the episode
|
|
|
|
*
|
|
|
|
* @return PodcastEpisodes the newly created PodcastEpisodes object
|
|
|
|
*
|
|
|
|
* @throws Exception
|
|
|
|
* @throws PropelException
|
|
|
|
*/
|
|
|
|
private function _buildEpisode($podcastId, $url, $guid, $publicationDate) {
|
2015-10-15 20:44:17 +02:00
|
|
|
$e = new PodcastEpisodes();
|
|
|
|
$e->setDbPodcastId($podcastId);
|
|
|
|
$e->setDbDownloadUrl($url);
|
2015-10-21 01:03:34 +02:00
|
|
|
$e->setDbEpisodeGuid($guid);
|
|
|
|
$e->setDbPublicationDate($publicationDate);
|
2015-10-15 20:44:17 +02:00
|
|
|
$e->save();
|
|
|
|
return $e;
|
|
|
|
}
|
|
|
|
|
2015-09-24 21:57:38 +02:00
|
|
|
/**
|
|
|
|
* Given an array of episodes, extract the IDs and download URLs and send them to Celery
|
|
|
|
*
|
2015-09-24 18:58:02 +02:00
|
|
|
* @param array $episodes array of podcast episodes
|
|
|
|
*/
|
|
|
|
public function downloadEpisodes($episodes) {
|
2015-09-24 21:57:38 +02:00
|
|
|
/** @var PodcastEpisodes $episode */
|
2015-09-24 18:58:02 +02:00
|
|
|
foreach($episodes as $episode) {
|
2015-10-21 01:03:34 +02:00
|
|
|
$this->_download($episode->getDbId(), $episode->getDbDownloadUrl());
|
2015-09-24 18:58:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-10-21 01:03:34 +02:00
|
|
|
* Given an episode ID and a download URL, send a Celery task
|
|
|
|
* to download an RSS feed track
|
2015-09-24 18:58:02 +02:00
|
|
|
*
|
2015-10-21 01:03:34 +02:00
|
|
|
* @param int $id episode unique ID
|
|
|
|
* @param string $url download url for the episode
|
2015-09-23 02:22:06 +02:00
|
|
|
*/
|
2015-10-21 01:03:34 +02:00
|
|
|
private function _download($id, $url) {
|
2015-09-23 02:22:06 +02:00
|
|
|
$CC_CONFIG = Config::getConfig();
|
|
|
|
$data = array(
|
2015-10-21 01:03:34 +02:00
|
|
|
'id' => $id,
|
|
|
|
'url' => $url,
|
2015-09-23 02:22:06 +02:00
|
|
|
'callback_url' => Application_Common_HTTPHelper::getStationUrl() . '/rest/media',
|
|
|
|
'api_key' => $apiKey = $CC_CONFIG["apiKey"][0],
|
|
|
|
);
|
2015-09-24 21:57:38 +02:00
|
|
|
$this->_executeTask(static::$_CELERY_TASKS[self::DOWNLOAD], $data);
|
2015-09-23 02:22:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update a ThirdPartyTrackReferences object for a completed upload
|
|
|
|
*
|
2015-09-24 21:57:38 +02:00
|
|
|
* @param $task CeleryTasks the completed CeleryTasks object
|
|
|
|
* @param $episodeId int PodcastEpisodes identifier
|
2015-10-21 01:03:34 +02:00
|
|
|
* @param $episode stdClass simple object containing Podcast episode information
|
2015-09-24 21:57:38 +02:00
|
|
|
* @param $status string Celery task status
|
2015-09-23 02:22:06 +02:00
|
|
|
*
|
|
|
|
* @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object
|
|
|
|
*
|
|
|
|
* @throws Exception
|
|
|
|
* @throws PropelException
|
|
|
|
*/
|
2015-10-21 01:03:34 +02:00
|
|
|
public function updateTrackReference($task, $episodeId, $episode, $status) {
|
|
|
|
$ref = parent::updateTrackReference($task, $episodeId, $episode, $status);
|
2015-09-23 02:22:06 +02:00
|
|
|
|
2015-10-21 01:03:34 +02:00
|
|
|
$dbEpisode = PodcastEpisodesQuery::create()
|
|
|
|
->findOneByDbId($episode->episodeid);
|
2015-10-29 22:53:45 +01:00
|
|
|
|
|
|
|
// If the placeholder for the episode is somehow removed, return with a warning
|
|
|
|
if (!$dbEpisode) {
|
|
|
|
Logging::warn("Celery task $task episode $episode->episodeid unsuccessful: episode placeholder removed");
|
|
|
|
return $ref;
|
|
|
|
}
|
|
|
|
|
2015-10-21 01:03:34 +02:00
|
|
|
// Even if the task itself succeeds, the download could have failed, so check the status
|
2015-10-29 22:53:45 +01:00
|
|
|
if ($status == CELERY_SUCCESS_STATUS && $episode->status == 1) {
|
2015-10-21 01:03:34 +02:00
|
|
|
$dbEpisode->setDbFileId($episode->fileid)->save();
|
|
|
|
} else {
|
2015-10-29 22:53:45 +01:00
|
|
|
Logging::warn("Celery task $task episode $episode->episodeid unsuccessful with message $episode->error");
|
2015-10-21 01:03:34 +02:00
|
|
|
$dbEpisode->delete();
|
2015-09-23 02:22:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $ref;
|
|
|
|
}
|
2015-10-21 01:03:34 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Publish the file with the given file ID to the station podcast
|
|
|
|
*
|
|
|
|
* @param int $fileId ID of the file to be published
|
|
|
|
*/
|
|
|
|
public function publish($fileId) {
|
|
|
|
$id = Application_Model_Preference::getStationPodcastId();
|
|
|
|
$url = $guid = Application_Common_HTTPHelper::getStationUrl()."rest/media/$fileId/download";
|
2015-10-23 00:03:38 +02:00
|
|
|
if (!PodcastEpisodesQuery::create()
|
|
|
|
->filterByDbPodcastId($id)
|
|
|
|
->findOneByDbFileId($fileId)) { // Don't allow duplicate episodes
|
|
|
|
$e = $this->_buildEpisode($id, $url, $guid, date('r'));
|
|
|
|
$e->setDbFileId($fileId)->save();
|
|
|
|
}
|
2015-10-21 01:03:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unpublish the file with the given file ID from the station podcast
|
|
|
|
*
|
|
|
|
* @param int $fileId ID of the file to be unpublished
|
|
|
|
*/
|
|
|
|
public function unpublish($fileId) {
|
|
|
|
$id = Application_Model_Preference::getStationPodcastId();
|
|
|
|
PodcastEpisodesQuery::create()
|
|
|
|
->filterByDbPodcastId($id)
|
|
|
|
->findOneByDbFileId($fileId)
|
|
|
|
->delete();
|
|
|
|
}
|
2015-10-22 20:51:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $episodeId
|
|
|
|
* @return array
|
|
|
|
* @throws PodcastEpisodeNotFoundException
|
|
|
|
*/
|
|
|
|
public static function getPodcastEpisodeById($episodeId)
|
|
|
|
{
|
|
|
|
$episode = PodcastEpisodesQuery::create()->findPk($episodeId);
|
|
|
|
if (!$episode) {
|
|
|
|
throw new PodcastEpisodeNotFoundException();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $episode->toArray(BasePeer::TYPE_FIELDNAME);
|
|
|
|
}
|
|
|
|
|
2015-10-26 16:28:10 +01:00
|
|
|
/**
|
|
|
|
* Returns an array of Podcast episodes, with the option to paginate the results.
|
|
|
|
*
|
|
|
|
* @param $podcastId
|
|
|
|
* @param int $offset
|
|
|
|
* @param int $limit
|
|
|
|
* @param string $sortColumn
|
2015-10-26 20:09:06 +01:00
|
|
|
* @param string $sortDir "ASC" || "DESC"
|
2015-10-26 16:28:10 +01:00
|
|
|
* @return array
|
|
|
|
* @throws PodcastNotFoundException
|
|
|
|
*/
|
|
|
|
public function getPodcastEpisodes($podcastId,
|
|
|
|
$offset=0,
|
|
|
|
$limit=10,
|
2015-11-03 00:07:16 +01:00
|
|
|
$sortColumn=PodcastEpisodesPeer::PUBLICATION_DATE,
|
2015-10-26 20:09:06 +01:00
|
|
|
$sortDir="ASC")
|
2015-10-22 20:51:39 +02:00
|
|
|
{
|
|
|
|
$podcast = PodcastQuery::create()->findPk($podcastId);
|
|
|
|
if (!$podcast) {
|
|
|
|
throw new PodcastNotFoundException();
|
|
|
|
}
|
|
|
|
|
2015-11-03 00:07:16 +01:00
|
|
|
$sortDir = ($sortDir === "DESC") ? $sortDir = Criteria::DESC : Criteria::ASC;
|
2015-11-03 22:23:17 +01:00
|
|
|
$isStationPodcast = $podcastId == Application_Model_Preference::getStationPodcastId();
|
2015-10-26 20:09:06 +01:00
|
|
|
|
2015-10-26 16:28:10 +01:00
|
|
|
$episodes = PodcastEpisodesQuery::create()
|
2015-11-03 00:07:16 +01:00
|
|
|
->filterByDbPodcastId($podcastId);
|
|
|
|
if ($isStationPodcast) {
|
|
|
|
$episodes = $episodes->setLimit($limit);
|
|
|
|
}
|
2015-11-03 22:23:17 +01:00
|
|
|
$episodes = $episodes->joinWith('PodcastEpisodes.CcFiles')
|
|
|
|
->setOffset($offset)
|
2015-10-26 16:28:10 +01:00
|
|
|
->orderBy($sortColumn, $sortDir)
|
|
|
|
->find();
|
|
|
|
|
2015-11-03 00:07:16 +01:00
|
|
|
return $isStationPodcast ? $this->_getStationPodcastEpisodeArray($episodes)
|
|
|
|
: $this->_getImportedPodcastEpisodeArray($podcast, $episodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function _getStationPodcastEpisodeArray($episodes) {
|
2015-10-22 20:51:39 +02:00
|
|
|
$episodesArray = array();
|
|
|
|
foreach ($episodes as $episode) {
|
2015-11-03 00:07:16 +01:00
|
|
|
/** @var PodcastEpisodes $episode */
|
|
|
|
$episodeArr = $episode->toArray(BasePeer::TYPE_FIELDNAME, true, [], true);
|
2015-10-26 20:09:06 +01:00
|
|
|
array_push($episodesArray, $episodeArr);
|
2015-10-22 20:51:39 +02:00
|
|
|
}
|
2015-11-03 00:07:16 +01:00
|
|
|
return $episodesArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function _getImportedPodcastEpisodeArray($podcast, $episodes) {
|
|
|
|
$rss = Application_Service_PodcastService::getPodcastFeed($podcast->getDbUrl());
|
|
|
|
$episodeIds = array();
|
|
|
|
foreach ($episodes as $e) {
|
|
|
|
array_push($episodeIds, $e->getDbEpisodeGuid());
|
|
|
|
}
|
|
|
|
|
|
|
|
$episodesArray = array();
|
|
|
|
foreach ($rss->get_items() as $item) {
|
|
|
|
/** @var SimplePie_Item $item */
|
|
|
|
array_push($episodesArray, array(
|
|
|
|
"guid" => $item->get_id(),
|
|
|
|
"ingested" => in_array($item->get_id(), $episodeIds),
|
|
|
|
"title" => $item->get_title(),
|
|
|
|
// From the RSS spec best practices:
|
|
|
|
// 'An item's author element provides the e-mail address of the person who wrote the item'
|
|
|
|
"author" => $item->get_author()->get_email(),
|
|
|
|
"description" => $item->get_description(),
|
|
|
|
"pub_date" => $item->get_gmdate(),
|
|
|
|
"link" => $item->get_link(),
|
|
|
|
"enclosure" => $item->get_enclosure()
|
|
|
|
));
|
|
|
|
}
|
2015-10-22 20:51:39 +02:00
|
|
|
|
|
|
|
return $episodesArray;
|
|
|
|
}
|
|
|
|
|
2015-10-22 21:03:38 +02:00
|
|
|
public function deletePodcastEpisodeById($episodeId)
|
2015-10-22 20:51:39 +02:00
|
|
|
{
|
|
|
|
$episode = PodcastEpisodesQuery::create()->findByDbId($episodeId);
|
|
|
|
|
|
|
|
if ($episode) {
|
|
|
|
$episode->delete();
|
|
|
|
} else {
|
|
|
|
throw new PodcastEpisodeNotFoundException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-22 21:03:38 +02:00
|
|
|
private function removePrivateFields(&$data)
|
2015-10-22 20:51:39 +02:00
|
|
|
{
|
|
|
|
foreach (self::$privateFields as $key) {
|
|
|
|
unset($data[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-16 20:22:13 +02:00
|
|
|
}
|