summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/PVRManager.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/PVRManager.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/pvr/PVRManager.cpp')
-rw-r--r--xbmc/pvr/PVRManager.cpp1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp
new file mode 100644
index 0000000..715397e
--- /dev/null
+++ b/xbmc/pvr/PVRManager.cpp
@@ -0,0 +1,1029 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRManager.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "interfaces/AnnouncementManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pvr/PVRComponentRegistration.h"
+#include "pvr/PVRDatabase.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupInternal.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/guilib/PVRGUIChannelIconUpdater.h"
+#include "pvr/guilib/PVRGUIProgressHandler.h"
+#include "pvr/guilib/guiinfo/PVRGUIInfo.h"
+#include "pvr/providers/PVRProvider.h"
+#include "pvr/providers/PVRProviders.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/Settings.h"
+#include "utils/JobManager.h"
+#include "utils/Stopwatch.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace PVR;
+using namespace std::chrono_literals;
+
+namespace
+{
+
+class CPVRJob
+{
+public:
+ virtual ~CPVRJob() = default;
+
+ virtual bool DoWork() = 0;
+ virtual const std::string GetType() const = 0;
+
+protected:
+};
+
+template<typename F>
+class CPVRLambdaJob : public CPVRJob
+{
+public:
+ CPVRLambdaJob() = delete;
+ CPVRLambdaJob(const std::string& type, F&& f) : m_type(type), m_f(std::forward<F>(f)) {}
+
+ bool DoWork() override
+ {
+ m_f();
+ return true;
+ }
+
+ const std::string GetType() const override { return m_type; }
+
+private:
+ std::string m_type;
+ F m_f;
+};
+
+} // unnamed namespace
+
+namespace PVR
+{
+
+class CPVRManagerJobQueue
+{
+public:
+ CPVRManagerJobQueue() : m_triggerEvent(false) {}
+
+ void Start();
+ void Stop();
+ void Clear();
+
+ template<typename F>
+ void Append(const std::string& type, F&& f)
+ {
+ AppendJob(new CPVRLambdaJob<F>(type, std::forward<F>(f)));
+ }
+
+ void ExecutePendingJobs();
+
+ bool WaitForJobs(unsigned int milliSeconds)
+ {
+ return m_triggerEvent.Wait(std::chrono::milliseconds(milliSeconds));
+ }
+
+private:
+ void AppendJob(CPVRJob* job);
+
+ CCriticalSection m_critSection;
+ CEvent m_triggerEvent;
+ std::vector<CPVRJob*> m_pendingUpdates;
+ bool m_bStopped = true;
+};
+
+} // namespace PVR
+
+void CPVRManagerJobQueue::Start()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bStopped = false;
+ m_triggerEvent.Set();
+}
+
+void CPVRManagerJobQueue::Stop()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bStopped = true;
+ m_triggerEvent.Reset();
+}
+
+void CPVRManagerJobQueue::Clear()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (CPVRJob* updateJob : m_pendingUpdates)
+ delete updateJob;
+
+ m_pendingUpdates.clear();
+ m_triggerEvent.Set();
+}
+
+void CPVRManagerJobQueue::AppendJob(CPVRJob* job)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // check for another pending job of given type...
+ if (std::any_of(m_pendingUpdates.cbegin(), m_pendingUpdates.cend(),
+ [job](CPVRJob* updateJob) { return updateJob->GetType() == job->GetType(); }))
+ {
+ delete job;
+ return;
+ }
+
+ m_pendingUpdates.push_back(job);
+ m_triggerEvent.Set();
+}
+
+void CPVRManagerJobQueue::ExecutePendingJobs()
+{
+ std::vector<CPVRJob*> pendingUpdates;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_bStopped)
+ return;
+
+ pendingUpdates = std::move(m_pendingUpdates);
+ m_triggerEvent.Reset();
+ }
+
+ CPVRJob* job = nullptr;
+ while (!pendingUpdates.empty())
+ {
+ job = pendingUpdates.front();
+ pendingUpdates.erase(pendingUpdates.begin());
+
+ job->DoWork();
+ delete job;
+ }
+}
+
+CPVRManager::CPVRManager()
+ : CThread("PVRManager"),
+ m_providers(new CPVRProviders),
+ m_channelGroups(new CPVRChannelGroupsContainer),
+ m_recordings(new CPVRRecordings),
+ m_timers(new CPVRTimers),
+ m_addons(new CPVRClients),
+ m_guiInfo(new CPVRGUIInfo),
+ m_components(new CPVRComponentRegistration),
+ m_epgContainer(m_events),
+ m_pendingUpdates(new CPVRManagerJobQueue),
+ m_database(new CPVRDatabase),
+ m_parentalTimer(new CStopWatch),
+ m_playbackState(new CPVRPlaybackState),
+ m_settings({CSettings::SETTING_PVRPOWERMANAGEMENT_ENABLED,
+ CSettings::SETTING_PVRPOWERMANAGEMENT_SETWAKEUPCMD,
+ CSettings::SETTING_PVRPARENTAL_ENABLED, CSettings::SETTING_PVRPARENTAL_DURATION})
+{
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+ m_actionListener.Init(*this);
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "PVR Manager instance created");
+}
+
+CPVRManager::~CPVRManager()
+{
+ m_actionListener.Deinit(*this);
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "PVR Manager instance destroyed");
+}
+
+void CPVRManager::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (!IsStarted())
+ return;
+
+ if ((flag & (ANNOUNCEMENT::GUI)))
+ {
+ if (message == "OnScreensaverActivated")
+ m_addons->OnPowerSavingActivated();
+ else if (message == "OnScreensaverDeactivated")
+ m_addons->OnPowerSavingDeactivated();
+ }
+}
+
+std::shared_ptr<CPVRDatabase> CPVRManager::GetTVDatabase() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_database || !m_database->IsOpen())
+ CLog::LogF(LOGERROR, "Failed to open the PVR database");
+
+ return m_database;
+}
+
+std::shared_ptr<CPVRProviders> CPVRManager::Providers() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_providers;
+}
+
+std::shared_ptr<CPVRChannelGroupsContainer> CPVRManager::ChannelGroups() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_channelGroups;
+}
+
+std::shared_ptr<CPVRRecordings> CPVRManager::Recordings() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_recordings;
+}
+
+std::shared_ptr<CPVRTimers> CPVRManager::Timers() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_timers;
+}
+
+std::shared_ptr<CPVRClients> CPVRManager::Clients() const
+{
+ // note: m_addons is const (only set/reset in ctor/dtor). no need for a lock here.
+ return m_addons;
+}
+
+std::shared_ptr<CPVRClient> CPVRManager::GetClient(const CFileItem& item) const
+{
+ int iClientID = PVR_INVALID_CLIENT_ID;
+
+ if (item.HasPVRChannelInfoTag())
+ iClientID = item.GetPVRChannelInfoTag()->ClientID();
+ else if (item.HasPVRRecordingInfoTag())
+ iClientID = item.GetPVRRecordingInfoTag()->ClientID();
+ else if (item.HasPVRTimerInfoTag())
+ iClientID = item.GetPVRTimerInfoTag()->ClientID();
+ else if (item.HasEPGInfoTag())
+ iClientID = item.GetEPGInfoTag()->ClientID();
+ else if (URIUtils::IsPVRChannel(item.GetPath()))
+ {
+ const std::shared_ptr<CPVRChannel> channel = m_channelGroups->GetByPath(item.GetPath());
+ if (channel)
+ iClientID = channel->ClientID();
+ }
+ else if (URIUtils::IsPVRRecording(item.GetPath()))
+ {
+ const std::shared_ptr<CPVRRecording> recording = m_recordings->GetByPath(item.GetPath());
+ if (recording)
+ iClientID = recording->ClientID();
+ }
+ return GetClient(iClientID);
+}
+
+std::shared_ptr<CPVRClient> CPVRManager::GetClient(int iClientId) const
+{
+ return m_addons->GetCreatedClient(iClientId);
+}
+
+std::shared_ptr<CPVRPlaybackState> CPVRManager::PlaybackState() const
+{
+ // note: m_playbackState is const (only set/reset in ctor/dtor). no need for a lock here.
+ return m_playbackState;
+}
+
+CPVREpgContainer& CPVRManager::EpgContainer()
+{
+ // note: m_epgContainer is const (only set/reset in ctor/dtor). no need for a lock here.
+ return m_epgContainer;
+}
+
+void CPVRManager::Clear()
+{
+ m_playbackState->Clear();
+ m_pendingUpdates->Clear();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_guiInfo.reset();
+ m_timers.reset();
+ m_recordings.reset();
+ m_providers.reset();
+ m_channelGroups.reset();
+ m_parentalTimer.reset();
+ m_database.reset();
+
+ m_bEpgsCreated = false;
+}
+
+void CPVRManager::ResetProperties()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ Clear();
+
+ m_database.reset(new CPVRDatabase);
+ m_providers.reset(new CPVRProviders);
+ m_channelGroups.reset(new CPVRChannelGroupsContainer);
+ m_recordings.reset(new CPVRRecordings);
+ m_timers.reset(new CPVRTimers);
+ m_guiInfo.reset(new CPVRGUIInfo);
+ m_parentalTimer.reset(new CStopWatch);
+ m_knownClients.clear();
+}
+
+void CPVRManager::Init()
+{
+ // initial check for enabled addons
+ // if at least one pvr addon is enabled, PVRManager start up
+ CServiceBroker::GetJobManager()->Submit([this] {
+ Clients()->Start();
+ return true;
+ });
+}
+
+void CPVRManager::Start()
+{
+ std::unique_lock<CCriticalSection> initLock(m_startStopMutex);
+
+ // Prevent concurrent starts
+ if (IsInitialising())
+ return;
+
+ // Note: Stop() must not be called while holding pvr manager's mutex. Stop() calls
+ // StopThread() which can deadlock if the worker thread tries to acquire pvr manager's
+ // lock while StopThread() is waiting for the worker to exit. Thus, we introduce another
+ // lock here (m_startStopMutex), which only gets hold while starting/restarting pvr manager.
+ Stop(true);
+
+ if (!m_addons->HasCreatedClients())
+ return;
+
+ CLog::Log(LOGINFO, "PVR Manager: Starting");
+ SetState(ManagerState::STATE_STARTING);
+
+ /* create the pvrmanager thread, which will ensure that all data will be loaded */
+ Create();
+ SetPriority(ThreadPriority::BELOW_NORMAL);
+}
+
+void CPVRManager::Stop(bool bRestart /* = false */)
+{
+ std::unique_lock<CCriticalSection> initLock(m_startStopMutex);
+
+ // Prevent concurrent stops
+ if (IsStopped())
+ return;
+
+ /* stop playback if needed */
+ if (!bRestart && m_playbackState->IsPlaying())
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Stopping PVR playback");
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP);
+ }
+
+ CLog::Log(LOGINFO, "PVR Manager: Stopping");
+ SetState(ManagerState::STATE_SSTOPPING);
+
+ StopThread();
+}
+
+void CPVRManager::Unload()
+{
+ // stop pvr manager thread and clear all pvr data
+ Stop();
+ Clear();
+}
+
+void CPVRManager::Deinit()
+{
+ SetWakeupCommand();
+ Unload();
+
+ // release addons
+ m_addons.reset();
+}
+
+CPVRManager::ManagerState CPVRManager::GetState() const
+{
+ std::unique_lock<CCriticalSection> lock(m_managerStateMutex);
+ return m_managerState;
+}
+
+void CPVRManager::SetState(CPVRManager::ManagerState state)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_managerStateMutex);
+ if (m_managerState == state)
+ return;
+
+ m_managerState = state;
+ }
+
+ PVREvent event;
+ switch (state)
+ {
+ case ManagerState::STATE_ERROR:
+ event = PVREvent::ManagerError;
+ break;
+ case ManagerState::STATE_STOPPED:
+ event = PVREvent::ManagerStopped;
+ break;
+ case ManagerState::STATE_STARTING:
+ event = PVREvent::ManagerStarting;
+ break;
+ case ManagerState::STATE_SSTOPPING:
+ event = PVREvent::ManagerStopped;
+ break;
+ case ManagerState::STATE_INTERRUPTED:
+ event = PVREvent::ManagerInterrupted;
+ break;
+ case ManagerState::STATE_STARTED:
+ event = PVREvent::ManagerStarted;
+ break;
+ default:
+ return;
+ }
+
+ PublishEvent(event);
+}
+
+void CPVRManager::PublishEvent(PVREvent event)
+{
+ m_events.Publish(event);
+}
+
+void CPVRManager::Process()
+{
+ m_addons->Continue();
+ m_database->Open();
+
+ if (!IsInitialising())
+ {
+ CLog::Log(LOGINFO, "PVR Manager: Start aborted");
+ return;
+ }
+
+ UnloadComponents();
+
+ if (!IsInitialising())
+ {
+ CLog::Log(LOGINFO, "PVR Manager: Start aborted");
+ return;
+ }
+
+ // Wait for at least one client to come up and load/update data
+ UpdateComponents(ManagerState::STATE_STARTING);
+
+ if (!IsInitialising())
+ {
+ CLog::Log(LOGINFO, "PVR Manager: Start aborted");
+ return;
+ }
+
+ // Load EPGs from database.
+ m_epgContainer.Load();
+
+ // Reinit playbackstate
+ m_playbackState->ReInit();
+
+ m_guiInfo->Start();
+ m_epgContainer.Start();
+ m_timers->Start();
+ m_pendingUpdates->Start();
+
+ SetState(ManagerState::STATE_STARTED);
+ CLog::Log(LOGINFO, "PVR Manager: Started");
+
+ bool bRestart(false);
+ XbmcThreads::EndTime<> cachedImagesCleanupTimeout(30s); // first timeout after 30 secs
+
+ while (IsStarted() && m_addons->HasCreatedClients() && !bRestart)
+ {
+ // In case any new client connected, load from db and fetch data update from new client(s)
+ UpdateComponents(ManagerState::STATE_STARTED);
+
+ if (cachedImagesCleanupTimeout.IsTimePast())
+ {
+ // We don't know for sure what to delete if there are not (yet) connected clients
+ if (m_addons->HasIgnoredClients())
+ {
+ cachedImagesCleanupTimeout.Set(10s); // try again in 10 secs
+ }
+ else
+ {
+ // start a job to erase stale texture db entries and image files
+ TriggerCleanupCachedImages();
+ cachedImagesCleanupTimeout.Set(12h); // following timeouts after 12 hours
+ }
+ }
+
+ /* first startup */
+ if (m_bFirstStart)
+ {
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bFirstStart = false;
+ }
+
+ /* start job to search for missing channel icons */
+ TriggerSearchMissingChannelIcons();
+
+ /* try to play channel on startup */
+ TriggerPlayChannelOnStartup();
+ }
+
+ if (m_addons->AnyClientSupportingRecordingsSize())
+ TriggerRecordingsSizeInProgressUpdate();
+
+ /* execute the next pending jobs if there are any */
+ try
+ {
+ m_pendingUpdates->ExecutePendingJobs();
+ }
+ catch (...)
+ {
+ CLog::LogF(
+ LOGERROR,
+ "An error occurred while trying to execute the last PVR update job, trying to recover");
+ bRestart = true;
+ }
+
+ if (IsStarted() && !bRestart)
+ m_pendingUpdates->WaitForJobs(1000);
+ }
+
+ m_addons->Stop();
+ m_pendingUpdates->Stop();
+ m_timers->Stop();
+ m_epgContainer.Stop();
+ m_guiInfo->Stop();
+
+ SetState(ManagerState::STATE_INTERRUPTED);
+
+ UnloadComponents();
+ m_database->Close();
+
+ ResetProperties();
+
+ CLog::Log(LOGINFO, "PVR Manager: Stopped");
+ SetState(ManagerState::STATE_STOPPED);
+}
+
+bool CPVRManager::SetWakeupCommand()
+{
+#if !defined(TARGET_DARWIN_EMBEDDED) && !defined(TARGET_WINDOWS_STORE)
+ if (!m_settings.GetBoolValue(CSettings::SETTING_PVRPOWERMANAGEMENT_ENABLED))
+ return false;
+
+ const std::string strWakeupCommand(
+ m_settings.GetStringValue(CSettings::SETTING_PVRPOWERMANAGEMENT_SETWAKEUPCMD));
+ if (!strWakeupCommand.empty() && m_timers)
+ {
+ const CDateTime nextEvent = m_timers->GetNextEventTime();
+ if (nextEvent.IsValid())
+ {
+ time_t iWakeupTime;
+ nextEvent.GetAsTime(iWakeupTime);
+
+ std::string strExecCommand = StringUtils::Format("{} {}", strWakeupCommand, iWakeupTime);
+
+ const int iReturn = system(strExecCommand.c_str());
+ if (iReturn != 0)
+ CLog::LogF(LOGERROR, "PVR Manager failed to execute wakeup command '{}': {} ({})",
+ strExecCommand, strerror(iReturn), iReturn);
+
+ return iReturn == 0;
+ }
+ }
+#endif
+ return false;
+}
+
+void CPVRManager::OnSleep()
+{
+ PublishEvent(PVREvent::SystemSleep);
+
+ SetWakeupCommand();
+
+ m_epgContainer.OnSystemSleep();
+
+ m_addons->OnSystemSleep();
+}
+
+void CPVRManager::OnWake()
+{
+ m_addons->OnSystemWake();
+
+ m_epgContainer.OnSystemWake();
+
+ PublishEvent(PVREvent::SystemWake);
+
+ /* start job to search for missing channel icons */
+ TriggerSearchMissingChannelIcons();
+
+ /* try to play channel on startup */
+ TriggerPlayChannelOnStartup();
+
+ /* trigger PVR data updates */
+ TriggerChannelGroupsUpdate();
+ TriggerProvidersUpdate();
+ TriggerChannelsUpdate();
+ TriggerRecordingsUpdate();
+ TriggerEpgsCreate();
+ TriggerTimersUpdate();
+}
+
+void CPVRManager::UpdateComponents(ManagerState stateToCheck)
+{
+ XbmcThreads::EndTime<> progressTimeout(30s);
+ std::unique_ptr<CPVRGUIProgressHandler> progressHandler(
+ new CPVRGUIProgressHandler(g_localizeStrings.Get(19235))); // PVR manager is starting up
+
+ // Wait for at least one client to come up and load/update data
+ while (!UpdateComponents(stateToCheck, progressHandler) && m_addons->HasCreatedClients() &&
+ (stateToCheck == GetState()))
+ {
+ CThread::Sleep(1000ms);
+
+ if (progressTimeout.IsTimePast())
+ progressHandler.reset();
+ }
+}
+
+bool CPVRManager::UpdateComponents(ManagerState stateToCheck,
+ const std::unique_ptr<CPVRGUIProgressHandler>& progressHandler)
+{
+ // find clients which appeared since last check and update them
+ const CPVRClientMap clientMap = m_addons->GetCreatedClients();
+ if (clientMap.empty())
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "All created PVR clients gone!");
+ m_knownClients.clear(); // start over
+ PublishEvent(PVREvent::ClientsInvalidated);
+ return false;
+ }
+
+ std::vector<std::shared_ptr<CPVRClient>> newClients;
+ for (const auto& entry : clientMap)
+ {
+ // skip not (yet) connected clients
+ if (entry.second->IgnoreClient())
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Skipping not (yet) connected PVR client '{}'",
+ entry.second->ID());
+ continue;
+ }
+
+ if (!IsKnownClient(entry.first))
+ {
+ m_knownClients.emplace_back(entry.second);
+ newClients.emplace_back(entry.second);
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Adding new PVR client '{}' to list of known clients",
+ entry.second->ID());
+ }
+ }
+
+ if (newClients.empty())
+ return !m_knownClients.empty();
+
+ // Load all channels and groups
+ if (progressHandler)
+ progressHandler->UpdateProgress(g_localizeStrings.Get(19236), 0); // Loading channels and groups
+
+ if (!m_providers->Update(newClients) || (stateToCheck != GetState()))
+ {
+ CLog::LogF(LOGERROR, "Failed to load PVR providers.");
+ m_knownClients.clear(); // start over
+ PublishEvent(PVREvent::ClientsInvalidated);
+ return false;
+ }
+
+ if (!m_channelGroups->Update(newClients) || (stateToCheck != GetState()))
+ {
+ CLog::LogF(LOGERROR, "Failed to load PVR channels / groups.");
+ m_knownClients.clear(); // start over
+ PublishEvent(PVREvent::ClientsInvalidated);
+ return false;
+ }
+
+ // Load all timers
+ if (progressHandler)
+ progressHandler->UpdateProgress(g_localizeStrings.Get(19237), 50); // Loading timers
+
+ if (!m_timers->Update(newClients) || (stateToCheck != GetState()))
+ {
+ CLog::LogF(LOGERROR, "Failed to load PVR timers.");
+ m_knownClients.clear(); // start over
+ PublishEvent(PVREvent::ClientsInvalidated);
+ return false;
+ }
+
+ // Load all recordings
+ if (progressHandler)
+ progressHandler->UpdateProgress(g_localizeStrings.Get(19238), 75); // Loading recordings
+
+ if (!m_recordings->Update(newClients) || (stateToCheck != GetState()))
+ {
+ CLog::LogF(LOGERROR, "Failed to load PVR recordings.");
+ m_knownClients.clear(); // start over
+ PublishEvent(PVREvent::ClientsInvalidated);
+ return false;
+ }
+
+ // reinit playbackstate as new client may provide new last opened group / last played channel
+ m_playbackState->ReInit();
+
+ PublishEvent(PVREvent::ClientsInvalidated);
+ return true;
+}
+
+void CPVRManager::UnloadComponents()
+{
+ m_recordings->Unload();
+ m_timers->Unload();
+ m_channelGroups->Unload();
+ m_providers->Unload();
+ m_epgContainer.Unload();
+}
+
+bool CPVRManager::IsKnownClient(int clientID) const
+{
+ return std::any_of(m_knownClients.cbegin(), m_knownClients.cend(),
+ [clientID](const auto& client) { return client->GetID() == clientID; });
+}
+
+void CPVRManager::TriggerPlayChannelOnStartup()
+{
+ if (IsStarted())
+ {
+ CServiceBroker::GetJobManager()->Submit(
+ [this] { return Get<PVR::GUI::Playback>().PlayChannelOnStartup(); });
+ }
+}
+
+void CPVRManager::RestartParentalTimer()
+{
+ if (m_parentalTimer)
+ m_parentalTimer->StartZero();
+}
+
+bool CPVRManager::IsParentalLocked(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ return m_channelGroups && epgTag &&
+ IsCurrentlyParentalLocked(
+ m_channelGroups->GetByUniqueID(epgTag->UniqueChannelID(), epgTag->ClientID()),
+ epgTag->IsParentalLocked());
+}
+
+bool CPVRManager::IsParentalLocked(const std::shared_ptr<CPVRChannel>& channel) const
+{
+ return channel && IsCurrentlyParentalLocked(channel, channel->IsLocked());
+}
+
+bool CPVRManager::IsCurrentlyParentalLocked(const std::shared_ptr<CPVRChannel>& channel,
+ bool bGenerallyLocked) const
+{
+ bool bReturn = false;
+
+ if (!channel || !bGenerallyLocked)
+ return bReturn;
+
+ const std::shared_ptr<CPVRChannel> currentChannel = m_playbackState->GetPlayingChannel();
+
+ if ( // if channel in question is currently playing it must be currently unlocked.
+ (!currentChannel || channel != currentChannel) &&
+ // parental control enabled
+ m_settings.GetBoolValue(CSettings::SETTING_PVRPARENTAL_ENABLED))
+ {
+ float parentalDurationMs =
+ m_settings.GetIntValue(CSettings::SETTING_PVRPARENTAL_DURATION) * 1000.0f;
+ bReturn = m_parentalTimer && (!m_parentalTimer->IsRunning() ||
+ m_parentalTimer->GetElapsedMilliseconds() > parentalDurationMs);
+ }
+
+ return bReturn;
+}
+
+void CPVRManager::OnPlaybackStarted(const CFileItem& item)
+{
+ m_playbackState->OnPlaybackStarted(item);
+ Get<PVR::GUI::Channels>().OnPlaybackStarted(item);
+ m_epgContainer.OnPlaybackStarted();
+}
+
+void CPVRManager::OnPlaybackStopped(const CFileItem& item)
+{
+ // Playback ended due to user interaction
+ if (m_playbackState->OnPlaybackStopped(item))
+ PublishEvent(PVREvent::ChannelPlaybackStopped);
+
+ Get<PVR::GUI::Channels>().OnPlaybackStopped(item);
+ m_epgContainer.OnPlaybackStopped();
+}
+
+void CPVRManager::OnPlaybackEnded(const CFileItem& item)
+{
+ // Playback ended, but not due to user interaction
+ OnPlaybackStopped(item);
+}
+
+void CPVRManager::LocalizationChanged()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (IsStarted())
+ {
+ static_cast<CPVRChannelGroupInternal*>(m_channelGroups->GetGroupAllRadio().get())
+ ->CheckGroupName();
+ static_cast<CPVRChannelGroupInternal*>(m_channelGroups->GetGroupAllTV().get())
+ ->CheckGroupName();
+ }
+}
+
+bool CPVRManager::EpgsCreated() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bEpgsCreated;
+}
+
+void CPVRManager::TriggerEpgsCreate()
+{
+ m_pendingUpdates->Append("pvr-create-epgs", [this]() { return CreateChannelEpgs(); });
+}
+
+void CPVRManager::TriggerRecordingsSizeInProgressUpdate()
+{
+ m_pendingUpdates->Append("pvr-update-recordings-size",
+ [this]() { return Recordings()->UpdateInProgressSize(); });
+}
+
+void CPVRManager::TriggerRecordingsUpdate(int clientId)
+{
+ m_pendingUpdates->Append("pvr-update-recordings-" + std::to_string(clientId), [this, clientId]() {
+ if (!IsKnownClient(clientId))
+ return;
+
+ const std::shared_ptr<CPVRClient> client = GetClient(clientId);
+ if (client)
+ Recordings()->UpdateFromClients({client});
+ });
+}
+
+void CPVRManager::TriggerRecordingsUpdate()
+{
+ m_pendingUpdates->Append("pvr-update-recordings",
+ [this]() { Recordings()->UpdateFromClients({}); });
+}
+
+void CPVRManager::TriggerTimersUpdate(int clientId)
+{
+ m_pendingUpdates->Append("pvr-update-timers-" + std::to_string(clientId), [this, clientId]() {
+ if (!IsKnownClient(clientId))
+ return;
+
+ const std::shared_ptr<CPVRClient> client = GetClient(clientId);
+ if (client)
+ Timers()->UpdateFromClients({client});
+ });
+}
+
+void CPVRManager::TriggerTimersUpdate()
+{
+ m_pendingUpdates->Append("pvr-update-timers", [this]() { Timers()->UpdateFromClients({}); });
+}
+
+void CPVRManager::TriggerProvidersUpdate(int clientId)
+{
+ m_pendingUpdates->Append("pvr-update-channel-providers-" + std::to_string(clientId),
+ [this, clientId]() {
+ if (!IsKnownClient(clientId))
+ return;
+
+ const std::shared_ptr<CPVRClient> client = GetClient(clientId);
+ if (client)
+ Providers()->UpdateFromClients({client});
+ });
+}
+
+void CPVRManager::TriggerProvidersUpdate()
+{
+ m_pendingUpdates->Append("pvr-update-channel-providers",
+ [this]() { Providers()->UpdateFromClients({}); });
+}
+
+void CPVRManager::TriggerChannelsUpdate(int clientId)
+{
+ m_pendingUpdates->Append("pvr-update-channels-" + std::to_string(clientId), [this, clientId]() {
+ if (!IsKnownClient(clientId))
+ return;
+
+ const std::shared_ptr<CPVRClient> client = GetClient(clientId);
+ if (client)
+ ChannelGroups()->UpdateFromClients({client}, true);
+ });
+}
+
+void CPVRManager::TriggerChannelsUpdate()
+{
+ m_pendingUpdates->Append("pvr-update-channels",
+ [this]() { ChannelGroups()->UpdateFromClients({}, true); });
+}
+
+void CPVRManager::TriggerChannelGroupsUpdate(int clientId)
+{
+ m_pendingUpdates->Append("pvr-update-channelgroups-" + std::to_string(clientId),
+ [this, clientId]() {
+ if (!IsKnownClient(clientId))
+ return;
+
+ const std::shared_ptr<CPVRClient> client = GetClient(clientId);
+ if (client)
+ ChannelGroups()->UpdateFromClients({client}, false);
+ });
+}
+
+void CPVRManager::TriggerChannelGroupsUpdate()
+{
+ m_pendingUpdates->Append("pvr-update-channelgroups",
+ [this]() { ChannelGroups()->UpdateFromClients({}, false); });
+}
+
+void CPVRManager::TriggerSearchMissingChannelIcons()
+{
+ m_pendingUpdates->Append("pvr-search-missing-channel-icons", [this]() {
+ CPVRGUIChannelIconUpdater updater(
+ {ChannelGroups()->GetGroupAllTV(), ChannelGroups()->GetGroupAllRadio()}, true);
+ updater.SearchAndUpdateMissingChannelIcons();
+ return true;
+ });
+}
+
+void CPVRManager::TriggerSearchMissingChannelIcons(const std::shared_ptr<CPVRChannelGroup>& group)
+{
+ m_pendingUpdates->Append("pvr-search-missing-channel-icons-" + std::to_string(group->GroupID()),
+ [group]() {
+ CPVRGUIChannelIconUpdater updater({group}, false);
+ updater.SearchAndUpdateMissingChannelIcons();
+ return true;
+ });
+}
+
+void CPVRManager::TriggerCleanupCachedImages()
+{
+ m_pendingUpdates->Append("pvr-cleanup-cached-images", [this]() {
+ int iCleanedImages = 0;
+ CLog::Log(LOGINFO, "PVR Manager: Starting cleanup of cached images.");
+ iCleanedImages += Recordings()->CleanupCachedImages();
+ iCleanedImages += ChannelGroups()->CleanupCachedImages();
+ iCleanedImages += Providers()->CleanupCachedImages();
+ iCleanedImages += EpgContainer().CleanupCachedImages();
+ CLog::Log(LOGINFO, "PVR Manager: Cleaned up {} cached images.", iCleanedImages);
+ return true;
+ });
+}
+
+void CPVRManager::ConnectionStateChange(CPVRClient* client,
+ const std::string& connectString,
+ PVR_CONNECTION_STATE state,
+ const std::string& message)
+{
+ CServiceBroker::GetJobManager()->Submit([this, client, connectString, state, message] {
+ Clients()->ConnectionStateChange(client, connectString, state, message);
+ return true;
+ });
+}
+
+bool CPVRManager::CreateChannelEpgs()
+{
+ if (EpgsCreated())
+ return true;
+
+ bool bEpgsCreated = m_channelGroups->CreateChannelEpgs();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bEpgsCreated = bEpgsCreated;
+ return m_bEpgsCreated;
+}