* SAAS-1084 - initial work on publishing API backend

* More work on automatic ingest
* Add automatic_ingest_timestamp column to ImportedPodcast
This commit is contained in:
Duncan Sommerville 2015-10-20 19:03:34 -04:00
parent 3a791ef9b5
commit 0b1df6baf3
27 changed files with 490 additions and 106 deletions

View File

@ -42,6 +42,10 @@ require_once "MediaType.php";
/* Interfaces */ /* Interfaces */
require_once "OAuth2.php"; require_once "OAuth2.php";
require_once "OAuth2Controller.php"; require_once "OAuth2Controller.php";
require_once "Publish.php";
/* Factories */
require_once __DIR__.'/services/CeleryServiceFactory.php';
require_once __DIR__.'/services/PublishServiceFactory.php';
require_once __DIR__.'/forms/helpers/ValidationTypes.php'; require_once __DIR__.'/forms/helpers/ValidationTypes.php';
require_once __DIR__.'/forms/helpers/CustomDecorators.php'; require_once __DIR__.'/forms/helpers/CustomDecorators.php';

View File

@ -1,7 +1,5 @@
<?php <?php
require_once "CeleryServiceFactory.php";
class CeleryManager { class CeleryManager {
/** /**

View File

@ -28,23 +28,43 @@ class PodcastManager {
public static function downloadNewestEpisodes() { public static function downloadNewestEpisodes() {
$autoIngestPodcasts = static::_getAutoIngestPodcasts(); $autoIngestPodcasts = static::_getAutoIngestPodcasts();
$service = new Application_Service_PodcastEpisodeService(); $service = new Application_Service_PodcastEpisodeService();
$episodes = array();
foreach ($autoIngestPodcasts as $podcast) { foreach ($autoIngestPodcasts as $podcast) {
/** @var ImportedPodcast $podcast */ $episodes = static::_findUningestedEpisodes($podcast, $service);
$podcastArray = Application_Service_PodcastService::getPodcastById($podcast->getDbPodcastId()); $podcast->setDbAutoIngestTimestamp(date('r'))->save();
// A bit hacky... sort the episodes by publication date to get the most recent $service->downloadEpisodes($episodes);
usort($podcastArray["episodes"], array(static::class, "_sortByEpisodePubDate")); }
$episodeData = $podcastArray["episodes"][0];
Application_Model_Preference::setPodcastPollLock(microtime(true));
}
/**
* Given an ImportedPodcast, find all uningested episodes since the last automatic ingest,
* and add them to a given episodes array
*
* @param ImportedPodcast $podcast the podcast to search
* @param Application_Service_PodcastEpisodeService $service podcast episode service object
*
* @return array array of episodes to append be downloaded
*/
protected static function _findUningestedEpisodes($podcast, $service) {
$podcastArray = Application_Service_PodcastService::getPodcastById($podcast->getDbPodcastId());
$episodeList = $podcastArray["episodes"];
$episodes = array();
// A bit hacky... sort the episodes by publication date to get the most recent
usort($episodeList, array(static::class, "_sortByEpisodePubDate"));
for ($i = 0; $i < sizeof($episodeList); $i++) {
$episodeData = $episodeList[$i];
// If the publication date of this episode is before the ingest timestamp, we don't need to ingest it
// Since we're sorting by publication date, we can break
if ($episodeData["pub_date"] < $podcast->getDbAutoIngestTimestamp()) break;
$episode = PodcastEpisodesQuery::create()->findOneByDbEpisodeGuid($episodeData["guid"]); $episode = PodcastEpisodesQuery::create()->findOneByDbEpisodeGuid($episodeData["guid"]);
// Make sure there's no existing episode placeholder or import, and that the data is non-empty // Make sure there's no existing episode placeholder or import, and that the data is non-empty
if (empty($episode) && !empty($episodeData)) { if (empty($episode) && !empty($episodeData)) {
$placeholder = $service->addPodcastEpisodePlaceholder($podcast->getDbPodcastId(), $episodeData); $placeholder = $service->addPlaceholder($podcast->getDbPodcastId(), $episodeData);
array_push($episodes, $placeholder); array_push($episodes, $placeholder);
} }
} }
return $episodes;
$service->downloadEpisodes($episodes);
Application_Model_Preference::setPodcastPollLock(microtime(true));
} }
/** /**

View File

@ -0,0 +1,23 @@
<?php
interface Publish {
/**
* Publish the file with the given file ID
*
* @param int $fileId ID of the file to be published
*
* @return void
*/
public function publish($fileId);
/**
* Unpublish the file with the given file ID
*
* @param int $fileId ID of the file to be unpublished
*
* @return void
*/
public function unpublish($fileId);
}

View File

@ -126,6 +126,9 @@ define('CELERY_FAILED_STATUS', 'FAILED');
define('SOUNDCLOUD_SERVICE_NAME', 'soundcloud'); define('SOUNDCLOUD_SERVICE_NAME', 'soundcloud');
define('PODCAST_SERVICE_NAME', 'podcast'); define('PODCAST_SERVICE_NAME', 'podcast');
// Publish Services
define('STATION_PODCAST_SERVICE_NAME', 'station_podcast');
// Podcast Types // Podcast Types
//define('STATION_PODCAST', 0); //define('STATION_PODCAST', 0);
//define('IMPORTED_PODCAST', 1); //define('IMPORTED_PODCAST', 1);

View File

