Refactored code out to Core ns and data classes
This commit is contained in:
parent
4e5547e1f8
commit
6b0550f14b
@ -3,42 +3,27 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\GPodderSync\Controller;
|
namespace OCA\GPodderSync\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use OCA\GPodderSync\Core\PodcastData\PodcastMetrics;
|
||||||
|
use OCA\GPodderSync\Core\PodcastData\PodcastMetricsReader;
|
||||||
use OCA\GPodderSync\Service\PodcastCacheService;
|
|
||||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
|
||||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeEntity;
|
|
||||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeRepository;
|
|
||||||
|
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
class PersonalSettingsController extends Controller {
|
class PersonalSettingsController extends Controller {
|
||||||
|
|
||||||
private LoggerInterface $logger;
|
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private SubscriptionChangeRepository $subscriptionChangeRepository;
|
private PodcastMetricsReader $metricsReader;
|
||||||
private EpisodeActionRepository $episodeActionRepository;
|
|
||||||
private PodcastCacheService $podcastCacheService;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $AppName,
|
string $AppName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
LoggerInterface $logger,
|
|
||||||
string $UserId,
|
string $UserId,
|
||||||
SubscriptionChangeRepository $subscriptionChangeRepository,
|
PodcastMetricsReader $metricsReader,
|
||||||
EpisodeActionRepository $episodeActionRepository,
|
|
||||||
PodcastCacheService $podcastCacheService,
|
|
||||||
) {
|
) {
|
||||||
parent::__construct($AppName, $request);
|
parent::__construct($AppName, $request);
|
||||||
$this->logger = $logger;
|
|
||||||
$this->userId = $UserId ?? '';
|
$this->userId = $UserId ?? '';
|
||||||
$this->subscriptionChangeRepository = $subscriptionChangeRepository;
|
$this->metricsReader = $metricsReader;
|
||||||
$this->episodeActionRepository = $episodeActionRepository;
|
|
||||||
$this->podcastCacheService = $podcastCacheService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,65 +34,13 @@ class PersonalSettingsController extends Controller {
|
|||||||
* @return JSONResponse
|
* @return JSONResponse
|
||||||
*/
|
*/
|
||||||
public function metrics(): JSONResponse {
|
public function metrics(): JSONResponse {
|
||||||
$sinceDatetime = (new DateTime)->setTimestamp(0);
|
$metrics = $this->metricsReader->metrics($this->userId);
|
||||||
$subscriptionChanges = $this->subscriptionChangeRepository->findAllSubscribed($sinceDatetime, $this->userId);
|
|
||||||
$episodeActions = $this->episodeActionRepository->findAll(0, $this->userId);
|
|
||||||
|
|
||||||
$subStats = array();
|
|
||||||
foreach ($episodeActions as $ep) {
|
|
||||||
$url = $ep->getPodcast();
|
|
||||||
$stats = $subStats[$url] ?? self::defaultSubscriptionData();
|
|
||||||
$actionCounts = $stats['actionCounts'];
|
|
||||||
$actionLower = strtolower($ep->getAction());
|
|
||||||
if (array_key_exists($actionLower, $actionCounts)) {
|
|
||||||
$actionCounts[$actionLower]++;
|
|
||||||
}
|
|
||||||
$stats['actionCounts'] = $actionCounts;
|
|
||||||
if ($actionLower == 'play') {
|
|
||||||
$seconds = $ep->getPosition();
|
|
||||||
if ($seconds && $seconds != -1) {
|
|
||||||
$stats['listenedSeconds'] += $seconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$subStats[$url] = $stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
$subscriptions = array_map(function (SubscriptionChangeEntity $sub) use ($subStats) {
|
|
||||||
$url = $sub->getUrl();
|
|
||||||
$stats = $subStats[$url] ?? self::defaultSubscriptionData();
|
|
||||||
$sub = [
|
|
||||||
'url' => $url ?? '',
|
|
||||||
'listenedSeconds' => $stats['listenedSeconds'],
|
|
||||||
'actionCounts' => $stats['actionCounts'],
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$podcast = $this->podcastCacheService->getCachedOrFetchPodcastData($url);
|
|
||||||
$sub['podcast'] = $podcast;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$sub['podcast'] = null;
|
|
||||||
$this->logger->error("Failed to get podcast data.", [
|
|
||||||
'exception' => $e,
|
|
||||||
'podcastUrl' => $url,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return $sub;
|
|
||||||
}, $subscriptionChanges);
|
|
||||||
|
|
||||||
|
$metricsArrays = array_map(function (PodcastMetrics $metric) {
|
||||||
|
return $metric->toArray();
|
||||||
|
}, $metrics);
|
||||||
return new JSONResponse([
|
return new JSONResponse([
|
||||||
'subscriptions' => $subscriptions,
|
'subscriptions' => $metricsArrays,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function defaultSubscriptionData(): array {
|
|
||||||
return [
|
|
||||||
'listenedSeconds' => 0,
|
|
||||||
'actionCounts' => [
|
|
||||||
'download' => 0,
|
|
||||||
'delete' => 0,
|
|
||||||
'play' => 0,
|
|
||||||
'new' => 0,
|
|
||||||
'flattr' => 0,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
36
lib/Core/PodcastData/PodcastActionCounts.php
Normal file
36
lib/Core/PodcastData/PodcastActionCounts.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\GPodderSync\Core\PodcastData;
|
||||||
|
|
||||||
|
class PodcastActionCounts {
|
||||||
|
private int $delete = 0;
|
||||||
|
private int $download = 0;
|
||||||
|
private int $flattr = 0;
|
||||||
|
private int $new = 0;
|
||||||
|
private int $play = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $action
|
||||||
|
*/
|
||||||
|
public function incrementAction(string $action): void {
|
||||||
|
switch ($action) {
|
||||||
|
case 'delete': $this->delete++; break;
|
||||||
|
case 'download': $this->download++; break;
|
||||||
|
case 'flattr': $this->flattr++; break;
|
||||||
|
case 'new': $this->new++; break;
|
||||||
|
case 'play': $this->play++; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array {
|
||||||
|
return
|
||||||
|
[
|
||||||
|
'delete' => $this->delete,
|
||||||
|
'download' => $this->download,
|
||||||
|
'flattr' => $this->flattr,
|
||||||
|
'new' => $this->new,
|
||||||
|
'play' => $this->play,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
122
lib/Core/PodcastData/PodcastData.php
Normal file
122
lib/Core/PodcastData/PodcastData.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\GPodderSync\Core\PodcastData;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use SimpleXMLElement;
|
||||||
|
|
||||||
|
class PodcastData {
|
||||||
|
private string $title;
|
||||||
|
private string $author;
|
||||||
|
private string $link;
|
||||||
|
private string $description;
|
||||||
|
private string $image;
|
||||||
|
private int $fetchedAtUnix;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $title,
|
||||||
|
string $author,
|
||||||
|
string $link,
|
||||||
|
string $description,
|
||||||
|
string $image,
|
||||||
|
int $fetchedAtUnix,
|
||||||
|
) {
|
||||||
|
$this->title = $title;
|
||||||
|
$this->author = $author;
|
||||||
|
$this->link = $link;
|
||||||
|
$this->description = $description;
|
||||||
|
$this->image = $image;
|
||||||
|
$this->fetchedAtUnix = $fetchedAtUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parseXml(string $xmlString): PodcastData {
|
||||||
|
$xml = new SimpleXMLElement($xmlString);
|
||||||
|
$channel = $xml->channel;
|
||||||
|
return new PodcastData(
|
||||||
|
title: (string)$channel->title,
|
||||||
|
author: self::getXPathContent($xml, '/rss/channel/itunes:author'),
|
||||||
|
link: (string)$channel->link,
|
||||||
|
description: (string)$channel->description,
|
||||||
|
image:
|
||||||
|
self::getXPathContent($xml, '/rss/channel/image/url')
|
||||||
|
?? self::getXPathAttribute($xml, '/rss/channel/itunes:image/@href'),
|
||||||
|
fetchedAtUnix: (new DateTime())->getTimestamp(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getXPathContent(SimpleXMLElement $xml, string $xpath) {
|
||||||
|
$match = $xml->xpath($xpath);
|
||||||
|
if ($match) {
|
||||||
|
return (string)$match[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getXPathAttribute(SimpleXMLElement $xml, string $xpath) {
|
||||||
|
$match = $xml->xpath($xpath);
|
||||||
|
if ($match) {
|
||||||
|
return (string)$match[0][0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTitle(): string {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAuthor(): string {
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLink(): string {
|
||||||
|
return $this->link;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDescription(): string {
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getImage(): string {
|
||||||
|
return $this->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getFetchedAtUnix(): int {
|
||||||
|
return $this->fetchedAtUnix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString() : String {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array {
|
||||||
|
return
|
||||||
|
[
|
||||||
|
'title' => $this->title,
|
||||||
|
'author' => $this->author,
|
||||||
|
'link' => $this->link,
|
||||||
|
'description' => $this->description,
|
||||||
|
'image' => $this->image,
|
||||||
|
'fetchedAtUnix' => $this->fetchedAtUnix,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
66
lib/Core/PodcastData/PodcastDataCache.php
Normal file
66
lib/Core/PodcastData/PodcastDataCache.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\GPodderSync\Core\PodcastData;
|
||||||
|
|
||||||
|
use OCP\Http\Client\IClient;
|
||||||
|
use OCP\Http\Client\IClientService;
|
||||||
|
use OCP\ICache;
|
||||||
|
use OCP\ICacheFactory;
|
||||||
|
|
||||||
|
class PodcastDataCache {
|
||||||
|
private ?ICache $cache = null;
|
||||||
|
private IClient $httpClient;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ICacheFactory $cacheFactory,
|
||||||
|
IClientService $httpClientService,
|
||||||
|
) {
|
||||||
|
if ($cacheFactory->isLocalCacheAvailable()) {
|
||||||
|
$this->cache = $cacheFactory->createLocal('GPodderSync-Podcasts');
|
||||||
|
}
|
||||||
|
$this->httpClient = $httpClientService->newClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCachedOrFetchPodcastData(string $url): PodcastData {
|
||||||
|
if ($this->cache == null) {
|
||||||
|
return $this->fetchPodcastData($url);
|
||||||
|
}
|
||||||
|
$oldData = $this->tryGetCachedPodcastData($url);
|
||||||
|
if ($oldData) {
|
||||||
|
return $oldData;
|
||||||
|
}
|
||||||
|
$newData = $this->fetchPodcastData($url);
|
||||||
|
$this->trySetCachedPodcastData($url, $newData);
|
||||||
|
return $newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchPodcastData(string $url): PodcastData {
|
||||||
|
$resp = $this->httpClient->get($url);
|
||||||
|
$statusCode = $resp->getStatusCode();
|
||||||
|
if ($statusCode < 200 || $statusCode >= 300) {
|
||||||
|
throw new \ErrorException("Podcast RSS URL returned non-2xx status code: $statusCode");
|
||||||
|
}
|
||||||
|
$body = $resp->getBody();
|
||||||
|
return PodcastData::parseXml($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tryGetCachedPodcastData(string $url): ?PodcastData {
|
||||||
|
$oldData = $this->cache->get($url);
|
||||||
|
if (!$oldData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new PodcastData(
|
||||||
|
title: $oldData['title'],
|
||||||
|
author: $oldData['author'],
|
||||||
|
link: $oldData['link'],
|
||||||
|
description: $oldData['description'],
|
||||||
|
image: $oldData['image'],
|
||||||
|
fetchedAtUnix: $oldData['fetchedAtUnix'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function trySetCachedPodcastData(string $url, PodcastData $data) {
|
||||||
|
$this->cache->set($url, $data->toArray());
|
||||||
|
}
|
||||||
|
}
|
68
lib/Core/PodcastData/PodcastMetrics.php
Normal file
68
lib/Core/PodcastData/PodcastMetrics.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\GPodderSync\Core\PodcastData;
|
||||||
|
|
||||||
|
class PodcastMetrics {
|
||||||
|
private string $url;
|
||||||
|
private int $listenedSeconds;
|
||||||
|
private PodcastActionCounts $actionCounts;
|
||||||
|
private ?PodcastData $podcastData;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $url,
|
||||||
|
int $listenedSeconds = 0,
|
||||||
|
?PodcastActionCounts $actionCounts = null,
|
||||||
|
?PodcastData $podcastData = null,
|
||||||
|
) {
|
||||||
|
$this->url = $url;
|
||||||
|
$this->actionCounts = $actionCounts ?? new PodcastActionCounts;
|
||||||
|
$this->listenedSeconds = $listenedSeconds;
|
||||||
|
$this->podcastData = $podcastData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUrl(): string {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PodcastActionCounts
|
||||||
|
*/
|
||||||
|
public function getActionCounts(): PodcastActionCounts {
|
||||||
|
return $this->actionCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getListenedSeconds(): int {
|
||||||
|
return $this->listenedSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $seconds
|
||||||
|
*/
|
||||||
|
public function addListenedSeconds(int $seconds): void {
|
||||||
|
$this->listenedSeconds += $seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PodcastData|null
|
||||||
|
*/
|
||||||
|
public function getPodcastData(): ?PodcastData {
|
||||||
|
return $this->podcastData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array {
|
||||||
|
return
|
||||||
|
[
|
||||||
|
'url' => $this->url,
|
||||||
|
'listenedSeconds' => $this->listenedSeconds,
|
||||||
|
'actionCounts' => $this->actionCounts->toArray(),
|
||||||
|
'podcastData' => $this->podcastData->toArray(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
92
lib/Core/PodcastData/PodcastMetricsReader.php
Normal file
92
lib/Core/PodcastData/PodcastMetricsReader.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\GPodderSync\Core\PodcastData;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
||||||
|
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeEntity;
|
||||||
|
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeRepository;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class PodcastMetricsReader {
|
||||||
|
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
private SubscriptionChangeRepository $subscriptionChangeRepository;
|
||||||
|
private EpisodeActionRepository $episodeActionRepository;
|
||||||
|
private PodcastDataCache $cache;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
LoggerInterface $logger,
|
||||||
|
SubscriptionChangeRepository $subscriptionChangeRepository,
|
||||||
|
EpisodeActionRepository $episodeActionRepository,
|
||||||
|
PodcastDataCache $cache,
|
||||||
|
) {
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->subscriptionChangeRepository = $subscriptionChangeRepository;
|
||||||
|
$this->episodeActionRepository = $episodeActionRepository;
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId
|
||||||
|
*
|
||||||
|
* @return PodcastMetrics[]
|
||||||
|
*/
|
||||||
|
public function metrics(string $userId): array {
|
||||||
|
$episodeActions = $this->episodeActionRepository->findAll(0, $userId);
|
||||||
|
|
||||||
|
$metricsPerUrl = array();
|
||||||
|
foreach ($episodeActions as $ep) {
|
||||||
|
$url = $ep->getPodcast();
|
||||||
|
/** @var PodcastMetrics */
|
||||||
|
$metrics = $metricsPerUrl[$url] ?? $this->createMetricsForUrl($url);
|
||||||
|
|
||||||
|
$actionLower = strtolower($ep->getAction());
|
||||||
|
$metrics->getActionCounts()->incrementAction($actionLower);
|
||||||
|
|
||||||
|
if ($actionLower == 'play') {
|
||||||
|
$seconds = $ep->getPosition();
|
||||||
|
if ($seconds && $seconds != -1) {
|
||||||
|
$metrics->addListenedSeconds($seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$metricsPerUrl[$url] = $metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sinceDatetime = (new DateTime)->setTimestamp(0);
|
||||||
|
$subscriptionChanges = $this->subscriptionChangeRepository->findAllSubscribed($sinceDatetime, $userId);
|
||||||
|
/** @var PodcastMetrics[] */
|
||||||
|
$subscriptions = array_map(function (SubscriptionChangeEntity $sub) use ($metricsPerUrl) {
|
||||||
|
$url = $sub->getUrl();
|
||||||
|
$metrics = $metricsPerUrl[$url] ?? $this->createMetricsForUrl($url);
|
||||||
|
return $metrics;
|
||||||
|
}, $subscriptionChanges);
|
||||||
|
|
||||||
|
return $subscriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function tryGetParsedPodcastData(string $url): ?PodcastData {
|
||||||
|
try {
|
||||||
|
return $this->cache->getCachedOrFetchPodcastData($url);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error("Failed to get podcast data.", [
|
||||||
|
'exception' => $e,
|
||||||
|
'podcastUrl' => $url,
|
||||||
|
]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createMetricsForUrl(string $url): PodcastMetrics {
|
||||||
|
return new PodcastMetrics(
|
||||||
|
url: $url,
|
||||||
|
listenedSeconds: 0,
|
||||||
|
actionCounts: new PodcastActionCounts(),
|
||||||
|
podcastData: $this->tryGetParsedPodcastData($url),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,77 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace OCA\GPodderSync\Service;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use SimpleXMLElement;
|
|
||||||
|
|
||||||
use OCP\Http\Client\IClient;
|
|
||||||
use OCP\Http\Client\IClientService;
|
|
||||||
use OCP\ICache;
|
|
||||||
use OCP\ICacheFactory;
|
|
||||||
|
|
||||||
class PodcastCacheService {
|
|
||||||
private ?ICache $cache = null;
|
|
||||||
private IClient $httpClient;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
ICacheFactory $cacheFactory,
|
|
||||||
IClientService $httpClientService,
|
|
||||||
) {
|
|
||||||
if ($cacheFactory->isLocalCacheAvailable()) {
|
|
||||||
$this->cache = $cacheFactory->createLocal('GPodderSync-Podcasts');
|
|
||||||
}
|
|
||||||
$this->httpClient = $httpClientService->newClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCachedOrFetchPodcastData(string $url) {
|
|
||||||
if ($this->cache == null) {
|
|
||||||
return $this->fetchPodcastData($url);
|
|
||||||
}
|
|
||||||
$oldData = $this->cache->get($url);
|
|
||||||
if ($oldData) {
|
|
||||||
return $oldData;
|
|
||||||
}
|
|
||||||
$newData = $this->fetchPodcastData($url);
|
|
||||||
$this->cache->set($url, $newData);
|
|
||||||
return $newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fetchPodcastData(string $url) {
|
|
||||||
$resp = $this->httpClient->get($url);
|
|
||||||
$statusCode = $resp->getStatusCode();
|
|
||||||
if ($statusCode < 200 || $statusCode >= 300) {
|
|
||||||
throw new ErrorException("Podcast RSS URL returned non-2xx status code: $statusCode");
|
|
||||||
}
|
|
||||||
$body = $resp->getBody();
|
|
||||||
$xml = new SimpleXMLElement($body);
|
|
||||||
$channel = $xml->channel;
|
|
||||||
return [
|
|
||||||
'title' => (string)$channel->title,
|
|
||||||
'author' => self::getXPathContent($xml, '/rss/channel/itunes:author'),
|
|
||||||
'link' => (string)$channel->link,
|
|
||||||
'description' => (string)$channel->description,
|
|
||||||
'image' =>
|
|
||||||
self::getXPathContent($xml, '/rss/channel/image/url')
|
|
||||||
?? self::getXPathAttribute($xml, '/rss/channel/itunes:image/@href'),
|
|
||||||
'fetchedAtUnix' => (new DateTime())->getTimestamp(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getXPathContent(SimpleXMLElement $xml, string $xpath) {
|
|
||||||
$match = $xml->xpath($xpath);
|
|
||||||
if ($match) {
|
|
||||||
return (string)$match[0];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getXPathAttribute(SimpleXMLElement $xml, string $xpath) {
|
|
||||||
$match = $xml->xpath($xpath);
|
|
||||||
if ($match) {
|
|
||||||
return (string)$match[0][0];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user