From c04dcc2e7d834218ef2d4194331e383402495ae1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 20:07:22 +0200 Subject: Adding upstream version 2:20.4+dfsg. Signed-off-by: Daniel Baumann --- xbmc/pvr/epg/EpgContainer.cpp | 1028 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1028 insertions(+) create mode 100644 xbmc/pvr/epg/EpgContainer.cpp (limited to 'xbmc/pvr/epg/EpgContainer.cpp') diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp new file mode 100644 index 0000000..d6f2015 --- /dev/null +++ b/xbmc/pvr/epg/EpgContainer.cpp @@ -0,0 +1,1028 @@ +/* + * 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 "EpgContainer.h" + +#include "ServiceBroker.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRManager.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgChannelData.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgDatabase.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/guilib/PVRGUIProgressHandler.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "threads/SystemClock.h" +#include "utils/log.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace PVR +{ + +class CEpgUpdateRequest +{ +public: + CEpgUpdateRequest() : CEpgUpdateRequest(-1, PVR_CHANNEL_INVALID_UID) {} + CEpgUpdateRequest(int iClientID, int iUniqueChannelID) : m_iClientID(iClientID), m_iUniqueChannelID(iUniqueChannelID) {} + + void Deliver(); + +private: + int m_iClientID; + int m_iUniqueChannelID; +}; + +void CEpgUpdateRequest::Deliver() +{ + const std::shared_ptr epg = CServiceBroker::GetPVRManager().EpgContainer().GetByChannelUid(m_iClientID, m_iUniqueChannelID); + if (!epg) + { + CLog::LogF(LOGERROR, + "Unable to obtain EPG for client {} and channel {}! Unable to deliver the epg " + "update request!", + m_iClientID, m_iUniqueChannelID); + return; + } + + epg->ForceUpdate(); +} + +class CEpgTagStateChange +{ +public: + CEpgTagStateChange() = default; + CEpgTagStateChange(const std::shared_ptr& tag, EPG_EVENT_STATE eNewState) : m_epgtag(tag), m_state(eNewState) {} + + void Deliver(); + +private: + std::shared_ptr m_epgtag; + EPG_EVENT_STATE m_state = EPG_EVENT_CREATED; +}; + +void CEpgTagStateChange::Deliver() +{ + const CPVREpgContainer& epgContainer = CServiceBroker::GetPVRManager().EpgContainer(); + + const std::shared_ptr epg = epgContainer.GetByChannelUid(m_epgtag->ClientID(), m_epgtag->UniqueChannelID()); + if (!epg) + { + CLog::LogF(LOGERROR, + "Unable to obtain EPG for client {} and channel {}! Unable to deliver state change " + "for tag '{}'!", + m_epgtag->ClientID(), m_epgtag->UniqueChannelID(), m_epgtag->UniqueBroadcastID()); + return; + } + + if (m_epgtag->EpgID() < 0) + { + // now that we have the epg instance, fully initialize the tag + m_epgtag->SetEpgID(epg->EpgID()); + m_epgtag->SetChannelData(epg->GetChannelData()); + } + + epg->UpdateEntry(m_epgtag, m_state); +} + +CPVREpgContainer::CPVREpgContainer(CEventSource& eventSource) + : CThread("EPGUpdater"), + m_database(new CPVREpgDatabase), + m_settings({CSettings::SETTING_EPG_EPGUPDATE, CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY, + CSettings::SETTING_EPG_PAST_DAYSTODISPLAY, + CSettings::SETTING_EPG_PREVENTUPDATESWHILEPLAYINGTV}), + m_events(eventSource) +{ + m_bStop = true; // base class member + m_updateEvent.Reset(); +} + +CPVREpgContainer::~CPVREpgContainer() +{ + Stop(); + Unload(); +} + +std::shared_ptr CPVREpgContainer::GetEpgDatabase() const +{ + std::unique_lock lock(m_critSection); + + if (!m_database->IsOpen()) + m_database->Open(); + + return m_database; +} + +bool CPVREpgContainer::IsStarted() const +{ + std::unique_lock lock(m_critSection); + return m_bStarted; +} + +int CPVREpgContainer::NextEpgId() +{ + std::unique_lock lock(m_critSection); + return ++m_iNextEpgId; +} + +void CPVREpgContainer::Start() +{ + Stop(); + + { + std::unique_lock lock(m_critSection); + m_bIsInitialising = true; + + Create(); + SetPriority(ThreadPriority::BELOW_NORMAL); + + m_bStarted = true; + } +} + +void CPVREpgContainer::Stop() +{ + StopThread(); + + { + std::unique_lock lock(m_critSection); + m_bStarted = false; + } +} + +bool CPVREpgContainer::Load() +{ + // EPGs must be loaded via PVR Manager -> channel groups -> EPG container to associate the + // channels with the right EPG. + CServiceBroker::GetPVRManager().TriggerEpgsCreate(); + return true; +} + +void CPVREpgContainer::Unload() +{ + { + std::unique_lock lock(m_updateRequestsLock); + m_updateRequests.clear(); + } + + { + std::unique_lock lock(m_epgTagChangesLock); + m_epgTagChanges.clear(); + } + + std::vector> epgs; + { + std::unique_lock lock(m_critSection); + + /* clear all epg tables and remove pointers to epg tables on channels */ + std::transform(m_epgIdToEpgMap.cbegin(), m_epgIdToEpgMap.cend(), std::back_inserter(epgs), + [](const auto& epgEntry) { return epgEntry.second; }); + + m_epgIdToEpgMap.clear(); + m_channelUidToEpgMap.clear(); + + m_iNextEpgUpdate = 0; + m_iNextEpgId = 0; + m_iNextEpgActiveTagCheck = 0; + m_bUpdateNotificationPending = false; + m_bLoaded = false; + + m_database->Close(); + } + + for (const auto& epg : epgs) + { + epg->Events().Unsubscribe(this); + epg->RemovedFromContainer(); + } +} + +void CPVREpgContainer::Notify(const PVREvent& event) +{ + if (event == PVREvent::EpgItemUpdate) + { + // there can be many of these notifications during short time period. Thus, announce async and not every event. + std::unique_lock lock(m_critSection); + m_bUpdateNotificationPending = true; + return; + } + else if (event == PVREvent::EpgUpdatePending) + { + SetHasPendingUpdates(true); + return; + } + else if (event == PVREvent::EpgActiveItem) + { + // No need to propagate the change. See CPVREpgContainer::CheckPlayingEvents + return; + } + + m_events.Publish(event); +} + +void CPVREpgContainer::LoadFromDatabase() +{ + std::unique_lock lock(m_critSection); + + if (m_bLoaded) + return; + + const std::shared_ptr database = GetEpgDatabase(); + database->Lock(); + m_iNextEpgId = database->GetLastEPGId(); + const std::vector> result = database->GetAll(); + database->Unlock(); + + for (const auto& entry : result) + InsertFromDB(entry); + + m_bLoaded = true; +} + +bool CPVREpgContainer::PersistAll(unsigned int iMaxTimeslice) const +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return false; + } + + std::vector> changedEpgs; + { + std::unique_lock lock(m_critSection); + for (const auto& epg : m_epgIdToEpgMap) + { + if (epg.second && epg.second->NeedsSave()) + { + // Note: We need to obtain a lock for every epg instance before we can lock + // the epg db. This order is important. Otherwise deadlocks may occur. + epg.second->Lock(); + changedEpgs.emplace_back(epg.second); + } + } + } + + bool bReturn = true; + + if (!changedEpgs.empty()) + { + // Note: We must lock the db the whole time, otherwise races may occur. + database->Lock(); + + XbmcThreads::EndTime<> processTimeslice{std::chrono::milliseconds(iMaxTimeslice)}; + for (const auto& epg : changedEpgs) + { + if (!processTimeslice.IsTimePast()) + { + CLog::LogFC(LOGDEBUG, LOGEPG, "EPG Container: Persisting events for channel '{}'...", + epg->GetChannelData()->ChannelName()); + + bReturn &= epg->QueuePersistQuery(database); + + size_t queryCount = database->GetInsertQueriesCount() + database->GetDeleteQueriesCount(); + if (queryCount > EPG_COMMIT_QUERY_COUNT_LIMIT) + { + CLog::LogFC(LOGDEBUG, LOGEPG, "EPG Container: committing {} queries in loop.", + queryCount); + database->CommitDeleteQueries(); + database->CommitInsertQueries(); + CLog::LogFC(LOGDEBUG, LOGEPG, "EPG Container: committed {} queries in loop.", queryCount); + } + } + + epg->Unlock(); + } + + if (bReturn) + { + database->CommitDeleteQueries(); + database->CommitInsertQueries(); + } + + database->Unlock(); + } + + return bReturn; +} + +void CPVREpgContainer::Process() +{ + time_t iNow = 0; + time_t iLastSave = 0; + + SetPriority(ThreadPriority::LOWEST); + + while (!m_bStop) + { + time_t iLastEpgCleanup = 0; + bool bUpdateEpg = true; + + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow); + { + std::unique_lock lock(m_critSection); + bUpdateEpg = (iNow >= m_iNextEpgUpdate) && !m_bSuspended; + iLastEpgCleanup = m_iLastEpgCleanup; + } + + /* update the EPG */ + if (!InterruptUpdate() && bUpdateEpg && CServiceBroker::GetPVRManager().EpgsCreated() && UpdateEPG()) + m_bIsInitialising = false; + + /* clean up old entries */ + if (!m_bStop && !m_bSuspended && + iNow >= iLastEpgCleanup + CServiceBroker::GetSettingsComponent() + ->GetAdvancedSettings() + ->m_iEpgCleanupInterval) + RemoveOldEntries(); + + /* check for pending manual EPG updates */ + + while (!m_bStop && !m_bSuspended) + { + CEpgUpdateRequest request; + { + std::unique_lock lock(m_updateRequestsLock); + if (m_updateRequests.empty()) + break; + + request = m_updateRequests.front(); + m_updateRequests.pop_front(); + } + + // do the update + request.Deliver(); + } + + /* check for pending EPG tag changes */ + + // during Kodi startup, addons may push updates very early, even before EPGs are ready to use. + if (!m_bStop && !m_bSuspended && CServiceBroker::GetPVRManager().EpgsCreated()) + { + unsigned int iProcessed = 0; + XbmcThreads::EndTime<> processTimeslice( + 1000ms); // max 1 sec per cycle, regardless of how many events are in the queue + + while (!InterruptUpdate()) + { + CEpgTagStateChange change; + { + std::unique_lock lock(m_epgTagChangesLock); + if (processTimeslice.IsTimePast() || m_epgTagChanges.empty()) + { + if (iProcessed > 0) + CLog::LogFC(LOGDEBUG, LOGEPG, "Processed {} queued epg event changes.", iProcessed); + + break; + } + + change = m_epgTagChanges.front(); + m_epgTagChanges.pop_front(); + } + + iProcessed++; + + // deliver the updated tag to the respective epg + change.Deliver(); + } + } + + if (!m_bStop && !m_bSuspended) + { + bool bHasPendingUpdates = false; + + { + std::unique_lock lock(m_critSection); + bHasPendingUpdates = (m_pendingUpdates > 0); + } + + if (bHasPendingUpdates) + UpdateEPG(true); + } + + /* check for updated active tag */ + if (!m_bStop) + CheckPlayingEvents(); + + /* check for pending update notifications */ + if (!m_bStop) + { + std::unique_lock lock(m_critSection); + if (m_bUpdateNotificationPending) + { + m_bUpdateNotificationPending = false; + m_events.Publish(PVREvent::Epg); + } + } + + /* check for changes that need to be saved every 60 seconds */ + if ((iNow - iLastSave > 60) && !InterruptUpdate()) + { + PersistAll(1000); + iLastSave = iNow; + } + + CThread::Sleep(1000ms); + } + + // store data on exit + CLog::Log(LOGINFO, "EPG Container: Persisting unsaved events..."); + PersistAll(std::numeric_limits::max()); + CLog::Log(LOGINFO, "EPG Container: Persisting events done"); +} + +std::vector> CPVREpgContainer::GetAllEpgs() const +{ + std::vector> epgs; + + std::unique_lock lock(m_critSection); + std::transform(m_epgIdToEpgMap.cbegin(), m_epgIdToEpgMap.cend(), std::back_inserter(epgs), + [](const auto& epgEntry) { return epgEntry.second; }); + + return epgs; +} + +std::shared_ptr CPVREpgContainer::GetById(int iEpgId) const +{ + std::shared_ptr retval; + + if (iEpgId < 0) + return retval; + + std::unique_lock lock(m_critSection); + const auto& epgEntry = m_epgIdToEpgMap.find(iEpgId); + if (epgEntry != m_epgIdToEpgMap.end()) + retval = epgEntry->second; + + return retval; +} + +std::shared_ptr CPVREpgContainer::GetByChannelUid(int iClientId, int iChannelUid) const +{ + std::shared_ptr epg; + + if (iClientId < 0 || iChannelUid < 0) + return epg; + + std::unique_lock lock(m_critSection); + const auto& epgEntry = m_channelUidToEpgMap.find(std::pair(iClientId, iChannelUid)); + if (epgEntry != m_channelUidToEpgMap.end()) + epg = epgEntry->second; + + return epg; +} + +std::shared_ptr CPVREpgContainer::GetTagById(const std::shared_ptr& epg, unsigned int iBroadcastId) const +{ + std::shared_ptr retval; + + if (iBroadcastId == EPG_TAG_INVALID_UID) + return retval; + + if (epg) + { + retval = epg->GetTagByBroadcastId(iBroadcastId); + } + + return retval; +} + +std::shared_ptr CPVREpgContainer::GetTagByDatabaseId(int iDatabaseId) const +{ + std::shared_ptr retval; + + if (iDatabaseId <= 0) + return retval; + + m_critSection.lock(); + const auto epgs = m_epgIdToEpgMap; + m_critSection.unlock(); + + for (const auto& epgEntry : epgs) + { + retval = epgEntry.second->GetTagByDatabaseId(iDatabaseId); + if (retval) + break; + } + + return retval; +} + +std::vector> CPVREpgContainer::GetTags( + const PVREpgSearchData& searchData) const +{ + // make sure we have up-to-date data in the database. + PersistAll(std::numeric_limits::max()); + + const std::shared_ptr database = GetEpgDatabase(); + std::vector> results = database->GetEpgTags(searchData); + + std::unique_lock lock(m_critSection); + for (const auto& tag : results) + { + const auto& it = m_epgIdToEpgMap.find(tag->EpgID()); + if (it != m_epgIdToEpgMap.cend()) + tag->SetChannelData((*it).second->GetChannelData()); + } + + return results; +} + +void CPVREpgContainer::InsertFromDB(const std::shared_ptr& newEpg) +{ + std::unique_lock lock(m_critSection); + + // table might already have been created when pvr channels were loaded + std::shared_ptr epg = GetById(newEpg->EpgID()); + if (!epg) + { + // create a new epg table + epg = newEpg; + m_epgIdToEpgMap.insert({epg->EpgID(), epg}); + epg->Events().Subscribe(this, &CPVREpgContainer::Notify); + } +} + +std::shared_ptr CPVREpgContainer::CreateChannelEpg(int iEpgId, const std::string& strScraperName, const std::shared_ptr& channelData) +{ + std::shared_ptr epg; + + WaitForUpdateFinish(); + LoadFromDatabase(); + + if (iEpgId > 0) + epg = GetById(iEpgId); + + if (!epg) + { + if (iEpgId <= 0) + iEpgId = NextEpgId(); + + epg.reset(new CPVREpg(iEpgId, channelData->ChannelName(), strScraperName, channelData, + GetEpgDatabase())); + + std::unique_lock lock(m_critSection); + m_epgIdToEpgMap.insert({iEpgId, epg}); + m_channelUidToEpgMap.insert({{channelData->ClientId(), channelData->UniqueClientChannelId()}, epg}); + epg->Events().Subscribe(this, &CPVREpgContainer::Notify); + } + else if (epg->ChannelID() == -1) + { + std::unique_lock lock(m_critSection); + m_channelUidToEpgMap.insert({{channelData->ClientId(), channelData->UniqueClientChannelId()}, epg}); + epg->SetChannelData(channelData); + } + + { + std::unique_lock lock(m_critSection); + m_bPreventUpdates = false; + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate); + } + + m_events.Publish(PVREvent::EpgContainer); + + return epg; +} + +bool CPVREpgContainer::RemoveOldEntries() +{ + const CDateTime cleanupTime(CDateTime::GetUTCDateTime() - CDateTimeSpan(GetPastDaysToDisplay(), 0, 0, 0)); + + m_critSection.lock(); + const auto epgs = m_epgIdToEpgMap; + m_critSection.unlock(); + + for (const auto& epgEntry : epgs) + epgEntry.second->Cleanup(cleanupTime); + + std::unique_lock lock(m_critSection); + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup); + + return true; +} + +bool CPVREpgContainer::QueueDeleteEpgs(const std::vector>& epgs) +{ + if (epgs.empty()) + return true; + + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return false; + } + + for (const auto& epg : epgs) + { + // Note: We need to obtain a lock for every epg instance before we can lock + // the epg db. This order is important. Otherwise deadlocks may occur. + epg->Lock(); + } + + database->Lock(); + for (const auto& epg : epgs) + { + QueueDeleteEpg(epg, database); + epg->Unlock(); + + size_t queryCount = database->GetDeleteQueriesCount(); + if (queryCount > EPG_COMMIT_QUERY_COUNT_LIMIT) + database->CommitDeleteQueries(); + } + database->CommitDeleteQueries(); + database->Unlock(); + + return true; +} + +bool CPVREpgContainer::QueueDeleteEpg(const std::shared_ptr& epg, + const std::shared_ptr& database) +{ + if (!epg || epg->EpgID() < 0) + return false; + + std::shared_ptr epgToDelete; + { + std::unique_lock lock(m_critSection); + + const auto& epgEntry = m_epgIdToEpgMap.find(epg->EpgID()); + if (epgEntry == m_epgIdToEpgMap.end()) + return false; + + const auto& epgEntry1 = m_channelUidToEpgMap.find(std::make_pair( + epg->GetChannelData()->ClientId(), epg->GetChannelData()->UniqueClientChannelId())); + if (epgEntry1 != m_channelUidToEpgMap.end()) + m_channelUidToEpgMap.erase(epgEntry1); + + CLog::LogFC(LOGDEBUG, LOGEPG, "Deleting EPG table {} ({})", epg->Name(), epg->EpgID()); + epgEntry->second->QueueDeleteQueries(database); + + epgToDelete = epgEntry->second; + m_epgIdToEpgMap.erase(epgEntry); + } + + epgToDelete->Events().Unsubscribe(this); + epgToDelete->RemovedFromContainer(); + return true; +} + +bool CPVREpgContainer::InterruptUpdate() const +{ + std::unique_lock lock(m_critSection); + return m_bStop || + m_bPreventUpdates || + (m_bPlaying && m_settings.GetBoolValue(CSettings::SETTING_EPG_PREVENTUPDATESWHILEPLAYINGTV)); +} + +void CPVREpgContainer::WaitForUpdateFinish() +{ + { + std::unique_lock lock(m_critSection); + m_bPreventUpdates = true; + + if (!m_bIsUpdating) + return; + + m_updateEvent.Reset(); + } + + m_updateEvent.Wait(); +} + +bool CPVREpgContainer::UpdateEPG(bool bOnlyPending /* = false */) +{ + bool bInterrupted = false; + unsigned int iUpdatedTables = 0; + const std::shared_ptr advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + + /* set start and end time */ + time_t start; + time_t end; + CDateTime::GetUTCDateTime().GetAsTime(start); + end = start + GetFutureDaysToDisplay() * 24 * 60 * 60; + start -= GetPastDaysToDisplay() * 24 * 60 * 60; + + bool bShowProgress = (m_bIsInitialising || advancedSettings->m_bEpgDisplayIncrementalUpdatePopup) && + advancedSettings->m_bEpgDisplayUpdatePopup; + int pendingUpdates = 0; + + { + std::unique_lock lock(m_critSection); + if (m_bIsUpdating || InterruptUpdate()) + return false; + + m_bIsUpdating = true; + pendingUpdates = m_pendingUpdates; + } + + std::vector> invalidTables; + + unsigned int iCounter = 0; + const std::shared_ptr database = GetEpgDatabase(); + + m_critSection.lock(); + const auto epgsToUpdate = m_epgIdToEpgMap; + m_critSection.unlock(); + + std::unique_ptr progressHandler; + if (bShowProgress && !bOnlyPending && !epgsToUpdate.empty()) + progressHandler.reset( + new CPVRGUIProgressHandler(g_localizeStrings.Get(19004))); // Loading programme guide + + for (const auto& epgEntry : epgsToUpdate) + { + if (InterruptUpdate()) + { + bInterrupted = true; + break; + } + + const std::shared_ptr epg = epgEntry.second; + if (!epg) + continue; + + if (progressHandler) + progressHandler->UpdateProgress(epg->GetChannelData()->ChannelName(), ++iCounter, + epgsToUpdate.size()); + + if ((!bOnlyPending || epg->UpdatePending()) && + epg->Update(start, + end, + m_settings.GetIntValue(CSettings::SETTING_EPG_EPGUPDATE) * 60, + m_settings.GetIntValue(CSettings::SETTING_EPG_PAST_DAYSTODISPLAY), + database, + bOnlyPending)) + { + iUpdatedTables++; + } + else if (!epg->IsValid()) + { + invalidTables.push_back(epg); + } + } + + progressHandler.reset(); + + QueueDeleteEpgs(invalidTables); + + if (bInterrupted) + { + /* the update has been interrupted. try again later */ + time_t iNow; + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow); + + std::unique_lock lock(m_critSection); + m_iNextEpgUpdate = iNow + advancedSettings->m_iEpgRetryInterruptedUpdateInterval; + } + else + { + std::unique_lock lock(m_critSection); + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate); + m_iNextEpgUpdate += advancedSettings->m_iEpgUpdateCheckInterval; + if (m_pendingUpdates == pendingUpdates) + m_pendingUpdates = 0; + } + + if (iUpdatedTables > 0) + m_events.Publish(PVREvent::EpgContainer); + + std::unique_lock lock(m_critSection); + m_bIsUpdating = false; + m_updateEvent.Set(); + + return !bInterrupted; +} + +std::pair CPVREpgContainer::GetFirstAndLastEPGDate() const +{ + // Get values from db + std::pair dbDates; + const std::shared_ptr database = GetEpgDatabase(); + if (database) + dbDates = database->GetFirstAndLastEPGDate(); + + // Merge not yet commited changes + m_critSection.lock(); + const auto epgs = m_epgIdToEpgMap; + m_critSection.unlock(); + + CDateTime first(dbDates.first); + CDateTime last(dbDates.second); + + for (const auto& epgEntry : epgs) + { + const auto dates = epgEntry.second->GetFirstAndLastUncommitedEPGDate(); + + if (dates.first.IsValid() && (!first.IsValid() || dates.first < first)) + first = dates.first; + + if (dates.second.IsValid() && (!last.IsValid() || dates.second > last)) + last = dates.second; + } + + return {first, last}; +} + +bool CPVREpgContainer::CheckPlayingEvents() +{ + bool bReturn = false; + bool bFoundChanges = false; + + m_critSection.lock(); + const auto epgs = m_epgIdToEpgMap; + time_t iNextEpgActiveTagCheck = m_iNextEpgActiveTagCheck; + m_critSection.unlock(); + + time_t iNow; + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow); + if (iNow >= iNextEpgActiveTagCheck) + { + bFoundChanges = std::accumulate(epgs.cbegin(), epgs.cend(), bFoundChanges, + [](bool found, const auto& epgEntry) { + return epgEntry.second->CheckPlayingEvent() ? true : found; + }); + + CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNextEpgActiveTagCheck); + iNextEpgActiveTagCheck += CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEpgActiveTagCheckInterval; + + /* pvr tags always start on the full minute */ + if (CServiceBroker::GetPVRManager().IsStarted()) + iNextEpgActiveTagCheck -= iNextEpgActiveTagCheck % 60; + + bReturn = true; + } + + if (bReturn) + { + std::unique_lock lock(m_critSection); + m_iNextEpgActiveTagCheck = iNextEpgActiveTagCheck; + } + + if (bFoundChanges) + m_events.Publish(PVREvent::EpgActiveItem); + + return bReturn; +} + +void CPVREpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */) +{ + std::unique_lock lock(m_critSection); + if (bHasPendingUpdates) + m_pendingUpdates++; + else + m_pendingUpdates = 0; +} + +void CPVREpgContainer::UpdateRequest(int iClientID, int iUniqueChannelID) +{ + std::unique_lock lock(m_updateRequestsLock); + m_updateRequests.emplace_back(CEpgUpdateRequest(iClientID, iUniqueChannelID)); +} + +void CPVREpgContainer::UpdateFromClient(const std::shared_ptr& tag, EPG_EVENT_STATE eNewState) +{ + std::unique_lock lock(m_epgTagChangesLock); + m_epgTagChanges.emplace_back(CEpgTagStateChange(tag, eNewState)); +} + +int CPVREpgContainer::GetPastDaysToDisplay() const +{ + return m_settings.GetIntValue(CSettings::SETTING_EPG_PAST_DAYSTODISPLAY); +} + +int CPVREpgContainer::GetFutureDaysToDisplay() const +{ + return m_settings.GetIntValue(CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY); +} + +void CPVREpgContainer::OnPlaybackStarted() +{ + std::unique_lock lock(m_critSection); + m_bPlaying = true; +} + +void CPVREpgContainer::OnPlaybackStopped() +{ + std::unique_lock lock(m_critSection); + m_bPlaying = false; +} + +void CPVREpgContainer::OnSystemSleep() +{ + m_bSuspended = true; +} + +void CPVREpgContainer::OnSystemWake() +{ + m_bSuspended = false; +} + +int CPVREpgContainer::CleanupCachedImages() +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return 0; + } + + // Processing can take some time. Do not block. + m_critSection.lock(); + const std::map> epgIdToEpgMap = m_epgIdToEpgMap; + m_critSection.unlock(); + + return std::accumulate(epgIdToEpgMap.cbegin(), epgIdToEpgMap.cend(), 0, + [&database](int cleanedImages, const auto& epg) { + return cleanedImages + epg.second->CleanupCachedImages(database); + }); +} + +std::vector> CPVREpgContainer::GetSavedSearches(bool bRadio) +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return {}; + } + + return database->GetSavedSearches(bRadio); +} + +std::shared_ptr CPVREpgContainer::GetSavedSearchById(bool bRadio, int iId) +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return {}; + } + + return database->GetSavedSearchById(bRadio, iId); +} + +bool CPVREpgContainer::PersistSavedSearch(CPVREpgSearchFilter& search) +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return {}; + } + + if (database->Persist(search)) + { + m_events.Publish(PVREvent::SavedSearchesInvalidated); + return true; + } + return false; +} + +bool CPVREpgContainer::UpdateSavedSearchLastExecuted(const CPVREpgSearchFilter& epgSearch) +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return {}; + } + + return database->UpdateSavedSearchLastExecuted(epgSearch); +} + +bool CPVREpgContainer::DeleteSavedSearch(const CPVREpgSearchFilter& search) +{ + const std::shared_ptr database = GetEpgDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return {}; + } + + if (database->Delete(search)) + { + m_events.Publish(PVREvent::SavedSearchesInvalidated); + return true; + } + return false; +} + +} // namespace PVR -- cgit v1.2.3