219 lines
6.9 KiB
PHP
219 lines
6.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Entity\Repository;
|
|
|
|
use App\Doctrine\ReloadableEntityManagerInterface;
|
|
use App\Doctrine\Repository;
|
|
use App\Entity;
|
|
use App\Environment;
|
|
use Carbon\CarbonImmutable;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\Serializer\Serializer;
|
|
|
|
class SongHistoryRepository extends Repository
|
|
{
|
|
protected ListenerRepository $listenerRepository;
|
|
|
|
protected StationQueueRepository $stationQueueRepository;
|
|
|
|
public function __construct(
|
|
ReloadableEntityManagerInterface $em,
|
|
Serializer $serializer,
|
|
Environment $environment,
|
|
LoggerInterface $logger,
|
|
ListenerRepository $listenerRepository,
|
|
StationQueueRepository $stationQueueRepository
|
|
) {
|
|
$this->listenerRepository = $listenerRepository;
|
|
$this->stationQueueRepository = $stationQueueRepository;
|
|
|
|
parent::__construct($em, $serializer, $environment, $logger);
|
|
}
|
|
|
|
/**
|
|
* @param Entity\Station $station
|
|
*
|
|
* @return Entity\SongHistory[]
|
|
*/
|
|
public function getVisibleHistory(Entity\Station $station): array
|
|
{
|
|
$numEntries = $station->getApiHistoryItems();
|
|
if (0 === $numEntries) {
|
|
return [];
|
|
}
|
|
|
|
$recordsRaw = $this->em->createQuery(
|
|
<<<'DQL'
|
|
SELECT sh FROM App\Entity\SongHistory sh
|
|
LEFT JOIN sh.media sm
|
|
WHERE sh.station_id = :station_id
|
|
AND sh.timestamp_end != 0
|
|
ORDER BY sh.id DESC
|
|
DQL
|
|
)->setParameter('station_id', $station->getId())
|
|
->setMaxResults($numEntries)
|
|
->execute();
|
|
|
|
$records = [];
|
|
foreach ($recordsRaw as $row) {
|
|
/** @var Entity\SongHistory $row */
|
|
if ($row->showInApis()) {
|
|
$records[] = $row;
|
|
}
|
|
}
|
|
return $records;
|
|
}
|
|
|
|
public function register(
|
|
Entity\Interfaces\SongInterface $song,
|
|
Entity\Station $station,
|
|
Entity\Api\NowPlaying\NowPlaying $np
|
|
): Entity\SongHistory {
|
|
// Pull the most recent history item for this station.
|
|
$last_sh = $this->getCurrent($station);
|
|
|
|
$listeners = $np->listeners->current;
|
|
|
|
if ($last_sh instanceof Entity\SongHistory) {
|
|
if ($last_sh->getSongId() === $song->getSongId()) {
|
|
// Updating the existing SongHistory item with a new data point.
|
|
$last_sh->addDeltaPoint($listeners);
|
|
|
|
$this->em->persist($last_sh);
|
|
$this->em->flush();
|
|
|
|
return $last_sh;
|
|
}
|
|
|
|
// Wrapping up processing on the previous SongHistory item (if present).
|
|
$last_sh->setTimestampEnd(time());
|
|
$last_sh->setListenersEnd($listeners);
|
|
|
|
// Calculate "delta" data for previous item, based on all data points.
|
|
$last_sh->addDeltaPoint($listeners);
|
|
|
|
$delta_points = (array)$last_sh->getDeltaPoints();
|
|
|
|
$delta_positive = 0;
|
|
$delta_negative = 0;
|
|
$delta_total = 0;
|
|
|
|
for ($i = 1, $iMax = count($delta_points); $i < $iMax; $i++) {
|
|
$current_delta = $delta_points[$i];
|
|
$previous_delta = $delta_points[$i - 1];
|
|
|
|
$delta_delta = $current_delta - $previous_delta;
|
|
$delta_total += $delta_delta;
|
|
|
|
if ($delta_delta > 0) {
|
|
$delta_positive += $delta_delta;
|
|
} elseif ($delta_delta < 0) {
|
|
$delta_negative += (int)abs($delta_delta);
|
|
}
|
|
}
|
|
|
|
$last_sh->setDeltaPositive((int)$delta_positive);
|
|
$last_sh->setDeltaNegative((int)$delta_negative);
|
|
$last_sh->setDeltaTotal((int)$delta_total);
|
|
|
|
$last_sh->setUniqueListeners(
|
|
$this->listenerRepository->getUniqueListeners(
|
|
$station,
|
|
$last_sh->getTimestampStart(),
|
|
time()
|
|
)
|
|
);
|
|
|
|
$this->em->persist($last_sh);
|
|
}
|
|
|
|
// Look for an already cued but unplayed song.
|
|
$sq = $this->stationQueueRepository->findRecentlyCuedSong($station, $song);
|
|
|
|
if ($sq instanceof Entity\StationQueue) {
|
|
$sh = Entity\SongHistory::fromQueue($sq);
|
|
} else {
|
|
// Processing a new SongHistory item.
|
|
$sh = new Entity\SongHistory($station, $song);
|
|
|
|
$currentStreamer = $station->getCurrentStreamer();
|
|
if ($currentStreamer instanceof Entity\StationStreamer) {
|
|
$sh->setStreamer($currentStreamer);
|
|
}
|
|
}
|
|
|
|
$sh->setTimestampStart(time());
|
|
$sh->setListenersStart($listeners);
|
|
$sh->addDeltaPoint($listeners);
|
|
|
|
$this->em->persist($sh);
|
|
$this->em->flush();
|
|
|
|
return $sh;
|
|
}
|
|
|
|
public function getCurrent(Entity\Station $station): ?Entity\SongHistory
|
|
{
|
|
return $this->em->createQuery(
|
|
<<<'DQL'
|
|
SELECT sh
|
|
FROM App\Entity\SongHistory sh
|
|
WHERE sh.station = :station
|
|
AND sh.timestamp_start != 0
|
|
AND (sh.timestamp_end IS NULL OR sh.timestamp_end = 0)
|
|
ORDER BY sh.timestamp_start DESC
|
|
DQL
|
|
)->setParameter('station', $station)
|
|
->setMaxResults(1)
|
|
->getOneOrNullResult();
|
|
}
|
|
|
|
/**
|
|
* @param Entity\Station $station
|
|
* @param int $start
|
|
* @param int $end
|
|
*
|
|
* @return mixed[] [int $minimumListeners, int $maximumListeners, float $averageListeners]
|
|
*/
|
|
public function getStatsByTimeRange(Entity\Station $station, int $start, int $end): array
|
|
{
|
|
$historyTotals = $this->em->createQuery(
|
|
<<<'DQL'
|
|
SELECT AVG(sh.listeners_end) AS listeners_avg, MAX(sh.listeners_end) AS listeners_max,
|
|
MIN(sh.listeners_end) AS listeners_min
|
|
FROM App\Entity\SongHistory sh
|
|
WHERE sh.station = :station
|
|
AND sh.timestamp_end >= :start
|
|
AND sh.timestamp_start <= :end
|
|
DQL
|
|
)->setParameter('station', $station)
|
|
->setParameter('start', $start)
|
|
->setParameter('end', $end)
|
|
->getSingleResult();
|
|
|
|
$min = (int)$historyTotals['listeners_min'];
|
|
$max = (int)$historyTotals['listeners_max'];
|
|
$avg = round((float)$historyTotals['listeners_avg'], 2);
|
|
|
|
return [$min, $max, $avg];
|
|
}
|
|
|
|
public function cleanup(int $daysToKeep): void
|
|
{
|
|
$threshold = CarbonImmutable::now()
|
|
->subDays($daysToKeep)
|
|
->getTimestamp();
|
|
|
|
$this->em->createQuery(
|
|
<<<'DQL'
|
|
DELETE FROM App\Entity\SongHistory sh
|
|
WHERE sh.timestamp_start != 0
|
|
AND sh.timestamp_start <= :threshold
|
|
DQL
|
|
)->setParameter('threshold', $threshold)
|
|
->execute();
|
|
}
|
|
}
|