Initial Upload
This commit is contained in:
330
lib/XTR/DynamicPlaylistSyncTask.php
Normal file
330
lib/XTR/DynamicPlaylistSyncTask.php
Normal file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\XTR;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Entity\Media;
|
||||
use Xibo\Entity\Playlist;
|
||||
use Xibo\Entity\Task;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\PlaylistFactory;
|
||||
use Xibo\Factory\WidgetFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class DynamicPlaylistSyncTask
|
||||
* @package Xibo\XTR
|
||||
*
|
||||
* Keep dynamic Playlists in sync with changes to the Media table.
|
||||
*/
|
||||
class DynamicPlaylistSyncTask implements TaskInterface
|
||||
{
|
||||
use TaskTrait;
|
||||
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/** @var PlaylistFactory */
|
||||
private $playlistFactory;
|
||||
|
||||
/** @var MediaFactory */
|
||||
private $mediaFactory;
|
||||
|
||||
/** @var ModuleFactory */
|
||||
private $moduleFactory;
|
||||
|
||||
/** @var WidgetFactory */
|
||||
private $widgetFactory;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function setFactories($container)
|
||||
{
|
||||
$this->store = $container->get('store');
|
||||
$this->playlistFactory = $container->get('playlistFactory');
|
||||
$this->mediaFactory = $container->get('mediaFactory');
|
||||
$this->moduleFactory = $container->get('moduleFactory');
|
||||
$this->widgetFactory = $container->get('widgetFactory');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function run()
|
||||
{
|
||||
// If we're in the error state, then always run, otherwise check the dates we modified various triggers
|
||||
if ($this->getTask()->lastRunStatus !== Task::$STATUS_ERROR) {
|
||||
// Run a little query to get the last modified date from the media table
|
||||
$lastMediaUpdate = $this->store->select('
|
||||
SELECT MAX(modifiedDt) AS modifiedDt
|
||||
FROM `media`
|
||||
WHERE `type` <> \'module\' AND `type` <> \'genericfile\'
|
||||
', [])[0]['modifiedDt'];
|
||||
|
||||
$lastPlaylistUpdate = $this->store->select('
|
||||
SELECT MAX(modifiedDt) AS modifiedDt
|
||||
FROM `playlist`
|
||||
', [])[0]['modifiedDt'];
|
||||
|
||||
if (empty($lastMediaUpdate) || empty($lastPlaylistUpdate)) {
|
||||
$this->appendRunMessage('No library media or Playlists to assess');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log->debug('Last media updated date is ' . $lastMediaUpdate);
|
||||
$this->log->debug('Last playlist updated date is ' . $lastPlaylistUpdate);
|
||||
|
||||
$lastMediaUpdate = Carbon::createFromFormat(DateFormatHelper::getSystemFormat(), $lastMediaUpdate);
|
||||
$lastPlaylistUpdate = Carbon::createFromFormat(DateFormatHelper::getSystemFormat(), $lastPlaylistUpdate);
|
||||
$lastTaskRun = Carbon::createFromTimestamp($this->getTask()->lastRunDt);
|
||||
|
||||
if ($lastMediaUpdate->lessThanOrEqualTo($lastTaskRun)
|
||||
&& $lastPlaylistUpdate->lessThanOrEqualTo($lastTaskRun))
|
||||
{
|
||||
$this->appendRunMessage('No library media/playlist updates since we last ran');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
|
||||
// Get all Dynamic Playlists
|
||||
foreach ($this->playlistFactory->query(null, ['isDynamic' => 1]) as $playlist) {
|
||||
try {
|
||||
// We want to detect any differences in what should be assigned to this Playlist.
|
||||
$playlist->load(['checkDisplayOrder' => true]);
|
||||
|
||||
$this->log->debug('Assessing Playlist: ' . $playlist->name);
|
||||
|
||||
if (empty($playlist->filterMediaName) && empty($playlist->filterMediaTags) && empty($playlist->filterFolderId)) {
|
||||
// if this Dynamic Playlist was populated will all Media in the system
|
||||
// before we introduced measures against it, we need to go through and unassign all Widgets from it.
|
||||
// if it is fresh Playlist added recently, it will not have any Widgets on it with empty filters.
|
||||
if (!empty($playlist->widgets)) {
|
||||
foreach ($playlist->widgets as $widget) {
|
||||
$playlist->deleteWidget($widget);
|
||||
}
|
||||
}
|
||||
$this->log->debug(sprintf(
|
||||
'Dynamic Playlist ID %d , with no filters set, skipping.',
|
||||
$playlist->playlistId
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query for media which would be assigned to this Playlist and see if there are any differences
|
||||
$media = [];
|
||||
$mediaIds = [];
|
||||
$displayOrder = [];
|
||||
foreach ($this->mediaFactory->query(null, [
|
||||
'name' => $playlist->filterMediaName,
|
||||
'logicalOperatorName' => $playlist->filterMediaNameLogicalOperator,
|
||||
'tags' => $playlist->filterMediaTags,
|
||||
'exactTags' => $playlist->filterExactTags,
|
||||
'logicalOperator' => $playlist->filterMediaTagsLogicalOperator,
|
||||
'folderId' => !empty($playlist->filterFolderId) ? $playlist->filterFolderId : null,
|
||||
'userCheckUserId' => $playlist->getOwnerId(),
|
||||
'start' => 0,
|
||||
'length' => $playlist->maxNumberOfItems
|
||||
]) as $index => $item) {
|
||||
$media[$item->mediaId] = $item;
|
||||
$mediaIds[] = $item->mediaId;
|
||||
// store the expected display order
|
||||
$displayOrder[$item->mediaId] = $index + 1;
|
||||
}
|
||||
|
||||
// Work out if the set of widgets is different or not.
|
||||
// This is only the first loose check
|
||||
$different = (count($playlist->widgets) !== count($media));
|
||||
|
||||
$this->log->debug('There are ' . count($media) . ' that should be assigned and '
|
||||
. count($playlist->widgets) . ' currently assigned with max number of items set to '
|
||||
. $playlist->maxNumberOfItems . ' First check difference is '
|
||||
. var_export($different, true));
|
||||
|
||||
if (!$different) {
|
||||
// Try a more complete check, using mediaIds
|
||||
$compareMediaIds = $mediaIds;
|
||||
|
||||
// ordering should be the same, so the first time we get one out of order, we can stop
|
||||
foreach ($playlist->widgets as $widget) {
|
||||
try {
|
||||
$widgetMediaId = $widget->getPrimaryMediaId();
|
||||
if ($widgetMediaId !== $compareMediaIds[0]
|
||||
|| $widget->duration !== $media[$widgetMediaId]->duration
|
||||
) {
|
||||
$different = true;
|
||||
break;
|
||||
}
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$this->log->error('Playlist ' . $playlist->getId()
|
||||
. ' has a Widget without any associated media. widgetId = ' . $widget->getId());
|
||||
|
||||
// We ought to recalculate
|
||||
$different = true;
|
||||
break;
|
||||
}
|
||||
|
||||
array_shift($compareMediaIds);
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->debug('Second check difference is ' . var_export($different, true));
|
||||
|
||||
if ($different) {
|
||||
// We will update this Playlist
|
||||
$assignmentMade = false;
|
||||
$count++;
|
||||
|
||||
// Remove the ones no-longer present, add the ones we're missing
|
||||
// we don't delete and re-add the lot to avoid regenerating the widgetIds (makes stats harder to
|
||||
// interpret)
|
||||
foreach ($playlist->widgets as $widget) {
|
||||
try {
|
||||
$widgetMediaId = $widget->getPrimaryMediaId();
|
||||
|
||||
if (!in_array($widgetMediaId, $mediaIds)) {
|
||||
$playlist->deleteWidget($widget);
|
||||
} else {
|
||||
// It's present in the array
|
||||
// Check to see if the duration is different
|
||||
if ($widget->duration !== $media[$widgetMediaId]->duration) {
|
||||
// The media duration has changed, so update the widget
|
||||
$widget->useDuration = 1;
|
||||
$widget->duration = $media[$widgetMediaId]->duration;
|
||||
$widget->calculatedDuration = $widget->duration;
|
||||
$widget->save([
|
||||
'saveWidgetOptions' => false,
|
||||
'saveWidgetAudio' => false,
|
||||
'saveWidgetMedia' => false,
|
||||
'notify' => false,
|
||||
'notifyPlaylists' => false,
|
||||
'notifyDisplays' => false,
|
||||
'audit' => true,
|
||||
'alwaysUpdate' => true
|
||||
]);
|
||||
}
|
||||
|
||||
// Pop it off the list of ones to assign.
|
||||
$mediaIds = array_diff($mediaIds, [$widgetMediaId]);
|
||||
|
||||
// We do want to save the Playlist here.
|
||||
$assignmentMade = true;
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
// Delete it
|
||||
$playlist->deleteWidget($widget);
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have any mediaId's left which should be assigned and aren't?
|
||||
// Add the ones we have left
|
||||
foreach ($media as $item) {
|
||||
if (in_array($item->mediaId, $mediaIds)) {
|
||||
if (count($playlist->widgets) >= $playlist->maxNumberOfItems) {
|
||||
$this->log->debug(
|
||||
sprintf(
|
||||
'Dynamic Playlist ID %d, has reached the maximum number of items %d, finishing assignments',//phpcs:ignore
|
||||
$playlist->playlistId,
|
||||
$playlist->maxNumberOfItems
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
$assignmentMade = true;
|
||||
// make sure we pass the expected displayOrder for the new item we are about to add.
|
||||
$this->createAndAssign($playlist, $item, $displayOrder[$item->mediaId]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($assignmentMade) {
|
||||
// We've made an assignment change, so audit this change
|
||||
// don't audit any downstream save operations
|
||||
$playlist->save([
|
||||
'auditPlaylist' => true,
|
||||
'audit' => false
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$this->log->debug('No differences detected');
|
||||
}
|
||||
} catch (GeneralException $exception) {
|
||||
$this->log->debug($exception->getTraceAsString());
|
||||
$this->log->error('Problem with PlaylistId: ' . $playlist->getId()
|
||||
. ', e = ' . $exception->getMessage());
|
||||
$this->appendRunMessage('Error with Playlist: ' . $playlist->name);
|
||||
}
|
||||
}
|
||||
|
||||
$this->appendRunMessage('Updated ' . $count . ' Playlists');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Playlist $playlist
|
||||
* @param Media $media
|
||||
* @param int $displayOrder
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function createAndAssign(Playlist $playlist, Media $media, int $displayOrder): void
|
||||
{
|
||||
$this->log->debug('Media Item needs to be assigned ' . $media->name . ' in sequence ' . $displayOrder);
|
||||
|
||||
// Create a module
|
||||
try {
|
||||
$module = $this->moduleFactory->getByType($media->mediaType);
|
||||
} catch (NotFoundException) {
|
||||
$this->log->error('createAndAssign: dynamic playlist matched missing module: ' . $media->mediaType);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($module->assignable == 0) {
|
||||
$this->log->error('createAndAssign: dynamic playlist matched unassignable media: ' . $media->mediaId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the duration
|
||||
$mediaDuration = $media->duration;
|
||||
if ($mediaDuration <= 0) {
|
||||
$mediaDuration = $module->defaultDuration;
|
||||
}
|
||||
|
||||
// Create a widget
|
||||
$widget = $this->widgetFactory->create(
|
||||
$playlist->getOwnerId(),
|
||||
$playlist->playlistId,
|
||||
$media->mediaType,
|
||||
$mediaDuration,
|
||||
$module->schemaVersion
|
||||
);
|
||||
$widget->useDuration = 1;
|
||||
$widget->displayOrder = $displayOrder;
|
||||
$widget->calculateDuration($module);
|
||||
$widget->assignMedia($media->mediaId);
|
||||
|
||||
// Assign the widget to the playlist
|
||||
// making sure we pass the displayOrder here, otherwise it would be added to the end of the array.
|
||||
$playlist->assignWidget($widget, $displayOrder);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user