@ -21,7 +21,7 @@ class SoundcloudController extends ThirdPartyController implements OAuth2Control
} }
/** /**
* Upload the file with the given id to a third-party service * Upload the file with the given id to SoundCloud
* *
* @return void * @return void
* *
@ -34,7 +34,7 @@ class SoundcloudController extends ThirdPartyController implements OAuth2Control
} }
/** /**
* Download the file with the given id from a third-party service * Download the file with the given id from SoundCloud
* *
* @return void * @return void
* *
@ -47,7 +47,7 @@ class SoundcloudController extends ThirdPartyController implements OAuth2Control
} }
/** /**
* Delete the file with the given id from a third-party service * Delete the file with the given id from SoundCloud
* *
* @return void * @return void
* *

View File

@ -223,7 +223,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
} }
private function verifyAuth() { private function verifyAuth() {
if ($this->verifyAPIKey()) { if ($this->isVerifiedDownload() || $this->verifyAPIKey()) {
return true; return true;
} }
@ -233,7 +233,32 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
return false; return false;
} }
/**
* Check if the requested file can be downloaded.
* It should satisfy the following requirements:
* * request path is /rest/media/:id/download
* * download key is correct
* * requested file belongs to the station podcast
*
* @return bool
*/
private function isVerifiedDownload() {
$request = $this->getRequest();
$fileId = $request->getParam("id");
$key = $request->getParam("download_key");
$module = $request->getModuleName();
$controller = $request->getControllerName();
$action = $request->getActionName();
$stationPodcast = StationPodcastQuery::create()
->findOneByDbPodcastId(Application_Model_Preference::getStationPodcastId());
return $module == "rest"
&& $controller == "media"
&& $action == "download"
&& $key === Application_Model_Preference::getStationPodcastDownloadKey()
&& $stationPodcast->hasEpisodeForFile($fileId);
}
private function verifyCSRFToken($token) { private function verifyCSRFToken($token) {
return SecurityHelper::verifyCSRFToken($token); return SecurityHelper::verifyCSRFToken($token);
} }

View File

@ -157,6 +157,7 @@ class PageLayoutInitPlugin extends Zend_Controller_Plugin_Abstract
$view->headScript()->appendFile($baseUrl . 'js/libs/jquery-1.8.3.min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript') $view->headScript()->appendFile($baseUrl . 'js/libs/jquery-1.8.3.min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript')
->appendFile($baseUrl . 'js/libs/jquery-ui-1.8.24.min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript') ->appendFile($baseUrl . 'js/libs/jquery-ui-1.8.24.min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript')
->appendFile($baseUrl . 'js/libs/angular.min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript')
->appendFile($baseUrl . 'js/bootstrap/bootstrap.js?' . $CC_CONFIG['airtime_version'], 'text/javascript') ->appendFile($baseUrl . 'js/bootstrap/bootstrap.js?' . $CC_CONFIG['airtime_version'], 'text/javascript')
->appendFile($baseUrl . 'js/libs/underscore-min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript') ->appendFile($baseUrl . 'js/libs/underscore-min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript')

View File

@ -1525,4 +1525,13 @@ class Application_Model_Preference
{ {
self::setValue("station_podcast_id", $value); self::setValue("station_podcast_id", $value);
} }
public static function getStationPodcastDownloadKey() {
return self::getValue("station_podcast_download_key");
}
public static function setStationPodcastDownloadKey($value = null) {
$value = empty($value) ? (new Application_Model_Auth())->generateRandomString() : $value;
self::setValue("station_podcast_download_key", $value);
}
} }

View File

@ -15,4 +15,26 @@
*/ */
class PodcastEpisodes extends BasePodcastEpisodes class PodcastEpisodes extends BasePodcastEpisodes
{ {
/**
* @override
* We need to override this function in order to provide the rotating
* download key for the station podcast.
*
* Get the [download_url] column value.
*
* @return string
*/
public function getDbDownloadUrl() {
$podcastId = $this->getDbPodcastId();
// We may have more station podcasts later, so use this instead of checking the id stored in Preference
$podcast = StationPodcastQuery::create()->findOneByDbPodcastId($podcastId);
if ($podcast) {
$fileId = $this->getDbFileId();
$key = Application_Model_Preference::getStationPodcastDownloadKey();
return Application_Common_HTTPHelper::getStationUrl()."rest/media/$fileId/download?download_key=$key";
}
return parent::getDbDownloadUrl();
}
} }

View File

@ -15,4 +15,24 @@
*/ */
class StationPodcast extends BaseStationPodcast class StationPodcast extends BaseStationPodcast
{ {
/**
* Utility function to check whether an episode for the file with the given ID
* is contained within the station podcast
*
* @param int $fileId the file ID to check for
*
* @return bool true if the station podcast contains an episode with
* the given file ID, otherwise false
*/
public function hasEpisodeForFile($fileId) {
$episodes = PodcastEpisodesQuery::create()
->filterByDbPodcastId($this->getDbPodcastId())
->find();
foreach ($episodes as $e) {
if ($e->getDbFileId() == $fileId) return true;
}
return false;
}
} }

View File

@ -41,6 +41,7 @@ class ImportedPodcastTableMap extends TableMap
// columns // columns
$this->addPrimaryKey('id', 'DbId', 'INTEGER', true, null, null); $this->addPrimaryKey('id', 'DbId', 'INTEGER', true, null, null);
$this->addColumn('auto_ingest', 'DbAutoIngest', 'BOOLEAN', true, null, false); $this->addColumn('auto_ingest', 'DbAutoIngest', 'BOOLEAN', true, null, false);
$this->addColumn('auto_ingest_timestamp', 'DbAutoIngestTimestamp', 'TIMESTAMP', false, null, null);
$this->addForeignKey('podcast_id', 'DbPodcastId', 'INTEGER', 'podcast', 'id', true, null, null); $this->addForeignKey('podcast_id', 'DbPodcastId', 'INTEGER', 'podcast', 'id', true, null, null);
// validators // validators
} // initialize() } // initialize()

View File

