diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/epg/Epg.cpp | |
parent | Initial commit. (diff) | |
download | kodi-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/epg/Epg.cpp')
-rw-r--r-- | xbmc/pvr/epg/Epg.cpp | 554 |
1 files changed, 554 insertions, 0 deletions
diff --git a/xbmc/pvr/epg/Epg.cpp b/xbmc/pvr/epg/Epg.cpp new file mode 100644 index 0000000..1112b2f --- /dev/null +++ b/xbmc/pvr/epg/Epg.cpp @@ -0,0 +1,554 @@ +/* + * 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 "Epg.h" + +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRCachedImages.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/epg/EpgChannelData.h" +#include "pvr/epg/EpgDatabase.h" +#include "pvr/epg/EpgInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <memory> +#include <mutex> +#include <utility> +#include <vector> + +using namespace PVR; + +CPVREpg::CPVREpg(int iEpgID, + const std::string& strName, + const std::string& strScraperName, + const std::shared_ptr<CPVREpgDatabase>& database) + : m_iEpgID(iEpgID), + m_strName(strName), + m_strScraperName(strScraperName), + m_channelData(new CPVREpgChannelData), + m_tags(m_iEpgID, m_channelData, database) +{ +} + +CPVREpg::CPVREpg(int iEpgID, + const std::string& strName, + const std::string& strScraperName, + const std::shared_ptr<CPVREpgChannelData>& channelData, + const std::shared_ptr<CPVREpgDatabase>& database) + : m_bChanged(true), + m_iEpgID(iEpgID), + m_strName(strName), + m_strScraperName(strScraperName), + m_channelData(channelData), + m_tags(m_iEpgID, m_channelData, database) +{ +} + +CPVREpg::~CPVREpg() +{ + Clear(); +} + +void CPVREpg::ForceUpdate() +{ + m_bUpdatePending = true; + m_events.Publish(PVREvent::EpgUpdatePending); +} + +void CPVREpg::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_tags.Clear(); +} + +void CPVREpg::Cleanup(int iPastDays) +{ + const CDateTime cleanupTime = CDateTime::GetUTCDateTime() - CDateTimeSpan(iPastDays, 0, 0, 0); + Cleanup(cleanupTime); +} + +void CPVREpg::Cleanup(const CDateTime& time) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_tags.Cleanup(time); +} + +std::shared_ptr<CPVREpgInfoTag> CPVREpg::GetTagNow() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetActiveTag(); +} + +std::shared_ptr<CPVREpgInfoTag> CPVREpg::GetTagNext() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetNextStartingTag(); +} + +std::shared_ptr<CPVREpgInfoTag> CPVREpg::GetTagPrevious() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetLastEndedTag(); +} + +bool CPVREpg::CheckPlayingEvent() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_tags.UpdateActiveTag()) + { + m_events.Publish(PVREvent::EpgActiveItem); + return true; + } + return false; +} + +std::shared_ptr<CPVREpgInfoTag> CPVREpg::GetTagByBroadcastId(unsigned int iUniqueBroadcastId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetTag(iUniqueBroadcastId); +} + +std::shared_ptr<CPVREpgInfoTag> CPVREpg::GetTagByDatabaseId(int iDatabaseId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetTagByDatabaseID(iDatabaseId); +} + +std::shared_ptr<CPVREpgInfoTag> CPVREpg::GetTagBetween(const CDateTime& beginTime, const CDateTime& endTime, bool bUpdateFromClient /* = false */) +{ + std::shared_ptr<CPVREpgInfoTag> tag; + + std::unique_lock<CCriticalSection> lock(m_critSection); + tag = m_tags.GetTagBetween(beginTime, endTime); + + if (!tag && bUpdateFromClient) + { + // not found locally; try to fetch from client + time_t b; + beginTime.GetAsTime(b); + time_t e; + endTime.GetAsTime(e); + + const std::shared_ptr<CPVREpg> tmpEpg = std::make_shared<CPVREpg>( + m_iEpgID, m_strName, m_strScraperName, m_channelData, std::shared_ptr<CPVREpgDatabase>()); + if (tmpEpg->UpdateFromScraper(b, e, true)) + tag = tmpEpg->GetTagBetween(beginTime, endTime, false); + + if (tag) + m_tags.UpdateEntry(tag); + } + + return tag; +} + +std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpg::GetTimeline( + const CDateTime& timelineStart, + const CDateTime& timelineEnd, + const CDateTime& minEventEnd, + const CDateTime& maxEventStart) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetTimeline(timelineStart, timelineEnd, minEventEnd, maxEventStart); +} + +bool CPVREpg::UpdateEntries(const CPVREpg& epg) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + /* copy over tags */ + m_tags.UpdateEntries(epg.m_tags); + + /* update the last scan time of this table */ + m_lastScanTime = CDateTime::GetUTCDateTime(); + m_bUpdateLastScanTime = true; + + m_events.Publish(PVREvent::Epg); + return true; +} + +namespace +{ + +bool IsTagExpired(const std::shared_ptr<CPVREpgInfoTag>& tag) +{ + // Respect epg linger time. + const int iPastDays = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_EPG_PAST_DAYSTODISPLAY); + const CDateTime cleanupTime(CDateTime::GetUTCDateTime() - CDateTimeSpan(iPastDays, 0, 0, 0)); + + return tag->EndAsUTC() < cleanupTime; +} + +} // unnamed namespace + +bool CPVREpg::UpdateEntry(const EPG_TAG* data, int iClientId) +{ + if (!data) + return false; + + const std::shared_ptr<CPVREpgInfoTag> tag = + std::make_shared<CPVREpgInfoTag>(*data, iClientId, m_channelData, m_iEpgID); + + return !IsTagExpired(tag) && m_tags.UpdateEntry(tag); +} + +bool CPVREpg::UpdateEntry(const std::shared_ptr<CPVREpgInfoTag>& tag, EPG_EVENT_STATE newState) +{ + bool bRet = true; + bool bNotify = true; + + if (newState == EPG_EVENT_CREATED || newState == EPG_EVENT_UPDATED) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bRet = !IsTagExpired(tag) && m_tags.UpdateEntry(tag); + } + else if (newState == EPG_EVENT_DELETED) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + const std::shared_ptr<CPVREpgInfoTag> existingTag = m_tags.GetTag(tag->UniqueBroadcastID()); + if (!existingTag) + { + bRet = false; + } + else + { + if (IsTagExpired(existingTag)) + { + m_tags.DeleteEntry(existingTag); + } + else + { + bNotify = false; + } + } + } + else + { + CLog::LogF(LOGERROR, "Unknown epg event state value: {}", newState); + bRet = false; + } + + if (bRet && bNotify) + m_events.Publish(PVREvent::EpgItemUpdate); + + return bRet; +} + +bool CPVREpg::Update(time_t start, + time_t end, + int iUpdateTime, + int iPastDays, + const std::shared_ptr<CPVREpgDatabase>& database, + bool bForceUpdate /* = false */) +{ + bool bUpdate = false; + std::shared_ptr<CPVREpg> tmpEpg; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (!m_lastScanTime.IsValid()) + { + database->GetLastEpgScanTime(m_iEpgID, &m_lastScanTime); + + if (!m_lastScanTime.IsValid()) + { + m_lastScanTime.SetFromUTCDateTime(time_t(0)); + m_bUpdateLastScanTime = true; + } + } + + // enforce advanced settings update interval override for channels with no EPG data + if (m_tags.IsEmpty() && m_channelData->ChannelId() > 0) //! @todo why the channelid check? + iUpdateTime = CServiceBroker::GetSettingsComponent() + ->GetAdvancedSettings() + ->m_iEpgUpdateEmptyTagsInterval; + + if (bForceUpdate) + { + bUpdate = true; + } + else + { + // check if we have to update + time_t iNow = 0; + CDateTime::GetUTCDateTime().GetAsTime(iNow); + + time_t iLastUpdate = 0; + m_lastScanTime.GetAsTime(iLastUpdate); + + bUpdate = (iNow > iLastUpdate + iUpdateTime); + } + + if (bUpdate) + { + tmpEpg = std::make_shared<CPVREpg>(m_iEpgID, m_strName, m_strScraperName, m_channelData, + std::shared_ptr<CPVREpgDatabase>()); + } + } + + // remove obsolete tags + Cleanup(iPastDays); + + bool bGrabSuccess = true; + + if (bUpdate) + { + bGrabSuccess = tmpEpg->UpdateFromScraper(start, end, bForceUpdate) && UpdateEntries(*tmpEpg); + + if (!bGrabSuccess) + CLog::LogF(LOGERROR, "Failed to update table '{}'", Name()); + } + + m_bUpdatePending = false; + return bGrabSuccess; +} + +std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpg::GetTags() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetAllTags(); +} + +bool CPVREpg::QueuePersistQuery(const std::shared_ptr<CPVREpgDatabase>& database) +{ + // Note: It is guaranteed that both this EPG instance and database instance are already + // locked when this method gets called! No additional locking is needed here! + + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return false; + } + + if (m_iEpgID <= 0 || m_bChanged) + { + const int iId = database->Persist(*this, m_iEpgID > 0); + if (iId > 0 && m_iEpgID != iId) + { + m_iEpgID = iId; + m_tags.SetEpgID(iId); + } + } + + if (m_tags.NeedsSave()) + m_tags.QueuePersistQuery(); + + if (m_bUpdateLastScanTime) + database->QueuePersistLastEpgScanTimeQuery(m_iEpgID, m_lastScanTime); + + m_bChanged = false; + m_bUpdateLastScanTime = false; + + return true; +} + +bool CPVREpg::QueueDeleteQueries(const std::shared_ptr<CPVREpgDatabase>& database) +{ + if (!database) + { + CLog::LogF(LOGERROR, "No EPG database"); + return false; + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + // delete own epg db entry + database->QueueDeleteEpgQuery(*this); + + // delete last scan time db entry for this epg + database->QueueDeleteLastEpgScanTimeQuery(*this); + + // delete all tags for this epg from db + m_tags.QueueDelete(); + + Clear(); + + return true; +} + +std::pair<CDateTime, CDateTime> CPVREpg::GetFirstAndLastUncommitedEPGDate() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_tags.GetFirstAndLastUncommitedEPGDate(); +} + +bool CPVREpg::UpdateFromScraper(time_t start, time_t end, bool bForceUpdate) +{ + if (m_strScraperName.empty()) + { + CLog::LogF(LOGERROR, "No EPG scraper defined for table '{}'", m_strName); + } + else if (m_strScraperName == "client") + { + if (!CServiceBroker::GetPVRManager().EpgsCreated()) + return false; + + if (!m_channelData->IsEPGEnabled() || m_channelData->IsHidden()) + { + // ignore. not interested in any updates. + return true; + } + + const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_channelData->ClientId()); + if (client) + { + if (!client->GetClientCapabilities().SupportsEPG()) + { + CLog::LogF(LOGERROR, "The backend for channel '{}' on client '{}' does not support EPGs", + m_channelData->ChannelName(), m_channelData->ClientId()); + } + else if (!bForceUpdate && client->GetClientCapabilities().SupportsAsyncEPGTransfer()) + { + // nothing to do. client will provide epg updates asynchronously + return true; + } + else + { + CLog::LogFC(LOGDEBUG, LOGEPG, "Updating EPG for channel '{}' from client '{}'", + m_channelData->ChannelName(), m_channelData->ClientId()); + return (client->GetEPGForChannel(m_channelData->UniqueClientChannelId(), this, start, end) == PVR_ERROR_NO_ERROR); + } + } + else + { + CLog::LogF(LOGERROR, "Client '{}' not found, can't update", m_channelData->ClientId()); + } + } + else // other non-empty scraper name... + { + CLog::LogF(LOGERROR, "Loading the EPG via scraper is not yet implemented!"); + //! @todo Add Support for Web EPG Scrapers here + } + + return false; +} + +const std::string& CPVREpg::ConvertGenreIdToString(int iID, int iSubID) +{ + unsigned int iLabelId = 19499; + switch (iID) + { + case EPG_EVENT_CONTENTMASK_MOVIEDRAMA: + iLabelId = (iSubID <= 8) ? 19500 + iSubID : 19500; + break; + case EPG_EVENT_CONTENTMASK_NEWSCURRENTAFFAIRS: + iLabelId = (iSubID <= 4) ? 19516 + iSubID : 19516; + break; + case EPG_EVENT_CONTENTMASK_SHOW: + iLabelId = (iSubID <= 3) ? 19532 + iSubID : 19532; + break; + case EPG_EVENT_CONTENTMASK_SPORTS: + iLabelId = (iSubID <= 11) ? 19548 + iSubID : 19548; + break; + case EPG_EVENT_CONTENTMASK_CHILDRENYOUTH: + iLabelId = (iSubID <= 5) ? 19564 + iSubID : 19564; + break; + case EPG_EVENT_CONTENTMASK_MUSICBALLETDANCE: + iLabelId = (iSubID <= 6) ? 19580 + iSubID : 19580; + break; + case EPG_EVENT_CONTENTMASK_ARTSCULTURE: + iLabelId = (iSubID <= 11) ? 19596 + iSubID : 19596; + break; + case EPG_EVENT_CONTENTMASK_SOCIALPOLITICALECONOMICS: + iLabelId = (iSubID <= 3) ? 19612 + iSubID : 19612; + break; + case EPG_EVENT_CONTENTMASK_EDUCATIONALSCIENCE: + iLabelId = (iSubID <= 7) ? 19628 + iSubID : 19628; + break; + case EPG_EVENT_CONTENTMASK_LEISUREHOBBIES: + iLabelId = (iSubID <= 7) ? 19644 + iSubID : 19644; + break; + case EPG_EVENT_CONTENTMASK_SPECIAL: + iLabelId = (iSubID <= 3) ? 19660 + iSubID : 19660; + break; + case EPG_EVENT_CONTENTMASK_USERDEFINED: + iLabelId = (iSubID <= 8) ? 19676 + iSubID : 19676; + break; + default: + break; + } + + return g_localizeStrings.Get(iLabelId); +} + +std::shared_ptr<CPVREpgChannelData> CPVREpg::GetChannelData() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_channelData; +} + +void CPVREpg::SetChannelData(const std::shared_ptr<CPVREpgChannelData>& data) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_channelData = data; + m_tags.SetChannelData(data); +} + +int CPVREpg::ChannelID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_channelData->ChannelId(); +} + +const std::string& CPVREpg::ScraperName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strScraperName; +} + +const std::string& CPVREpg::Name() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strName; +} + +int CPVREpg::EpgID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iEpgID; +} + +bool CPVREpg::UpdatePending() const +{ + return m_bUpdatePending; +} + +bool CPVREpg::NeedsSave() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bChanged || m_bUpdateLastScanTime || m_tags.NeedsSave(); +} + +bool CPVREpg::IsValid() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (ScraperName() == "client") + return m_channelData->ClientId() != -1 && m_channelData->UniqueClientChannelId() != PVR_CHANNEL_INVALID_UID; + + return true; +} + +void CPVREpg::RemovedFromContainer() +{ + m_events.Publish(PVREvent::EpgDeleted); +} + +int CPVREpg::CleanupCachedImages(const std::shared_ptr<CPVREpgDatabase>& database) +{ + const std::vector<std::string> urlsToCheck = database->GetAllIconPaths(EpgID()); + const std::string owner = StringUtils::Format(CPVREpgInfoTag::IMAGE_OWNER_PATTERN, EpgID()); + + return CPVRCachedImages::Cleanup({{owner, ""}}, urlsToCheck); +} |