diff options
Diffstat (limited to 'xbmc/pvr/channels')
23 files changed, 6867 insertions, 0 deletions
diff --git a/xbmc/pvr/channels/CMakeLists.txt b/xbmc/pvr/channels/CMakeLists.txt new file mode 100644 index 0000000..714f232 --- /dev/null +++ b/xbmc/pvr/channels/CMakeLists.txt @@ -0,0 +1,23 @@ +set(SOURCES PVRChannel.cpp + PVRChannelGroup.cpp + PVRChannelGroupInternal.cpp + PVRChannelGroupMember.cpp + PVRChannelGroupSettings.cpp + PVRChannelGroups.cpp + PVRChannelGroupsContainer.cpp + PVRChannelNumber.cpp + PVRRadioRDSInfoTag.cpp + PVRChannelsPath.cpp) + +set(HEADERS PVRChannel.h + PVRChannelGroup.h + PVRChannelGroupInternal.h + PVRChannelGroupMember.h + PVRChannelGroupSettings.h + PVRChannelGroups.h + PVRChannelGroupsContainer.h + PVRChannelNumber.h + PVRRadioRDSInfoTag.h + PVRChannelsPath.h) + +core_add_library(pvr_channels) diff --git a/xbmc/pvr/channels/PVRChannel.cpp b/xbmc/pvr/channels/PVRChannel.cpp new file mode 100644 index 0000000..135719f --- /dev/null +++ b/xbmc/pvr/channels/PVRChannel.cpp @@ -0,0 +1,843 @@ +/* + * 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 "PVRChannel.h" + +#include "ServiceBroker.h" +#include "XBDateTime.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgChannelData.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/providers/PVRProviders.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <memory> +#include <mutex> +#include <string> + +using namespace PVR; + +const std::string CPVRChannel::IMAGE_OWNER_PATTERN = "pvrchannel_{}"; + +bool CPVRChannel::operator==(const CPVRChannel& right) const +{ + return (m_bIsRadio == right.m_bIsRadio && m_iUniqueId == right.m_iUniqueId && + m_iClientId == right.m_iClientId); +} + +bool CPVRChannel::operator!=(const CPVRChannel& right) const +{ + return !(*this == right); +} + +CPVRChannel::CPVRChannel(bool bRadio) + : m_bIsRadio(bRadio), + m_iconPath("", StringUtils::Format(IMAGE_OWNER_PATTERN, bRadio ? "radio" : "tv")) +{ + UpdateEncryptionName(); +} + +CPVRChannel::CPVRChannel(bool bRadio, const std::string& iconPath) + : m_bIsRadio(bRadio), + m_iconPath(iconPath, StringUtils::Format(IMAGE_OWNER_PATTERN, bRadio ? "radio" : "tv")) +{ + UpdateEncryptionName(); +} + +CPVRChannel::CPVRChannel(const PVR_CHANNEL& channel, unsigned int iClientId) + : m_bIsRadio(channel.bIsRadio), + m_bIsHidden(channel.bIsHidden), + m_iconPath(channel.strIconPath, + StringUtils::Format(IMAGE_OWNER_PATTERN, channel.bIsRadio ? "radio" : "tv")), + m_strChannelName(channel.strChannelName), + m_bHasArchive(channel.bHasArchive), + m_bEPGEnabled(!channel.bIsHidden), + m_iUniqueId(channel.iUniqueId), + m_iClientId(iClientId), + m_clientChannelNumber(channel.iChannelNumber, channel.iSubChannelNumber), + m_strClientChannelName(channel.strChannelName), + m_strMimeType(channel.strMimeType), + m_iClientEncryptionSystem(channel.iEncryptionSystem), + m_iClientOrder(channel.iOrder), + m_iClientProviderUid(channel.iClientProviderUid) +{ + if (m_strChannelName.empty()) + m_strChannelName = StringUtils::Format("{} {}", g_localizeStrings.Get(19029), m_iUniqueId); + + UpdateEncryptionName(); +} + +CPVRChannel::~CPVRChannel() +{ + ResetEPG(); +} + +void CPVRChannel::FillAddonData(PVR_CHANNEL& channel) const +{ + channel = {}; + channel.iUniqueId = UniqueID(); + channel.iChannelNumber = ClientChannelNumber().GetChannelNumber(); + channel.iSubChannelNumber = ClientChannelNumber().GetSubChannelNumber(); + strncpy(channel.strChannelName, ClientChannelName().c_str(), sizeof(channel.strChannelName) - 1); + strncpy(channel.strIconPath, ClientIconPath().c_str(), sizeof(channel.strIconPath) - 1); + channel.iEncryptionSystem = EncryptionSystem(); + channel.bIsRadio = IsRadio(); + channel.bIsHidden = IsHidden(); + strncpy(channel.strMimeType, MimeType().c_str(), sizeof(channel.strMimeType) - 1); + channel.iClientProviderUid = ClientProviderUid(); + channel.bHasArchive = HasArchive(); +} + +void CPVRChannel::Serialize(CVariant& value) const +{ + value["channelid"] = m_iChannelId; + value["channeltype"] = m_bIsRadio ? "radio" : "tv"; + value["hidden"] = m_bIsHidden; + value["locked"] = m_bIsLocked; + value["icon"] = ClientIconPath(); + value["channel"] = m_strChannelName; + value["uniqueid"] = m_iUniqueId; + CDateTime lastPlayed(m_iLastWatched); + value["lastplayed"] = lastPlayed.IsValid() ? lastPlayed.GetAsDBDate() : ""; + + std::shared_ptr<CPVREpgInfoTag> epg = GetEPGNow(); + if (epg) + { + // add the properties of the current EPG item to the main object + epg->Serialize(value); + // and add an extra sub-object with only the current EPG details + epg->Serialize(value["broadcastnow"]); + } + + epg = GetEPGNext(); + if (epg) + epg->Serialize(value["broadcastnext"]); + + value["hasarchive"] = m_bHasArchive; + value["clientid"] = m_iClientId; +} + +bool CPVRChannel::QueueDelete() +{ + bool bReturn = false; + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (!database) + return bReturn; + + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + ResetEPG(); + + bReturn = database->QueueDeleteQuery(*this); + return bReturn; +} + +std::shared_ptr<CPVREpg> CPVRChannel::GetEPG() const +{ + const_cast<CPVRChannel*>(this)->CreateEPG(); + + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_bIsHidden && m_bEPGEnabled) + return m_epg; + + return {}; +} + +bool CPVRChannel::CreateEPG() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_epg) + { + m_epg = CServiceBroker::GetPVRManager().EpgContainer().CreateChannelEpg( + m_iEpgId, m_strEPGScraper, std::make_shared<CPVREpgChannelData>(*this)); + if (m_epg) + { + if (m_epg->EpgID() != m_iEpgId) + { + m_iEpgId = m_epg->EpgID(); + m_bChanged = true; + } + + // Subscribe for EPG delete event + m_epg->Events().Subscribe(this, &CPVRChannel::Notify); + return true; + } + } + return false; +} + +void CPVRChannel::Notify(const PVREvent& event) +{ + if (event == PVREvent::EpgDeleted) + { + ResetEPG(); + } +} + +void CPVRChannel::ResetEPG() +{ + std::shared_ptr<CPVREpg> epgToUnsubscribe; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_epg) + { + epgToUnsubscribe = m_epg; + m_epg.reset(); + } + } + + if (epgToUnsubscribe) + epgToUnsubscribe->Events().Unsubscribe(this); +} + +bool CPVRChannel::UpdateFromClient(const std::shared_ptr<CPVRChannel>& channel) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + SetClientID(channel->ClientID()); + SetArchive(channel->HasArchive()); + SetClientProviderUid(channel->ClientProviderUid()); + + m_clientChannelNumber = channel->m_clientChannelNumber; + m_strMimeType = channel->MimeType(); + m_iClientEncryptionSystem = channel->EncryptionSystem(); + m_strClientChannelName = channel->ClientChannelName(); + + UpdateEncryptionName(); + + // only update the channel name, icon, and hidden flag if the user hasn't changed them manually + if (m_strChannelName.empty() || !IsUserSetName()) + SetChannelName(channel->ClientChannelName()); + if (IconPath().empty() || !IsUserSetIcon()) + SetIconPath(channel->ClientIconPath()); + if (!IsUserSetHidden()) + SetHidden(channel->IsHidden()); + + return m_bChanged; +} + +bool CPVRChannel::Persist() +{ + { + // not changed + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_bChanged && m_iChannelId > 0) + return true; + } + + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (database) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting channel '{}'", m_strChannelName); + + bool bReturn = database->Persist(*this, true); + + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bChanged = !bReturn; + return bReturn; + } + + return false; +} + +bool CPVRChannel::SetChannelID(int iChannelId) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_iChannelId != iChannelId) + { + m_iChannelId = iChannelId; + + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + epg->GetChannelData()->SetChannelId(m_iChannelId); + + m_bChanged = true; + return true; + } + + return false; +} + +bool CPVRChannel::SetHidden(bool bIsHidden, bool bIsUserSetHidden /*= false*/) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_bIsHidden != bIsHidden) + { + m_bIsHidden = bIsHidden; + m_bIsUserSetHidden = bIsUserSetHidden; + + if (m_epg) + m_epg->GetChannelData()->SetHidden(m_bIsHidden); + + m_bChanged = true; + return true; + } + + return false; +} + +bool CPVRChannel::SetLocked(bool bIsLocked) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_bIsLocked != bIsLocked) + { + m_bIsLocked = bIsLocked; + + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + epg->GetChannelData()->SetLocked(m_bIsLocked); + + m_bChanged = true; + return true; + } + + return false; +} + +std::shared_ptr<CPVRRadioRDSInfoTag> CPVRChannel::GetRadioRDSInfoTag() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_rdsTag; +} + +void CPVRChannel::SetRadioRDSInfoTag(const std::shared_ptr<CPVRRadioRDSInfoTag>& tag) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_rdsTag = tag; +} + +bool CPVRChannel::HasArchive() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bHasArchive; +} + +bool CPVRChannel::SetArchive(bool bHasArchive) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_bHasArchive != bHasArchive) + { + m_bHasArchive = bHasArchive; + m_bChanged = true; + return true; + } + + return false; +} + +bool CPVRChannel::SetIconPath(const std::string& strIconPath, bool bIsUserSetIcon /* = false */) +{ + if (StringUtils::StartsWith(strIconPath, "image://")) + { + CLog::LogF(LOGERROR, "Not allowed to call this method with an image URL"); + return false; + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + if (ClientIconPath() == strIconPath) + return false; + + m_iconPath.SetClientImage(strIconPath); + + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + epg->GetChannelData()->SetChannelIconPath(strIconPath); + + m_bChanged = true; + m_bIsUserSetIcon = bIsUserSetIcon && !IconPath().empty(); + return true; +} + +bool CPVRChannel::SetChannelName(const std::string& strChannelName, bool bIsUserSetName /*= false*/) +{ + std::string strName(strChannelName); + + if (strName.empty()) + strName = StringUtils::Format(g_localizeStrings.Get(19085), + m_clientChannelNumber.FormattedChannelNumber()); + + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_strChannelName != strName) + { + m_strChannelName = strName; + m_bIsUserSetName = bIsUserSetName; + + /* if the user changes the name manually to an empty string we reset the + flag and use the name from the client instead */ + if (bIsUserSetName && strChannelName.empty()) + { + m_bIsUserSetName = false; + m_strChannelName = ClientChannelName(); + } + + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + epg->GetChannelData()->SetChannelName(m_strChannelName); + + m_bChanged = true; + return true; + } + + return false; +} + +bool CPVRChannel::SetLastWatched(time_t iLastWatched) +{ + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iLastWatched = iLastWatched; + } + + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (database) + return database->UpdateLastWatched(*this); + + return false; +} + +/********** Client related channel methods **********/ + +bool CPVRChannel::SetClientID(int iClientId) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iClientId != iClientId) + { + m_iClientId = iClientId; + m_bChanged = true; + return true; + } + + return false; +} + +std::string CPVRChannel::GetEncryptionName(int iCaid) +{ + // http://www.dvb.org/index.php?id=174 + // http://en.wikipedia.org/wiki/Conditional_access_system + std::string strName(g_localizeStrings.Get(13205)); /* Unknown */ + + if (iCaid == 0x0000) + strName = g_localizeStrings.Get(19013); /* Free To Air */ + else if (iCaid >= 0x0001 && iCaid <= 0x009F) + strName = g_localizeStrings.Get(19014); /* Fixed */ + else if (iCaid >= 0x00A0 && iCaid <= 0x00A1) + strName = g_localizeStrings.Get(338); /* Analog */ + else if (iCaid >= 0x00A2 && iCaid <= 0x00FF) + strName = g_localizeStrings.Get(19014); /* Fixed */ + else if (iCaid >= 0x0100 && iCaid <= 0x01FF) + strName = "SECA Mediaguard"; + else if (iCaid == 0x0464) + strName = "EuroDec"; + else if (iCaid >= 0x0500 && iCaid <= 0x05FF) + strName = "Viaccess"; + else if (iCaid >= 0x0600 && iCaid <= 0x06FF) + strName = "Irdeto"; + else if (iCaid >= 0x0900 && iCaid <= 0x09FF) + strName = "NDS Videoguard"; + else if (iCaid >= 0x0B00 && iCaid <= 0x0BFF) + strName = "Conax"; + else if (iCaid >= 0x0D00 && iCaid <= 0x0DFF) + strName = "CryptoWorks"; + else if (iCaid >= 0x0E00 && iCaid <= 0x0EFF) + strName = "PowerVu"; + else if (iCaid == 0x1000) + strName = "RAS"; + else if (iCaid >= 0x1200 && iCaid <= 0x12FF) + strName = "NagraVision"; + else if (iCaid >= 0x1700 && iCaid <= 0x17FF) + strName = "BetaCrypt"; + else if (iCaid >= 0x1800 && iCaid <= 0x18FF) + strName = "NagraVision"; + else if (iCaid == 0x22F0) + strName = "Codicrypt"; + else if (iCaid == 0x2600) + strName = "BISS"; + else if (iCaid == 0x4347) + strName = "CryptOn"; + else if (iCaid == 0x4800) + strName = "Accessgate"; + else if (iCaid == 0x4900) + strName = "China Crypt"; + else if (iCaid == 0x4A10) + strName = "EasyCas"; + else if (iCaid == 0x4A20) + strName = "AlphaCrypt"; + else if (iCaid == 0x4A70) + strName = "DreamCrypt"; + else if (iCaid == 0x4A60) + strName = "SkyCrypt"; + else if (iCaid == 0x4A61) + strName = "Neotioncrypt"; + else if (iCaid == 0x4A62) + strName = "SkyCrypt"; + else if (iCaid == 0x4A63) + strName = "Neotion SHL"; + else if (iCaid >= 0x4A64 && iCaid <= 0x4A6F) + strName = "SkyCrypt"; + else if (iCaid == 0x4A80) + strName = "ThalesCrypt"; + else if (iCaid == 0x4AA1) + strName = "KeyFly"; + else if (iCaid == 0x4ABF) + strName = "DG-Crypt"; + else if (iCaid >= 0x4AD0 && iCaid <= 0x4AD1) + strName = "X-Crypt"; + else if (iCaid == 0x4AD4) + strName = "OmniCrypt"; + else if (iCaid == 0x4AE0) + strName = "RossCrypt"; + else if (iCaid == 0x5500) + strName = "Z-Crypt"; + else if (iCaid == 0x5501) + strName = "Griffin"; + else if (iCaid == 0x5601) + strName = "Verimatrix"; + + if (iCaid >= 0) + strName += StringUtils::Format(" ({:04X})", iCaid); + + return strName; +} + +void CPVRChannel::UpdateEncryptionName() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strClientEncryptionName = GetEncryptionName(m_iClientEncryptionSystem); +} + +bool CPVRChannel::SetClientProviderUid(int iClientProviderUid) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iClientProviderUid != iClientProviderUid) + { + m_iClientProviderUid = iClientProviderUid; + m_bChanged = true; + return true; + } + + return false; +} + +/********** EPG methods **********/ + +std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVRChannel::GetEpgTags() const +{ + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (!epg) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Cannot get EPG for channel '{}'", m_strChannelName); + return {}; + } + + return epg->GetTags(); +} + +std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVRChannel::GetEPGTimeline( + const CDateTime& timelineStart, + const CDateTime& timelineEnd, + const CDateTime& minEventEnd, + const CDateTime& maxEventStart) const +{ + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + { + return epg->GetTimeline(timelineStart, timelineEnd, minEventEnd, maxEventStart); + } + else + { + // return single gap tag spanning whole timeline + return std::vector<std::shared_ptr<CPVREpgInfoTag>>{ + CreateEPGGapTag(timelineStart, timelineEnd)}; + } +} + +std::shared_ptr<CPVREpgInfoTag> CPVRChannel::CreateEPGGapTag(const CDateTime& start, + const CDateTime& end) const +{ + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + return std::make_shared<CPVREpgInfoTag>(epg->GetChannelData(), epg->EpgID(), start, end, true); + else + return std::make_shared<CPVREpgInfoTag>(std::make_shared<CPVREpgChannelData>(*this), -1, start, + end, true); +} + +std::shared_ptr<CPVREpgInfoTag> CPVRChannel::GetEPGNow() const +{ + std::shared_ptr<CPVREpgInfoTag> tag; + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + tag = epg->GetTagNow(); + + return tag; +} + +std::shared_ptr<CPVREpgInfoTag> CPVRChannel::GetEPGNext() const +{ + std::shared_ptr<CPVREpgInfoTag> tag; + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + tag = epg->GetTagNext(); + + return tag; +} + +std::shared_ptr<CPVREpgInfoTag> CPVRChannel::GetEPGPrevious() const +{ + std::shared_ptr<CPVREpgInfoTag> tag; + const std::shared_ptr<CPVREpg> epg = GetEPG(); + if (epg) + tag = epg->GetTagPrevious(); + + return tag; +} + +bool CPVRChannel::SetEPGEnabled(bool bEPGEnabled) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_bEPGEnabled != bEPGEnabled) + { + m_bEPGEnabled = bEPGEnabled; + + if (m_epg) + { + m_epg->GetChannelData()->SetEPGEnabled(m_bEPGEnabled); + + if (m_bEPGEnabled) + m_epg->ForceUpdate(); + else + m_epg->Clear(); + } + + m_bChanged = true; + return true; + } + + return false; +} + +bool CPVRChannel::SetEPGScraper(const std::string& strScraper) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_strEPGScraper != strScraper) + { + bool bCleanEPG = !m_strEPGScraper.empty() || strScraper.empty(); + + m_strEPGScraper = strScraper; + + if (bCleanEPG && m_epg) + m_epg->Clear(); + + m_bChanged = true; + return true; + } + + return false; +} + +void CPVRChannel::ToSortable(SortItem& sortable, Field field) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (field == FieldChannelName) + sortable[FieldChannelName] = m_strChannelName; + else if (field == FieldLastPlayed) + { + const CDateTime lastWatched(m_iLastWatched); + sortable[FieldLastPlayed] = + lastWatched.IsValid() ? lastWatched.GetAsDBDateTime() : StringUtils::Empty; + } + else if (field == FieldProvider) + sortable[FieldProvider] = StringUtils::Format("{} {}", m_iClientId, m_iClientProviderUid); +} + +int CPVRChannel::ChannelID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iChannelId; +} + +bool CPVRChannel::IsNew() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iChannelId <= 0; +} + +bool CPVRChannel::IsHidden() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsHidden; +} + +bool CPVRChannel::IsLocked() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsLocked; +} + +std::string CPVRChannel::ClientIconPath() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iconPath.GetClientImage(); +} + +std::string CPVRChannel::IconPath() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iconPath.GetLocalImage(); +} + +bool CPVRChannel::IsUserSetIcon() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsUserSetIcon; +} + +bool CPVRChannel::IsUserSetName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsUserSetName; +} + +bool CPVRChannel::IsUserSetHidden() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsUserSetHidden; +} + +std::string CPVRChannel::ChannelName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strChannelName; +} + +time_t CPVRChannel::LastWatched() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iLastWatched; +} + +bool CPVRChannel::IsChanged() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bChanged; +} + +void CPVRChannel::Persisted() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bChanged = false; +} + +int CPVRChannel::UniqueID() const +{ + return m_iUniqueId; +} + +int CPVRChannel::ClientID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iClientId; +} + +const CPVRChannelNumber& CPVRChannel::ClientChannelNumber() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_clientChannelNumber; +} + +std::string CPVRChannel::ClientChannelName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strClientChannelName; +} + +std::string CPVRChannel::MimeType() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strMimeType; +} + +bool CPVRChannel::IsEncrypted() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iClientEncryptionSystem > 0; +} + +int CPVRChannel::EncryptionSystem() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iClientEncryptionSystem; +} + +std::string CPVRChannel::EncryptionName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strClientEncryptionName; +} + +int CPVRChannel::EpgID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iEpgId; +} + +bool CPVRChannel::EPGEnabled() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bEPGEnabled; +} + +std::string CPVRChannel::EPGScraper() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strEPGScraper; +} + +bool CPVRChannel::CanRecord() const +{ + const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId); + return client && client->GetClientCapabilities().SupportsRecordings() && + client->GetClientCapabilities().SupportsTimers(); +} + +std::shared_ptr<CPVRProvider> CPVRChannel::GetDefaultProvider() const +{ + return CServiceBroker::GetPVRManager().Providers()->GetByClient(m_iClientId, + PVR_PROVIDER_INVALID_UID); +} + +bool CPVRChannel::HasClientProvider() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iClientProviderUid != PVR_PROVIDER_INVALID_UID; +} + +std::shared_ptr<CPVRProvider> CPVRChannel::GetProvider() const +{ + auto provider = + CServiceBroker::GetPVRManager().Providers()->GetByClient(m_iClientId, m_iClientProviderUid); + + if (!provider) + provider = GetDefaultProvider(); + + return provider; +} diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h new file mode 100644 index 0000000..8ff5054 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannel.h @@ -0,0 +1,550 @@ +/* + * 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. + */ + +#pragma once + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" +#include "pvr/PVRCachedImage.h" +#include "pvr/channels/PVRChannelNumber.h" +#include "threads/CriticalSection.h" +#include "utils/ISerializable.h" +#include "utils/ISortable.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +class CDateTime; + +namespace PVR +{ +enum class PVREvent; + +class CPVRProvider; +class CPVREpg; +class CPVREpgInfoTag; +class CPVRRadioRDSInfoTag; + +class CPVRChannel : public ISerializable, public ISortable +{ + friend class CPVRDatabase; + +public: + static const std::string IMAGE_OWNER_PATTERN; + + explicit CPVRChannel(bool bRadio); + CPVRChannel(bool bRadio, const std::string& iconPath); + CPVRChannel(const PVR_CHANNEL& channel, unsigned int iClientId); + + virtual ~CPVRChannel(); + + bool operator==(const CPVRChannel& right) const; + bool operator!=(const CPVRChannel& right) const; + + /*! + * @brief Copy over data to the given PVR_CHANNEL instance. + * @param channel The channel instance to fill. + */ + void FillAddonData(PVR_CHANNEL& channel) const; + + void Serialize(CVariant& value) const override; + + /*! @name Kodi related channel methods + */ + //@{ + + /*! + * @brief Delete this channel from the database. + * @return True if it was deleted successfully, false otherwise. + */ + bool QueueDelete(); + + /*! + * @brief Update this channel tag with the data of the given channel tag. + * @param channel The new channel data. + * @return True if something changed, false otherwise. + */ + bool UpdateFromClient(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Persists the changes in the database. + * @return True if the changes were saved successfully, false otherwise. + */ + bool Persist(); + + /*! + * @return The identifier given to this channel by the TV database. + */ + int ChannelID() const; + + /*! + * @return True when not persisted yet, false otherwise. + */ + bool IsNew() const; + + /*! + * @brief Set the identifier for this channel. + * @param iDatabaseId The new channel ID + * @return True if the something changed, false otherwise. + */ + bool SetChannelID(int iDatabaseId); + + /*! + * @return True if this channel is a radio channel, false if not. + */ + bool IsRadio() const { return m_bIsRadio; } + + /*! + * @return True if this channel is hidden. False if not. + */ + bool IsHidden() const; + + /*! + * @brief Set to true to hide this channel. Set to false to unhide it. + * + * Set to true to hide this channel. Set to false to unhide it. + * The EPG of hidden channels won't be updated. + * @param bIsHidden The new setting. + * @param bIsUserSetIcon true if user changed the hidden flag via GUI, false otherwise. + * @return True if the something changed, false otherwise. + */ + bool SetHidden(bool bIsHidden, bool bIsUserSetHidden = false); + + /*! + * @return True if this channel is locked. False if not. + */ + bool IsLocked() const; + + /*! + * @brief Set to true to lock this channel. Set to false to unlock it. + * + * Set to true to lock this channel. Set to false to unlock it. + * Locked channels need can only be viewed if parental PIN entered. + * @param bIsLocked The new setting. + * @return True if the something changed, false otherwise. + */ + bool SetLocked(bool bIsLocked); + + /*! + * @brief Obtain the Radio RDS data for this channel, if available. + * @return The Radio RDS data or nullptr. + */ + std::shared_ptr<CPVRRadioRDSInfoTag> GetRadioRDSInfoTag() const; + + /*! + * @brief Set the Radio RDS data for the channel. + * @param tag The RDS data. + */ + void SetRadioRDSInfoTag(const std::shared_ptr<CPVRRadioRDSInfoTag>& tag); + + /*! + * @return True if this channel has archive support, false otherwise + */ + bool HasArchive() const; + + /*! + * @brief Set the archive support flag for this channel. + * @param bHasArchive True to set the flag, false to reset. + * @return True if the flag was changed, false otherwise. + */ + bool SetArchive(bool bHasArchive); + + /*! + * @return The path to the icon for this channel as given by the client. + */ + std::string ClientIconPath() const; + + /*! + * @return The path to the icon for this channel. + */ + std::string IconPath() const; + + /*! + * @return True if this user changed icon via GUI. False if not. + */ + bool IsUserSetIcon() const; + + /*! + * @return whether the user has changed the channel name through the GUI + */ + bool IsUserSetName() const; + + /*! + * @return True if user changed the hidden flag via GUI, False if not + */ + bool IsUserSetHidden() const; + + /*! + * @brief Set the path to the icon for this channel. + * @param strIconPath The new path. + * @param bIsUserSetIcon true if user changed the icon via GUI, false otherwise. + * @return True if the something changed, false otherwise. + */ + bool SetIconPath(const std::string& strIconPath, bool bIsUserSetIcon = false); + + /*! + * @return The name for this channel used by XBMC. + */ + std::string ChannelName() const; + + /*! + * @brief Set the name for this channel used by XBMC. + * @param strChannelName The new channel name. + * @param bIsUserSetName whether the change was triggered by the user directly + * @return True if the something changed, false otherwise. + */ + bool SetChannelName(const std::string& strChannelName, bool bIsUserSetName = false); + + /*! + * @return Time channel has been watched last. + */ + time_t LastWatched() const; + + /*! + * @brief Last time channel has been watched + * @param iLastWatched The new value. + * @return True if the something changed, false otherwise. + */ + bool SetLastWatched(time_t iLastWatched); + + /*! + * @brief Check whether this channel has unpersisted data changes. + * @return True if this channel has changes to persist, false otherwise + */ + bool IsChanged() const; + + /*! + * @brief reset changed flag after persist + */ + void Persisted(); + //@} + + /*! @name Client related channel methods + */ + //@{ + + /*! + * @brief A unique identifier for this channel. + * + * A unique identifier for this channel. + * It can be used to find the same channel on different providers + * + * @return The Unique ID. + */ + int UniqueID() const; + + /*! + * @return The identifier of the client that serves this channel. + */ + int ClientID() const; + + /*! + * @brief Set the identifier of the client that serves this channel. + * @param iClientId The new ID. + * @return True if the something changed, false otherwise. + */ + bool SetClientID(int iClientId); + + /*! + * Get the channel number on the client. + * @return The channel number on the client. + */ + const CPVRChannelNumber& ClientChannelNumber() const; + + /*! + * @return The name of this channel on the client. + */ + std::string ClientChannelName() const; + + /*! + * @brief The stream input mime type + * + * The stream input type + * If it is empty, ffmpeg will try to scan the stream to find the right input format. + * See https://www.iana.org/assignments/media-types/media-types.xhtml for a + * list of the input formats. + * + * @return The stream input type + */ + std::string MimeType() const; + + // ISortable implementation + void ToSortable(SortItem& sortable, Field field) const override; + + /*! + * @return Storage id for this channel in CPVRChannelGroup + */ + std::pair<int, int> StorageId() const { return std::make_pair(m_iClientId, m_iUniqueId); } + + /*! + * @brief Return true if this channel is encrypted. + * + * Return true if this channel is encrypted. Does not inform whether XBMC can play the file. + * Decryption should be done by the client. + * + * @return Return true if this channel is encrypted. + */ + bool IsEncrypted() const; + + /*! + * @brief Return the encryption system ID for this channel. 0 for FTA. + * + * Return the encryption system ID for this channel. 0 for FTA. + * The values are documented on: http://www.dvb.org/index.php?id=174. + * + * @return Return the encryption system ID for this channel. + */ + int EncryptionSystem() const; + + /*! + * @return A friendly name for the used encryption system. + */ + std::string EncryptionName() const; + //@} + + /*! @name EPG methods + */ + //@{ + + /*! + * @return The ID of the EPG table to use for this channel or -1 if it isn't set. + */ + int EpgID() const; + + /*! + * @brief Create the EPG for this channel, if it does not yet exist + * @return true if a new epg was created, false otherwise. + */ + bool CreateEPG(); + + /*! + * @brief Get the EPG table for this channel. + * @return The EPG for this channel. + */ + std::shared_ptr<CPVREpg> GetEPG() const; + + /*! + * @brief Get the EPG tags for this channel. + * @return The tags. + */ + std::vector<std::shared_ptr<CPVREpgInfoTag>> GetEpgTags() const; + + /*! + * @brief Get all EPG tags for the given time frame, including "gap" tags. + * @param timelineStart Start of time line. + * @param timelineEnd End of time line. + * @param minEventEnd The minimum end time of the events to return. + * @param maxEventStart The maximum start time of the events to return. + * @return The matching tags. + */ + std::vector<std::shared_ptr<CPVREpgInfoTag>> GetEPGTimeline(const CDateTime& timelineStart, + const CDateTime& timelineEnd, + const CDateTime& minEventEnd, + const CDateTime& maxEventStart) const; + + /*! + * @brief Create a "gap" EPG tag. + * @param start Start of gap. + * @param end End of gap. + * @return The tag. + */ + std::shared_ptr<CPVREpgInfoTag> CreateEPGGapTag(const CDateTime& start, + const CDateTime& end) const; + + /*! + * @brief Get the EPG tag that is now active on this channel. + * + * Get the EPG tag that is now active on this channel. + * Will return an empty tag if there is none. + * + * @return The EPG tag that is now active. + */ + std::shared_ptr<CPVREpgInfoTag> GetEPGNow() const; + + /*! + * @brief Get the EPG tag that was previously active on this channel. + * + * Get the EPG tag that was previously active on this channel. + * Will return an empty tag if there is none. + * + * @return The EPG tag that was previously active. + */ + std::shared_ptr<CPVREpgInfoTag> GetEPGPrevious() const; + + /*! + * @brief Get the EPG tag that will be next active on this channel. + * + * Get the EPG tag that will be next active on this channel. + * Will return an empty tag if there is none. + * + * @return The EPG tag that will be next active. + */ + std::shared_ptr<CPVREpgInfoTag> GetEPGNext() const; + + /*! + * @return Don't use an EPG for this channel if set to false. + */ + bool EPGEnabled() const; + + /*! + * @brief Set to true if an EPG should be used for this channel. Set to false otherwise. + * @param bEPGEnabled The new value. + * @return True if the something changed, false otherwise. + */ + bool SetEPGEnabled(bool bEPGEnabled); + + /*! + * @brief Get the name of the scraper to be used for this channel. + * + * Get the name of the scraper to be used for this channel. + * The default is 'client', which means the EPG should be loaded from the backend. + * + * @return The name of the scraper to be used for this channel. + */ + std::string EPGScraper() const; + + /*! + * @brief Set the name of the scraper to be used for this channel. + * + * Set the name of the scraper to be used for this channel. + * Set to "client" to load the EPG from the backend + * + * @param strScraper The new scraper name. + * @return True if the something changed, false otherwise. + */ + bool SetEPGScraper(const std::string& strScraper); + + bool CanRecord() const; + + static std::string GetEncryptionName(int iCaid); + + /*! + * @brief Get the client order for this channel + * @return iOrder The order for this channel + */ + int ClientOrder() const { return m_iClientOrder; } + + /*! + * @brief Get the client provider Uid for this channel + * @return m_iClientProviderUid The provider Uid for this channel + */ + int ClientProviderUid() const { return m_iClientProviderUid; } + + /*! + * @brief CEventStream callback for PVR events. + * @param event The event. + */ + void Notify(const PVREvent& event); + + /*! + * @brief Lock the instance. No other thread gets access to this channel until Unlock was called. + */ + void Lock() { m_critSection.lock(); } + + /*! + * @brief Unlock the instance. Other threads may get access to this channel again. + */ + void Unlock() { m_critSection.unlock(); } + + /*! + * @brief Get the default provider of this channel. The default + * provider represents the PVR add-on itself. + * @return The default provider of this channel + */ + std::shared_ptr<CPVRProvider> GetDefaultProvider() const; + + /*! + * @brief Whether or not this channel has a provider set by the client. + * @return True if a provider was set by the client, false otherwise. + */ + bool HasClientProvider() const; + + /*! + * @brief Get the provider of this channel. This may be the default provider or a + * custom provider set by the client. If @ref "HasClientProvider()" returns true + * the provider will be custom from the client, otherwise the default provider. + * @return The provider of this channel + */ + std::shared_ptr<CPVRProvider> GetProvider() const; + + //@} +private: + CPVRChannel() = delete; + CPVRChannel(const CPVRChannel& tag) = delete; + CPVRChannel& operator=(const CPVRChannel& channel) = delete; + + /*! + * @brief Update the encryption name after SetEncryptionSystem() has been called. + */ + void UpdateEncryptionName(); + + /*! + * @brief Reset the EPG instance pointer. + */ + void ResetEPG(); + + /*! + * @brief Set the client provider Uid for this channel + * @param iClientProviderUid The provider Uid for this channel + * @return True if the something changed, false otherwise. + */ + bool SetClientProviderUid(int iClientProviderUid); + + /*! @name Kodi related channel data + */ + //@{ + int m_iChannelId = -1; /*!< the identifier given to this channel by the TV database */ + bool m_bIsRadio = false; /*!< true if this channel is a radio channel, false if not */ + bool m_bIsHidden = false; /*!< true if this channel is hidden, false if not */ + bool m_bIsUserSetName = false; /*!< true if user set the channel name via GUI, false if not */ + bool m_bIsUserSetIcon = false; /*!< true if user set the icon via GUI, false if not */ + bool m_bIsUserSetHidden = false; /*!< true if user set the hidden flag via GUI, false if not */ + bool m_bIsLocked = false; /*!< true if channel is locked, false if not */ + CPVRCachedImage m_iconPath; /*!< the path to the icon for this channel */ + std::string m_strChannelName; /*!< the name for this channel used by Kodi */ + time_t m_iLastWatched = 0; /*!< last time channel has been watched */ + bool m_bChanged = + false; /*!< true if anything in this entry was changed that needs to be persisted */ + std::shared_ptr<CPVRRadioRDSInfoTag> + m_rdsTag; /*! < the radio rds data, if available for the channel. */ + bool m_bHasArchive = false; /*!< true if this channel supports archive */ + //@} + + /*! @name EPG related channel data + */ + //@{ + int m_iEpgId = -1; /*!< the id of the EPG for this channel */ + bool m_bEPGEnabled = false; /*!< don't use an EPG for this channel if set to false */ + std::string m_strEPGScraper = + "client"; /*!< the name of the scraper to be used for this channel */ + std::shared_ptr<CPVREpg> m_epg; + //@} + + /*! @name Client related channel data + */ + //@{ + int m_iUniqueId = -1; /*!< the unique identifier for this channel */ + int m_iClientId = -1; /*!< the identifier of the client that serves this channel */ + CPVRChannelNumber m_clientChannelNumber; /*!< the channel number on the client */ + std::string m_strClientChannelName; /*!< the name of this channel on the client */ + std::string + m_strMimeType; /*!< the stream input type based mime type, see @ref https://www.iana.org/assignments/media-types/media-types.xhtml#video */ + int m_iClientEncryptionSystem = + -1; /*!< the encryption system used by this channel. 0 for FreeToAir, -1 for unknown */ + std::string + m_strClientEncryptionName; /*!< the name of the encryption system used by this channel */ + int m_iClientOrder = 0; /*!< the order from this channels group member */ + int m_iClientProviderUid = + PVR_PROVIDER_INVALID_UID; /*!< the unique id for this provider from the client */ + //@} + + mutable CCriticalSection m_critSection; +}; +} // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp new file mode 100644 index 0000000..7f03430 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -0,0 +1,1176 @@ +/* + * 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. + */ + +//! @todo use Observable here, so we can use event driven operations later + +#include "PVRChannelGroup.h" + +#include "ServiceBroker.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h" +#include "pvr/PVRCachedImages.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgChannelData.h" +#include "pvr/epg/EpgInfoTag.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <iterator> +#include <memory> +#include <mutex> +#include <numeric> +#include <string> +#include <utility> +#include <vector> + +using namespace PVR; + +CPVRChannelGroup::CPVRChannelGroup(const CPVRChannelsPath& path, + const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup) + : m_allChannelsGroup(allChannelsGroup), m_path(path) +{ + GetSettings()->RegisterCallback(this); +} + +CPVRChannelGroup::CPVRChannelGroup(const PVR_CHANNEL_GROUP& group, + const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup) + : m_iPosition(group.iPosition), + m_allChannelsGroup(allChannelsGroup), + m_path(group.bIsRadio, group.strGroupName) +{ + GetSettings()->RegisterCallback(this); +} + +CPVRChannelGroup::~CPVRChannelGroup() +{ + GetSettings()->UnregisterCallback(this); +} + +bool CPVRChannelGroup::operator==(const CPVRChannelGroup& right) const +{ + return (m_iGroupType == right.m_iGroupType && m_iGroupId == right.m_iGroupId && + m_iPosition == right.m_iPosition && m_path == right.m_path); +} + +bool CPVRChannelGroup::operator!=(const CPVRChannelGroup& right) const +{ + return !(*this == right); +} + +void CPVRChannelGroup::FillAddonData(PVR_CHANNEL_GROUP& group) const +{ + group = {}; + group.bIsRadio = IsRadio(); + strncpy(group.strGroupName, GroupName().c_str(), sizeof(group.strGroupName) - 1); + group.iPosition = GetPosition(); +} + +CCriticalSection CPVRChannelGroup::m_settingsSingletonCritSection; +std::weak_ptr<CPVRChannelGroupSettings> CPVRChannelGroup::m_settingsSingleton; + +std::shared_ptr<CPVRChannelGroupSettings> CPVRChannelGroup::GetSettings() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_settings) + { + std::unique_lock<CCriticalSection> singletonLock(m_settingsSingletonCritSection); + const std::shared_ptr<CPVRChannelGroupSettings> settings = m_settingsSingleton.lock(); + if (settings) + { + m_settings = settings; + } + else + { + m_settings = std::make_shared<CPVRChannelGroupSettings>(); + m_settingsSingleton = m_settings; + } + } + return m_settings; +} + +bool CPVRChannelGroup::LoadFromDatabase( + const std::map<std::pair<int, int>, std::shared_ptr<CPVRChannel>>& channels, + const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + const int iChannelCount = m_iGroupId > 0 ? LoadFromDatabase(clients) : 0; + CLog::LogFC(LOGDEBUG, LOGPVR, "Fetched {} {} group members from the database for group '{}'", + iChannelCount, IsRadio() ? "radio" : "TV", GroupName()); + + for (const auto& groupMember : m_members) + { + if (groupMember.second->Channel()) + continue; + + auto channel = channels.find(groupMember.first); + if (channel == channels.end()) + { + CLog::Log(LOGERROR, "Cannot find group member '{},{}' in channels!", groupMember.first.first, + groupMember.first.second); + // No workaround here, please. We need to find and fix the root cause of this case! + } + groupMember.second->SetChannel((*channel).second); + } + + m_bLoaded = true; + return true; +} + +void CPVRChannelGroup::Unload() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_sortedMembers.clear(); + m_members.clear(); + m_failedClients.clear(); +} + +bool CPVRChannelGroup::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + if (GroupType() == PVR_GROUP_TYPE_USER_DEFINED || !GetSettings()->SyncChannelGroups()) + return true; + + // get the channel group members from the backends. + std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers; + CServiceBroker::GetPVRManager().Clients()->GetChannelGroupMembers(clients, this, groupMembers, + m_failedClients); + return UpdateGroupEntries(groupMembers); +} + +const CPVRChannelsPath& CPVRChannelGroup::GetPath() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_path; +} + +void CPVRChannelGroup::SetPath(const CPVRChannelsPath& path) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_path != path) + { + m_path = path; + if (m_bLoaded) + { + // note: path contains both the radio flag and the group name, which are stored in the db + m_bChanged = true; + Persist(); //! @todo why must we persist immediately? + } + } +} + +bool CPVRChannelGroup::SetChannelNumber(const std::shared_ptr<CPVRChannel>& channel, + const CPVRChannelNumber& channelNumber) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = + std::find_if(m_sortedMembers.cbegin(), m_sortedMembers.cend(), + [&channel](const auto& member) { return *member->Channel() == *channel; }); + + if (it != m_sortedMembers.cend() && (*it)->ChannelNumber() != channelNumber) + { + (*it)->SetChannelNumber(channelNumber); + return true; + } + + return false; +} + +/********** sort methods **********/ + +struct sortByClientChannelNumber +{ + bool operator()(const std::shared_ptr<CPVRChannelGroupMember>& channel1, + const std::shared_ptr<CPVRChannelGroupMember>& channel2) const + { + if (channel1->ClientPriority() == channel2->ClientPriority()) + { + if (channel1->ClientChannelNumber() == channel2->ClientChannelNumber()) + return channel1->Channel()->ChannelName() < channel2->Channel()->ChannelName(); + + return channel1->ClientChannelNumber() < channel2->ClientChannelNumber(); + } + return channel1->ClientPriority() > channel2->ClientPriority(); + } +}; + +struct sortByChannelNumber +{ + bool operator()(const std::shared_ptr<CPVRChannelGroupMember>& channel1, + const std::shared_ptr<CPVRChannelGroupMember>& channel2) const + { + return channel1->ChannelNumber() < channel2->ChannelNumber(); + } +}; + +void CPVRChannelGroup::Sort() +{ + if (GetSettings()->UseBackendChannelOrder()) + SortByClientChannelNumber(); + else + SortByChannelNumber(); +} + +bool CPVRChannelGroup::SortAndRenumber() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + Sort(); + return Renumber(); +} + +void CPVRChannelGroup::SortByClientChannelNumber() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + std::sort(m_sortedMembers.begin(), m_sortedMembers.end(), sortByClientChannelNumber()); +} + +void CPVRChannelGroup::SortByChannelNumber() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + std::sort(m_sortedMembers.begin(), m_sortedMembers.end(), sortByChannelNumber()); +} + +bool CPVRChannelGroup::UpdateClientPriorities() +{ + const std::shared_ptr<CPVRClients> clients = CServiceBroker::GetPVRManager().Clients(); + bool bChanged = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + const bool bUseBackendChannelOrder = GetSettings()->UseBackendChannelOrder(); + for (auto& member : m_sortedMembers) + { + int iNewPriority = 0; + + if (bUseBackendChannelOrder) + { + const std::shared_ptr<CPVRClient> client = + clients->GetCreatedClient(member->Channel()->ClientID()); + if (!client) + continue; + + iNewPriority = client->GetPriority(); + } + else + { + iNewPriority = 0; + } + + bChanged |= (member->ClientPriority() != iNewPriority); + member->SetClientPriority(iNewPriority); + } + + return bChanged; +} + +/********** getters **********/ +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetByUniqueID( + const std::pair<int, int>& id) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = m_members.find(id); + return it != m_members.end() ? it->second : std::shared_ptr<CPVRChannelGroupMember>(); +} + +std::shared_ptr<CPVRChannel> CPVRChannelGroup::GetByUniqueID(int iUniqueChannelId, + int iClientID) const +{ + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + GetByUniqueID(std::make_pair(iClientID, iUniqueChannelId)); + return groupMember ? groupMember->Channel() : std::shared_ptr<CPVRChannel>(); +} + +std::shared_ptr<CPVRChannel> CPVRChannelGroup::GetByChannelID(int iChannelID) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = + std::find_if(m_members.cbegin(), m_members.cend(), [iChannelID](const auto& member) { + return member.second->Channel()->ChannelID() == iChannelID; + }); + return it != m_members.cend() ? (*it).second->Channel() : std::shared_ptr<CPVRChannel>(); +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetLastPlayedChannelGroupMember( + int iCurrentChannel /* = -1 */) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + std::shared_ptr<CPVRChannelGroupMember> groupMember; + for (const auto& memberPair : m_members) + { + const std::shared_ptr<CPVRChannel> channel = memberPair.second->Channel(); + if (channel->ChannelID() != iCurrentChannel && + CServiceBroker::GetPVRManager().Clients()->IsCreatedClient(channel->ClientID()) && + channel->LastWatched() > 0 && + (!groupMember || channel->LastWatched() > groupMember->Channel()->LastWatched())) + { + groupMember = memberPair.second; + } + } + + return groupMember; +} + +GroupMemberPair CPVRChannelGroup::GetLastAndPreviousToLastPlayedChannelGroupMember() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_sortedMembers.empty()) + return {}; + + auto members = m_sortedMembers; + lock.unlock(); + + std::sort(members.begin(), members.end(), [](const auto& a, const auto& b) { + return a->Channel()->LastWatched() > b->Channel()->LastWatched(); + }); + + std::shared_ptr<CPVRChannelGroupMember> last; + std::shared_ptr<CPVRChannelGroupMember> previousToLast; + if (members[0]->Channel()->LastWatched()) + { + last = members[0]; + if (members.size() > 1 && members[1]->Channel()->LastWatched()) + previousToLast = members[1]; + } + + return {last, previousToLast}; +} + +CPVRChannelNumber CPVRChannelGroup::GetChannelNumber( + const std::shared_ptr<CPVRChannel>& channel) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const std::shared_ptr<CPVRChannelGroupMember> member = GetByUniqueID(channel->StorageId()); + return member ? member->ChannelNumber() : CPVRChannelNumber(); +} + +CPVRChannelNumber CPVRChannelGroup::GetClientChannelNumber( + const std::shared_ptr<CPVRChannel>& channel) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const std::shared_ptr<CPVRChannelGroupMember> member = GetByUniqueID(channel->StorageId()); + return member ? member->ClientChannelNumber() : CPVRChannelNumber(); +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetByChannelNumber( + const CPVRChannelNumber& channelNumber) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const bool bUseBackendChannelNumbers = GetSettings()->UseBackendChannelNumbers(); + for (const auto& member : m_sortedMembers) + { + CPVRChannelNumber activeChannelNumber = + bUseBackendChannelNumbers ? member->ClientChannelNumber() : member->ChannelNumber(); + if (activeChannelNumber == channelNumber) + return member; + } + + return {}; +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetNextChannelGroupMember( + const std::shared_ptr<CPVRChannelGroupMember>& groupMember) const +{ + std::shared_ptr<CPVRChannelGroupMember> nextMember; + + if (groupMember) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto it = m_sortedMembers.cbegin(); !nextMember && it != m_sortedMembers.cend(); ++it) + { + if (*it == groupMember) + { + do + { + if ((++it) == m_sortedMembers.cend()) + it = m_sortedMembers.cbegin(); + if ((*it)->Channel() && !(*it)->Channel()->IsHidden()) + nextMember = *it; + } while (!nextMember && *it != groupMember); + + break; + } + } + } + + return nextMember; +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetPreviousChannelGroupMember( + const std::shared_ptr<CPVRChannelGroupMember>& groupMember) const +{ + std::shared_ptr<CPVRChannelGroupMember> previousMember; + + if (groupMember) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto it = m_sortedMembers.crbegin(); !previousMember && it != m_sortedMembers.crend(); + ++it) + { + if (*it == groupMember) + { + do + { + if ((++it) == m_sortedMembers.crend()) + it = m_sortedMembers.crbegin(); + if ((*it)->Channel() && !(*it)->Channel()->IsHidden()) + previousMember = *it; + } while (!previousMember && *it != groupMember); + + break; + } + } + } + return previousMember; +} + +std::vector<std::shared_ptr<CPVRChannelGroupMember>> CPVRChannelGroup::GetMembers( + Include eFilter /* = Include::ALL */) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (eFilter == Include::ALL) + return m_sortedMembers; + + std::vector<std::shared_ptr<CPVRChannelGroupMember>> members; + for (const auto& member : m_sortedMembers) + { + switch (eFilter) + { + case Include::ONLY_HIDDEN: + if (!member->Channel()->IsHidden()) + continue; + break; + case Include::ONLY_VISIBLE: + if (member->Channel()->IsHidden()) + continue; + break; + default: + break; + } + + members.emplace_back(member); + } + + return members; +} + +void CPVRChannelGroup::GetChannelNumbers(std::vector<std::string>& channelNumbers) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const bool bUseBackendChannelNumbers = GetSettings()->UseBackendChannelNumbers(); + for (const auto& member : m_sortedMembers) + { + CPVRChannelNumber activeChannelNumber = + bUseBackendChannelNumbers ? member->ClientChannelNumber() : member->ChannelNumber(); + channelNumbers.emplace_back(activeChannelNumber.FormattedChannelNumber()); + } +} + +int CPVRChannelGroup::LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + const std::shared_ptr<CPVRDatabase> database(CServiceBroker::GetPVRManager().GetTVDatabase()); + if (!database) + return -1; + + const std::vector<std::shared_ptr<CPVRChannelGroupMember>> results = + database->Get(*this, clients); + + std::vector<std::shared_ptr<CPVRChannelGroupMember>> membersToDelete; + if (!results.empty()) + { + const std::shared_ptr<CPVRClients> allClients = CServiceBroker::GetPVRManager().Clients(); + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& member : results) + { + // Consistency check. + if (member->ClientID() > 0 && member->ChannelUID() > 0 && member->IsRadio() == IsRadio()) + { + // Ignore data from unknown/disabled clients + if (allClients->IsEnabledClient(member->ClientID())) + { + m_sortedMembers.emplace_back(member); + m_members.emplace(std::make_pair(member->ClientID(), member->ChannelUID()), member); + } + } + else + { + CLog::LogF(LOGWARNING, + "Skipping member with channel database id {} of {} channel group '{}'. " + "Channel not found in the database or radio flag changed.", + member->ChannelDatabaseID(), IsRadio() ? "radio" : "TV", GroupName()); + membersToDelete.emplace_back(member); + } + } + + SortByChannelNumber(); + } + + DeleteGroupMembersFromDb(membersToDelete); + + return results.size() - membersToDelete.size(); +} + +void CPVRChannelGroup::DeleteGroupMembersFromDb( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& membersToDelete) +{ + if (!membersToDelete.empty()) + { + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No TV database"); + return; + } + + // Note: We must lock the db the whole time, otherwise races may occur. + database->Lock(); + + bool commitPending = false; + + for (const auto& member : membersToDelete) + { + commitPending |= member->QueueDelete(); + + size_t queryCount = database->GetDeleteQueriesCount(); + if (queryCount > CHANNEL_COMMIT_QUERY_COUNT_LIMIT) + database->CommitDeleteQueries(); + } + + if (commitPending) + database->CommitDeleteQueries(); + + database->Unlock(); + } +} + +bool CPVRChannelGroup::UpdateFromClient(const std::shared_ptr<CPVRChannelGroupMember>& groupMember) +{ + bool bChanged = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + const std::shared_ptr<CPVRChannel> channel = groupMember->Channel(); + const std::shared_ptr<CPVRChannelGroupMember> existingMember = + GetByUniqueID(channel->StorageId()); + if (existingMember) + { + // update existing channel + if (IsInternalGroup() && existingMember->Channel()->UpdateFromClient(channel)) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Updated {} channel '{}' from PVR client", + IsRadio() ? "radio" : "TV", channel->ChannelName()); + bChanged = true; + } + + existingMember->SetClientChannelNumber(channel->ClientChannelNumber()); + existingMember->SetOrder(groupMember->Order()); + + if (existingMember->NeedsSave()) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Updated {} channel group member '{}' in group '{}'", + IsRadio() ? "radio" : "TV", channel->ChannelName(), GroupName()); + bChanged = true; + } + } + else + { + if (groupMember->GroupID() == -1) + groupMember->SetGroupID(GroupID()); + + m_sortedMembers.emplace_back(groupMember); + m_members.emplace(channel->StorageId(), groupMember); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Added {} channel group member '{}' to group '{}'", + IsRadio() ? "radio" : "TV", channel->ChannelName(), GroupName()); + + // create EPG for new channel + if (IsInternalGroup() && channel->CreateEPG()) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Created EPG for {} channel '{}' from PVR client", + IsRadio() ? "radio" : "TV", channel->ChannelName()); + } + + bChanged = true; + } + + return bChanged; +} + +bool CPVRChannelGroup::AddAndUpdateGroupMembers( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) +{ + return std::accumulate(groupMembers.cbegin(), groupMembers.cend(), false, + [this](bool changed, const auto& groupMember) { + return UpdateFromClient(groupMember) ? true : changed; + }); +} + +bool CPVRChannelGroup::HasValidDataForClient(int iClientId) const +{ + return std::find(m_failedClients.begin(), m_failedClients.end(), iClientId) == + m_failedClients.end(); +} + +bool CPVRChannelGroup::HasValidDataForClients( + const std::vector<std::shared_ptr<CPVRClient>>& clients) const +{ + return m_failedClients.empty() || std::none_of(clients.cbegin(), clients.cend(), + [this](const std::shared_ptr<CPVRClient>& client) { + return !HasValidDataForClient(client->GetID()); + }); +} + +bool CPVRChannelGroup::UpdateChannelNumbersFromAllChannelsGroup() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + bool bChanged = false; + + if (!IsInternalGroup()) + { + // If we don't sync channel groups make sure the channel numbers are set from + // the all channels group using the non default renumber call before sorting + if (Renumber(IGNORE_NUMBERING_FROM_ONE) || SortAndRenumber()) + bChanged = true; + } + + m_events.Publish(IsInternalGroup() || bChanged ? PVREvent::ChannelGroupInvalidated + : PVREvent::ChannelGroup); + + return bChanged; +} + +std::vector<std::shared_ptr<CPVRChannelGroupMember>> CPVRChannelGroup::RemoveDeletedGroupMembers( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) +{ + std::vector<std::shared_ptr<CPVRChannelGroupMember>> membersToRemove; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + // put group members into map to speedup the following lookups + std::map<std::pair<int, int>, std::shared_ptr<CPVRChannelGroupMember>> membersMap; + std::transform(groupMembers.begin(), groupMembers.end(), + std::inserter(membersMap, membersMap.end()), + [](const std::shared_ptr<CPVRChannelGroupMember>& member) { + return std::make_pair(member->Channel()->StorageId(), member); + }); + + // check for deleted channels + for (auto it = m_sortedMembers.begin(); it != m_sortedMembers.end();) + { + const std::shared_ptr<CPVRChannel> channel = (*it)->Channel(); + auto mapIt = membersMap.find(channel->StorageId()); + if (mapIt == membersMap.end()) + { + if (HasValidDataForClient(channel->ClientID())) + { + CLog::Log(LOGINFO, "Removed stale {} channel '{}' from group '{}'", + IsRadio() ? "radio" : "TV", channel->ChannelName(), GroupName()); + membersToRemove.emplace_back(*it); + + m_members.erase(channel->StorageId()); + it = m_sortedMembers.erase(it); + continue; + } + } + else + { + membersMap.erase(mapIt); + } + ++it; + } + + DeleteGroupMembersFromDb(membersToRemove); + + return membersToRemove; +} + +bool CPVRChannelGroup::UpdateGroupEntries( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) +{ + bool bReturn = false; + bool bChanged = false; + bool bRemoved = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + bRemoved = !RemoveDeletedGroupMembers(groupMembers).empty(); + bChanged = AddAndUpdateGroupMembers(groupMembers) || bRemoved; + bChanged |= UpdateClientPriorities(); + + if (bChanged) + { + // renumber to make sure all group members have a channel number. New members were added at the + // back, so they'll get the highest numbers + bool bRenumbered = SortAndRenumber(); + bReturn = Persist(); + m_events.Publish(HasNewChannels() || bRemoved || bRenumbered ? PVREvent::ChannelGroupInvalidated + : PVREvent::ChannelGroup); + } + else + { + bReturn = true; + } + + return bReturn; +} + +bool CPVRChannelGroup::RemoveFromGroup(const std::shared_ptr<CPVRChannel>& channel) +{ + bool bReturn = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + for (auto it = m_sortedMembers.begin(); it != m_sortedMembers.end(); ++it) + { + const auto storageId = (*it)->Channel()->StorageId(); + if (channel->StorageId() == storageId) + { + m_members.erase(storageId); + m_sortedMembers.erase(it); + bReturn = true; + break; + } + } + + // no need to renumber if nothing was removed + if (bReturn) + Renumber(); + + return bReturn; +} + +bool CPVRChannelGroup::AppendToGroup(const std::shared_ptr<CPVRChannel>& channel) +{ + bool bReturn = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (!CPVRChannelGroup::IsGroupMember(channel)) + { + const std::shared_ptr<CPVRChannelGroupMember> allGroupMember = + m_allChannelsGroup->GetByUniqueID(channel->StorageId()); + + if (allGroupMember) + { + unsigned int channelNumberMax = + std::accumulate(m_sortedMembers.cbegin(), m_sortedMembers.cend(), 0, + [](unsigned int last, const auto& member) { + return (member->ChannelNumber().GetChannelNumber() > last) + ? member->ChannelNumber().GetChannelNumber() + : last; + }); + + const auto newMember = std::make_shared<CPVRChannelGroupMember>(GroupID(), GroupName(), + allGroupMember->Channel()); + newMember->SetChannelNumber(CPVRChannelNumber(channelNumberMax + 1, 0)); + newMember->SetClientPriority(allGroupMember->ClientPriority()); + + m_sortedMembers.emplace_back(newMember); + m_members.emplace(allGroupMember->Channel()->StorageId(), newMember); + + SortAndRenumber(); + bReturn = true; + } + } + return bReturn; +} + +bool CPVRChannelGroup::IsGroupMember(const std::shared_ptr<CPVRChannel>& channel) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_members.find(channel->StorageId()) != m_members.end(); +} + +bool CPVRChannelGroup::Persist() +{ + bool bReturn(true); + const std::shared_ptr<CPVRDatabase> database(CServiceBroker::GetPVRManager().GetTVDatabase()); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + // do not persist if the group is not fully loaded and was saved before. + if (!m_bLoaded && m_iGroupId != INVALID_GROUP_ID) + return bReturn; + + // Mark newly created groups as loaded so future updates will also be persisted... + if (m_iGroupId == INVALID_GROUP_ID) + m_bLoaded = true; + + if (database) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting channel group '{}' with {} channels", GroupName(), + static_cast<int>(m_members.size())); + + bReturn = database->Persist(*this); + m_bChanged = false; + } + else + { + bReturn = false; + } + + return bReturn; +} + +void CPVRChannelGroup::Delete() +{ + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No TV database"); + return; + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iGroupId > 0) + { + if (database->Delete(*this)) + m_bDeleted = true; + } +} + +bool CPVRChannelGroup::Renumber(RenumberMode mode /* = NORMAL */) +{ + bool bReturn(false); + unsigned int iChannelNumber(0); + + std::unique_lock<CCriticalSection> lock(m_critSection); + const bool bUseBackendChannelNumbers = GetSettings()->UseBackendChannelNumbers(); + const bool bStartGroupChannelNumbersFromOne = GetSettings()->StartGroupChannelNumbersFromOne(); + + CPVRChannelNumber currentChannelNumber; + CPVRChannelNumber currentClientChannelNumber; + for (auto& sortedMember : m_sortedMembers) + { + currentClientChannelNumber = sortedMember->ClientChannelNumber(); + if (m_allChannelsGroup && !currentClientChannelNumber.IsValid()) + currentClientChannelNumber = + m_allChannelsGroup->GetClientChannelNumber(sortedMember->Channel()); + + if (bUseBackendChannelNumbers) + { + currentChannelNumber = currentClientChannelNumber; + } + else if (sortedMember->Channel()->IsHidden()) + { + currentChannelNumber = CPVRChannelNumber(0, 0); + } + else + { + if (IsInternalGroup()) + { + currentChannelNumber = CPVRChannelNumber(++iChannelNumber, 0); + } + else + { + if (bStartGroupChannelNumbersFromOne && mode != IGNORE_NUMBERING_FROM_ONE) + currentChannelNumber = CPVRChannelNumber(++iChannelNumber, 0); + else + currentChannelNumber = m_allChannelsGroup->GetChannelNumber(sortedMember->Channel()); + } + } + + if (sortedMember->ChannelNumber() != currentChannelNumber || + sortedMember->ClientChannelNumber() != currentClientChannelNumber) + { + bReturn = true; + sortedMember->SetChannelNumber(currentChannelNumber); + sortedMember->SetClientChannelNumber(currentClientChannelNumber); + } + } + + if (bReturn) + Sort(); + + return bReturn; +} + +bool CPVRChannelGroup::HasNewChannels() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_members.cbegin(), m_members.cend(), + [](const auto& member) { return member.second->Channel()->ChannelID() <= 0; }); +} + +bool CPVRChannelGroup::HasChanges() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bChanged; +} + +bool CPVRChannelGroup::IsNew() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iGroupId <= 0; +} + +void CPVRChannelGroup::UseBackendChannelOrderChanged() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + UpdateClientPriorities(); + OnSettingChanged(); +} + +void CPVRChannelGroup::UseBackendChannelNumbersChanged() +{ + OnSettingChanged(); +} + +void CPVRChannelGroup::StartGroupChannelNumbersFromOneChanged() +{ + OnSettingChanged(); +} + +void CPVRChannelGroup::OnSettingChanged() +{ + //! @todo while pvr manager is starting up do accept setting changes. + if (!CServiceBroker::GetPVRManager().IsStarted()) + { + CLog::Log(LOGWARNING, "Channel group setting change ignored while PVR Manager is starting"); + return; + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + CLog::LogFC(LOGDEBUG, LOGPVR, + "Renumbering channel group '{}' to use the backend channel order and/or numbers", + GroupName()); + + // If we don't sync channel groups make sure the channel numbers are set from + // the all channels group using the non default renumber call before sorting + if (!GetSettings()->SyncChannelGroups()) + Renumber(IGNORE_NUMBERING_FROM_ONE); + + const bool bRenumbered = SortAndRenumber(); + Persist(); + + m_events.Publish(bRenumbered ? PVREvent::ChannelGroupInvalidated : PVREvent::ChannelGroup); +} + +int CPVRChannelGroup::GroupID() const +{ + return m_iGroupId; +} + +void CPVRChannelGroup::SetGroupID(int iGroupId) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (iGroupId >= 0 && m_iGroupId != iGroupId) + { + m_iGroupId = iGroupId; + + // propagate the new id to the group members + for (const auto& member : m_members) + member.second->SetGroupID(iGroupId); + } +} + +void CPVRChannelGroup::SetGroupType(int iGroupType) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_iGroupType != iGroupType) + { + m_iGroupType = iGroupType; + if (m_bLoaded) + m_bChanged = true; + } +} + +int CPVRChannelGroup::GroupType() const +{ + return m_iGroupType; +} + +std::string CPVRChannelGroup::GroupName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_path.GetGroupName(); +} + +void CPVRChannelGroup::SetGroupName(const std::string& strGroupName) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_path.GetGroupName() != strGroupName) + { + m_path = CPVRChannelsPath(m_path.IsRadio(), strGroupName); + if (m_bLoaded) + { + m_bChanged = true; + Persist(); //! @todo why must we persist immediately? + } + } +} + +bool CPVRChannelGroup::IsRadio() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_path.IsRadio(); +} + +time_t CPVRChannelGroup::LastWatched() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iLastWatched; +} + +void CPVRChannelGroup::SetLastWatched(time_t iLastWatched) +{ + const std::shared_ptr<CPVRDatabase> database(CServiceBroker::GetPVRManager().GetTVDatabase()); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iLastWatched != iLastWatched) + { + m_iLastWatched = iLastWatched; + if (m_bLoaded && database) + database->UpdateLastWatched(*this); + } +} + +uint64_t CPVRChannelGroup::LastOpened() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iLastOpened; +} + +void CPVRChannelGroup::SetLastOpened(uint64_t iLastOpened) +{ + const std::shared_ptr<CPVRDatabase> database(CServiceBroker::GetPVRManager().GetTVDatabase()); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iLastOpened != iLastOpened) + { + m_iLastOpened = iLastOpened; + if (m_bLoaded && database) + database->UpdateLastOpened(*this); + } +} + +bool CPVRChannelGroup::UpdateChannel(const std::pair<int, int>& storageId, + const std::string& strChannelName, + const std::string& strIconPath, + int iEPGSource, + int iChannelNumber, + bool bHidden, + bool bEPGEnabled, + bool bParentalLocked, + bool bUserSetIcon, + bool bUserSetHidden) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + /* get the real channel from the group */ + const std::shared_ptr<CPVRChannel> channel = GetByUniqueID(storageId)->Channel(); + if (!channel) + return false; + + channel->SetChannelName(strChannelName, true); + channel->SetHidden(bHidden, bUserSetHidden); + channel->SetLocked(bParentalLocked); + channel->SetIconPath(strIconPath, bUserSetIcon); + + if (iEPGSource == 0) + channel->SetEPGScraper("client"); + + //! @todo add other scrapers + channel->SetEPGEnabled(bEPGEnabled); + + /* set new values in the channel tag */ + if (bHidden) + { + // sort or previous changes will be overwritten + Sort(); + + RemoveFromGroup(channel); + } + else if (iChannelNumber > 0) + { + SetChannelNumber(channel, CPVRChannelNumber(iChannelNumber, 0)); + } + + return true; +} + +size_t CPVRChannelGroup::Size() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_members.size(); +} + +bool CPVRChannelGroup::HasChannels() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return !m_members.empty(); +} + +bool CPVRChannelGroup::CreateChannelEpgs(bool bForce /* = false */) +{ + /* used only by internal channel groups */ + return true; +} + +bool CPVRChannelGroup::SetHidden(bool bHidden) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_bHidden != bHidden) + { + m_bHidden = bHidden; + if (m_bLoaded) + m_bChanged = true; + } + + return m_bChanged; +} + +bool CPVRChannelGroup::IsHidden() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bHidden; +} + +int CPVRChannelGroup::GetPosition() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iPosition; +} + +void CPVRChannelGroup::SetPosition(int iPosition) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iPosition != iPosition) + { + m_iPosition = iPosition; + if (m_bLoaded) + m_bChanged = true; + } +} + +int CPVRChannelGroup::CleanupCachedImages() +{ + std::vector<std::string> urlsToCheck; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + std::transform( + m_members.cbegin(), m_members.cend(), std::back_inserter(urlsToCheck), + [](const auto& groupMember) { return groupMember.second->Channel()->ClientIconPath(); }); + } + + const std::string owner = + StringUtils::Format(CPVRChannel::IMAGE_OWNER_PATTERN, IsRadio() ? "radio" : "tv"); + return CPVRCachedImages::Cleanup({{owner, ""}}, urlsToCheck); +} diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h new file mode 100644 index 0000000..66c45d6 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -0,0 +1,548 @@ +/* + * 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. + */ + +#pragma once + +#include "pvr/channels/PVRChannelGroupSettings.h" +#include "pvr/channels/PVRChannelNumber.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "utils/EventStream.h" + +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +struct PVR_CHANNEL_GROUP; + +namespace PVR +{ +#define PVR_GROUP_TYPE_DEFAULT 0 +#define PVR_GROUP_TYPE_INTERNAL 1 +#define PVR_GROUP_TYPE_USER_DEFINED 2 + +enum class PVREvent; + +class CPVRChannel; +class CPVRChannelGroupMember; +class CPVRClient; +class CPVREpgInfoTag; + +enum RenumberMode +{ + NORMAL = 0, + IGNORE_NUMBERING_FROM_ONE = 1 +}; + +using GroupMemberPair = + std::pair<std::shared_ptr<CPVRChannelGroupMember>, std::shared_ptr<CPVRChannelGroupMember>>; + +class CPVRChannelGroup : public IChannelGroupSettingsCallback +{ + friend class CPVRDatabase; + +public: + static const int INVALID_GROUP_ID = -1; + + /*! + * @brief Create a new channel group instance. + * @param path The channel group path. + * @param allChannelsGroup The channel group containing all TV or radio channels. + */ + CPVRChannelGroup(const CPVRChannelsPath& path, + const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup); + + /*! + * @brief Create a new channel group instance from a channel group provided by an add-on. + * @param group The channel group provided by the add-on. + * @param allChannelsGroup The channel group containing all TV or radio channels. + */ + CPVRChannelGroup(const PVR_CHANNEL_GROUP& group, + const std::shared_ptr<CPVRChannelGroup>& allChannelsGroup); + + ~CPVRChannelGroup() override; + + bool operator==(const CPVRChannelGroup& right) const; + bool operator!=(const CPVRChannelGroup& right) const; + + /*! + * @brief Copy over data to the given PVR_CHANNEL_GROUP instance. + * @param group The group instance to fill. + */ + void FillAddonData(PVR_CHANNEL_GROUP& group) const; + + /*! + * @brief Query the events available for CEventStream + */ + CEventStream<PVREvent>& Events() { return m_events; } + + /*! + * @brief Load the channels from the database. + * @param channels All available channels. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True when loaded successfully, false otherwise. + */ + virtual bool LoadFromDatabase( + const std::map<std::pair<int, int>, std::shared_ptr<CPVRChannel>>& channels, + const std::vector<std::shared_ptr<CPVRClient>>& clients); + + /*! + * @brief Clear all data. + */ + virtual void Unload(); + + /*! + * @return The amount of group members + */ + size_t Size() const; + + /*! + * @brief Update data with channel group members from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @return True on success, false otherwise. + */ + virtual bool UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients); + + /*! + * @brief Get the path of this group. + * @return the path. + */ + const CPVRChannelsPath& GetPath() const; + + /*! + * @brief Set the path of this group. + * @param the path. + */ + void SetPath(const CPVRChannelsPath& path); + + /*! + * @brief Change the channelnumber of a group. Used by CGUIDialogPVRChannelManager. + * Call SortByChannelNumber() and Renumber() after all changes are done. + * @param channel The channel to change the channel number for. + * @param channelNumber The new channel number. + */ + bool SetChannelNumber(const std::shared_ptr<CPVRChannel>& channel, + const CPVRChannelNumber& channelNumber); + + /*! + * @brief Remove a channel from this container. + * @param channel The channel to remove. + * @return True if the channel was found and removed, false otherwise. + */ + virtual bool RemoveFromGroup(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Append a channel to this container. + * @param channel The channel to append. + * @return True if the channel was appended, false otherwise. + */ + virtual bool AppendToGroup(const std::shared_ptr<CPVRChannel>& channel); + + /*! + * @brief Change the name of this group. + * @param strGroupName The new group name. + */ + void SetGroupName(const std::string& strGroupName); + + /*! + * @brief Persist changed or new data. + * @return True if the channel was persisted, false otherwise. + */ + bool Persist(); + + /*! + * @brief Check whether a channel is in this container. + * @param channel The channel to find. + * @return True if the channel was found, false otherwise. + */ + virtual bool IsGroupMember(const std::shared_ptr<CPVRChannel>& channel) const; + + /*! + * @brief Check if this group is the internal group containing all channels. + * @return True if it's the internal group, false otherwise. + */ + bool IsInternalGroup() const { return m_iGroupType == PVR_GROUP_TYPE_INTERNAL; } + + /*! + * @brief True if this group holds radio channels, false if it holds TV channels. + * @return True if this group holds radio channels, false if it holds TV channels. + */ + bool IsRadio() const; + + /*! + * @brief The database ID of this group. + * @return The database ID of this group. + */ + int GroupID() const; + + /*! + * @brief Set the database ID of this group. + * @param iGroupId The new database ID. + */ + void SetGroupID(int iGroupId); + + /*! + * @brief Set the type of this group. + * @param the new type for this group. + */ + void SetGroupType(int iGroupType); + + /*! + * @brief Return the type of this group. + */ + int GroupType() const; + + /*! + * @return Time group has been watched last. + */ + time_t LastWatched() const; + + /*! + * @brief Last time group has been watched + * @param iLastWatched The new value. + */ + void SetLastWatched(time_t iLastWatched); + + /*! + * @return Time in milliseconds from epoch this group was last opened. + */ + uint64_t LastOpened() const; + + /*! + * @brief Set the time in milliseconds from epoch this group was last opened. + * @param iLastOpened The new value. + */ + void SetLastOpened(uint64_t iLastOpened); + + /*! + * @brief The name of this group. + * @return The name of this group. + */ + std::string GroupName() const; + + /*! @name Sort methods + */ + //@{ + + /*! + * @brief Sort the group. + */ + void Sort(); + + /*! + * @brief Sort the group and fix up channel numbers. + * @return True when numbering changed, false otherwise + */ + bool SortAndRenumber(); + + /*! + * @brief Remove invalid channels and updates the channel numbers. + * @param mode the numbering mode to use + * @return True if something changed, false otherwise. + */ + bool Renumber(RenumberMode mode = NORMAL); + + //@} + + // IChannelGroupSettingsCallback implementation + void UseBackendChannelOrderChanged() override; + void UseBackendChannelNumbersChanged() override; + void StartGroupChannelNumbersFromOneChanged() override; + + /*! + * @brief Get the channel group member that was played last. + * @param iCurrentChannel The channelid of the current channel that is playing, or -1 if none + * @return The requested channel group member or nullptr. + */ + std::shared_ptr<CPVRChannelGroupMember> GetLastPlayedChannelGroupMember( + int iCurrentChannel = -1) const; + + /*! + * @brief Get the last and previous to last played channel group members. + * @return The members. pair.first contains the last, pair.second the previous to last member. + */ + GroupMemberPair GetLastAndPreviousToLastPlayedChannelGroupMember() const; + + /*! + * @brief Get a channel group member given it's active channel number + * @param channelNumber The channel number. + * @return The channel group member or nullptr if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroupMember> GetByChannelNumber( + const CPVRChannelNumber& channelNumber) const; + + /*! + * @brief Get the channel number in this group of the given channel. + * @param channel The channel to get the channel number for. + * @return The channel number in this group. + */ + CPVRChannelNumber GetChannelNumber(const std::shared_ptr<CPVRChannel>& channel) const; + + /*! + * @brief Get the client channel number in this group of the given channel. + * @param channel The channel to get the channel number for. + * @return The client channel number in this group. + */ + CPVRChannelNumber GetClientChannelNumber(const std::shared_ptr<CPVRChannel>& channel) const; + + /*! + * @brief Get the next channel group member in this group. + * @param groupMember The current channel group member. + * @return The channel group member or nullptr if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroupMember> GetNextChannelGroupMember( + const std::shared_ptr<CPVRChannelGroupMember>& groupMember) const; + + /*! + * @brief Get the previous channel group member in this group. + * @param groupMember The current channel group member. + * @return The channel group member or nullptr if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroupMember> GetPreviousChannelGroupMember( + const std::shared_ptr<CPVRChannelGroupMember>& groupMember) const; + + /*! + * @brief Get a channel given it's channel ID. + * @param iChannelID The channel ID. + * @return The channel or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannel> GetByChannelID(int iChannelID) const; + + enum class Include + { + ALL, + ONLY_HIDDEN, + ONLY_VISIBLE + }; + + /*! + * @brief Get the current members of this group + * @param eFilter A filter to apply. + * @return The group members + */ + std::vector<std::shared_ptr<CPVRChannelGroupMember>> GetMembers( + Include eFilter = Include::ALL) const; + + /*! + * @brief Get the list of active channel numbers in a group. + * @param channelNumbers The list to store the numbers in. + */ + void GetChannelNumbers(std::vector<std::string>& channelNumbers) const; + + /*! + * @brief The amount of hidden channels in this container. + * @return The amount of hidden channels in this container. + */ + virtual size_t GetNumHiddenChannels() const { return 0; } + + /*! + * @brief Does this container holds channels. + * @return True if there is at least one channel in this container, otherwise false. + */ + bool HasChannels() const; + + /*! + * @return True if there is at least one new channel in this group that hasn't been persisted, false otherwise. + */ + bool HasNewChannels() const; + + /*! + * @return True if anything changed in this group that hasn't been persisted, false otherwise. + */ + bool HasChanges() const; + + /*! + * @return True if the group was never persisted, false otherwise. + */ + bool IsNew() const; + + /*! + * @brief Create an EPG table for each channel. + * @brief bForce Create the tables, even if they already have been created before. + * @return True if all tables were created successfully, false otherwise. + */ + virtual bool CreateChannelEpgs(bool bForce = false); + + /*! + * @brief Update a channel group member with given data. + * @param storageId The storage id of the channel. + * @param strChannelName The channel name to set. + * @param strIconPath The icon path to set. + * @param iEPGSource The EPG id. + * @param iChannelNumber The channel number to set. + * @param bHidden Set/Remove hidden flag for the channel group member identified by storage id. + * @param bEPGEnabled Set/Remove EPG enabled flag for the channel group member identified by storage id. + * @param bParentalLocked Set/Remove parental locked flag for the channel group member identified by storage id. + * @param bUserSetIcon Set/Remove user set icon flag for the channel group member identified by storage id. + * @param bUserSetHidden Set/Remove user set hidden flag for the channel group member identified by storage id. + * @return True on success, false otherwise. + */ + bool UpdateChannel(const std::pair<int, int>& storageId, + const std::string& strChannelName, + const std::string& strIconPath, + int iEPGSource, + int iChannelNumber, + bool bHidden, + bool bEPGEnabled, + bool bParentalLocked, + bool bUserSetIcon, + bool bUserSetHidden); + + /*! + * @brief Get a channel given the channel number on the client. + * @param iUniqueChannelId The unique channel id on the client. + * @param iClientID The ID of the client. + * @return The channel or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannel> GetByUniqueID(int iUniqueChannelId, int iClientID) const; + + /*! + * @brief Get a channel group member given its storage id. + * @param id The storage id (a pair of client id and unique channel id). + * @return The channel group member or nullptr if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroupMember> GetByUniqueID(const std::pair<int, int>& id) const; + + bool SetHidden(bool bHidden); + bool IsHidden() const; + + int GetPosition() const; + void SetPosition(int iPosition); + + /*! + * @brief Check, whether data for a given pvr client are currently valid. For instance, data + * can be invalid because the client's backend was offline when data was last queried. + * @param iClientId The id of the client. + * @return True, if data is currently valid, false otherwise. + */ + bool HasValidDataForClient(int iClientId) const; + + /*! + * @brief Check, whether data for given pvr clients are currently valid. For instance, data + * can be invalid because the client's backend was offline when data was last queried. + * @param clients The clients to check. Check all active clients if vector is empty. + * @return True, if data is currently valid, false otherwise. + */ + bool HasValidDataForClients(const std::vector<std::shared_ptr<CPVRClient>>& clients) const; + + /*! + * @brief Update the channel numbers according to the all channels group and publish event. + * @return True, if a channel number was changed, false otherwise. + */ + bool UpdateChannelNumbersFromAllChannelsGroup(); + + /*! + * @brief Remove this group from database. + */ + void Delete(); + + /*! + * @brief Whether this group is deleted. + * @return True, if deleted, false otherwise. + */ + bool IsDeleted() const { return m_bDeleted; } + + /*! + * @brief Erase stale texture db entries and image files. + * @return number of cleaned up images. + */ + int CleanupCachedImages(); + +protected: + /*! + * @brief Remove deleted group members from this group. + * @param groupMembers The group members to use to update this list. + * @return The removed members . + */ + virtual std::vector<std::shared_ptr<CPVRChannelGroupMember>> RemoveDeletedGroupMembers( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers); + + /*! + * @brief Update the current channel group members with the given list. + * @param groupMembers The group members to use to update this list. + * @return True if everything went well, false otherwise. + */ + bool UpdateGroupEntries(const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers); + + /*! + * @brief Sort the current channel list by client channel number. + */ + void SortByClientChannelNumber(); + + /*! + * @brief Sort the current channel list by channel number. + */ + void SortByChannelNumber(); + + /*! + * @brief Update the priority for all members of all channel groups. + */ + bool UpdateClientPriorities(); + + std::shared_ptr<CPVRChannelGroupSettings> GetSettings() const; + + int m_iGroupType = PVR_GROUP_TYPE_DEFAULT; /*!< The type of this group */ + int m_iGroupId = INVALID_GROUP_ID; /*!< The ID of this group in the database */ + bool m_bLoaded = false; /*!< True if this container is loaded, false otherwise */ + bool m_bChanged = + false; /*!< true if anything changed in this group that hasn't been persisted, false otherwise */ + time_t m_iLastWatched = 0; /*!< last time group has been watched */ + uint64_t m_iLastOpened = 0; /*!< time in milliseconds from epoch this group was last opened */ + bool m_bHidden = false; /*!< true if this group is hidden, false otherwise */ + int m_iPosition = 0; /*!< the position of this group within the group list */ + std::vector<std::shared_ptr<CPVRChannelGroupMember>> + m_sortedMembers; /*!< members sorted by channel number */ + std::map<std::pair<int, int>, std::shared_ptr<CPVRChannelGroupMember>> + m_members; /*!< members with key clientid+uniqueid */ + mutable CCriticalSection m_critSection; + std::vector<int> m_failedClients; + CEventSource<PVREvent> m_events; + mutable std::shared_ptr<CPVRChannelGroupSettings> m_settings; + + // the settings singleton shared between all group instances + static CCriticalSection m_settingsSingletonCritSection; + static std::weak_ptr<CPVRChannelGroupSettings> m_settingsSingleton; + +private: + /*! + * @brief Load the channel group members stored in the database. + * @param clients The PVR clients to load data for. Leave empty for all clients. + * @return The amount of channel group members that were added. + */ + int LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients); + + /*! + * @brief Delete channel group members from database. + * @param membersToDelete The channel group members. + */ + void DeleteGroupMembersFromDb( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& membersToDelete); + + /*! + * @brief Update this group's data with a channel group member provided by a client. + * @param groupMember The updated group member. + * @return True if group member data were changed, false otherwise. + */ + bool UpdateFromClient(const std::shared_ptr<CPVRChannelGroupMember>& groupMember); + + /*! + * @brief Add new channel group members to this group; update data. + * @param groupMembers The group members to use to update this list. + * @return True if group member data were changed, false otherwise. + */ + bool AddAndUpdateGroupMembers( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers); + + void OnSettingChanged(); + + std::shared_ptr<CPVRChannelGroup> m_allChannelsGroup; + CPVRChannelsPath m_path; + bool m_bDeleted = false; +}; +} // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupInternal.cpp b/xbmc/pvr/channels/PVRChannelGroupInternal.cpp new file mode 100644 index 0000000..f79e02f --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupInternal.cpp @@ -0,0 +1,245 @@ +/* + * 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 "PVRChannelGroupInternal.h" + +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/epg/EpgContainer.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <algorithm> +#include <iterator> +#include <mutex> +#include <string> +#include <utility> +#include <vector> + +using namespace PVR; + +CPVRChannelGroupInternal::CPVRChannelGroupInternal(bool bRadio) + : CPVRChannelGroup(CPVRChannelsPath(bRadio, g_localizeStrings.Get(19287)), nullptr), + m_iHiddenChannels(0) +{ + m_iGroupType = PVR_GROUP_TYPE_INTERNAL; +} + +CPVRChannelGroupInternal::CPVRChannelGroupInternal(const CPVRChannelsPath& path) + : CPVRChannelGroup(path, nullptr), m_iHiddenChannels(0) +{ + m_iGroupType = PVR_GROUP_TYPE_INTERNAL; +} + +CPVRChannelGroupInternal::~CPVRChannelGroupInternal() +{ + CServiceBroker::GetPVRManager().Events().Unsubscribe(this); +} + +bool CPVRChannelGroupInternal::LoadFromDatabase( + const std::map<std::pair<int, int>, std::shared_ptr<CPVRChannel>>& channels, + const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + if (CPVRChannelGroup::LoadFromDatabase(channels, clients)) + { + for (const auto& groupMember : m_members) + { + const std::shared_ptr<CPVRChannel> channel = groupMember.second->Channel(); + + // create the EPG for the channel + if (channel->CreateEPG()) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Created EPG for {} channel '{}'", IsRadio() ? "radio" : "TV", + channel->ChannelName()); + } + } + + UpdateChannelPaths(); + CServiceBroker::GetPVRManager().Events().Subscribe(this, &CPVRChannelGroupInternal::OnPVRManagerEvent); + return true; + } + + CLog::LogF(LOGERROR, "Failed to load channels"); + return false; +} + +void CPVRChannelGroupInternal::Unload() +{ + CServiceBroker::GetPVRManager().Events().Unsubscribe(this); + CPVRChannelGroup::Unload(); +} + +void CPVRChannelGroupInternal::CheckGroupName() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + /* check whether the group name is still correct, or channels will fail to load after the language setting changed */ + const std::string& strNewGroupName = g_localizeStrings.Get(19287); + if (GroupName() != strNewGroupName) + { + SetGroupName(strNewGroupName); + UpdateChannelPaths(); + } +} + +void CPVRChannelGroupInternal::UpdateChannelPaths() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iHiddenChannels = 0; + for (auto& groupMemberPair : m_members) + { + if (groupMemberPair.second->Channel()->IsHidden()) + ++m_iHiddenChannels; + else + groupMemberPair.second->SetGroupName(GroupName()); + } +} + +bool CPVRChannelGroupInternal::UpdateFromClients( + const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + // get the channels from the given clients + std::vector<std::shared_ptr<CPVRChannel>> channels; + CServiceBroker::GetPVRManager().Clients()->GetChannels(clients, IsRadio(), channels, + m_failedClients); + + // create group members for the channels + std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers; + std::transform(channels.cbegin(), channels.cend(), std::back_inserter(groupMembers), + [this](const auto& channel) { + return std::make_shared<CPVRChannelGroupMember>(GroupID(), GroupName(), channel); + }); + + return UpdateGroupEntries(groupMembers); +} + +std::vector<std::shared_ptr<CPVRChannelGroupMember>> CPVRChannelGroupInternal:: + RemoveDeletedGroupMembers( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) +{ + std::vector<std::shared_ptr<CPVRChannelGroupMember>> removedMembers = + CPVRChannelGroup::RemoveDeletedGroupMembers(groupMembers); + if (!removedMembers.empty()) + { + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (!database) + { + CLog::LogF(LOGERROR, "No TV database"); + } + else + { + std::vector<std::shared_ptr<CPVREpg>> epgsToRemove; + for (const auto& member : removedMembers) + { + const auto channel = member->Channel(); + const auto epg = channel->GetEPG(); + if (epg) + epgsToRemove.emplace_back(epg); + + // Note: We need to obtain a lock for every channel instance before we can lock + // the TV db. This order is important. Otherwise deadlocks may occur. + channel->Lock(); + } + + // Note: We must lock the db the whole time, otherwise races may occur. + database->Lock(); + + bool commitPending = false; + + for (const auto& member : removedMembers) + { + // since channel was not found in the internal group, it was deleted from the backend + + const auto channel = member->Channel(); + commitPending |= channel->QueueDelete(); + channel->Unlock(); + + size_t queryCount = database->GetDeleteQueriesCount(); + if (queryCount > CHANNEL_COMMIT_QUERY_COUNT_LIMIT) + database->CommitDeleteQueries(); + } + + if (commitPending) + database->CommitDeleteQueries(); + + database->Unlock(); + + // delete the EPG data for the removed channels + CServiceBroker::GetPVRManager().EpgContainer().QueueDeleteEpgs(epgsToRemove); + } + } + return removedMembers; +} + +bool CPVRChannelGroupInternal::AppendToGroup(const std::shared_ptr<CPVRChannel>& channel) +{ + if (IsGroupMember(channel)) + return false; + + const std::shared_ptr<CPVRChannelGroupMember> groupMember = GetByUniqueID(channel->StorageId()); + if (!groupMember) + return false; + + channel->SetHidden(false, true); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_iHiddenChannels > 0) + m_iHiddenChannels--; + + const unsigned int iChannelNumber = m_members.size() - m_iHiddenChannels; + groupMember->SetChannelNumber(CPVRChannelNumber(iChannelNumber, 0)); + + SortAndRenumber(); + return true; +} + +bool CPVRChannelGroupInternal::RemoveFromGroup(const std::shared_ptr<CPVRChannel>& channel) +{ + if (!IsGroupMember(channel)) + return false; + + channel->SetHidden(true, true); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + ++m_iHiddenChannels; + + SortAndRenumber(); + return true; +} + +bool CPVRChannelGroupInternal::IsGroupMember(const std::shared_ptr<CPVRChannel>& channel) const +{ + return !channel->IsHidden(); +} + +bool CPVRChannelGroupInternal::CreateChannelEpgs(bool bForce /* = false */) +{ + if (!CServiceBroker::GetPVRManager().EpgContainer().IsStarted()) + return false; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto& groupMemberPair : m_members) + groupMemberPair.second->Channel()->CreateEPG(); + } + + return Persist(); +} + +void CPVRChannelGroupInternal::OnPVRManagerEvent(const PVR::PVREvent& event) +{ + if (event == PVREvent::ManagerStarted) + CServiceBroker::GetPVRManager().TriggerEpgsCreate(); +} diff --git a/xbmc/pvr/channels/PVRChannelGroupInternal.h b/xbmc/pvr/channels/PVRChannelGroupInternal.h new file mode 100644 index 0000000..5f8a06d --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupInternal.h @@ -0,0 +1,116 @@ +/* + * 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. + */ + +#pragma once + +#include "pvr/channels/PVRChannelGroup.h" + +#include <memory> +#include <vector> + +namespace PVR +{ + enum class PVREvent; + + class CPVRChannel; + class CPVRChannelNumber; + + class CPVRChannelGroupInternal : public CPVRChannelGroup + { + public: + CPVRChannelGroupInternal() = delete; + + /*! + * @brief Create a new internal channel group. + * @param bRadio True if this group holds radio channels. + */ + explicit CPVRChannelGroupInternal(bool bRadio); + + /*! + * @brief Create a new internal channel group. + * @param path The path for the new group. + */ + explicit CPVRChannelGroupInternal(const CPVRChannelsPath& path); + + ~CPVRChannelGroupInternal() override; + + /** + * @brief The amount of channels in this container. + * @return The amount of channels in this container. + */ + size_t GetNumHiddenChannels() const override { return m_iHiddenChannels; } + + /*! + * @see CPVRChannelGroup::IsGroupMember + */ + bool IsGroupMember(const std::shared_ptr<CPVRChannel>& channel) const override; + + /*! + * @see CPVRChannelGroup::AppendToGroup + */ + bool AppendToGroup(const std::shared_ptr<CPVRChannel>& channel) override; + + /*! + * @see CPVRChannelGroup::RemoveFromGroup + */ + bool RemoveFromGroup(const std::shared_ptr<CPVRChannel>& channel) override; + + /*! + * @brief Check whether the group name is still correct after the language setting changed. + */ + void CheckGroupName(); + + /*! + * @brief Create an EPG table for each channel. + * @brief bForce Create the tables, even if they already have been created before. + * @return True if all tables were created successfully, false otherwise. + */ + bool CreateChannelEpgs(bool bForce = false) override; + + protected: + /*! + * @brief Remove deleted group members from this group. Delete stale channels. + * @param groupMembers The group members to use to update this list. + * @return The removed members . + */ + std::vector<std::shared_ptr<CPVRChannelGroupMember>> RemoveDeletedGroupMembers( + const std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) override; + + /*! + * @brief Update data with 'all channels' group members from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @return True on success, false otherwise. + */ + bool UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients) override; + + /*! + * @brief Load the channels from the database. + * @param channels All available channels. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True when loaded successfully, false otherwise. + */ + bool LoadFromDatabase( + const std::map<std::pair<int, int>, std::shared_ptr<CPVRChannel>>& channels, + const std::vector<std::shared_ptr<CPVRClient>>& clients) override; + + /*! + * @brief Clear all data. + */ + void Unload() override; + + /*! + * @brief Update the vfs paths of all channels. + */ + void UpdateChannelPaths(); + + size_t m_iHiddenChannels; /*!< the amount of hidden channels in this container */ + + private: + void OnPVRManagerEvent(const PVREvent& event); + }; +} diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.cpp b/xbmc/pvr/channels/PVRChannelGroupMember.cpp new file mode 100644 index 0000000..27592b5 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupMember.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012-2021 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 "PVRChannelGroupMember.h" + +#include "ServiceBroker.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "utils/DatabaseUtils.h" +#include "utils/SortUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +using namespace PVR; + +CPVRChannelGroupMember::CPVRChannelGroupMember(const std::string& groupName, + int order, + const std::shared_ptr<CPVRChannel>& channel) + : m_clientChannelNumber(channel->ClientChannelNumber()), m_iOrder(order) +{ + SetChannel(channel); + SetGroupName(groupName); +} + +CPVRChannelGroupMember::CPVRChannelGroupMember(int iGroupID, + const std::string& groupName, + const std::shared_ptr<CPVRChannel>& channel) + : m_iGroupID(iGroupID), + m_clientChannelNumber(channel->ClientChannelNumber()), + m_iOrder(channel->ClientOrder()) +{ + SetChannel(channel); + SetGroupName(groupName); +} + +void CPVRChannelGroupMember::Serialize(CVariant& value) const +{ + value["channelnumber"] = m_channelNumber.GetChannelNumber(); + value["subchannelnumber"] = m_channelNumber.GetSubChannelNumber(); +} + +void CPVRChannelGroupMember::SetChannel(const std::shared_ptr<CPVRChannel>& channel) +{ + // note: no need to set m_bChanged, as these values are not persisted in the db + m_channel = channel; + m_iClientID = channel->ClientID(); + m_iChannelUID = channel->UniqueID(); + m_iChannelDatabaseID = channel->ChannelID(); + m_bIsRadio = channel->IsRadio(); +} + +void CPVRChannelGroupMember::ToSortable(SortItem& sortable, Field field) const +{ + if (field == FieldChannelNumber) + { + sortable[FieldChannelNumber] = m_channelNumber.SortableChannelNumber(); + } + else if (field == FieldClientChannelOrder) + { + if (m_iOrder) + sortable[FieldClientChannelOrder] = m_iOrder; + else + sortable[FieldClientChannelOrder] = m_clientChannelNumber.SortableChannelNumber(); + } +} + +void CPVRChannelGroupMember::SetGroupID(int iGroupID) +{ + if (m_iGroupID != iGroupID) + { + m_iGroupID = iGroupID; + m_bNeedsSave = true; + } +} + +void CPVRChannelGroupMember::SetGroupName(const std::string& groupName) +{ + const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientID); + if (client) + m_path = + CPVRChannelsPath(m_bIsRadio, groupName, client->ID(), client->InstanceId(), m_iChannelUID); + else + CLog::LogF(LOGERROR, "Unable to obtain instance for client id: {}", m_iClientID); +} + +void CPVRChannelGroupMember::SetChannelNumber(const CPVRChannelNumber& channelNumber) +{ + if (m_channelNumber != channelNumber) + { + m_channelNumber = channelNumber; + m_bNeedsSave = true; + } +} + +void CPVRChannelGroupMember::SetClientChannelNumber(const CPVRChannelNumber& clientChannelNumber) +{ + if (m_clientChannelNumber != clientChannelNumber) + { + m_clientChannelNumber = clientChannelNumber; + m_bNeedsSave = true; + } +} + +void CPVRChannelGroupMember::SetClientPriority(int iClientPriority) +{ + if (m_iClientPriority != iClientPriority) + { + m_iClientPriority = iClientPriority; + // Note: do not set m_bNeedsSave here as priority is not stored in database + } +} + +void CPVRChannelGroupMember::SetOrder(int iOrder) +{ + if (m_iOrder != iOrder) + { + m_iOrder = iOrder; + m_bNeedsSave = true; + } +} + +bool CPVRChannelGroupMember::QueueDelete() +{ + const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase(); + if (!database) + return false; + + return database->QueueDeleteQuery(*this); +} diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.h b/xbmc/pvr/channels/PVRChannelGroupMember.h new file mode 100644 index 0000000..cb3ac83 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupMember.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012-2021 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. + */ + +#pragma once + +#include "pvr/channels/PVRChannelNumber.h" +#include "utils/ISerializable.h" +#include "utils/ISortable.h" + +#include <memory> +#include <string> + +namespace PVR +{ + +class CPVRChannel; + +class CPVRChannelGroupMember : public ISerializable, public ISortable +{ + friend class CPVRDatabase; + +public: + CPVRChannelGroupMember() : m_bNeedsSave(false) {} + + CPVRChannelGroupMember(const std::string& groupName, + int order, + const std::shared_ptr<CPVRChannel>& channel); + + CPVRChannelGroupMember(int iGroupID, + const std::string& groupName, + const std::shared_ptr<CPVRChannel>& channel); + + virtual ~CPVRChannelGroupMember() = default; + + // ISerializable implementation + void Serialize(CVariant& value) const override; + + // ISortable implementation + void ToSortable(SortItem& sortable, Field field) const override; + + std::shared_ptr<CPVRChannel> Channel() const { return m_channel; } + void SetChannel(const std::shared_ptr<CPVRChannel>& channel); + + int GroupID() const { return m_iGroupID; } + void SetGroupID(int iGroupID); + + const std::string& Path() const { return m_path; } + void SetGroupName(const std::string& groupName); + + const CPVRChannelNumber& ChannelNumber() const { return m_channelNumber; } + void SetChannelNumber(const CPVRChannelNumber& channelNumber); + + const CPVRChannelNumber& ClientChannelNumber() const { return m_clientChannelNumber; } + void SetClientChannelNumber(const CPVRChannelNumber& clientChannelNumber); + + int ClientPriority() const { return m_iClientPriority; } + void SetClientPriority(int iClientPriority); + + int Order() const { return m_iOrder; } + void SetOrder(int iOrder); + + bool NeedsSave() const { return m_bNeedsSave; } + void SetSaved() { m_bNeedsSave = false; } + + int ClientID() const { return m_iClientID; } + + int ChannelUID() const { return m_iChannelUID; } + + int ChannelDatabaseID() const { return m_iChannelDatabaseID; } + + bool IsRadio() const { return m_bIsRadio; } + + /*! + * @brief Delete this group member from the database. + * @return True if it was deleted successfully, false otherwise. + */ + bool QueueDelete(); + +private: + int m_iGroupID = -1; + int m_iClientID = -1; + int m_iChannelUID = -1; + int m_iChannelDatabaseID = -1; + bool m_bIsRadio = false; + std::shared_ptr<CPVRChannel> m_channel; + std::string m_path; + CPVRChannelNumber m_channelNumber; // the channel number this channel has in the group + CPVRChannelNumber + m_clientChannelNumber; // the client channel number this channel has in the group + int m_iClientPriority = 0; + int m_iOrder = 0; // The value denoting the order of this member in the group + + bool m_bNeedsSave = true; +}; + +} // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupSettings.cpp b/xbmc/pvr/channels/PVRChannelGroupSettings.cpp new file mode 100644 index 0000000..3cbc0bd --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupSettings.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012-2021 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 "PVRChannelGroupSettings.h" + +#include "ServiceBroker.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClients.h" +#include "settings/Settings.h" +#include "settings/lib/Setting.h" + +using namespace PVR; + +CPVRChannelGroupSettings::CPVRChannelGroupSettings() + : m_settings({CSettings::SETTING_PVRMANAGER_SYNCCHANNELGROUPS, + CSettings::SETTING_PVRMANAGER_BACKENDCHANNELORDER, + CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERS, + CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERSALWAYS, + CSettings::SETTING_PVRMANAGER_STARTGROUPCHANNELNUMBERSFROMONE}) +{ + UpdateSyncChannelGroups(); + UpdateUseBackendChannelOrder(); + UpdateUseBackendChannelNumbers(); + UpdateStartGroupChannelNumbersFromOne(); + + m_settings.RegisterCallback(this); +} + +CPVRChannelGroupSettings::~CPVRChannelGroupSettings() +{ + m_settings.UnregisterCallback(this); +} + +void CPVRChannelGroupSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (!setting) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::CSettings::SETTING_PVRMANAGER_SYNCCHANNELGROUPS) + { + if (SyncChannelGroups() != UpdateSyncChannelGroups()) + { + for (const auto& callback : m_callbacks) + callback->SyncChannelGroupsChanged(); + } + } + else if (settingId == CSettings::CSettings::SETTING_PVRMANAGER_BACKENDCHANNELORDER) + { + if (UseBackendChannelOrder() != UpdateUseBackendChannelOrder()) + { + for (const auto& callback : m_callbacks) + callback->UseBackendChannelOrderChanged(); + } + } + else if (settingId == CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERS || + settingId == CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERSALWAYS) + { + if (UseBackendChannelNumbers() != UpdateUseBackendChannelNumbers()) + { + for (const auto& callback : m_callbacks) + callback->UseBackendChannelNumbersChanged(); + } + } + else if (settingId == CSettings::SETTING_PVRMANAGER_STARTGROUPCHANNELNUMBERSFROMONE) + { + if (StartGroupChannelNumbersFromOne() != UpdateStartGroupChannelNumbersFromOne()) + { + for (const auto& callback : m_callbacks) + callback->StartGroupChannelNumbersFromOneChanged(); + } + } +} + +void CPVRChannelGroupSettings::RegisterCallback(IChannelGroupSettingsCallback* callback) +{ + m_callbacks.insert(callback); +} + +void CPVRChannelGroupSettings::UnregisterCallback(IChannelGroupSettingsCallback* callback) +{ + m_callbacks.erase(callback); +} + +bool CPVRChannelGroupSettings::UpdateSyncChannelGroups() +{ + m_bSyncChannelGroups = m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_SYNCCHANNELGROUPS); + return m_bSyncChannelGroups; +} + +bool CPVRChannelGroupSettings::UpdateUseBackendChannelOrder() +{ + m_bUseBackendChannelOrder = + m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_BACKENDCHANNELORDER); + return m_bUseBackendChannelOrder; +} + +bool CPVRChannelGroupSettings::UpdateUseBackendChannelNumbers() +{ + const int enabledClientAmount = CServiceBroker::GetPVRManager().Clients()->EnabledClientAmount(); + m_bUseBackendChannelNumbers = + m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERS) && + (enabledClientAmount == 1 || + (m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERSALWAYS) && + enabledClientAmount > 1)); + return m_bUseBackendChannelNumbers; +} + +bool CPVRChannelGroupSettings::UpdateStartGroupChannelNumbersFromOne() +{ + m_bStartGroupChannelNumbersFromOne = + m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_STARTGROUPCHANNELNUMBERSFROMONE) && + !UseBackendChannelNumbers(); + return m_bStartGroupChannelNumbersFromOne; +} diff --git a/xbmc/pvr/channels/PVRChannelGroupSettings.h b/xbmc/pvr/channels/PVRChannelGroupSettings.h new file mode 100644 index 0000000..abf842a --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupSettings.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012-2021 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. + */ + +#pragma once + +#include "pvr/settings/PVRSettings.h" +#include "settings/lib/ISettingCallback.h" + +#include <memory> +#include <set> + +namespace PVR +{ + +class IChannelGroupSettingsCallback +{ +public: + virtual ~IChannelGroupSettingsCallback() = default; + + virtual void SyncChannelGroupsChanged() {} + virtual void UseBackendChannelOrderChanged() {} + virtual void UseBackendChannelNumbersChanged() {} + virtual void StartGroupChannelNumbersFromOneChanged() {} +}; + +class CPVRChannelGroupSettings : public ISettingCallback +{ +public: + CPVRChannelGroupSettings(); + virtual ~CPVRChannelGroupSettings(); + + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + void RegisterCallback(IChannelGroupSettingsCallback* callback); + void UnregisterCallback(IChannelGroupSettingsCallback* callback); + + bool SyncChannelGroups() const { return m_bSyncChannelGroups; } + bool UseBackendChannelOrder() const { return m_bUseBackendChannelOrder; } + bool UseBackendChannelNumbers() const { return m_bUseBackendChannelNumbers; } + bool StartGroupChannelNumbersFromOne() const { return m_bStartGroupChannelNumbersFromOne; } + +private: + bool UpdateSyncChannelGroups(); + bool UpdateUseBackendChannelOrder(); + bool UpdateUseBackendChannelNumbers(); + bool UpdateStartGroupChannelNumbersFromOne(); + + bool m_bSyncChannelGroups = false; + bool m_bUseBackendChannelOrder = false; + bool m_bUseBackendChannelNumbers = false; + bool m_bStartGroupChannelNumbersFromOne = false; + + CPVRSettings m_settings; + std::set<IChannelGroupSettingsCallback*> m_callbacks; +}; + +} // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp new file mode 100644 index 0000000..77cc2b5 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -0,0 +1,621 @@ +/* + * 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 "PVRChannelGroups.h" + +#include "ServiceBroker.h" +#include "pvr/PVRCachedImages.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/addons/PVRClientUID.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupInternal.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <chrono> +#include <iterator> +#include <memory> +#include <mutex> +#include <numeric> +#include <string> +#include <vector> + +using namespace PVR; + +CPVRChannelGroups::CPVRChannelGroups(bool bRadio) : + m_bRadio(bRadio) +{ +} + +CPVRChannelGroups::~CPVRChannelGroups() +{ + Unload(); +} + +void CPVRChannelGroups::Unload() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& group : m_groups) + group->Unload(); + + m_groups.clear(); + m_failedClientsForChannelGroups.clear(); +} + +bool CPVRChannelGroups::Update(const std::shared_ptr<CPVRChannelGroup>& group, + bool bUpdateFromClient /* = false */) +{ + if (group->GroupName().empty() && group->GroupID() <= 0) + return true; + + std::shared_ptr<CPVRChannelGroup> updateGroup; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + + // There can be only one internal group! Make sure we never push a new one! + if (group->IsInternalGroup()) + updateGroup = GetGroupAll(); + + // try to find the group by id + if (!updateGroup && group->GroupID() > 0) + updateGroup = GetById(group->GroupID()); + + // try to find the group by name if we didn't find it yet + if (!updateGroup) + updateGroup = GetByName(group->GroupName()); + + if (updateGroup) + { + updateGroup->SetPath(group->GetPath()); + updateGroup->SetGroupID(group->GroupID()); + updateGroup->SetGroupType(group->GroupType()); + updateGroup->SetPosition(group->GetPosition()); + + // don't override properties we only store locally in our PVR database + if (!bUpdateFromClient) + { + updateGroup->SetLastWatched(group->LastWatched()); + updateGroup->SetHidden(group->IsHidden()); + updateGroup->SetLastOpened(group->LastOpened()); + } + } + else + { + updateGroup = group; + m_groups.emplace_back(updateGroup); + } + } + + // sort groups + SortGroups(); + + // persist changes + if (bUpdateFromClient) + return updateGroup->Persist(); + + return true; +} + +void CPVRChannelGroups::SortGroups() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + // check if one of the group holds a valid sort position + const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), + [](const auto& group) { return (group->GetPosition() > 0); }); + + // sort by position if we found a valid sort position + if (it != m_groups.cend()) + { + std::sort(m_groups.begin(), m_groups.end(), [](const auto& group1, const auto& group2) { + return group1->GetPosition() < group2->GetPosition(); + }); + } +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroups::GetChannelGroupMemberByPath( + const CPVRChannelsPath& path) const +{ + if (path.IsChannel()) + { + const std::shared_ptr<CPVRChannelGroup> group = GetByName(path.GetGroupName()); + if (group) + return group->GetByUniqueID( + {CPVRClientUID(path.GetAddonID(), path.GetInstanceID()).GetUID(), path.GetChannelUID()}); + } + + return {}; +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetById(int iGroupId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [iGroupId](const auto& group) { + return group->GroupID() == iGroupId; + }); + return (it != m_groups.cend()) ? (*it) : std::shared_ptr<CPVRChannelGroup>(); +} + +std::vector<std::shared_ptr<CPVRChannelGroup>> CPVRChannelGroups::GetGroupsByChannel(const std::shared_ptr<CPVRChannel>& channel, bool bExcludeHidden /* = false */) const +{ + std::vector<std::shared_ptr<CPVRChannelGroup>> groups; + + std::unique_lock<CCriticalSection> lock(m_critSection); + std::copy_if(m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups), + [bExcludeHidden, &channel](const auto& group) { + return (!bExcludeHidden || !group->IsHidden()) && group->IsGroupMember(channel); + }); + return groups; +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetGroupByPath(const std::string& strInPath) const +{ + const CPVRChannelsPath path(strInPath); + if (path.IsChannelGroup()) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), + [&path](const auto& group) { return group->GetPath() == path; }); + if (it != m_groups.cend()) + return (*it); + } + return {}; +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetByName(const std::string& strName) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [&strName](const auto& group) { + return group->GroupName() == strName; + }); + return (it != m_groups.cend()) ? (*it) : std::shared_ptr<CPVRChannelGroup>(); +} + +bool CPVRChannelGroups::HasValidDataForClients( + const std::vector<std::shared_ptr<CPVRClient>>& clients) const +{ + return m_failedClientsForChannelGroups.empty() || + std::none_of(clients.cbegin(), clients.cend(), + [this](const std::shared_ptr<CPVRClient>& client) { + return std::find(m_failedClientsForChannelGroups.cbegin(), + m_failedClientsForChannelGroups.cend(), + client->GetID()) != m_failedClientsForChannelGroups.cend(); + }); +} + +bool CPVRChannelGroups::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients, + bool bChannelsOnly /* = false */) +{ + bool bSyncWithBackends = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_PVRMANAGER_SYNCCHANNELGROUPS); + bool bUpdateAllGroups = !bChannelsOnly && bSyncWithBackends; + bool bReturn = true; + + // sync groups + const int iSize = m_groups.size(); + if (bUpdateAllGroups) + { + // get channel groups from the clients + CServiceBroker::GetPVRManager().Clients()->GetChannelGroups(clients, this, + m_failedClientsForChannelGroups); + CLog::LogFC(LOGDEBUG, LOGPVR, "{} new user defined {} channel groups fetched from clients", + (m_groups.size() - iSize), m_bRadio ? "radio" : "TV"); + } + else if (!bSyncWithBackends) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "'sync channelgroups' is disabled; skipping groups from clients"); + } + + // sync channels in groups + std::vector<std::shared_ptr<CPVRChannelGroup>> groups; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + groups = m_groups; + } + + std::vector<std::shared_ptr<CPVRChannelGroup>> emptyGroups; + + for (const auto& group : groups) + { + if (bUpdateAllGroups || group->IsInternalGroup()) + { + const int iMemberCount = group->Size(); + if (!group->UpdateFromClients(clients)) + { + CLog::LogFC(LOGERROR, LOGPVR, "Failed to update channel group '{}'", group->GroupName()); + bReturn = false; + } + + const int iChangedMembersCount = static_cast<int>(group->Size()) - iMemberCount; + if (iChangedMembersCount > 0) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "{} channel group members added to group '{}'", + iChangedMembersCount, group->GroupName()); + } + else if (iChangedMembersCount < 0) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "{} channel group members removed from group '{}'", + -iChangedMembersCount, group->GroupName()); + } + else + { + // could still be changed if same amount of members was removed as was added, but too + // complicated to calculate just for debug logging + } + } + + // remove empty groups if sync with backend is enabled and we have valid data from all clients + if (bSyncWithBackends && group->Size() == 0 && !group->IsInternalGroup() && + HasValidDataForClients(clients) && group->HasValidDataForClients(clients)) + { + emptyGroups.emplace_back(group); + } + + if (bReturn && + group->IsInternalGroup() && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bPVRChannelIconsAutoScan) + { + CServiceBroker::GetPVRManager().TriggerSearchMissingChannelIcons(group); + } + } + + for (const auto& group : emptyGroups) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting empty channel group '{}'", group->GroupName()); + DeleteGroup(group); + } + + if (bChannelsOnly) + { + // changes in the all channels group may require resorting/renumbering of other groups. + // if we updated all groups this already has been done while updating the single groups. + UpdateChannelNumbersFromAllChannelsGroup(); + } + + CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + + // persist changes + return PersistAll() && bReturn; +} + +bool CPVRChannelGroups::UpdateChannelNumbersFromAllChannelsGroup() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::accumulate( + m_groups.cbegin(), m_groups.cend(), false, [](bool changed, const auto& group) { + return group->UpdateChannelNumbersFromAllChannelsGroup() ? true : changed; + }); +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::CreateChannelGroup( + int iType, const CPVRChannelsPath& path) +{ + if (iType == PVR_GROUP_TYPE_INTERNAL) + return std::make_shared<CPVRChannelGroupInternal>(path); + else + return std::make_shared<CPVRChannelGroup>(path, GetGroupAll()); +} + +bool CPVRChannelGroups::LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + const std::shared_ptr<CPVRDatabase> database(CServiceBroker::GetPVRManager().GetTVDatabase()); + if (!database) + return false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + // Ensure we have an internal group. It is important that the internal group is created before + // loading contents from database and that it gets inserted in front of m_groups. Look at + // GetGroupAll() implementation to see why. + if (m_groups.empty()) + { + const auto internalGroup = std::make_shared<CPVRChannelGroupInternal>(m_bRadio); + m_groups.emplace_back(internalGroup); + } + + CLog::LogFC(LOGDEBUG, LOGPVR, "Loading all {} channel groups and members", + m_bRadio ? "radio" : "TV"); + + // load all channels from the database + std::map<std::pair<int, int>, std::shared_ptr<CPVRChannel>> channels; + database->Get(m_bRadio, clients, channels); + CLog::LogFC(LOGDEBUG, LOGPVR, "Fetched {} {} channels from the database", channels.size(), + m_bRadio ? "radio" : "TV"); + + // load all groups from the database + const int iLoaded = database->Get(*this); + CLog::LogFC(LOGDEBUG, LOGPVR, "Fetched {} {} groups from the database", iLoaded, + m_bRadio ? "radio" : "TV"); + + // load all group members from the database + for (const auto& group : m_groups) + { + if (!group->LoadFromDatabase(channels, clients)) + { + CLog::LogFC(LOGERROR, LOGPVR, + "Failed to load members of {} channel group '{}' from the database", + m_bRadio ? "radio" : "TV", group->GroupName()); + } + } + + // Hide empty groups + for (auto it = m_groups.begin(); it != m_groups.end();) + { + if ((*it)->Size() == 0 && !(*it)->IsInternalGroup()) + it = m_groups.erase(it); + else + ++it; + } + + return true; +} + +bool CPVRChannelGroups::PersistAll() +{ + CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting all channel group changes"); + + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::accumulate( + m_groups.cbegin(), m_groups.cend(), true, + [](bool success, const auto& group) { return !group->Persist() ? false : success; }); +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetGroupAll() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_groups.empty()) + return m_groups.front(); + + return std::shared_ptr<CPVRChannelGroup>(); +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetLastGroup() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_groups.empty()) + return m_groups.back(); + + return std::shared_ptr<CPVRChannelGroup>(); +} + +GroupMemberPair CPVRChannelGroups::GetLastAndPreviousToLastPlayedChannelGroupMember() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_groups.empty()) + return {}; + + auto groups = m_groups; + lock.unlock(); + + std::sort(groups.begin(), groups.end(), + [](const auto& a, const auto& b) { return a->LastWatched() > b->LastWatched(); }); + + // Last is always 'first' of last played group. + const GroupMemberPair members = groups[0]->GetLastAndPreviousToLastPlayedChannelGroupMember(); + std::shared_ptr<CPVRChannelGroupMember> last = members.first; + + // Previous to last is either 'second' of first group or 'first' of second group. + std::shared_ptr<CPVRChannelGroupMember> previousToLast = members.second; + if (groups.size() > 1 && groups[0]->LastWatched() && groups[1]->LastWatched() && members.second && + members.second->Channel()->LastWatched()) + { + if (groups[1]->LastWatched() >= members.second->Channel()->LastWatched()) + { + const GroupMemberPair membersPreviousToLastPlayedGroup = + groups[1]->GetLastAndPreviousToLastPlayedChannelGroupMember(); + previousToLast = membersPreviousToLastPlayedGroup.first; + } + } + + return {last, previousToLast}; +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetLastOpenedGroup() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::accumulate( + m_groups.cbegin(), m_groups.cend(), std::shared_ptr<CPVRChannelGroup>{}, + [](const std::shared_ptr<CPVRChannelGroup>& last, + const std::shared_ptr<CPVRChannelGroup>& group) + { + return group->LastOpened() > 0 && (!last || group->LastOpened() > last->LastOpened()) + ? group + : last; + }); +} + +std::vector<std::shared_ptr<CPVRChannelGroup>> CPVRChannelGroups::GetMembers(bool bExcludeHidden /* = false */) const +{ + std::vector<std::shared_ptr<CPVRChannelGroup>> groups; + + std::unique_lock<CCriticalSection> lock(m_critSection); + std::copy_if( + m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups), + [bExcludeHidden](const auto& group) { return (!bExcludeHidden || !group->IsHidden()); }); + return groups; +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetPreviousGroup(const CPVRChannelGroup& group) const +{ + { + bool bReturnNext = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_reverse_iterator it = m_groups.rbegin(); it != m_groups.rend(); ++it) + { + // return this entry + if (bReturnNext && !(*it)->IsHidden()) + return *it; + + // return the next entry + if ((*it)->GroupID() == group.GroupID()) + bReturnNext = true; + } + + // no match return last visible group + for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_reverse_iterator it = m_groups.rbegin(); it != m_groups.rend(); ++it) + { + if (!(*it)->IsHidden()) + return *it; + } + } + + // no match + return GetLastGroup(); +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroups::GetNextGroup(const CPVRChannelGroup& group) const +{ + { + bool bReturnNext = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_iterator it = m_groups.begin(); it != m_groups.end(); ++it) + { + // return this entry + if (bReturnNext && !(*it)->IsHidden()) + return *it; + + // return the next entry + if ((*it)->GroupID() == group.GroupID()) + bReturnNext = true; + } + + // no match return first visible group + for (std::vector<std::shared_ptr<CPVRChannelGroup>>::const_iterator it = m_groups.begin(); it != m_groups.end(); ++it) + { + if (!(*it)->IsHidden()) + return *it; + } + } + + // no match + return GetFirstGroup(); +} + +bool CPVRChannelGroups::AddGroup(const std::string& strName) +{ + bool bPersist(false); + std::shared_ptr<CPVRChannelGroup> group; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + + // check if there's no group with the same name yet + group = GetByName(strName); + if (!group) + { + // create a new group + group.reset(new CPVRChannelGroup(CPVRChannelsPath(m_bRadio, strName), GetGroupAll())); + + m_groups.push_back(group); + bPersist = true; + + CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + } + } + + // persist in the db if a new group was added + return bPersist ? group->Persist() : true; +} + +bool CPVRChannelGroups::DeleteGroup(const std::shared_ptr<CPVRChannelGroup>& group) +{ + // don't delete internal groups + if (group->IsInternalGroup()) + { + CLog::LogF(LOGERROR, "Internal channel group cannot be deleted"); + return false; + } + + bool bFound(false); + + // delete the group in this container + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto it = m_groups.begin(); it != m_groups.end(); ++it) + { + if (*it == group || (group->GroupID() > 0 && (*it)->GroupID() == group->GroupID())) + { + m_groups.erase(it); + bFound = true; + break; + } + } + } + + if (bFound && group->GroupID() > 0) + { + // delete the group from the database + group->Delete(); + CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + } + return bFound; +} + +bool CPVRChannelGroups::HideGroup(const std::shared_ptr<CPVRChannelGroup>& group, bool bHide) +{ + bool bReturn = false; + + if (group) + { + if (group->SetHidden(bHide)) + { + // state changed + CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + } + bReturn = true; + } + return bReturn; +} + +bool CPVRChannelGroups::CreateChannelEpgs() +{ + bool bReturn(false); + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (std::vector<std::shared_ptr<CPVRChannelGroup>>::iterator it = m_groups.begin(); it != m_groups.end(); ++it) + { + /* Only create EPGs for the internal groups */ + if ((*it)->IsInternalGroup()) + bReturn = (*it)->CreateChannelEpgs(); + } + return bReturn; +} + +int CPVRChannelGroups::CleanupCachedImages() +{ + int iCleanedImages = 0; + + // cleanup channels + iCleanedImages += GetGroupAll()->CleanupCachedImages(); + + // cleanup groups + std::vector<std::string> urlsToCheck; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + std::transform(m_groups.cbegin(), m_groups.cend(), std::back_inserter(urlsToCheck), + [](const auto& group) { return group->GetPath(); }); + } + + // kodi-generated thumbnail (see CPVRThumbLoader) + const std::string path = StringUtils::Format("pvr://channels/{}/", IsRadio() ? "radio" : "tv"); + iCleanedImages += CPVRCachedImages::Cleanup({{"pvr", path}}, urlsToCheck, true); + + return iCleanedImages; +} diff --git a/xbmc/pvr/channels/PVRChannelGroups.h b/xbmc/pvr/channels/PVRChannelGroups.h new file mode 100644 index 0000000..709dcfb --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroups.h @@ -0,0 +1,244 @@ +/* + * 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. + */ + +#pragma once + +#include "pvr/channels/PVRChannelGroup.h" +#include "threads/CriticalSection.h" + +#include <memory> +#include <mutex> +#include <string> +#include <vector> + +namespace PVR +{ + class CPVRChannel; + class CPVRClient; + + /** A container class for channel groups */ + + class CPVRChannelGroups + { + public: + /*! + * @brief Create a new group container. + * @param bRadio True if this is a container for radio channels, false if it is for tv channels. + */ + explicit CPVRChannelGroups(bool bRadio); + virtual ~CPVRChannelGroups(); + + /*! + * @brief Remove all groups from this container. + */ + void Unload(); + + /*! + * @brief Load all channel groups and all channels from PVR database. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True on success, false otherwise. + */ + bool LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients); + + /*! + * @brief Create a channel group matching the given type. + * @param iType The type for the group. + * @param path The path of the group. + * @return The new group. + */ + std::shared_ptr<CPVRChannelGroup> CreateChannelGroup(int iType, const CPVRChannelsPath& path); + + /*! + * @return Amount of groups in this container + */ + size_t Size() const + { + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_groups.size(); + } + + /*! + * @brief Update a group or add it if it's not in here yet. + * @param group The group to update. + * @param bUpdateFromClient True to save the changes in the db. + * @return True if the group was added or update successfully, false otherwise. + */ + bool Update(const std::shared_ptr<CPVRChannelGroup>& group, bool bUpdateFromClient = false); + + /*! + * @brief Called by the add-on callback to add a new group + * @param group The group to add + * @return True when updated, false otherwise + */ + bool UpdateFromClient(const std::shared_ptr<CPVRChannelGroup>& group) + { + return Update(group, true); + } + + /*! + * @brief Get a channel group member given its path + * @param strPath The path to the channel group member + * @return The channel group member, or nullptr if not found + */ + std::shared_ptr<CPVRChannelGroupMember> GetChannelGroupMemberByPath( + const CPVRChannelsPath& path) const; + + /*! + * @brief Get a pointer to a channel group given its ID. + * @param iGroupId The ID of the group. + * @return The group or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroup> GetById(int iGroupId) const; + + /*! + * @brief Get all groups the given channel is a member. + * @param channel The channel. + * @param bExcludeHidden Whenever to exclude hidden channel groups. + * @return A list of groups the channel is a member. + */ + std::vector<std::shared_ptr<CPVRChannelGroup>> GetGroupsByChannel(const std::shared_ptr<CPVRChannel>& channel, bool bExcludeHidden = false) const; + + /*! + * @brief Get a channel group given its path + * @param strPath The path to the channel group + * @return The channel group, or nullptr if not found + */ + std::shared_ptr<CPVRChannelGroup> GetGroupByPath(const std::string& strPath) const; + + /*! + * @brief Get a group given its name. + * @param strName The name. + * @return The group or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroup> GetByName(const std::string& strName) const; + + /*! + * @brief Get the group that contains all channels. + * @return The group that contains all channels. + */ + std::shared_ptr<CPVRChannelGroup> GetGroupAll() const; + + /*! + * @return The first group in this container, which always is the group with all channels. + */ + std::shared_ptr<CPVRChannelGroup> GetFirstGroup() const { return GetGroupAll(); } + + /*! + * @return The last group in this container. + */ + std::shared_ptr<CPVRChannelGroup> GetLastGroup() const; + + /*! + * @return The last and previous to last played channel group members. pair.first contains the last, pair.second the previous to last member. + */ + GroupMemberPair GetLastAndPreviousToLastPlayedChannelGroupMember() const; + + /*! + * @return The last opened group. + */ + std::shared_ptr<CPVRChannelGroup> GetLastOpenedGroup() const; + + /*! + * @brief Get the list of groups. + * @param groups The list to store the results in. + * @param bExcludeHidden Whenever to exclude hidden channel groups. + * @return The amount of items that were added. + */ + std::vector<std::shared_ptr<CPVRChannelGroup>> GetMembers(bool bExcludeHidden = false) const; + + /*! + * @brief Get the previous group in this container. + * @param group The current group. + * @return The previous group or the group containing all channels if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroup> GetPreviousGroup(const CPVRChannelGroup& group) const; + + /*! + * @brief Get the next group in this container. + * @param group The current group. + * @return The next group or the group containing all channels if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroup> GetNextGroup(const CPVRChannelGroup& group) const; + + /*! + * @brief Add a group to this container. + * @param strName The name of the group. + * @return True if the group was added, false otherwise. + */ + bool AddGroup(const std::string& strName); + + /*! + * @brief Remove a group from this container and delete it from the database. + * @param group The group to delete. + * @return True if it was deleted successfully, false if not. + */ + bool DeleteGroup(const std::shared_ptr<CPVRChannelGroup>& group); + + /*! + * @brief Hide/unhide a group in this container. + * @param group The group to hide/unhide. + * @param bHide True to hide the group, false to unhide it. + * @return True on success, false otherwise. + */ + bool HideGroup(const std::shared_ptr<CPVRChannelGroup>& group, bool bHide); + + /*! + * @brief Create EPG tags for all channels of the internal group. + * @return True if EPG tags where created successfully, false if not. + */ + bool CreateChannelEpgs(); + + /*! + * @brief Persist all changes in channel groups. + * @return True if everything was persisted, false otherwise. + */ + bool PersistAll(); + + /*! + * @return True when this container contains radio groups, false otherwise + */ + bool IsRadio() const { return m_bRadio; } + + /*! + * @brief Update data with groups and channels from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param bChannelsOnly Set to true to only update channels, not the groups themselves. + * @return True on success, false otherwise. + */ + bool UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients, + bool bChannelsOnly = false); + + /*! + * @brief Update the channel numbers across the channel groups from the all channels group + * @return True if any channel number was changed, false otherwise. + */ + bool UpdateChannelNumbersFromAllChannelsGroup(); + + /*! + * @brief Erase stale texture db entries and image files. + * @return number of cleaned up images. + */ + int CleanupCachedImages(); + + private: + void SortGroups(); + + /*! + * @brief Check, whether data for given pvr clients are currently valid. For instance, data + * can be invalid because the client's backend was offline when data was last queried. + * @param clients The clients to check. Check all active clients if vector is empty. + * @return True, if data is currently valid, false otherwise. + */ + bool HasValidDataForClients(const std::vector<std::shared_ptr<CPVRClient>>& clients) const; + + bool m_bRadio; /*!< true if this is a container for radio channels, false if it is for tv channels */ + std::vector<std::shared_ptr<CPVRChannelGroup>> m_groups; /*!< the groups in this container */ + mutable CCriticalSection m_critSection; + std::vector<int> m_failedClientsForChannelGroups; + }; +} diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp new file mode 100644 index 0000000..5f65cdd --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp @@ -0,0 +1,167 @@ +/* + * 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 "PVRChannelGroupsContainer.h" + +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/epg/EpgInfoTag.h" +#include "utils/log.h" + +#include <memory> +#include <mutex> + +using namespace PVR; + +CPVRChannelGroupsContainer::CPVRChannelGroupsContainer() : + m_groupsRadio(new CPVRChannelGroups(true)), + m_groupsTV(new CPVRChannelGroups(false)) +{ +} + +CPVRChannelGroupsContainer::~CPVRChannelGroupsContainer() +{ + Unload(); + delete m_groupsRadio; + delete m_groupsTV; +} + +bool CPVRChannelGroupsContainer::Update(const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + return LoadFromDatabase(clients) && UpdateFromClients(clients); +} + +bool CPVRChannelGroupsContainer::LoadFromDatabase( + const std::vector<std::shared_ptr<CPVRClient>>& clients) +{ + return m_groupsTV->LoadFromDatabase(clients) && m_groupsRadio->LoadFromDatabase(clients); +} + +bool CPVRChannelGroupsContainer::UpdateFromClients( + const std::vector<std::shared_ptr<CPVRClient>>& clients, bool bChannelsOnly /* = false */) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_bIsUpdating) + return false; + m_bIsUpdating = true; + lock.unlock(); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Updating {}", bChannelsOnly ? "channels" : "channel groups"); + bool bReturn = m_groupsTV->UpdateFromClients(clients, bChannelsOnly) && + m_groupsRadio->UpdateFromClients(clients, bChannelsOnly); + + lock.lock(); + m_bIsUpdating = false; + lock.unlock(); + + return bReturn; +} + +void CPVRChannelGroupsContainer::Unload() +{ + m_groupsRadio->Unload(); + m_groupsTV->Unload(); +} + +CPVRChannelGroups* CPVRChannelGroupsContainer::Get(bool bRadio) const +{ + return bRadio ? m_groupsRadio : m_groupsTV; +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroupsContainer::GetGroupAll(bool bRadio) const +{ + return Get(bRadio)->GetGroupAll(); +} + +std::shared_ptr<CPVRChannelGroup> CPVRChannelGroupsContainer::GetByIdFromAll(int iGroupId) const +{ + std::shared_ptr<CPVRChannelGroup> group = m_groupsTV->GetById(iGroupId); + if (!group) + group = m_groupsRadio->GetById(iGroupId); + + return group; +} + +std::shared_ptr<CPVRChannel> CPVRChannelGroupsContainer::GetChannelById(int iChannelId) const +{ + std::shared_ptr<CPVRChannel> channel = m_groupsTV->GetGroupAll()->GetByChannelID(iChannelId); + if (!channel) + channel = m_groupsRadio->GetGroupAll()->GetByChannelID(iChannelId); + + return channel; +} + +std::shared_ptr<CPVRChannel> CPVRChannelGroupsContainer::GetChannelForEpgTag(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const +{ + if (!epgTag) + return {}; + + return Get(epgTag->IsRadio())->GetGroupAll()->GetByUniqueID(epgTag->UniqueChannelID(), epgTag->ClientID()); +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroupsContainer::GetChannelGroupMemberByPath( + const std::string& strPath) const +{ + const CPVRChannelsPath path(strPath); + if (path.IsValid()) + return Get(path.IsRadio())->GetChannelGroupMemberByPath(path); + + return {}; +} + +std::shared_ptr<CPVRChannel> CPVRChannelGroupsContainer::GetByPath(const std::string& strPath) const +{ + const std::shared_ptr<CPVRChannelGroupMember> groupMember = GetChannelGroupMemberByPath(strPath); + if (groupMember) + return groupMember->Channel(); + + return {}; +} + +std::shared_ptr<CPVRChannel> CPVRChannelGroupsContainer::GetByUniqueID(int iUniqueChannelId, int iClientID) const +{ + std::shared_ptr<CPVRChannel> channel; + std::shared_ptr<CPVRChannelGroup> channelgroup = GetGroupAllTV(); + if (channelgroup) + channel = channelgroup->GetByUniqueID(iUniqueChannelId, iClientID); + + if (!channelgroup || !channel) + channelgroup = GetGroupAllRadio(); + if (channelgroup) + channel = channelgroup->GetByUniqueID(iUniqueChannelId, iClientID); + + return channel; +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroupsContainer:: + GetLastPlayedChannelGroupMember() const +{ + std::shared_ptr<CPVRChannelGroupMember> channelTV = + m_groupsTV->GetGroupAll()->GetLastPlayedChannelGroupMember(); + std::shared_ptr<CPVRChannelGroupMember> channelRadio = + m_groupsRadio->GetGroupAll()->GetLastPlayedChannelGroupMember(); + + if (!channelTV || (channelRadio && + channelRadio->Channel()->LastWatched() > channelTV->Channel()->LastWatched())) + return channelRadio; + + return channelTV; +} + +bool CPVRChannelGroupsContainer::CreateChannelEpgs() +{ + bool bReturn = m_groupsTV->CreateChannelEpgs(); + bReturn &= m_groupsRadio->CreateChannelEpgs(); + return bReturn; +} + +int CPVRChannelGroupsContainer::CleanupCachedImages() +{ + return m_groupsTV->CleanupCachedImages() + m_groupsRadio->CleanupCachedImages(); +} diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h new file mode 100644 index 0000000..d7b7f57 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#pragma once + +#include "threads/CriticalSection.h" + +#include <memory> +#include <vector> + +namespace PVR +{ + class CPVRChannel; + class CPVRChannelGroup; + class CPVRChannelGroupMember; + class CPVRChannelGroups; + class CPVRClient; + class CPVREpgInfoTag; + + class CPVRChannelGroupsContainer + { + public: + /*! + * @brief Create a new container for all channel groups + */ + CPVRChannelGroupsContainer(); + + /*! + * @brief Destroy this container. + */ + virtual ~CPVRChannelGroupsContainer(); + + /*! + * @brief Update all channel groups and all channels from PVR database and from given clients. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True on success, false otherwise. + */ + bool Update(const std::vector<std::shared_ptr<CPVRClient>>& clients); + + /*! + * @brief Update data with groups and channels from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param bChannelsOnly Set to true to only update channels, not the groups themselves. + * @return True on success, false otherwise. + */ + bool UpdateFromClients(const std::vector<std::shared_ptr<CPVRClient>>& clients, + bool bChannelsOnly = false); + + /*! + * @brief Unload and destruct all channel groups and all channels in them. + */ + void Unload(); + + /*! + * @brief Get the TV channel groups. + * @return The TV channel groups. + */ + CPVRChannelGroups* GetTV() const { return Get(false); } + + /*! + * @brief Get the radio channel groups. + * @return The radio channel groups. + */ + CPVRChannelGroups* GetRadio() const { return Get(true); } + + /*! + * @brief Get the radio or TV channel groups. + * @param bRadio If true, get the radio channel groups. Get the TV channel groups otherwise. + * @return The requested groups. + */ + CPVRChannelGroups* Get(bool bRadio) const; + + /*! + * @brief Get the group containing all TV channels. + * @return The group containing all TV channels. + */ + std::shared_ptr<CPVRChannelGroup> GetGroupAllTV() const { return GetGroupAll(false); } + + /*! + * @brief Get the group containing all radio channels. + * @return The group containing all radio channels. + */ + std::shared_ptr<CPVRChannelGroup> GetGroupAllRadio() const { return GetGroupAll(true); } + + /*! + * @brief Get the group containing all TV or radio channels. + * @param bRadio If true, get the group containing all radio channels. Get the group containing all TV channels otherwise. + * @return The requested group. + */ + std::shared_ptr<CPVRChannelGroup> GetGroupAll(bool bRadio) const; + + /*! + * @brief Get a group given it's ID. + * @param iGroupId The ID of the group. + * @return The requested group or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroup> GetByIdFromAll(int iGroupId) const; + + /*! + * @brief Get a channel given it's database ID. + * @param iChannelId The ID of the channel. + * @return The channel or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannel> GetChannelById(int iChannelId) const; + + /*! + * @brief Get the channel for the given epg tag. + * @param epgTag The epg tag. + * @return The channel. + */ + std::shared_ptr<CPVRChannel> GetChannelForEpgTag(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const; + + /*! + * @brief Get a channel given it's path. + * @param strPath The path. + * @return The channel or nullptr if it wasn't found. + */ + std::shared_ptr<CPVRChannel> GetByPath(const std::string& strPath) const; + + /*! + * @brief Get a channel group member given it's path. + * @param strPath The path. + * @return The channel group member or nullptr if it wasn't found. + */ + std::shared_ptr<CPVRChannelGroupMember> GetChannelGroupMemberByPath( + const std::string& strPath) const; + + /*! + * @brief Get a channel given it's channel ID from all containers. + * @param iUniqueChannelId The unique channel id on the client. + * @param iClientID The ID of the client. + * @return The channel or NULL if it wasn't found. + */ + std::shared_ptr<CPVRChannel> GetByUniqueID(int iUniqueChannelId, int iClientID) const; + + /*! + * @brief Get the channel group member that was played last. + * @return The requested channel group member or nullptr. + */ + std::shared_ptr<CPVRChannelGroupMember> GetLastPlayedChannelGroupMember() const; + + /*! + * @brief Create EPG tags for channels in all internal channel groups. + * @return True if EPG tags were created successfully. + */ + bool CreateChannelEpgs(); + + /*! + * @brief Erase stale texture db entries and image files. + * @return number of cleaned up images. + */ + int CleanupCachedImages(); + + private: + CPVRChannelGroupsContainer& operator=(const CPVRChannelGroupsContainer&) = delete; + CPVRChannelGroupsContainer(const CPVRChannelGroupsContainer&) = delete; + + /*! + * @brief Load all channel groups and all channels from PVR database. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True on success, false otherwise. + */ + bool LoadFromDatabase(const std::vector<std::shared_ptr<CPVRClient>>& clients); + + CPVRChannelGroups* m_groupsRadio; /*!< all radio channel groups */ + CPVRChannelGroups* m_groupsTV; /*!< all TV channel groups */ + CCriticalSection m_critSection; + bool m_bIsUpdating = false; + }; +} diff --git a/xbmc/pvr/channels/PVRChannelNumber.cpp b/xbmc/pvr/channels/PVRChannelNumber.cpp new file mode 100644 index 0000000..403938e --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelNumber.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-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 "PVRChannelNumber.h" + +#include "utils/StringUtils.h" + +using namespace PVR; + +const char CPVRChannelNumber::SEPARATOR = '.'; + +std::string CPVRChannelNumber::FormattedChannelNumber() const +{ + return ToString(SEPARATOR); +} + +std::string CPVRChannelNumber::SortableChannelNumber() const +{ + // Note: The subchannel separator is a character that does not work for a + // SortItem (at least not on all platforms). See SortUtils::Sort for + // details. Only numbers, letters and the blank are safe to use. + return ToString(' '); +} + +std::string CPVRChannelNumber::ToString(char separator) const +{ + if (m_iSubChannelNumber == 0) + return std::to_string(m_iChannelNumber); + else + return StringUtils::Format("{}{}{}", m_iChannelNumber, separator, m_iSubChannelNumber); +} diff --git a/xbmc/pvr/channels/PVRChannelNumber.h b/xbmc/pvr/channels/PVRChannelNumber.h new file mode 100644 index 0000000..b6e2efc --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelNumber.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <string> + +namespace PVR +{ + class CPVRChannelNumber + { + public: + CPVRChannelNumber() = default; + + constexpr CPVRChannelNumber(unsigned int iChannelNumber, unsigned int iSubChannelNumber) + : m_iChannelNumber(iChannelNumber), m_iSubChannelNumber(iSubChannelNumber) {} + + constexpr bool operator ==(const CPVRChannelNumber& right) const + { + return (m_iChannelNumber == right.m_iChannelNumber && + m_iSubChannelNumber == right.m_iSubChannelNumber); + } + + constexpr bool operator !=(const CPVRChannelNumber& right) const + { + return !(*this == right); + } + + constexpr bool operator <(const CPVRChannelNumber& right) const + { + return m_iChannelNumber == right.m_iChannelNumber + ? m_iSubChannelNumber < right.m_iSubChannelNumber + : m_iChannelNumber < right.m_iChannelNumber; + } + + /*! + * @brief Check whether this channel number is valid (main channel number > 0). + * @return True if valid, false otherwise.. + */ + constexpr bool IsValid() const { return m_iChannelNumber > 0; } + + /*! + * @brief Set the primary channel number. + * @return The channel number. + */ + constexpr unsigned int GetChannelNumber() const + { + return m_iChannelNumber; + } + + /*! + * @brief Set the sub channel number. + * @return The sub channel number (ATSC). + */ + constexpr unsigned int GetSubChannelNumber() const + { + return m_iSubChannelNumber; + } + + /*! + * @brief The character used to separate channel and subchannel number. + */ + static const char SEPARATOR; // '.' + + /*! + * @brief Get a string representation for the channel number. + * @return The formatted string in the form <channel>SEPARATOR<subchannel>. + */ + std::string FormattedChannelNumber() const; + + /*! + * @brief Get a string representation for the channel number that can be used for SortItems. + * @return The sortable string in the form <channel> <subchannel>. + */ + std::string SortableChannelNumber() const; + + private: + std::string ToString(char separator) const; + + unsigned int m_iChannelNumber = 0; + unsigned int m_iSubChannelNumber = 0; + }; +} diff --git a/xbmc/pvr/channels/PVRChannelsPath.cpp b/xbmc/pvr/channels/PVRChannelsPath.cpp new file mode 100644 index 0000000..4ae2e28 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelsPath.cpp @@ -0,0 +1,190 @@ +/* + * 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 "PVRChannelsPath.h" + +#include "URL.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <string> +#include <vector> + +using namespace PVR; + +const std::string CPVRChannelsPath::PATH_TV_CHANNELS = "pvr://channels/tv/"; +const std::string CPVRChannelsPath::PATH_RADIO_CHANNELS = "pvr://channels/radio/"; + + +CPVRChannelsPath::CPVRChannelsPath(const std::string& strPath) +{ + std::string strVarPath = TrimSlashes(strPath); + const std::vector<std::string> segments = URIUtils::SplitPath(strVarPath); + + for (const std::string& segment : segments) + { + switch (m_kind) + { + case Kind::INVALID: + if (segment == "pvr://") + m_kind = Kind::PROTO; // pvr:// followed by something => go on + else if (segment == "pvr:" && segments.size() == 1) // just pvr:// => invalid + strVarPath = "pvr:/"; + break; + + case Kind::PROTO: + if (segment == "channels") + m_kind = Kind::EMPTY; // pvr://channels + else + m_kind = Kind::INVALID; + break; + + case Kind::EMPTY: + if (segment == "tv" || segment == "radio") + { + m_kind = Kind::ROOT; // pvr://channels/(tv|radio) + m_bRadio = (segment == "radio"); + } + else + { + CLog::LogF(LOGERROR, "Invalid channels path '{}' - channel root segment syntax error.", + strPath); + m_kind = Kind::INVALID; + } + break; + + case Kind::ROOT: + m_kind = Kind::GROUP; // pvr://channels/(tv|radio)/<groupname> + m_group = CURL::Decode(segment); + break; + + case Kind::GROUP: + { + std::vector<std::string> tokens = StringUtils::Split(segment, "_"); + if (tokens.size() == 2) + { + std::vector<std::string> instance = StringUtils::Split(tokens[0], "@"); + if (instance.size() == 2) + { + m_instanceID = std::atoi(instance[0].c_str()); + m_addonID = instance[1]; + } + else + { + m_instanceID = ADDON::ADDON_SINGLETON_INSTANCE_ID; + m_addonID = tokens[0]; + } + + tokens = StringUtils::Split(tokens[1], "."); + if (tokens.size() == 2 && tokens[1] == "pvr") + { + std::string channelUID = tokens[0]; + if (!channelUID.empty() && channelUID.find_first_not_of("0123456789") == std::string::npos) + m_iChannelUID = std::atoi(channelUID.c_str()); + } + } + + if (!m_addonID.empty() && m_iChannelUID >= 0) + { + m_kind = Kind:: + CHANNEL; // pvr://channels/(tv|radio)/<groupname>/<instanceid>@<addonid>_<channeluid>.pvr + } + else + { + CLog::LogF(LOGERROR, "Invalid channels path '{}' - channel segment syntax error.", + strPath); + m_kind = Kind::INVALID; + } + break; + } + + case Kind::CHANNEL: + CLog::LogF(LOGERROR, "Invalid channels path '{}' - too many path segments.", strPath); + m_kind = Kind::INVALID; // too many segments + break; + } + + if (m_kind == Kind::INVALID) + break; + } + + // append slash to all folders + if (m_kind < Kind::CHANNEL) + strVarPath.append("/"); + + m_path = strVarPath; +} + +CPVRChannelsPath::CPVRChannelsPath(bool bRadio, bool bHidden, const std::string& strGroupName) + : m_bRadio(bRadio) +{ + if (!bHidden && strGroupName.empty()) + m_kind = Kind::EMPTY; + else + m_kind = Kind::GROUP; + + m_group = bHidden ? ".hidden" : strGroupName; + m_path = + StringUtils::Format("pvr://channels/{}/{}", bRadio ? "radio" : "tv", CURL::Encode(m_group)); + + if (!m_group.empty()) + m_path.append("/"); +} + +CPVRChannelsPath::CPVRChannelsPath(bool bRadio, const std::string& strGroupName) + : m_bRadio(bRadio) +{ + if (strGroupName.empty()) + m_kind = Kind::EMPTY; + else + m_kind = Kind::GROUP; + + m_group = strGroupName; + m_path = + StringUtils::Format("pvr://channels/{}/{}", bRadio ? "radio" : "tv", CURL::Encode(m_group)); + + if (!m_group.empty()) + m_path.append("/"); +} + +CPVRChannelsPath::CPVRChannelsPath(bool bRadio, + const std::string& strGroupName, + const std::string& strAddonID, + ADDON::AddonInstanceId instanceID, + int iChannelUID) + : m_bRadio(bRadio) +{ + if (!strGroupName.empty() && !strAddonID.empty() && iChannelUID >= 0) + { + m_kind = Kind::CHANNEL; + m_group = strGroupName; + m_addonID = strAddonID; + m_instanceID = instanceID; + m_iChannelUID = iChannelUID; + m_path = StringUtils::Format("pvr://channels/{}/{}/{}@{}_{}.pvr", bRadio ? "radio" : "tv", + CURL::Encode(m_group), m_instanceID, m_addonID, m_iChannelUID); + } +} + +bool CPVRChannelsPath::IsHiddenChannelGroup() const +{ + return m_kind == Kind::GROUP && m_group == ".hidden"; +} + +std::string CPVRChannelsPath::TrimSlashes(const std::string& strString) +{ + std::string strTrimmed = strString; + while (!strTrimmed.empty() && strTrimmed.front() == '/') + strTrimmed.erase(0, 1); + + while (!strTrimmed.empty() && strTrimmed.back() == '/') + strTrimmed.pop_back(); + + return strTrimmed; +} diff --git a/xbmc/pvr/channels/PVRChannelsPath.h b/xbmc/pvr/channels/PVRChannelsPath.h new file mode 100644 index 0000000..7b825c1 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelsPath.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#pragma once + +#include "addons/IAddon.h" + +class CDateTime; + +namespace PVR +{ + class CPVRChannelsPath + { + public: + static const std::string PATH_TV_CHANNELS; + static const std::string PATH_RADIO_CHANNELS; + + explicit CPVRChannelsPath(const std::string& strPath); + CPVRChannelsPath(bool bRadio, const std::string& strGroupName); + CPVRChannelsPath(bool bRadio, bool bHidden, const std::string& strGroupName); + CPVRChannelsPath(bool bRadio, + const std::string& strGroupName, + const std::string& strAddonID, + ADDON::AddonInstanceId instanceID, + int iChannelUID); + + operator std::string() const { return m_path; } + bool operator ==(const CPVRChannelsPath& right) const { return m_path == right.m_path; } + bool operator !=(const CPVRChannelsPath& right) const { return !(*this == right); } + + bool IsValid() const { return m_kind > Kind::PROTO; } + + bool IsEmpty() const { return m_kind == Kind::EMPTY; } + bool IsChannelsRoot() const { return m_kind == Kind::ROOT; } + bool IsChannelGroup() const { return m_kind == Kind::GROUP; } + bool IsChannel() const { return m_kind == Kind::CHANNEL; } + + bool IsHiddenChannelGroup() const; + + bool IsRadio() const { return m_bRadio; } + + const std::string& GetGroupName() const { return m_group; } + const std::string& GetAddonID() const { return m_addonID; } + ADDON::AddonInstanceId GetInstanceID() const { return m_instanceID; } + int GetChannelUID() const { return m_iChannelUID; } + + private: + static std::string TrimSlashes(const std::string& strString); + + enum class Kind + { + INVALID, + PROTO, + EMPTY, + ROOT, + GROUP, + CHANNEL, + }; + + Kind m_kind = Kind::INVALID; + bool m_bRadio = false;; + std::string m_path; + std::string m_group; + std::string m_addonID; + ADDON::AddonInstanceId m_instanceID{ADDON::ADDON_SINGLETON_INSTANCE_ID}; + int m_iChannelUID = -1; + }; +} diff --git a/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp b/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp new file mode 100644 index 0000000..e1ee649 --- /dev/null +++ b/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2005-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 "PVRRadioRDSInfoTag.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "utils/Archive.h" +#include "utils/CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <algorithm> +#include <mutex> +#include <string> +#include <utility> + +using namespace PVR; + +CPVRRadioRDSInfoTag::CPVRRadioRDSInfoTag() +{ + Clear(); +} + +void CPVRRadioRDSInfoTag::Serialize(CVariant& value) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + value["strLanguage"] = m_strLanguage; + value["strCountry"] = m_strCountry; + value["strTitle"] = m_strTitle; + value["strBand"] = m_strBand; + value["strArtist"] = m_strArtist; + value["strComposer"] = m_strComposer; + value["strConductor"] = m_strConductor; + value["strAlbum"] = m_strAlbum; + value["iAlbumTracknumber"] = m_iAlbumTracknumber; + value["strProgStation"] = m_strProgStation; + value["strProgStyle"] = m_strProgStyle; + value["strProgHost"] = m_strProgHost; + value["strProgWebsite"] = m_strProgWebsite; + value["strProgNow"] = m_strProgNow; + value["strProgNext"] = m_strProgNext; + value["strPhoneHotline"] = m_strPhoneHotline; + value["strEMailHotline"] = m_strEMailHotline; + value["strPhoneStudio"] = m_strPhoneStudio; + value["strEMailStudio"] = m_strEMailStudio; + value["strSMSStudio"] = m_strSMSStudio; + value["strRadioStyle"] = m_strRadioStyle; +} + +void CPVRRadioRDSInfoTag::Archive(CArchive& ar) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (ar.IsStoring()) + { + ar << m_strLanguage; + ar << m_strCountry; + ar << m_strTitle; + ar << m_strBand; + ar << m_strArtist; + ar << m_strComposer; + ar << m_strConductor; + ar << m_strAlbum; + ar << m_iAlbumTracknumber; + ar << m_strProgStation; + ar << m_strProgStyle; + ar << m_strProgHost; + ar << m_strProgWebsite; + ar << m_strProgNow; + ar << m_strProgNext; + ar << m_strPhoneHotline; + ar << m_strEMailHotline; + ar << m_strPhoneStudio; + ar << m_strEMailStudio; + ar << m_strSMSStudio; + ar << m_strRadioStyle; + } + else + { + ar >> m_strLanguage; + ar >> m_strCountry; + ar >> m_strTitle; + ar >> m_strBand; + ar >> m_strArtist; + ar >> m_strComposer; + ar >> m_strConductor; + ar >> m_strAlbum; + ar >> m_iAlbumTracknumber; + ar >> m_strProgStation; + ar >> m_strProgStyle; + ar >> m_strProgHost; + ar >> m_strProgWebsite; + ar >> m_strProgNow; + ar >> m_strProgNext; + ar >> m_strPhoneHotline; + ar >> m_strEMailHotline; + ar >> m_strPhoneStudio; + ar >> m_strEMailStudio; + ar >> m_strSMSStudio; + ar >> m_strRadioStyle; + } +} + +bool CPVRRadioRDSInfoTag::operator==(const CPVRRadioRDSInfoTag& right) const +{ + if (this == &right) + return true; + + std::unique_lock<CCriticalSection> lock(m_critSection); + return ( + m_strLanguage == right.m_strLanguage && m_strCountry == right.m_strCountry && + m_strTitle == right.m_strTitle && m_strBand == right.m_strBand && + m_strArtist == right.m_strArtist && m_strComposer == right.m_strComposer && + m_strConductor == right.m_strConductor && m_strAlbum == right.m_strAlbum && + m_iAlbumTracknumber == right.m_iAlbumTracknumber && m_strInfoNews == right.m_strInfoNews && + m_strInfoNewsLocal == right.m_strInfoNewsLocal && m_strInfoSport == right.m_strInfoSport && + m_strInfoStock == right.m_strInfoStock && m_strInfoWeather == right.m_strInfoWeather && + m_strInfoLottery == right.m_strInfoLottery && m_strInfoOther == right.m_strInfoOther && + m_strProgStyle == right.m_strProgStyle && m_strProgHost == right.m_strProgHost && + m_strProgStation == right.m_strProgStation && m_strProgWebsite == right.m_strProgWebsite && + m_strProgNow == right.m_strProgNow && m_strProgNext == right.m_strProgNext && + m_strPhoneHotline == right.m_strPhoneHotline && + m_strEMailHotline == right.m_strEMailHotline && m_strPhoneStudio == right.m_strPhoneStudio && + m_strEMailStudio == right.m_strEMailStudio && m_strSMSStudio == right.m_strSMSStudio && + m_strRadioStyle == right.m_strRadioStyle && m_strInfoHoroscope == right.m_strInfoHoroscope && + m_strInfoCinema == right.m_strInfoCinema && m_strComment == right.m_strComment && + m_strEditorialStaff == right.m_strEditorialStaff && m_strRadioText == right.m_strRadioText && + m_strProgramServiceText == right.m_strProgramServiceText && + m_strProgramServiceLine0 == right.m_strProgramServiceLine0 && + m_strProgramServiceLine1 == right.m_strProgramServiceLine1 && + m_bHaveRadioText == right.m_bHaveRadioText && + m_bHaveRadioTextPlus == right.m_bHaveRadioTextPlus); +} + +bool CPVRRadioRDSInfoTag::operator !=(const CPVRRadioRDSInfoTag& right) const +{ + if (this == &right) + return false; + + return !(*this == right); +} + +void CPVRRadioRDSInfoTag::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_RDS_SpeechActive = false; + + ResetSongInformation(); + + m_strLanguage.erase(); + m_strCountry.erase(); + m_strComment.erase(); + m_strInfoNews.Clear(); + m_strInfoNewsLocal.Clear(); + m_strInfoSport.Clear(); + m_strInfoStock.Clear(); + m_strInfoWeather.Clear(); + m_strInfoLottery.Clear(); + m_strInfoOther.Clear(); + m_strInfoHoroscope.Clear(); + m_strInfoCinema.Clear(); + m_strProgStyle.erase(); + m_strProgHost.erase(); + m_strProgStation.erase(); + m_strProgWebsite.erase(); + m_strPhoneHotline.erase(); + m_strEMailHotline.erase(); + m_strPhoneStudio.erase(); + m_strEMailStudio.erase(); + m_strSMSStudio.erase(); + m_strRadioStyle = "unknown"; + m_strEditorialStaff.Clear(); + m_strRadioText.Clear(); + m_strProgramServiceText.Clear(); + m_strProgramServiceLine0.erase(); + m_strProgramServiceLine1.erase(); + + m_bHaveRadioText = false; + m_bHaveRadioTextPlus = false; +} + +void CPVRRadioRDSInfoTag::ResetSongInformation() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_strTitle.erase(); + m_strBand.erase(); + m_strArtist.erase(); + m_strComposer.erase(); + m_strConductor.erase(); + m_strAlbum.erase(); + m_iAlbumTracknumber = 0; +} + +void CPVRRadioRDSInfoTag::SetSpeechActive(bool active) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_RDS_SpeechActive = active; +} + +void CPVRRadioRDSInfoTag::SetLanguage(const std::string& strLanguage) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strLanguage = Trim(strLanguage); +} + +const std::string& CPVRRadioRDSInfoTag::GetLanguage() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strLanguage; +} + +void CPVRRadioRDSInfoTag::SetCountry(const std::string& strCountry) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strCountry = Trim(strCountry); +} + +const std::string& CPVRRadioRDSInfoTag::GetCountry() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strCountry; +} + +void CPVRRadioRDSInfoTag::SetTitle(const std::string& strTitle) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strTitle = Trim(strTitle); +} + +void CPVRRadioRDSInfoTag::SetArtist(const std::string& strArtist) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strArtist = Trim(strArtist); +} + +void CPVRRadioRDSInfoTag::SetBand(const std::string& strBand) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strBand = Trim(strBand); + g_charsetConverter.unknownToUTF8(m_strBand); +} + +void CPVRRadioRDSInfoTag::SetComposer(const std::string& strComposer) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strComposer = Trim(strComposer); + g_charsetConverter.unknownToUTF8(m_strComposer); +} + +void CPVRRadioRDSInfoTag::SetConductor(const std::string& strConductor) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strConductor = Trim(strConductor); + g_charsetConverter.unknownToUTF8(m_strConductor); +} + +void CPVRRadioRDSInfoTag::SetAlbum(const std::string& strAlbum) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strAlbum = Trim(strAlbum); + g_charsetConverter.unknownToUTF8(m_strAlbum); +} + +void CPVRRadioRDSInfoTag::SetAlbumTrackNumber(int track) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iAlbumTracknumber = track; +} + +void CPVRRadioRDSInfoTag::SetComment(const std::string& strComment) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strComment = Trim(strComment); + g_charsetConverter.unknownToUTF8(m_strComment); +} + +const std::string& CPVRRadioRDSInfoTag::GetTitle() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strTitle; +} + +const std::string& CPVRRadioRDSInfoTag::GetArtist() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strArtist; +} + +const std::string& CPVRRadioRDSInfoTag::GetBand() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strBand; +} + +const std::string& CPVRRadioRDSInfoTag::GetComposer() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strComposer; +} + +const std::string& CPVRRadioRDSInfoTag::GetConductor() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strConductor; +} + +const std::string& CPVRRadioRDSInfoTag::GetAlbum() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strAlbum; +} + +int CPVRRadioRDSInfoTag::GetAlbumTrackNumber() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_iAlbumTracknumber; +} + +const std::string& CPVRRadioRDSInfoTag::GetComment() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strComment; +} + +void CPVRRadioRDSInfoTag::SetInfoNews(const std::string& strNews) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoNews.Add(strNews); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoNews() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoNews.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoNewsLocal(const std::string& strNews) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoNewsLocal.Add(strNews); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoNewsLocal() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoNewsLocal.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoSport(const std::string& strSport) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoSport.Add(strSport); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoSport() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoSport.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoStock(const std::string& strStock) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoStock.Add(strStock); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoStock() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoStock.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoWeather(const std::string& strWeather) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoWeather.Add(strWeather); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoWeather() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoWeather.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoLottery(const std::string& strLottery) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoLottery.Add(strLottery); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoLottery() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoLottery.GetText(); +} + +void CPVRRadioRDSInfoTag::SetEditorialStaff(const std::string& strEditorialStaff) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strEditorialStaff.Add(strEditorialStaff); +} + +const std::string CPVRRadioRDSInfoTag::GetEditorialStaff() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strEditorialStaff.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoHoroscope(const std::string& strHoroscope) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoHoroscope.Add(strHoroscope); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoHoroscope() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoHoroscope.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoCinema(const std::string& strCinema) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoCinema.Add(strCinema); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoCinema() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoCinema.GetText(); +} + +void CPVRRadioRDSInfoTag::SetInfoOther(const std::string& strOther) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strInfoOther.Add(strOther); +} + +const std::string CPVRRadioRDSInfoTag::GetInfoOther() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strInfoOther.GetText(); +} + +void CPVRRadioRDSInfoTag::SetProgStation(const std::string& strProgStation) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgStation = Trim(strProgStation); + g_charsetConverter.unknownToUTF8(m_strProgStation); +} + +void CPVRRadioRDSInfoTag::SetProgHost(const std::string& strProgHost) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgHost = Trim(strProgHost); + g_charsetConverter.unknownToUTF8(m_strProgHost); +} + +void CPVRRadioRDSInfoTag::SetProgStyle(const std::string& strProgStyle) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgStyle = Trim(strProgStyle); + g_charsetConverter.unknownToUTF8(m_strProgStyle); +} + +void CPVRRadioRDSInfoTag::SetProgWebsite(const std::string& strWebsite) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgWebsite = Trim(strWebsite); + g_charsetConverter.unknownToUTF8(m_strProgWebsite); +} + +void CPVRRadioRDSInfoTag::SetProgNow(const std::string& strNow) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgNow = Trim(strNow); + g_charsetConverter.unknownToUTF8(m_strProgNow); +} + +void CPVRRadioRDSInfoTag::SetProgNext(const std::string& strNext) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgNext = Trim(strNext); + g_charsetConverter.unknownToUTF8(m_strProgNext); +} + +void CPVRRadioRDSInfoTag::SetPhoneHotline(const std::string& strHotline) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strPhoneHotline = Trim(strHotline); + g_charsetConverter.unknownToUTF8(m_strPhoneHotline); +} + +void CPVRRadioRDSInfoTag::SetEMailHotline(const std::string& strHotline) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strEMailHotline = Trim(strHotline); + g_charsetConverter.unknownToUTF8(m_strEMailHotline); +} + +void CPVRRadioRDSInfoTag::SetPhoneStudio(const std::string& strPhone) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strPhoneStudio = Trim(strPhone); + g_charsetConverter.unknownToUTF8(m_strPhoneStudio); +} + +void CPVRRadioRDSInfoTag::SetEMailStudio(const std::string& strEMail) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strEMailStudio = Trim(strEMail); + g_charsetConverter.unknownToUTF8(m_strEMailStudio); +} + +void CPVRRadioRDSInfoTag::SetSMSStudio(const std::string& strSMS) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strSMSStudio = Trim(strSMS); + g_charsetConverter.unknownToUTF8(m_strSMSStudio); +} + +const std::string& CPVRRadioRDSInfoTag::GetProgStyle() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strProgStyle; +} + +const std::string& CPVRRadioRDSInfoTag::GetProgHost() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strProgHost; +} + +const std::string& CPVRRadioRDSInfoTag::GetProgStation() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strProgStation; +} + +const std::string& CPVRRadioRDSInfoTag::GetProgWebsite() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strProgWebsite; +} + +const std::string& CPVRRadioRDSInfoTag::GetProgNow() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strProgNow; +} + +const std::string& CPVRRadioRDSInfoTag::GetProgNext() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strProgNext; +} + +const std::string& CPVRRadioRDSInfoTag::GetPhoneHotline() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strPhoneHotline; +} + +const std::string& CPVRRadioRDSInfoTag::GetEMailHotline() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strEMailHotline; +} + +const std::string& CPVRRadioRDSInfoTag::GetPhoneStudio() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strPhoneStudio; +} + +const std::string& CPVRRadioRDSInfoTag::GetEMailStudio() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strEMailStudio; +} + +const std::string& CPVRRadioRDSInfoTag::GetSMSStudio() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strSMSStudio; +} + +void CPVRRadioRDSInfoTag::SetRadioStyle(const std::string& style) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strRadioStyle = style; +} + +const std::string CPVRRadioRDSInfoTag::GetRadioStyle() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strRadioStyle; +} + +void CPVRRadioRDSInfoTag::SetRadioText(const std::string& strRadioText) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strRadioText.Add(strRadioText); +} + +std::string CPVRRadioRDSInfoTag::GetRadioText(unsigned int line) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_strRadioText.Size() > 0) + { + return m_strRadioText.GetLine(line); + } + else if (line == 0) + { + return m_strProgramServiceLine0; + } + else if (line == 1) + { + return m_strProgramServiceLine1; + } + return {}; +} + +void CPVRRadioRDSInfoTag::SetProgramServiceText(const std::string& strPSText) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strProgramServiceText.Add(strPSText); + + m_strProgramServiceLine0.erase(); + for (size_t i = m_strProgramServiceText.MaxSize() / 2 + 1; i < m_strProgramServiceText.MaxSize(); + ++i) + { + m_strProgramServiceLine0 += m_strProgramServiceText.GetLine(i); + m_strProgramServiceLine0 += ' '; + } + + m_strProgramServiceLine1.erase(); + for (size_t i = 0; i < m_strProgramServiceText.MaxSize() / 2; ++i) + { + m_strProgramServiceLine1 += m_strProgramServiceText.GetLine(i); + m_strProgramServiceLine1 += ' '; + } +} + +void CPVRRadioRDSInfoTag::SetPlayingRadioText(bool yesNo) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bHaveRadioText = yesNo; +} + +bool CPVRRadioRDSInfoTag::IsPlayingRadioText() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bHaveRadioText; +} + +void CPVRRadioRDSInfoTag::SetPlayingRadioTextPlus(bool yesNo) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bHaveRadioTextPlus = yesNo; +} + +bool CPVRRadioRDSInfoTag::IsPlayingRadioTextPlus() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bHaveRadioTextPlus; +} + +std::string CPVRRadioRDSInfoTag::Trim(const std::string& value) +{ + std::string trimmedValue(value); + StringUtils::TrimLeft(trimmedValue); + StringUtils::TrimRight(trimmedValue, " \n\r"); + return trimmedValue; +} + +bool CPVRRadioRDSInfoTag::Info::operator==(const CPVRRadioRDSInfoTag::Info& right) const +{ + if (this == &right) + return true; + + return (m_infoText == right.m_infoText && m_data == right.m_data && + m_maxSize == right.m_maxSize && m_prependData == right.m_prependData); +} + +void CPVRRadioRDSInfoTag::Info::Clear() +{ + m_data.clear(); + m_infoText.clear(); +} + +void CPVRRadioRDSInfoTag::Info::Add(const std::string& text) +{ + std::string tmp = Trim(text); + g_charsetConverter.unknownToUTF8(tmp); + + if (std::find(m_data.begin(), m_data.end(), tmp) != m_data.end()) + return; + + if (m_data.size() >= m_maxSize) + { + if (m_prependData) + m_data.pop_back(); + else + m_data.pop_front(); + } + + if (m_prependData) + m_data.emplace_front(std::move(tmp)); + else + m_data.emplace_back(std::move(tmp)); + + m_infoText.clear(); + for (const std::string& data : m_data) + { + m_infoText += data; + m_infoText += '\n'; + } + + // send a message to all windows to tell them to update the radiotext + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_RADIOTEXT); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} diff --git a/xbmc/pvr/channels/PVRRadioRDSInfoTag.h b/xbmc/pvr/channels/PVRRadioRDSInfoTag.h new file mode 100644 index 0000000..bc66989 --- /dev/null +++ b/xbmc/pvr/channels/PVRRadioRDSInfoTag.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2005-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. + */ + +#pragma once + +#include "threads/CriticalSection.h" +#include "utils/IArchivable.h" +#include "utils/ISerializable.h" + +#include <deque> +#include <string> + +namespace PVR +{ + +class CPVRRadioRDSInfoTag final : public IArchivable, public ISerializable +{ +public: + CPVRRadioRDSInfoTag(); + + bool operator ==(const CPVRRadioRDSInfoTag& right) const; + bool operator !=(const CPVRRadioRDSInfoTag& right) const; + + void Archive(CArchive& ar) override; + void Serialize(CVariant& value) const override; + + void Clear(); + void ResetSongInformation(); + + /**! Basic RDS related information */ + void SetSpeechActive(bool active); + void SetLanguage(const std::string& strLanguage); + const std::string& GetLanguage() const; + void SetCountry(const std::string& strCountry); + const std::string& GetCountry() const; + void SetRadioText(const std::string& strRadioText); + std::string GetRadioText(unsigned int line) const; + void SetProgramServiceText(const std::string& strPSText); + + /**! RDS RadioText related information */ + void SetTitle(const std::string& strTitle); + void SetBand(const std::string& strBand); + void SetArtist(const std::string& strArtist); + void SetComposer(const std::string& strComposer); + void SetConductor(const std::string& strConductor); + void SetAlbum(const std::string& strAlbum); + void SetComment(const std::string& strComment); + void SetAlbumTrackNumber(int track); + + const std::string& GetTitle() const; + const std::string& GetBand() const; + const std::string& GetArtist() const; + const std::string& GetComposer() const; + const std::string& GetConductor() const; + const std::string& GetAlbum() const; + const std::string& GetComment() const; + int GetAlbumTrackNumber() const; + + void SetProgStation(const std::string& strProgStation); + void SetProgStyle(const std::string& strProgStyle); + void SetProgHost(const std::string& strProgHost); + void SetProgWebsite(const std::string& strWebsite); + void SetProgNow(const std::string& strNow); + void SetProgNext(const std::string& strNext); + void SetPhoneHotline(const std::string& strHotline); + void SetEMailHotline(const std::string& strHotline); + void SetPhoneStudio(const std::string& strPhone); + void SetEMailStudio(const std::string& strEMail); + void SetSMSStudio(const std::string& strSMS); + + const std::string& GetProgStation() const; + const std::string& GetProgStyle() const; + const std::string& GetProgHost() const; + const std::string& GetProgWebsite() const; + const std::string& GetProgNow() const; + const std::string& GetProgNext() const; + const std::string& GetPhoneHotline() const; + const std::string& GetEMailHotline() const; + const std::string& GetPhoneStudio() const; + const std::string& GetEMailStudio() const; + const std::string& GetSMSStudio() const; + + void SetInfoNews(const std::string& strNews); + const std::string GetInfoNews() const; + + void SetInfoNewsLocal(const std::string& strNews); + const std::string GetInfoNewsLocal() const; + + void SetInfoSport(const std::string& strSport); + const std::string GetInfoSport() const; + + void SetInfoStock(const std::string& strSport); + const std::string GetInfoStock() const; + + void SetInfoWeather(const std::string& strWeather); + const std::string GetInfoWeather() const; + + void SetInfoHoroscope(const std::string& strHoroscope); + const std::string GetInfoHoroscope() const; + + void SetInfoCinema(const std::string& strCinema); + const std::string GetInfoCinema() const; + + void SetInfoLottery(const std::string& strLottery); + const std::string GetInfoLottery() const; + + void SetInfoOther(const std::string& strOther); + const std::string GetInfoOther() const; + + void SetEditorialStaff(const std::string& strEditorialStaff); + const std::string GetEditorialStaff() const; + + void SetRadioStyle(const std::string& style); + const std::string GetRadioStyle() const; + + void SetPlayingRadioText(bool yesNo); + bool IsPlayingRadioText() const; + + void SetPlayingRadioTextPlus(bool yesNo); + bool IsPlayingRadioTextPlus() const; + +private: + CPVRRadioRDSInfoTag(const CPVRRadioRDSInfoTag& tag) = delete; + const CPVRRadioRDSInfoTag& operator =(const CPVRRadioRDSInfoTag& tag) = delete; + + static std::string Trim(const std::string& value); + + mutable CCriticalSection m_critSection; + + bool m_RDS_SpeechActive; + + std::string m_strLanguage; + std::string m_strCountry; + std::string m_strTitle; + std::string m_strBand; + std::string m_strArtist; + std::string m_strComposer; + std::string m_strConductor; + std::string m_strAlbum; + std::string m_strComment; + int m_iAlbumTracknumber; + std::string m_strRadioStyle; + + class Info + { + public: + Info() = delete; + Info(size_t maxSize, bool prependData) : m_maxSize(maxSize), m_prependData(prependData) {} + + bool operator==(const Info& right) const; + + size_t Size() const { return m_data.size(); } + size_t MaxSize() const { return m_maxSize; } + + void Clear(); + void Add(const std::string& text); + + const std::string& GetText() const { return m_infoText; } + std::string GetLine(unsigned int line) const + { + return line < m_data.size() ? m_data.at(line) : ""; + } + + private: + const size_t m_maxSize = 10; + const bool m_prependData = false; + std::deque<std::string> m_data; + std::string m_infoText; + }; + + Info m_strInfoNews{10, false}; + Info m_strInfoNewsLocal{10, false}; + Info m_strInfoSport{10, false}; + Info m_strInfoStock{10, false}; + Info m_strInfoWeather{10, false}; + Info m_strInfoLottery{10, false}; + Info m_strInfoOther{10, false}; + Info m_strInfoHoroscope{10, false}; + Info m_strInfoCinema{10, false}; + Info m_strEditorialStaff{10, false}; + + Info m_strRadioText{6, true}; + + Info m_strProgramServiceText{12, false}; + std::string m_strProgramServiceLine0; + std::string m_strProgramServiceLine1; + + std::string m_strProgStyle; + std::string m_strProgHost; + std::string m_strProgStation; + std::string m_strProgWebsite; + std::string m_strProgNow; + std::string m_strProgNext; + std::string m_strPhoneHotline; + std::string m_strEMailHotline; + std::string m_strPhoneStudio; + std::string m_strEMailStudio; + std::string m_strSMSStudio; + + bool m_bHaveRadioText; + bool m_bHaveRadioTextPlus; +}; +} diff --git a/xbmc/pvr/channels/test/CMakeLists.txt b/xbmc/pvr/channels/test/CMakeLists.txt new file mode 100644 index 0000000..d88bab8 --- /dev/null +++ b/xbmc/pvr/channels/test/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCES TestPVRChannelsPath.cpp) +set(HEADERS) + +core_add_test_library(pvrchannels_test) diff --git a/xbmc/pvr/channels/test/TestPVRChannelsPath.cpp b/xbmc/pvr/channels/test/TestPVRChannelsPath.cpp new file mode 100644 index 0000000..c7232db --- /dev/null +++ b/xbmc/pvr/channels/test/TestPVRChannelsPath.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2005-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 "pvr/channels/PVRChannelsPath.h" + +#include <gtest/gtest.h> + +TEST(TestPVRChannelsPath, Parse_Protocol) +{ + // pvr protocol is generally fine, but not sufficient for channels pvr paths - component is missing for that. + PVR::CPVRChannelsPath path("pvr://"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Component_1) +{ + PVR::CPVRChannelsPath path("pvr://channels"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_TRUE(path.IsEmpty()); +} + +TEST(TestPVRChannelsPath, Parse_Component_2) +{ + PVR::CPVRChannelsPath path("pvr://channels/"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_TRUE(path.IsEmpty()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_Component) +{ + PVR::CPVRChannelsPath path("pvr://foo/"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_TV_Root_1) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_TRUE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ""); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_TV_Root_2) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv/"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_TRUE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ""); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_Radio_Root_1) +{ + PVR::CPVRChannelsPath path("pvr://channels/radio"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/radio/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_TRUE(path.IsRadio()); + EXPECT_TRUE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ""); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_Radio_Root_2) +{ + PVR::CPVRChannelsPath path("pvr://channels/radio/"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/radio/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_TRUE(path.IsRadio()); + EXPECT_TRUE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ""); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_Root) +{ + PVR::CPVRChannelsPath path("pvr://channels/foo"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_TV_Group_1) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv/Group1"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/Group1/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_TV_Group_2) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv/Group1/"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/Group1/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_Hidden_TV_Group) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv/.hidden"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/.hidden/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_TRUE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ".hidden"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_Special_TV_Group) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv/foo%2Fbar%20baz"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/foo%2Fbar%20baz/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "foo/bar baz"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_Special_TV_Group) +{ + // special chars in group name not escaped + PVR::CPVRChannelsPath path("pvr://channels/tv/foo/bar baz"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Radio_Group) +{ + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/radio/Group1/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_TRUE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Parse_TV_Channel) +{ + PVR::CPVRChannelsPath path("pvr://channels/tv/Group1/5@pvr.demo_4711.pvr"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/Group1/5@pvr.demo_4711.pvr"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_TRUE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), "pvr.demo"); + EXPECT_EQ(path.GetInstanceID(), 5); + EXPECT_EQ(path.GetChannelUID(), 4711); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_1) +{ + // trailing ".pvr" missing + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/pvr.demo_4711"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_2) +{ + // '-' instead of '_' as clientid / channeluid delimiter + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/pvr.demo-4711.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_3) +{ + // channeluid not numerical + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/pvr.demo_abc4711.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_4) +{ + // channeluid not positive or zero + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/pvr.demo_-4711.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_5) +{ + // empty clientid + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/_4711.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_6) +{ + // empty channeluid + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/pvr.demo_.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_7) +{ + // empty clientid and empty channeluid + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/_.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, Parse_Invalid_TV_Channel_8) +{ + // empty clientid and empty channeluid, only extension ".pvr" given + PVR::CPVRChannelsPath path("pvr://channels/radio/Group1/.pvr"); + + EXPECT_FALSE(path.IsValid()); +} + +TEST(TestPVRChannelsPath, TV_Channelgroup) +{ + PVR::CPVRChannelsPath path(false, "Group1"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/Group1/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Radio_Channelgroup) +{ + PVR::CPVRChannelsPath path(true, "Group1"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/radio/Group1/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_TRUE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Hidden_TV_Channelgroup) +{ + PVR::CPVRChannelsPath path(false, true, "Group1"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/.hidden/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_TRUE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ".hidden"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, Hidden_Radio_Channelgroup) +{ + PVR::CPVRChannelsPath path(true, true, "Group1"); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/radio/.hidden/"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_TRUE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_TRUE(path.IsChannelGroup()); + EXPECT_TRUE(path.IsHiddenChannelGroup()); + EXPECT_FALSE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), ".hidden"); + EXPECT_EQ(path.GetAddonID(), ""); + EXPECT_EQ(path.GetChannelUID(), -1); +} + +TEST(TestPVRChannelsPath, TV_Channel) +{ + PVR::CPVRChannelsPath path(false, "Group1", "pvr.demo", ADDON::ADDON_SINGLETON_INSTANCE_ID, 4711); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/tv/Group1/0@pvr.demo_4711.pvr"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_FALSE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_TRUE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), "pvr.demo"); + EXPECT_EQ(path.GetChannelUID(), 4711); +} + +TEST(TestPVRChannelsPath, Radio_Channel) +{ + PVR::CPVRChannelsPath path(true, "Group1", "pvr.demo", ADDON::ADDON_SINGLETON_INSTANCE_ID, 4711); + + EXPECT_EQ(static_cast<std::string>(path), "pvr://channels/radio/Group1/0@pvr.demo_4711.pvr"); + EXPECT_TRUE(path.IsValid()); + EXPECT_FALSE(path.IsEmpty()); + EXPECT_TRUE(path.IsRadio()); + EXPECT_FALSE(path.IsChannelsRoot()); + EXPECT_FALSE(path.IsChannelGroup()); + EXPECT_FALSE(path.IsHiddenChannelGroup()); + EXPECT_TRUE(path.IsChannel()); + EXPECT_EQ(path.GetGroupName(), "Group1"); + EXPECT_EQ(path.GetAddonID(), "pvr.demo"); + EXPECT_EQ(path.GetChannelUID(), 4711); +} + +TEST(TestPVRChannelsPath, Operator_Equals) +{ + PVR::CPVRChannelsPath path2(true, "Group1"); + PVR::CPVRChannelsPath path(static_cast<std::string>(path2)); + + EXPECT_EQ(path, path2); +} |