@ -42,6 +42,12 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
*/ */
protected $auto_ingest; protected $auto_ingest;
/**
* The value for the auto_ingest_timestamp field.
* @var string
*/
protected $auto_ingest_timestamp;
/** /**
* The value for the podcast_id field. * The value for the podcast_id field.
* @var int * @var int
@ -116,6 +122,41 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
return $this->auto_ingest; return $this->auto_ingest;
} }
/**
* Get the [optionally formatted] temporal [auto_ingest_timestamp] column value.
*
*
* @param string $format The date/time format string (either date()-style or strftime()-style).
* If format is null, then the raw DateTime object will be returned.
* @return mixed Formatted date/time value as string or DateTime object (if format is null), null if column is null
* @throws PropelException - if unable to parse/validate the date/time value.
*/
public function getDbAutoIngestTimestamp($format = 'Y-m-d H:i:s')
{
if ($this->auto_ingest_timestamp === null) {
return null;
}
try {
$dt = new DateTime($this->auto_ingest_timestamp);
} catch (Exception $x) {
throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->auto_ingest_timestamp, true), $x);
}
if ($format === null) {
// Because propel.useDateTimeClass is true, we return a DateTime object.
return $dt;
}
if (strpos($format, '%') !== false) {
return strftime($format, $dt->format('U'));
}
return $dt->format($format);
}
/** /**
* Get the [podcast_id] column value. * Get the [podcast_id] column value.
* *
@ -177,6 +218,29 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
return $this; return $this;
} // setDbAutoIngest() } // setDbAutoIngest()
/**
* Sets the value of [auto_ingest_timestamp] column to a normalized version of the date/time value specified.
*
* @param mixed $v string, integer (timestamp), or DateTime value.
* Empty strings are treated as null.
* @return ImportedPodcast The current object (for fluent API support)
*/
public function setDbAutoIngestTimestamp($v)
{
$dt = PropelDateTime::newInstance($v, null, 'DateTime');
if ($this->auto_ingest_timestamp !== null || $dt !== null) {
$currentDateAsString = ($this->auto_ingest_timestamp !== null && $tmpDt = new DateTime($this->auto_ingest_timestamp)) ? $tmpDt->format('Y-m-d H:i:s') : null;
$newDateAsString = $dt ? $dt->format('Y-m-d H:i:s') : null;
if ($currentDateAsString !== $newDateAsString) {
$this->auto_ingest_timestamp = $newDateAsString;
$this->modifiedColumns[] = ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP;
}
} // if either are not null
return $this;
} // setDbAutoIngestTimestamp()
/** /**
* Set the value of [podcast_id] column. * Set the value of [podcast_id] column.
* *
@ -240,7 +304,8 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
$this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null;
$this->auto_ingest = ($row[$startcol + 1] !== null) ? (boolean) $row[$startcol + 1] : null; $this->auto_ingest = ($row[$startcol + 1] !== null) ? (boolean) $row[$startcol + 1] : null;
$this->podcast_id = ($row[$startcol + 2] !== null) ? (int) $row[$startcol + 2] : null; $this->auto_ingest_timestamp = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null;
$this->podcast_id = ($row[$startcol + 3] !== null) ? (int) $row[$startcol + 3] : null;
$this->resetModified(); $this->resetModified();
$this->setNew(false); $this->setNew(false);
@ -250,7 +315,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
} }
$this->postHydrate($row, $startcol, $rehydrate); $this->postHydrate($row, $startcol, $rehydrate);
return $startcol + 3; // 3 = ImportedPodcastPeer::NUM_HYDRATE_COLUMNS. return $startcol + 4; // 4 = ImportedPodcastPeer::NUM_HYDRATE_COLUMNS.
} catch (Exception $e) { } catch (Exception $e) {
throw new PropelException("Error populating ImportedPodcast object", $e); throw new PropelException("Error populating ImportedPodcast object", $e);
@ -494,6 +559,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST)) { if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST)) {
$modifiedColumns[':p' . $index++] = '"auto_ingest"'; $modifiedColumns[':p' . $index++] = '"auto_ingest"';
} }
if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP)) {
$modifiedColumns[':p' . $index++] = '"auto_ingest_timestamp"';
}
if ($this->isColumnModified(ImportedPodcastPeer::PODCAST_ID)) { if ($this->isColumnModified(ImportedPodcastPeer::PODCAST_ID)) {
$modifiedColumns[':p' . $index++] = '"podcast_id"'; $modifiedColumns[':p' . $index++] = '"podcast_id"';
} }
@ -514,6 +582,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
case '"auto_ingest"': case '"auto_ingest"':
$stmt->bindValue($identifier, $this->auto_ingest, PDO::PARAM_BOOL); $stmt->bindValue($identifier, $this->auto_ingest, PDO::PARAM_BOOL);
break; break;
case '"auto_ingest_timestamp"':
$stmt->bindValue($identifier, $this->auto_ingest_timestamp, PDO::PARAM_STR);
break;
case '"podcast_id"': case '"podcast_id"':
$stmt->bindValue($identifier, $this->podcast_id, PDO::PARAM_INT); $stmt->bindValue($identifier, $this->podcast_id, PDO::PARAM_INT);
break; break;
@ -663,6 +734,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
return $this->getDbAutoIngest(); return $this->getDbAutoIngest();
break; break;
case 2: case 2:
return $this->getDbAutoIngestTimestamp();
break;
case 3:
return $this->getDbPodcastId(); return $this->getDbPodcastId();
break; break;
default: default:
@ -696,7 +770,8 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
$result = array( $result = array(
$keys[0] => $this->getDbId(), $keys[0] => $this->getDbId(),
$keys[1] => $this->getDbAutoIngest(), $keys[1] => $this->getDbAutoIngest(),
$keys[2] => $this->getDbPodcastId(), $keys[2] => $this->getDbAutoIngestTimestamp(),
$keys[3] => $this->getDbPodcastId(),
); );
$virtualColumns = $this->virtualColumns; $virtualColumns = $this->virtualColumns;
foreach ($virtualColumns as $key => $virtualColumn) { foreach ($virtualColumns as $key => $virtualColumn) {
@ -748,6 +823,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
$this->setDbAutoIngest($value); $this->setDbAutoIngest($value);
break; break;
case 2: case 2:
$this->setDbAutoIngestTimestamp($value);
break;
case 3:
$this->setDbPodcastId($value); $this->setDbPodcastId($value);
break; break;
} // switch() } // switch()
@ -776,7 +854,8 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
if (array_key_exists($keys[0], $arr)) $this->setDbId($arr[$keys[0]]); if (array_key_exists($keys[0], $arr)) $this->setDbId($arr[$keys[0]]);
if (array_key_exists($keys[1], $arr)) $this->setDbAutoIngest($arr[$keys[1]]); if (array_key_exists($keys[1], $arr)) $this->setDbAutoIngest($arr[$keys[1]]);
if (array_key_exists($keys[2], $arr)) $this->setDbPodcastId($arr[$keys[2]]); if (array_key_exists($keys[2], $arr)) $this->setDbAutoIngestTimestamp($arr[$keys[2]]);
if (array_key_exists($keys[3], $arr)) $this->setDbPodcastId($arr[$keys[3]]);
} }
/** /**
@ -790,6 +869,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
if ($this->isColumnModified(ImportedPodcastPeer::ID)) $criteria->add(ImportedPodcastPeer::ID, $this->id); if ($this->isColumnModified(ImportedPodcastPeer::ID)) $criteria->add(ImportedPodcastPeer::ID, $this->id);
if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST)) $criteria->add(ImportedPodcastPeer::AUTO_INGEST, $this->auto_ingest); if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST)) $criteria->add(ImportedPodcastPeer::AUTO_INGEST, $this->auto_ingest);
if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP)) $criteria->add(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP, $this->auto_ingest_timestamp);
if ($this->isColumnModified(ImportedPodcastPeer::PODCAST_ID)) $criteria->add(ImportedPodcastPeer::PODCAST_ID, $this->podcast_id); if ($this->isColumnModified(ImportedPodcastPeer::PODCAST_ID)) $criteria->add(ImportedPodcastPeer::PODCAST_ID, $this->podcast_id);
return $criteria; return $criteria;
@ -855,6 +935,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
public function copyInto($copyObj, $deepCopy = false, $makeNew = true) public function copyInto($copyObj, $deepCopy = false, $makeNew = true)
{ {
$copyObj->setDbAutoIngest($this->getDbAutoIngest()); $copyObj->setDbAutoIngest($this->getDbAutoIngest());
$copyObj->setDbAutoIngestTimestamp($this->getDbAutoIngestTimestamp());
$copyObj->setDbPodcastId($this->getDbPodcastId()); $copyObj->setDbPodcastId($this->getDbPodcastId());
if ($deepCopy && !$this->startCopy) { if ($deepCopy && !$this->startCopy) {
@ -973,6 +1054,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
{ {
$this->id = null; $this->id = null;
$this->auto_ingest = null; $this->auto_ingest = null;
$this->auto_ingest_timestamp = null;
$this->podcast_id = null; $this->podcast_id = null;
$this->alreadyInSave = false; $this->alreadyInSave = false;
$this->alreadyInValidation = false; $this->alreadyInValidation = false;

View File

@ -24,13 +24,13 @@ abstract class BaseImportedPodcastPeer
const TM_CLASS = 'ImportedPodcastTableMap'; const TM_CLASS = 'ImportedPodcastTableMap';
/** The total number of columns. */ /** The total number of columns. */
const NUM_COLUMNS = 3; const NUM_COLUMNS = 4;
/** The number of lazy-loaded columns. */ /** The number of lazy-loaded columns. */
const NUM_LAZY_LOAD_COLUMNS = 0; const NUM_LAZY_LOAD_COLUMNS = 0;
/** The number of columns to hydrate (NUM_COLUMNS - NUM_LAZY_LOAD_COLUMNS) */ /** The number of columns to hydrate (NUM_COLUMNS - NUM_LAZY_LOAD_COLUMNS) */
const NUM_HYDRATE_COLUMNS = 3; const NUM_HYDRATE_COLUMNS = 4;
/** the column name for the id field */ /** the column name for the id field */
const ID = 'imported_podcast.id'; const ID = 'imported_podcast.id';
@ -38,6 +38,9 @@ abstract class BaseImportedPodcastPeer
/** the column name for the auto_ingest field */ /** the column name for the auto_ingest field */
const AUTO_INGEST = 'imported_podcast.auto_ingest'; const AUTO_INGEST = 'imported_podcast.auto_ingest';
/** the column name for the auto_ingest_timestamp field */
const AUTO_INGEST_TIMESTAMP = 'imported_podcast.auto_ingest_timestamp';
/** the column name for the podcast_id field */ /** the column name for the podcast_id field */
const PODCAST_ID = 'imported_podcast.podcast_id'; const PODCAST_ID = 'imported_podcast.podcast_id';
@ -60,12 +63,12 @@ abstract class BaseImportedPodcastPeer
* e.g. ImportedPodcastPeer::$fieldNames[ImportedPodcastPeer::TYPE_PHPNAME][0] = 'Id' * e.g. ImportedPodcastPeer::$fieldNames[ImportedPodcastPeer::TYPE_PHPNAME][0] = 'Id'
*/ */
protected static $fieldNames = array ( protected static $fieldNames = array (
BasePeer::TYPE_PHPNAME => array ('DbId', 'DbAutoIngest', 'DbPodcastId', ), BasePeer::TYPE_PHPNAME => array ('DbId', 'DbAutoIngest', 'DbAutoIngestTimestamp', 'DbPodcastId', ),
BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbAutoIngest', 'dbPodcastId', ), BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbAutoIngest', 'dbAutoIngestTimestamp', 'dbPodcastId', ),
BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID, ImportedPodcastPeer::AUTO_INGEST, ImportedPodcastPeer::PODCAST_ID, ), BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID, ImportedPodcastPeer::AUTO_INGEST, ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP, ImportedPodcastPeer::PODCAST_ID, ),
BasePeer::TYPE_RAW_COLNAME => array ('ID', 'AUTO_INGEST', 'PODCAST_ID', ), BasePeer::TYPE_RAW_COLNAME => array ('ID', 'AUTO_INGEST', 'AUTO_INGEST_TIMESTAMP', 'PODCAST_ID', ),
BasePeer::TYPE_FIELDNAME => array ('id', 'auto_ingest', 'podcast_id', ), BasePeer::TYPE_FIELDNAME => array ('id', 'auto_ingest', 'auto_ingest_timestamp', 'podcast_id', ),
BasePeer::TYPE_NUM => array (0, 1, 2, ) BasePeer::TYPE_NUM => array (0, 1, 2, 3, )
); );
/** /**
@ -75,12 +78,12 @@ abstract class BaseImportedPodcastPeer
* e.g. ImportedPodcastPeer::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 * e.g. ImportedPodcastPeer::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0
*/ */
protected static $fieldKeys = array ( protected static $fieldKeys = array (
BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbAutoIngest' => 1, 'DbPodcastId' => 2, ), BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbAutoIngest' => 1, 'DbAutoIngestTimestamp' => 2, 'DbPodcastId' => 3, ),
BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbAutoIngest' => 1, 'dbPodcastId' => 2, ), BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbAutoIngest' => 1, 'dbAutoIngestTimestamp' => 2, 'dbPodcastId' => 3, ),
BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID => 0, ImportedPodcastPeer::AUTO_INGEST => 1, ImportedPodcastPeer::PODCAST_ID => 2, ), BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID => 0, ImportedPodcastPeer::AUTO_INGEST => 1, ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP => 2, ImportedPodcastPeer::PODCAST_ID => 3, ),
BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'AUTO_INGEST' => 1, 'PODCAST_ID' => 2, ), BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'AUTO_INGEST' => 1, 'AUTO_INGEST_TIMESTAMP' => 2, 'PODCAST_ID' => 3, ),
BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'auto_ingest' => 1, 'podcast_id' => 2, ), BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'auto_ingest' => 1, 'auto_ingest_timestamp' => 2, 'podcast_id' => 3, ),
BasePeer::TYPE_NUM => array (0, 1, 2, ) BasePeer::TYPE_NUM => array (0, 1, 2, 3, )
); );
/** /**
@ -156,10 +159,12 @@ abstract class BaseImportedPodcastPeer
if (null === $alias) { if (null === $alias) {
$criteria->addSelectColumn(ImportedPodcastPeer::ID); $criteria->addSelectColumn(ImportedPodcastPeer::ID);
$criteria->addSelectColumn(ImportedPodcastPeer::AUTO_INGEST); $criteria->addSelectColumn(ImportedPodcastPeer::AUTO_INGEST);
$criteria->addSelectColumn(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP);
$criteria->addSelectColumn(ImportedPodcastPeer::PODCAST_ID); $criteria->addSelectColumn(ImportedPodcastPeer::PODCAST_ID);
} else { } else {
$criteria->addSelectColumn($alias . '.id'); $criteria->addSelectColumn($alias . '.id');
$criteria->addSelectColumn($alias . '.auto_ingest'); $criteria->addSelectColumn($alias . '.auto_ingest');
$criteria->addSelectColumn($alias . '.auto_ingest_timestamp');
$criteria->addSelectColumn($alias . '.podcast_id'); $criteria->addSelectColumn($alias . '.podcast_id');
} }
} }

View File

@ -8,10 +8,12 @@
* *
* @method ImportedPodcastQuery orderByDbId($order = Criteria::ASC) Order by the id column * @method ImportedPodcastQuery orderByDbId($order = Criteria::ASC) Order by the id column
* @method ImportedPodcastQuery orderByDbAutoIngest($order = Criteria::ASC) Order by the auto_ingest column * @method ImportedPodcastQuery orderByDbAutoIngest($order = Criteria::ASC) Order by the auto_ingest column
* @method ImportedPodcastQuery orderByDbAutoIngestTimestamp($order = Criteria::ASC) Order by the auto_ingest_timestamp column
* @method ImportedPodcastQuery orderByDbPodcastId($order = Criteria::ASC) Order by the podcast_id column * @method ImportedPodcastQuery orderByDbPodcastId($order = Criteria::ASC) Order by the podcast_id column
* *
* @method ImportedPodcastQuery groupByDbId() Group by the id column * @method ImportedPodcastQuery groupByDbId() Group by the id column
* @method ImportedPodcastQuery groupByDbAutoIngest() Group by the auto_ingest column * @method ImportedPodcastQuery groupByDbAutoIngest() Group by the auto_ingest column
* @method ImportedPodcastQuery groupByDbAutoIngestTimestamp() Group by the auto_ingest_timestamp column
* @method ImportedPodcastQuery groupByDbPodcastId() Group by the podcast_id column * @method ImportedPodcastQuery groupByDbPodcastId() Group by the podcast_id column
* *
* @method ImportedPodcastQuery leftJoin($relation) Adds a LEFT JOIN clause to the query * @method ImportedPodcastQuery leftJoin($relation) Adds a LEFT JOIN clause to the query
@ -26,10 +28,12 @@
* @method ImportedPodcast findOneOrCreate(PropelPDO $con = null) Return the first ImportedPodcast matching the query, or a new ImportedPodcast object populated from the query conditions when no match is found * @method ImportedPodcast findOneOrCreate(PropelPDO $con = null) Return the first ImportedPodcast matching the query, or a new ImportedPodcast object populated from the query conditions when no match is found
* *
* @method ImportedPodcast findOneByDbAutoIngest(boolean $auto_ingest) Return the first ImportedPodcast filtered by the auto_ingest column * @method ImportedPodcast findOneByDbAutoIngest(boolean $auto_ingest) Return the first ImportedPodcast filtered by the auto_ingest column
* @method ImportedPodcast findOneByDbAutoIngestTimestamp(string $auto_ingest_timestamp) Return the first ImportedPodcast filtered by the auto_ingest_timestamp column
* @method ImportedPodcast findOneByDbPodcastId(int $podcast_id) Return the first ImportedPodcast filtered by the podcast_id column * @method ImportedPodcast findOneByDbPodcastId(int $podcast_id) Return the first ImportedPodcast filtered by the podcast_id column
* *
* @method array findByDbId(int $id) Return ImportedPodcast objects filtered by the id column * @method array findByDbId(int $id) Return ImportedPodcast objects filtered by the id column
* @method array findByDbAutoIngest(boolean $auto_ingest) Return ImportedPodcast objects filtered by the auto_ingest column * @method array findByDbAutoIngest(boolean $auto_ingest) Return ImportedPodcast objects filtered by the auto_ingest column
* @method array findByDbAutoIngestTimestamp(string $auto_ingest_timestamp) Return ImportedPodcast objects filtered by the auto_ingest_timestamp column
* @method array findByDbPodcastId(int $podcast_id) Return ImportedPodcast objects filtered by the podcast_id column * @method array findByDbPodcastId(int $podcast_id) Return ImportedPodcast objects filtered by the podcast_id column
* *
* @package propel.generator.airtime.om * @package propel.generator.airtime.om
@ -138,7 +142,7 @@ abstract class BaseImportedPodcastQuery extends ModelCriteria
*/ */
protected function findPkSimple($key, $con) protected function findPkSimple($key, $con)
{ {
$sql = 'SELECT "id", "auto_ingest", "podcast_id" FROM "imported_podcast" WHERE "id" = :p0'; $sql = 'SELECT "id", "auto_ingest", "auto_ingest_timestamp", "podcast_id" FROM "imported_podcast" WHERE "id" = :p0';
try { try {
$stmt = $con->prepare($sql); $stmt = $con->prepare($sql);
$stmt->bindValue(':p0', $key, PDO::PARAM_INT); $stmt->bindValue(':p0', $key, PDO::PARAM_INT);
@ -296,6 +300,49 @@ abstract class BaseImportedPodcastQuery extends ModelCriteria
return $this->addUsingAlias(ImportedPodcastPeer::AUTO_INGEST, $dbAutoIngest, $comparison); return $this->addUsingAlias(ImportedPodcastPeer::AUTO_INGEST, $dbAutoIngest, $comparison);
} }
/**
* Filter the query on the auto_ingest_timestamp column
*
* Example usage:
* <code>
* $query->filterByDbAutoIngestTimestamp('2011-03-14'); // WHERE auto_ingest_timestamp = '2011-03-14'
* $query->filterByDbAutoIngestTimestamp('now'); // WHERE auto_ingest_timestamp = '2011-03-14'
* $query->filterByDbAutoIngestTimestamp(array('max' => 'yesterday')); // WHERE auto_ingest_timestamp < '2011-03-13'
* </code>
*
* @param mixed $dbAutoIngestTimestamp The value to use as filter.
* Values can be integers (unix timestamps), DateTime objects, or strings.
* Empty strings are treated as NULL.
* Use scalar values for equality.
* Use array values for in_array() equivalent.
* Use associative array('min' => $minValue, 'max' => $maxValue) for intervals.
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return ImportedPodcastQuery The current query, for fluid interface
*/
public function filterByDbAutoIngestTimestamp($dbAutoIngestTimestamp = null, $comparison = null)
{
if (is_array($dbAutoIngestTimestamp)) {
$useMinMax = false;
if (isset($dbAutoIngestTimestamp['min'])) {
$this->addUsingAlias(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP, $dbAutoIngestTimestamp['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($dbAutoIngestTimestamp['max'])) {
$this->addUsingAlias(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP, $dbAutoIngestTimestamp['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
$comparison = Criteria::IN;
}
}
return $this->addUsingAlias(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP, $dbAutoIngestTimestamp, $comparison);
}
/** /**
* Filter the query on the podcast_id column * Filter the query on the podcast_id column
* *

View File

@ -71,5 +71,18 @@ class Rest_Bootstrap extends Zend_Application_Module_Bootstrap
) )
); );
$router->addRoute('clear', $clearLibraryRoute); $router->addRoute('clear', $clearLibraryRoute);
$publishRoute = new Zend_Controller_Router_Route(
'rest/media/:id/publish',
array(
'controller' => 'media',
'action' => 'publish',
'module' => 'rest'
),
array(
'id' => '\d+'
)
);
$router->addRoute('publish', $publishRoute);
} }
} }

View File

@ -185,6 +185,23 @@ class Rest_MediaController extends Zend_Rest_Controller
} }
} }
/**
* Publish endpoint for individual media items
*/
public function publishAction() {
$id = $this->getId();
try {
// Is there a better way to do this?
$data = json_decode($this->getRequest()->getRawBody(), true)["sources"];
Application_Service_MediaService::publish($id, $data);
$this->getResponse()
->setHttpResponseCode(200);
} catch (Exception $e) {
$this->unknownErrorResponse();
Logging::error($e->getMessage());
}
}
private function getId() private function getId()
{ {
if (!$id = $this->_getParam('id', false)) { if (!$id = $this->_getParam('id', false)) {

View File

@ -111,9 +111,19 @@ class Application_Service_MediaService
} }
} }
/**
* Publish or remove the file with the given file ID from the services
* specified in the request data (ie. SoundCloud, the station podcast)
*
* @param int $fileId ID of the file to be published
* @param array $data request data containing what services to publish to
*/
public static function publish($fileId, $data) {
foreach ($data as $k => $v) {
$service = PublishServiceFactory::getService($k);
$service->$v($fileId);
}
}
} }

View File

@ -1,6 +1,6 @@
<?php <?php
class Application_Service_PodcastEpisodeService extends Application_Service_ThirdPartyCeleryService class Application_Service_PodcastEpisodeService extends Application_Service_ThirdPartyCeleryService implements Publish
{ {
/** /**
* Arbitrary constant identifiers for the internal tasks array * Arbitrary constant identifiers for the internal tasks array
@ -22,7 +22,7 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* @var array map of constant identifiers to Celery task names * @var array map of constant identifiers to Celery task names
*/ */
protected static $_CELERY_TASKS = [ protected static $_CELERY_TASKS = [
self::DOWNLOAD => 'podcast-download' // TODO: rename this to ingest? self::DOWNLOAD => 'podcast-download'
]; ];
/** /**
@ -37,7 +37,7 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
public function addPodcastEpisodePlaceholders($podcastId, $episodes) { public function addPodcastEpisodePlaceholders($podcastId, $episodes) {
$storedEpisodes = array(); $storedEpisodes = array();
foreach ($episodes as $episode) { foreach ($episodes as $episode) {
$e = $this->addPodcastEpisodePlaceholder($podcastId, $episode); $e = $this->addPlaceholder($podcastId, $episode);
array_push($storedEpisodes, $e); array_push($storedEpisodes, $e);
} }
return $storedEpisodes; return $storedEpisodes;
@ -52,19 +52,33 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* *
* @return PodcastEpisodes the stored PodcastEpisodes object * @return PodcastEpisodes the stored PodcastEpisodes object
*/ */
public function addPodcastEpisodePlaceholder($podcastId, $episode) { public function addPlaceholder($podcastId, $episode) {
// We need to check whether the array is parsed directly from the SimplePie // We need to check whether the array is parsed directly from the SimplePie
// feed object, or whether it's passed in as json // feed object, or whether it's passed in as json
if ($episode["enclosure"] instanceof SimplePie_Enclosure) { $enclosure = $episode["enclosure"];
$url = $episode["enclosure"]->get_link(); $url = $enclosure instanceof SimplePie_Enclosure ? $enclosure->get_link() : $enclosure["link"];
} else { return $this->_buildEpisode($podcastId, $url, $episode["guid"], $episode["pub_date"]);
$url = $episode["enclosure"]["link"]; }
}
/**
* 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) {
$e = new PodcastEpisodes(); $e = new PodcastEpisodes();
$e->setDbPodcastId($podcastId); $e->setDbPodcastId($podcastId);
$e->setDbDownloadUrl($url); $e->setDbDownloadUrl($url);
$e->setDbEpisodeGuid($episode["guid"]); $e->setDbEpisodeGuid($guid);
$e->setDbPublicationDate($episode["pub_date"]); $e->setDbPublicationDate($publicationDate);
$e->save(); $e->save();
return $e; return $e;
} }
@ -75,25 +89,24 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* @param array $episodes array of podcast episodes * @param array $episodes array of podcast episodes
*/ */
public function downloadEpisodes($episodes) { public function downloadEpisodes($episodes) {
$episodeUrls = array();
/** @var PodcastEpisodes $episode */ /** @var PodcastEpisodes $episode */
foreach($episodes as $episode) { foreach($episodes as $episode) {
array_push($episodeUrls, array("id" => $episode->getDbId(), $this->_download($episode->getDbId(), $episode->getDbDownloadUrl());
"url" => $episode->getDbDownloadUrl()));
} }
if (empty($episodeUrls)) return;
$this->_download($episodeUrls);
} }
/** /**
* Given an array of download URLs, download RSS feed tracks * Given an episode ID and a download URL, send a Celery task
* to download an RSS feed track
* *
* @param array $episodes array of episodes containing download URLs and IDs to send to Celery * @param int $id episode unique ID
* @param string $url download url for the episode
*/ */
private function _download($episodes) { private function _download($id, $url) {
$CC_CONFIG = Config::getConfig(); $CC_CONFIG = Config::getConfig();
$data = array( $data = array(
'episodes' => $episodes, 'id' => $id,
'url' => $url,
'callback_url' => Application_Common_HTTPHelper::getStationUrl() . '/rest/media', 'callback_url' => Application_Common_HTTPHelper::getStationUrl() . '/rest/media',
'api_key' => $apiKey = $CC_CONFIG["apiKey"][0], 'api_key' => $apiKey = $CC_CONFIG["apiKey"][0],
); );
@ -105,7 +118,7 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* *
* @param $task CeleryTasks the completed CeleryTasks object * @param $task CeleryTasks the completed CeleryTasks object
* @param $episodeId int PodcastEpisodes identifier * @param $episodeId int PodcastEpisodes identifier
* @param $episodes array array containing Podcast episode information * @param $episode stdClass simple object containing Podcast episode information
* @param $status string Celery task status * @param $status string Celery task status
* *
* @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object * @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object
@ -113,26 +126,44 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* @throws Exception * @throws Exception
* @throws PropelException * @throws PropelException
*/ */
public function updateTrackReference($task, $episodeId, $episodes, $status) { public function updateTrackReference($task, $episodeId, $episode, $status) {
$ref = parent::updateTrackReference($task, $episodeId, $episodes, $status); $ref = parent::updateTrackReference($task, $episodeId, $episode, $status);
if ($status == CELERY_SUCCESS_STATUS) { $dbEpisode = PodcastEpisodesQuery::create()
foreach ($episodes as $episode) { ->findOneByDbId($episode->episodeid);
// Since we process episode downloads as a batch, individual downloads can fail // Even if the task itself succeeds, the download could have failed, so check the status
// even if the task itself succeeds if ($status == CELERY_SUCCESS_STATUS && $episode->status) {
$dbEpisode = PodcastEpisodesQuery::create() $dbEpisode->setDbFileId($episode->fileid)->save();
->findOneByDbId($episode->episodeid); } else {
if ($episode->status) { Logging::warn("Celery task $task episode $episode->episodeid unsuccessful with status $episode->status");
$dbEpisode->setDbFileId($episode->fileid) $dbEpisode->delete();
->save();
} else {
Logging::warn("Celery task $task episode $episode->episodeid unsuccessful with status $episode->status");
$dbEpisode->delete();
}
}
} }
// TODO: do we need a broader fail condition here?
return $ref; return $ref;
} }
/**
* 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";
$e = $this->_buildEpisode($id, $url, $guid, date('r'));
$e->setDbFileId($fileId)->save();
}
/**
* 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();
}
} }

View File

@ -158,7 +158,9 @@ class Application_Service_PodcastService
$stationPodcast->save(); $stationPodcast->save();
Application_Model_Preference::setStationPodcastId($podcast->getDbId()); Application_Model_Preference::setStationPodcastId($podcast->getDbId());
// Set the download key when we create the station podcast
// The value is randomly generated in the setter
Application_Model_Preference::setStationPodcastDownloadKey();
} }
//TODO move this somewhere where it makes sense //TODO move this somewhere where it makes sense

View File

@ -0,0 +1,23 @@
<?php
class PublishServiceFactory {
/**
* Given an identifying string, get a PublishService object of that type
*
* @param $serviceName string the name of the service to create
*
* @return Publish|null
*/
public static function getService($serviceName) {
switch($serviceName) {
case SOUNDCLOUD_SERVICE_NAME:
return new Application_Service_SoundcloudService();
case STATION_PODCAST_SERVICE_NAME:
return new Application_Service_PodcastEpisodeService();
default:
return null;
}
}
}

View File

@ -7,7 +7,7 @@ require_once "ThirdPartyCeleryService.php";
* *
* Class Application_Service_SoundcloudService * Class Application_Service_SoundcloudService
*/ */
class Application_Service_SoundcloudService extends Application_Service_ThirdPartyCeleryService implements OAuth2 { class Application_Service_SoundcloudService extends Application_Service_ThirdPartyCeleryService implements OAuth2, Publish {
/** /**
* Arbitrary constant identifiers for the internal tasks array * Arbitrary constant identifiers for the internal tasks array
@ -145,7 +145,7 @@ class Application_Service_SoundcloudService extends Application_Service_ThirdPar
public function download($trackId = null) { public function download($trackId = null) {
$CC_CONFIG = Config::getConfig(); $CC_CONFIG = Config::getConfig();
$data = array( $data = array(
'callback_url' => Application_Common_HTTPHelper::getStationUrl() . '/rest/media', 'callback_url' => Application_Common_HTTPHelper::getStationUrl() . 'rest/media',
'api_key' => $apiKey = $CC_CONFIG["apiKey"][0], 'api_key' => $apiKey = $CC_CONFIG["apiKey"][0],
'token' => $this->_accessToken, 'token' => $this->_accessToken,
'track_id' => $trackId 'track_id' => $trackId
@ -287,4 +287,25 @@ class Application_Service_SoundcloudService extends Application_Service_ThirdPar
} }
} }
/**
* Publish the file with the given file ID to SoundCloud
*
* @param int $fileId ID of the file to be published
*/
public function publish($fileId) {
$this->upload($fileId);
}
/**
* Unpublish the file with the given file ID from SoundCloud
*
* @param int $fileId ID of the file to be unpublished
*
* @throws ServiceNotFoundException when a $fileId with no corresponding
* service identifier is given
*/
public function unpublish($fileId) {
$this->delete($fileId);
}
} }

View File

@ -16,8 +16,10 @@
</label> </label>
<fieldset> <fieldset>
<legend><?php echo _("Publish to:"); ?></legend> <legend><?php echo _("Publish to:"); ?></legend>
<input type="checkbox" name="publish_sources" id="station_podcast" value="station_podcast"><label for="station_podcast"><?php echo(_("My Station Podcast"));?></label><br/> <input ng-model="publishSources.station_podcast" type="checkbox" name="publish_sources" id="station_podcast" value="station_podcast">
<input type="checkbox" name="publish_sources" id="soundcloud" value="soundcloud"><label for="soundcloud">SoundCloud</label> <label for="station_podcast"><?php echo(_("My Station Podcast"));?></label><br/>
<input ng-model="publishSources.soundcloud" type="checkbox" name="publish_sources" id="soundcloud" value="soundcloud">
<label for="soundcloud">SoundCloud</label>
</fieldset> </fieldset>
</form> </form>

View File

@ -598,6 +598,7 @@
<table name="imported_podcast" phpName="ImportedPodcast"> <table name="imported_podcast" phpName="ImportedPodcast">
<column name="id" phpName="DbId" required="true" primaryKey="true" autoIncrement="true" type="INTEGER"/> <column name="id" phpName="DbId" required="true" primaryKey="true" autoIncrement="true" type="INTEGER"/>
<column name="auto_ingest" phpName="DbAutoIngest" type="BOOLEAN" required="true" defaultValue="false"/> <column name="auto_ingest" phpName="DbAutoIngest" type="BOOLEAN" required="true" defaultValue="false"/>
<column name="auto_ingest_timestamp" phpName="DbAutoIngestTimestamp" type="TIMESTAMP" required="false" />
<column name="podcast_id" phpName="DbPodcastId" required="true" type="INTEGER"/> <column name="podcast_id" phpName="DbPodcastId" required="true" type="INTEGER"/>
<foreign-key foreignTable="podcast" name="podcast_id_fkey" onDelete="CASCADE"> <foreign-key foreignTable="podcast" name="podcast_id_fkey" onDelete="CASCADE">
<reference local="podcast_id" foreign="id" /> <reference local="podcast_id" foreign="id" />

View File

@ -756,6 +756,7 @@ CREATE TABLE "imported_podcast"
( (
"id" serial NOT NULL, "id" serial NOT NULL,
"auto_ingest" BOOLEAN DEFAULT 'f' NOT NULL, "auto_ingest" BOOLEAN DEFAULT 'f' NOT NULL,
"auto_ingest_timestamp" TIMESTAMP,
"podcast_id" INTEGER NOT NULL, "podcast_id" INTEGER NOT NULL,
PRIMARY KEY ("id") PRIMARY KEY ("id")
); );

View File

@ -17,6 +17,8 @@ var AIRTIME = (function (AIRTIME) {
var publishApp = angular.module(PUBLISH_APP_NAME, []) var publishApp = angular.module(PUBLISH_APP_NAME, [])
.controller('RestController', function($scope, $http, mediaId, tab) { .controller('RestController', function($scope, $http, mediaId, tab) {
$scope.publishSources = {};
$http.get(endpoint + mediaId, { csrf_token: jQuery("#csrf").val() }) $http.get(endpoint + mediaId, { csrf_token: jQuery("#csrf").val() })
.success(function(json) { .success(function(json) {
console.log(json); console.log(json);
@ -24,8 +26,12 @@ var AIRTIME = (function (AIRTIME) {
tab.setName($scope.media.track_title); tab.setName($scope.media.track_title);
}); });
$scope.save = function() { $scope.publish = function() {
$http.put(endpoint + $scope.media.id, { csrf_token: jQuery("#csrf").val(), media: $scope.media }) var sources = {};
$.each($scope.publishSources, function(k, v) {
if (v) sources[k] = 'publish'; // Tentative TODO: decide on a robust implementation
});
$http.put(endpoint + $scope.media.id + '/publish', { csrf_token: jQuery("#csrf").val(), sources: sources })
.success(function() { .success(function() {
// TODO // TODO
}); });

View File

@ -86,35 +86,32 @@ def soundcloud_delete(token, track_id):
@celery.task(name='podcast-download', acks_late=True) @celery.task(name='podcast-download', acks_late=True)
def podcast_download(episodes, callback_url, api_key): def podcast_download(id, url, callback_url, api_key):
""" """
Download a batch of podcast episodes Download a batch of podcast episodes
:param episodes: array of episodes containing download URLs and IDs :param id: episode unique ID
:param url: download url for the episode
:param callback_url: callback URL to send the downloaded file to :param callback_url: callback URL to send the downloaded file to
:param api_key: API key for callback authentication :param api_key: API key for callback authentication
:rtype: None :rtype: None
""" """
response = [] # Object to store file IDs, episode IDs, and download status
for episode in episodes: # (important if there's an error before the file is posted)
logger.info(episode) obj = { 'episodeid': id }
# Object to store file IDs, episode IDs, and download status try:
# (important if there's an error before the file is posted) re = None
obj = { 'episodeid': episode['id'] } with closing(requests.get(url, stream=True)) as r:
try: filename = get_filename(r)
re = None re = requests.post(callback_url, files={'file': (filename, r.content)}, auth=requests.auth.HTTPBasicAuth(api_key, ''))
with closing(requests.get(episode['url'], stream=True)) as r: re.raise_for_status()
filename = get_filename(r) f = json.loads(re.content) # Read the response from the media API to get the file id
re = requests.post(callback_url, files={'file': (filename, r.content)}, auth=requests.auth.HTTPBasicAuth(api_key, '')) obj['fileid'] = f['id']
re.raise_for_status() obj['status'] = 1
f = json.loads(re.content) # Read the response from the media API to get the file id except Exception as e:
obj['fileid'] = f['id'] logger.info('Error during file download: {0}'.format(e.message))
obj['status'] = 1 obj['status'] = 0
except Exception as e: return json.dumps(obj)
logger.info('Error during file download: {0}'.format(e.message))
obj['status'] = 0
response.append(obj)
return json.dumps(response)
def get_filename(r): def get_filename(r):