diff --git a/airtime_mvc/application/Bootstrap.php b/airtime_mvc/application/Bootstrap.php
index ab250bc27..18ad96c9e 100644
--- a/airtime_mvc/application/Bootstrap.php
+++ b/airtime_mvc/application/Bootstrap.php
@@ -42,6 +42,10 @@ require_once "MediaType.php";
/* Interfaces */
require_once "OAuth2.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/CustomDecorators.php';
diff --git a/airtime_mvc/application/common/CeleryManager.php b/airtime_mvc/application/common/CeleryManager.php
index 6f8f361d5..6e0c900d0 100644
--- a/airtime_mvc/application/common/CeleryManager.php
+++ b/airtime_mvc/application/common/CeleryManager.php
@@ -1,7 +1,5 @@
getDbPodcastId());
- // A bit hacky... sort the episodes by publication date to get the most recent
- usort($podcastArray["episodes"], array(static::class, "_sortByEpisodePubDate"));
- $episodeData = $podcastArray["episodes"][0];
+ $episodes = static::_findUningestedEpisodes($podcast, $service);
+ $podcast->setDbAutoIngestTimestamp(date('r'))->save();
+ $service->downloadEpisodes($episodes);
+ }
+
+ 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"]);
// Make sure there's no existing episode placeholder or import, and that the data is non-empty
if (empty($episode) && !empty($episodeData)) {
- $placeholder = $service->addPodcastEpisodePlaceholder($podcast->getDbPodcastId(), $episodeData);
+ $placeholder = $service->addPlaceholder($podcast->getDbPodcastId(), $episodeData);
array_push($episodes, $placeholder);
}
}
-
- $service->downloadEpisodes($episodes);
- Application_Model_Preference::setPodcastPollLock(microtime(true));
+ return $episodes;
}
/**
diff --git a/airtime_mvc/application/common/interface/Publish.php b/airtime_mvc/application/common/interface/Publish.php
new file mode 100644
index 000000000..0a99314bf
--- /dev/null
+++ b/airtime_mvc/application/common/interface/Publish.php
@@ -0,0 +1,23 @@
+verifyAPIKey()) {
+ if ($this->isVerifiedDownload() || $this->verifyAPIKey()) {
return true;
}
@@ -233,7 +233,32 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
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) {
return SecurityHelper::verifyCSRFToken($token);
}
diff --git a/airtime_mvc/application/controllers/plugins/PageLayoutInitPlugin.php b/airtime_mvc/application/controllers/plugins/PageLayoutInitPlugin.php
index dca720028..67818ec84 100644
--- a/airtime_mvc/application/controllers/plugins/PageLayoutInitPlugin.php
+++ b/airtime_mvc/application/controllers/plugins/PageLayoutInitPlugin.php
@@ -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')
->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/libs/underscore-min.js?' . $CC_CONFIG['airtime_version'], 'text/javascript')
diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php
index 65236f2cf..49a8c09c0 100644
--- a/airtime_mvc/application/models/Preference.php
+++ b/airtime_mvc/application/models/Preference.php
@@ -1525,4 +1525,13 @@ class Application_Model_Preference
{
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);
+ }
}
diff --git a/airtime_mvc/application/models/airtime/PodcastEpisodes.php b/airtime_mvc/application/models/airtime/PodcastEpisodes.php
index 36019ff88..afd48e6b8 100644
--- a/airtime_mvc/application/models/airtime/PodcastEpisodes.php
+++ b/airtime_mvc/application/models/airtime/PodcastEpisodes.php
@@ -15,4 +15,26 @@
*/
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();
+ }
+
}
diff --git a/airtime_mvc/application/models/airtime/StationPodcast.php b/airtime_mvc/application/models/airtime/StationPodcast.php
index 24075d111..b033bab0c 100644
--- a/airtime_mvc/application/models/airtime/StationPodcast.php
+++ b/airtime_mvc/application/models/airtime/StationPodcast.php
@@ -15,4 +15,24 @@
*/
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;
+ }
+
}
diff --git a/airtime_mvc/application/models/airtime/map/ImportedPodcastTableMap.php b/airtime_mvc/application/models/airtime/map/ImportedPodcastTableMap.php
index 820a05539..53030858e 100644
--- a/airtime_mvc/application/models/airtime/map/ImportedPodcastTableMap.php
+++ b/airtime_mvc/application/models/airtime/map/ImportedPodcastTableMap.php
@@ -41,6 +41,7 @@ class ImportedPodcastTableMap extends TableMap
// columns
$this->addPrimaryKey('id', 'DbId', 'INTEGER', true, null, null);
$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);
// validators
} // initialize()
diff --git a/airtime_mvc/application/models/airtime/om/BaseImportedPodcast.php b/airtime_mvc/application/models/airtime/om/BaseImportedPodcast.php
index 21e84891b..1b72d0cef 100644
--- a/airtime_mvc/application/models/airtime/om/BaseImportedPodcast.php
+++ b/airtime_mvc/application/models/airtime/om/BaseImportedPodcast.php
@@ -42,6 +42,12 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
*/
protected $auto_ingest;
+ /**
+ * The value for the auto_ingest_timestamp field.
+ * @var string
+ */
+ protected $auto_ingest_timestamp;
+
/**
* The value for the podcast_id field.
* @var int
@@ -116,6 +122,41 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
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.
*
@@ -177,6 +218,29 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
return $this;
} // 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.
*
@@ -240,7 +304,8 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
$this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : 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->setNew(false);
@@ -250,7 +315,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
}
$this->postHydrate($row, $startcol, $rehydrate);
- return $startcol + 3; // 3 = ImportedPodcastPeer::NUM_HYDRATE_COLUMNS.
+ return $startcol + 4; // 4 = ImportedPodcastPeer::NUM_HYDRATE_COLUMNS.
} catch (Exception $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)) {
$modifiedColumns[':p' . $index++] = '"auto_ingest"';
}
+ if ($this->isColumnModified(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP)) {
+ $modifiedColumns[':p' . $index++] = '"auto_ingest_timestamp"';
+ }
if ($this->isColumnModified(ImportedPodcastPeer::PODCAST_ID)) {
$modifiedColumns[':p' . $index++] = '"podcast_id"';
}
@@ -514,6 +582,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
case '"auto_ingest"':
$stmt->bindValue($identifier, $this->auto_ingest, PDO::PARAM_BOOL);
break;
+ case '"auto_ingest_timestamp"':
+ $stmt->bindValue($identifier, $this->auto_ingest_timestamp, PDO::PARAM_STR);
+ break;
case '"podcast_id"':
$stmt->bindValue($identifier, $this->podcast_id, PDO::PARAM_INT);
break;
@@ -663,6 +734,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
return $this->getDbAutoIngest();
break;
case 2:
+ return $this->getDbAutoIngestTimestamp();
+ break;
+ case 3:
return $this->getDbPodcastId();
break;
default:
@@ -696,7 +770,8 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
$result = array(
$keys[0] => $this->getDbId(),
$keys[1] => $this->getDbAutoIngest(),
- $keys[2] => $this->getDbPodcastId(),
+ $keys[2] => $this->getDbAutoIngestTimestamp(),
+ $keys[3] => $this->getDbPodcastId(),
);
$virtualColumns = $this->virtualColumns;
foreach ($virtualColumns as $key => $virtualColumn) {
@@ -748,6 +823,9 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
$this->setDbAutoIngest($value);
break;
case 2:
+ $this->setDbAutoIngestTimestamp($value);
+ break;
+ case 3:
$this->setDbPodcastId($value);
break;
} // 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[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::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);
return $criteria;
@@ -855,6 +935,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
public function copyInto($copyObj, $deepCopy = false, $makeNew = true)
{
$copyObj->setDbAutoIngest($this->getDbAutoIngest());
+ $copyObj->setDbAutoIngestTimestamp($this->getDbAutoIngestTimestamp());
$copyObj->setDbPodcastId($this->getDbPodcastId());
if ($deepCopy && !$this->startCopy) {
@@ -973,6 +1054,7 @@ abstract class BaseImportedPodcast extends BaseObject implements Persistent
{
$this->id = null;
$this->auto_ingest = null;
+ $this->auto_ingest_timestamp = null;
$this->podcast_id = null;
$this->alreadyInSave = false;
$this->alreadyInValidation = false;
diff --git a/airtime_mvc/application/models/airtime/om/BaseImportedPodcastPeer.php b/airtime_mvc/application/models/airtime/om/BaseImportedPodcastPeer.php
index 9263b0a67..70aa4080f 100644
--- a/airtime_mvc/application/models/airtime/om/BaseImportedPodcastPeer.php
+++ b/airtime_mvc/application/models/airtime/om/BaseImportedPodcastPeer.php
@@ -24,13 +24,13 @@ abstract class BaseImportedPodcastPeer
const TM_CLASS = 'ImportedPodcastTableMap';
/** The total number of columns. */
- const NUM_COLUMNS = 3;
+ const NUM_COLUMNS = 4;
/** The number of lazy-loaded columns. */
const NUM_LAZY_LOAD_COLUMNS = 0;
/** 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 */
const ID = 'imported_podcast.id';
@@ -38,6 +38,9 @@ abstract class BaseImportedPodcastPeer
/** the column name for the auto_ingest field */
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 */
const PODCAST_ID = 'imported_podcast.podcast_id';
@@ -60,12 +63,12 @@ abstract class BaseImportedPodcastPeer
* e.g. ImportedPodcastPeer::$fieldNames[ImportedPodcastPeer::TYPE_PHPNAME][0] = 'Id'
*/
protected static $fieldNames = array (
- BasePeer::TYPE_PHPNAME => array ('DbId', 'DbAutoIngest', 'DbPodcastId', ),
- BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbAutoIngest', 'dbPodcastId', ),
- BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID, ImportedPodcastPeer::AUTO_INGEST, ImportedPodcastPeer::PODCAST_ID, ),
- BasePeer::TYPE_RAW_COLNAME => array ('ID', 'AUTO_INGEST', 'PODCAST_ID', ),
- BasePeer::TYPE_FIELDNAME => array ('id', 'auto_ingest', 'podcast_id', ),
- BasePeer::TYPE_NUM => array (0, 1, 2, )
+ BasePeer::TYPE_PHPNAME => array ('DbId', 'DbAutoIngest', 'DbAutoIngestTimestamp', 'DbPodcastId', ),
+ BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbAutoIngest', 'dbAutoIngestTimestamp', 'dbPodcastId', ),
+ BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID, ImportedPodcastPeer::AUTO_INGEST, ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP, ImportedPodcastPeer::PODCAST_ID, ),
+ BasePeer::TYPE_RAW_COLNAME => array ('ID', 'AUTO_INGEST', 'AUTO_INGEST_TIMESTAMP', 'PODCAST_ID', ),
+ BasePeer::TYPE_FIELDNAME => array ('id', 'auto_ingest', 'auto_ingest_timestamp', 'podcast_id', ),
+ BasePeer::TYPE_NUM => array (0, 1, 2, 3, )
);
/**
@@ -75,12 +78,12 @@ abstract class BaseImportedPodcastPeer
* e.g. ImportedPodcastPeer::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0
*/
protected static $fieldKeys = array (
- BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbAutoIngest' => 1, 'DbPodcastId' => 2, ),
- BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbAutoIngest' => 1, 'dbPodcastId' => 2, ),
- BasePeer::TYPE_COLNAME => array (ImportedPodcastPeer::ID => 0, ImportedPodcastPeer::AUTO_INGEST => 1, ImportedPodcastPeer::PODCAST_ID => 2, ),
- BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'AUTO_INGEST' => 1, 'PODCAST_ID' => 2, ),
- BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'auto_ingest' => 1, 'podcast_id' => 2, ),
- BasePeer::TYPE_NUM => array (0, 1, 2, )
+ BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbAutoIngest' => 1, 'DbAutoIngestTimestamp' => 2, 'DbPodcastId' => 3, ),
+ BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbAutoIngest' => 1, 'dbAutoIngestTimestamp' => 2, 'dbPodcastId' => 3, ),
+ 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, 'AUTO_INGEST_TIMESTAMP' => 2, 'PODCAST_ID' => 3, ),
+ BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'auto_ingest' => 1, 'auto_ingest_timestamp' => 2, 'podcast_id' => 3, ),
+ BasePeer::TYPE_NUM => array (0, 1, 2, 3, )
);
/**
@@ -156,10 +159,12 @@ abstract class BaseImportedPodcastPeer
if (null === $alias) {
$criteria->addSelectColumn(ImportedPodcastPeer::ID);
$criteria->addSelectColumn(ImportedPodcastPeer::AUTO_INGEST);
+ $criteria->addSelectColumn(ImportedPodcastPeer::AUTO_INGEST_TIMESTAMP);
$criteria->addSelectColumn(ImportedPodcastPeer::PODCAST_ID);
} else {
$criteria->addSelectColumn($alias . '.id');
$criteria->addSelectColumn($alias . '.auto_ingest');
+ $criteria->addSelectColumn($alias . '.auto_ingest_timestamp');
$criteria->addSelectColumn($alias . '.podcast_id');
}
}
diff --git a/airtime_mvc/application/models/airtime/om/BaseImportedPodcastQuery.php b/airtime_mvc/application/models/airtime/om/BaseImportedPodcastQuery.php
index d859e734f..fa52bc7cc 100644
--- a/airtime_mvc/application/models/airtime/om/BaseImportedPodcastQuery.php
+++ b/airtime_mvc/application/models/airtime/om/BaseImportedPodcastQuery.php
@@ -8,10 +8,12 @@
*
* @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 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 groupByDbId() Group by the id 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 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 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 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 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
*
* @package propel.generator.airtime.om
@@ -138,7 +142,7 @@ abstract class BaseImportedPodcastQuery extends ModelCriteria
*/
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 {
$stmt = $con->prepare($sql);
$stmt->bindValue(':p0', $key, PDO::PARAM_INT);
@@ -296,6 +300,49 @@ abstract class BaseImportedPodcastQuery extends ModelCriteria
return $this->addUsingAlias(ImportedPodcastPeer::AUTO_INGEST, $dbAutoIngest, $comparison);
}
+ /**
+ * Filter the query on the auto_ingest_timestamp column
+ *
+ * Example usage:
+ *
+ * $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'
+ *
+ *
+ * @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
*
diff --git a/airtime_mvc/application/modules/rest/Bootstrap.php b/airtime_mvc/application/modules/rest/Bootstrap.php
index 247e18887..08e249970 100644
--- a/airtime_mvc/application/modules/rest/Bootstrap.php
+++ b/airtime_mvc/application/modules/rest/Bootstrap.php
@@ -71,5 +71,18 @@ class Rest_Bootstrap extends Zend_Application_Module_Bootstrap
)
);
$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);
}
}
diff --git a/airtime_mvc/application/modules/rest/controllers/MediaController.php b/airtime_mvc/application/modules/rest/controllers/MediaController.php
index d276e829b..424aed435 100644
--- a/airtime_mvc/application/modules/rest/controllers/MediaController.php
+++ b/airtime_mvc/application/modules/rest/controllers/MediaController.php
@@ -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()
{
if (!$id = $this->_getParam('id', false)) {
diff --git a/airtime_mvc/application/services/MediaService.php b/airtime_mvc/application/services/MediaService.php
index 807d6de17..40464f1a9 100644
--- a/airtime_mvc/application/services/MediaService.php
+++ b/airtime_mvc/application/services/MediaService.php
@@ -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);
+ }
+ }
}
diff --git a/airtime_mvc/application/services/PodcastEpisodeService.php b/airtime_mvc/application/services/PodcastEpisodeService.php
index 6e8276ed5..db38df1e5 100644
--- a/airtime_mvc/application/services/PodcastEpisodeService.php
+++ b/airtime_mvc/application/services/PodcastEpisodeService.php
@@ -1,6 +1,6 @@
'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) {
$storedEpisodes = array();
foreach ($episodes as $episode) {
- $e = $this->addPodcastEpisodePlaceholder($podcastId, $episode);
+ $e = $this->addPlaceholder($podcastId, $episode);
array_push($storedEpisodes, $e);
}
return $storedEpisodes;
@@ -52,19 +52,33 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
*
* @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
// feed object, or whether it's passed in as json
- if ($episode["enclosure"] instanceof SimplePie_Enclosure) {
- $url = $episode["enclosure"]->get_link();
- } else {
- $url = $episode["enclosure"]["link"];
- }
+ $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) {
$e = new PodcastEpisodes();
$e->setDbPodcastId($podcastId);
$e->setDbDownloadUrl($url);
- $e->setDbEpisodeGuid($episode["guid"]);
- $e->setDbPublicationDate($episode["pub_date"]);
+ $e->setDbEpisodeGuid($guid);
+ $e->setDbPublicationDate($publicationDate);
$e->save();
return $e;
}
@@ -75,25 +89,24 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* @param array $episodes array of podcast episodes
*/
public function downloadEpisodes($episodes) {
- $episodeUrls = array();
/** @var PodcastEpisodes $episode */
foreach($episodes as $episode) {
- array_push($episodeUrls, array("id" => $episode->getDbId(),
- "url" => $episode->getDbDownloadUrl()));
+ $this->_download($episode->getDbId(), $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();
$data = array(
- 'episodes' => $episodes,
+ 'id' => $id,
+ 'url' => $url,
'callback_url' => Application_Common_HTTPHelper::getStationUrl() . '/rest/media',
'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 $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
*
* @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object
@@ -113,26 +126,44 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
* @throws Exception
* @throws PropelException
*/
- public function updateTrackReference($task, $episodeId, $episodes, $status) {
- $ref = parent::updateTrackReference($task, $episodeId, $episodes, $status);
+ public function updateTrackReference($task, $episodeId, $episode, $status) {
+ $ref = parent::updateTrackReference($task, $episodeId, $episode, $status);
- if ($status == CELERY_SUCCESS_STATUS) {
- foreach ($episodes as $episode) {
- // Since we process episode downloads as a batch, individual downloads can fail
- // even if the task itself succeeds
- $dbEpisode = PodcastEpisodesQuery::create()
- ->findOneByDbId($episode->episodeid);
- if ($episode->status) {
- $dbEpisode->setDbFileId($episode->fileid)
- ->save();
- } else {
- Logging::warn("Celery task $task episode $episode->episodeid unsuccessful with status $episode->status");
- $dbEpisode->delete();
- }
- }
+ $dbEpisode = PodcastEpisodesQuery::create()
+ ->findOneByDbId($episode->episodeid);
+ // Even if the task itself succeeds, the download could have failed, so check the status
+ if ($status == CELERY_SUCCESS_STATUS && $episode->status) {
+ $dbEpisode->setDbFileId($episode->fileid)->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;
}
+
+ /**
+ * 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();
+ }
}
\ No newline at end of file
diff --git a/airtime_mvc/application/services/PodcastService.php b/airtime_mvc/application/services/PodcastService.php
index 4e553ed9d..d7f977916 100644
--- a/airtime_mvc/application/services/PodcastService.php
+++ b/airtime_mvc/application/services/PodcastService.php
@@ -158,7 +158,9 @@ class Application_Service_PodcastService
$stationPodcast->save();
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
diff --git a/airtime_mvc/application/services/PublishServiceFactory.php b/airtime_mvc/application/services/PublishServiceFactory.php
new file mode 100644
index 000000000..23ee071f8
--- /dev/null
+++ b/airtime_mvc/application/services/PublishServiceFactory.php
@@ -0,0 +1,23 @@
+ Application_Common_HTTPHelper::getStationUrl() . '/rest/media',
+ 'callback_url' => Application_Common_HTTPHelper::getStationUrl() . 'rest/media',
'api_key' => $apiKey = $CC_CONFIG["apiKey"][0],
'token' => $this->_accessToken,
'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);
+ }
+
}
\ No newline at end of file
diff --git a/airtime_mvc/application/views/scripts/library/publish-dialog.phtml b/airtime_mvc/application/views/scripts/library/publish-dialog.phtml
index 10bd54eb5..5b5ebe314 100644
--- a/airtime_mvc/application/views/scripts/library/publish-dialog.phtml
+++ b/airtime_mvc/application/views/scripts/library/publish-dialog.phtml
@@ -16,8 +16,10 @@