From fae8be08d472acc7e1b4259def5178d0c869daaf Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:44:08 +0200 Subject: [PATCH] Add support for PVR Providers (HTSPv38+) --- CMakeLists.txt | 1 + pvr.hts/addon.xml.in | 2 +- pvr.hts/changelog.txt | 3 + src/Tvheadend.cpp | 111 +++++++++++++++++++++++++++- src/Tvheadend.h | 6 ++ src/tvheadend/HTSPTypes.h | 1 + src/tvheadend/entity/Channel.h | 6 +- src/tvheadend/entity/Provider.h | 45 +++++++++++ src/tvheadend/utilities/Utilities.h | 21 +++++- 9 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 src/tvheadend/entity/Provider.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cec7e6dd..97e3b903 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ set(HTS_SOURCES_TVHEADEND_ENTITY src/tvheadend/entity/Entity.h src/tvheadend/entity/Event.h src/tvheadend/entity/Event.cpp + src/tvheadend/entity/Provider.h src/tvheadend/entity/Recording.h src/tvheadend/entity/RecordingBase.h src/tvheadend/entity/RecordingBase.cpp diff --git a/pvr.hts/addon.xml.in b/pvr.hts/addon.xml.in index 50bcda0e..81fb54ee 100644 --- a/pvr.hts/addon.xml.in +++ b/pvr.hts/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.hts/changelog.txt b/pvr.hts/changelog.txt index a438f5d6..081821cd 100644 --- a/pvr.hts/changelog.txt +++ b/pvr.hts/changelog.txt @@ -1,3 +1,6 @@ +v22.3.0 +- Add support for PVR Providers (HTSPv38+) + v22.2.0 - Add support for TVH Parental Rating fields - Misc cleanup and smaller fixes diff --git a/src/Tvheadend.cpp b/src/Tvheadend.cpp index 061a01a0..40a77dad 100644 --- a/src/Tvheadend.cpp +++ b/src/Tvheadend.cpp @@ -119,6 +119,7 @@ PVR_ERROR CTvheadend::GetCapabilities(kodi::addon::PVRCapabilities& capabilities } capabilities.SetSupportsRecordingSize(m_conn->GetProtocol() >= 35); + capabilities.SetSupportsProviders(m_conn->GetProtocol() >= 38); return PVR_ERROR_NO_ERROR; } @@ -223,6 +224,46 @@ bool CTvheadend::HasStreamingProfile(const std::string& streamingProfile) const { return profile.GetName() == streamingProfile; }) != m_profiles.cend(); } +/* ************************************************************************** + * Providers + * *************************************************************************/ + +PVR_ERROR CTvheadend::GetProvidersAmount(int& amount) +{ + if (!m_asyncState.WaitForState(ASYNC_DVR)) + return PVR_ERROR_FAILED; + + std::lock_guard lock(m_mutex); + amount = m_providers.size(); + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CTvheadend::GetProviders(kodi::addon::PVRProvidersResultSet& results) +{ + std::vector providers; + { + std::lock_guard lock(m_mutex); + + providers.reserve(m_providers.size()); + for (const auto& entry : m_providers) + { + kodi::addon::PVRProvider provider; + provider.SetUniqueId(entry.second.GetId()); + provider.SetName(entry.second.GetName()); + + providers.emplace_back(std::move(provider)); + } + } + + for (const auto& provider : providers) + { + /* Callback. */ + results.Add(std::move(provider)); + } + + return PVR_ERROR_NO_ERROR; +} + /* ************************************************************************** * Tags * *************************************************************************/ @@ -360,6 +401,7 @@ PVR_ERROR CTvheadend::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& chn.SetIsHidden(false); chn.SetChannelName(channel.GetName()); chn.SetIconPath(channel.GetIcon()); + chn.SetClientProviderUid(channel.GetProviderUid()); channels.emplace_back(std::move(chn)); } @@ -490,11 +532,16 @@ PVR_ERROR CTvheadend::GetRecordings(bool deleted, kodi::addon::PVRRecordingsResu /* Setup entry */ kodi::addon::PVRRecording rec; - /* Channel icon */ const auto& cit = m_channels.find(recording.GetChannel()); if (cit != m_channels.end()) + { + /* Channel icon */ rec.SetIconPath(cit->second.GetIcon()); + /* Provider */ + rec.SetClientProviderUid(cit->second.GetProviderUid()); + } + /* Channel name */ rec.SetChannelName(recording.GetChannelName()); @@ -1848,6 +1895,9 @@ void CTvheadend::Process() { switch (event.m_type) { + case HTSP_EVENT_PRV_UPDATE: + kodi::addon::CInstancePVRClient::TriggerProvidersUpdate(); + break; case HTSP_EVENT_TAG_UPDATE: kodi::addon::CInstancePVRClient::TriggerChannelGroupsUpdate(); break; @@ -1868,6 +1918,11 @@ void CTvheadend::Process() } } +void CTvheadend::TriggerProviderUpdate() +{ + m_events.emplace_back(SHTSPEvent(HTSP_EVENT_PRV_UPDATE)); +} + void CTvheadend::TriggerChannelGroupsUpdate() { m_events.emplace_back(SHTSPEvent(HTSP_EVENT_TAG_UPDATE)); @@ -1919,6 +1974,8 @@ void CTvheadend::SyncInitCompleted() /* Flag all async fields in case they've been deleted */ for (auto& entry : m_channels) entry.second.SetDirty(true); + for (auto& entry : m_providers) + entry.second.SetDirty(true); for (auto& entry : m_tags) entry.second.SetDirty(true); for (auto& entry : m_schedules) @@ -1949,6 +2006,12 @@ void CTvheadend::SyncChannelsCompleted() TriggerChannelUpdate(); + /* Providers */ + utilities::erase_if(m_providers, + [](const ProviderMapEntry& entry) { return entry.second.IsDirty(); }); + + TriggerProvidersUpdate(); + /* Next */ m_asyncState.SetState(ASYNC_DVR); } @@ -2224,6 +2287,31 @@ void CTvheadend::ParseChannelAddOrUpdate(htsmsg_t* msg, bool bAdd) /* CAID */ if (caid == 0) htsmsg_get_u32(&f->hmf_msg, "caid", &caid); + + /* Service provider */ + str = htsmsg_get_str(&f->hmf_msg, "providername"); + if (str && strlen(str) > 0) + { + const int32_t uid{utilities::hash_str_int32(str)}; + channel.SetProviderUid(uid); + + /* Locate/create provider object */ + Provider& provider = m_providers[uid]; + Provider comparison = provider; + provider.SetId(uid); + provider.SetDirty(false); + + provider.SetName(str); + + if (provider != comparison) + { + Logger::Log(LogLevel::LEVEL_DEBUG, "provider %s id:%u, name:%s", + (bAdd ? "added" : "updated"), provider.GetId(), provider.GetName().c_str()); + + if (m_asyncState.GetState() > ASYNC_CHN) + TriggerProvidersUpdate(); + } + } } channel.SetCaid(caid); @@ -2256,10 +2344,29 @@ void CTvheadend::ParseChannelDelete(htsmsg_t* msg) } Logger::Log(LogLevel::LEVEL_DEBUG, "delete channel %u", u32); - /* Erase */ + /* We need to check and update providers. May be the deleted channel is the last channel with this provider */ + int32_t providerUid{0}; + const auto it = m_channels.find(u32); + if (it != m_channels.cend()) + providerUid = (*it).second.GetProviderUid(); + + /* Erase channel */ m_channels.erase(u32); m_channelTuningPredictor.RemoveChannel(u32); TriggerChannelUpdate(); + + if (providerUid != PVR_PROVIDER_INVALID_UID) + { + const auto it2 = m_providers.find(providerUid); + if (std::none_of(m_channels.cbegin(), m_channels.cend(), + [providerUid](const ChannelMapEntry& entry) + { return entry.second.GetProviderUid() == providerUid; })) + { + /* Erase provider */ + m_providers.erase(it2); + TriggerProvidersUpdate(); + } + } } void CTvheadend::ParseRecordingAddOrUpdate(htsmsg_t* msg, bool bAdd) diff --git a/src/Tvheadend.h b/src/Tvheadend.h index 25c8d1fc..db8224c3 100644 --- a/src/Tvheadend.h +++ b/src/Tvheadend.h @@ -22,6 +22,7 @@ extern "C" #include "tvheadend/Profile.h" #include "tvheadend/TimeRecordings.h" #include "tvheadend/entity/Channel.h" +#include "tvheadend/entity/Provider.h" #include "tvheadend/entity/Recording.h" #include "tvheadend/entity/Schedule.h" #include "tvheadend/entity/Tag.h" @@ -90,6 +91,9 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override; PVR_ERROR GetDriveSpace(uint64_t& total, uint64_t& used) override; + PVR_ERROR GetProvidersAmount(int& amount) override; + PVR_ERROR GetProviders(kodi::addon::PVRProvidersResultSet& results) override; + PVR_ERROR GetChannelGroupsAmount(int& amount) override; PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override; PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, @@ -161,6 +165,7 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, /* * Event handling */ + void TriggerProviderUpdate(); void TriggerChannelGroupsUpdate(); void TriggerChannelUpdate(); void TriggerRecordingUpdate(); @@ -277,6 +282,7 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, HTSPMessageQueue m_queue; tvheadend::entity::Channels m_channels; + tvheadend::entity::Providers m_providers; tvheadend::entity::Tags m_tags; tvheadend::entity::Recordings m_recordings; tvheadend::entity::Schedules m_schedules; diff --git a/src/tvheadend/HTSPTypes.h b/src/tvheadend/HTSPTypes.h index 54b67120..de04e7cd 100644 --- a/src/tvheadend/HTSPTypes.h +++ b/src/tvheadend/HTSPTypes.h @@ -109,6 +109,7 @@ enum eHTSPEventType HTSP_EVENT_TAG_UPDATE = 2, HTSP_EVENT_EPG_UPDATE = 3, HTSP_EVENT_REC_UPDATE = 4, + HTSP_EVENT_PRV_UPDATE = 5, }; struct SHTSPEvent diff --git a/src/tvheadend/entity/Channel.h b/src/tvheadend/entity/Channel.h index 94b16305..cfceabab 100644 --- a/src/tvheadend/entity/Channel.h +++ b/src/tvheadend/entity/Channel.h @@ -37,7 +37,7 @@ class Channel : public Entity { return m_id == other.m_id && m_num == other.m_num && m_numMinor == other.m_numMinor && m_type == other.m_type && m_caid == other.m_caid && m_name == other.m_name && - m_icon == other.m_icon; + m_icon == other.m_icon && m_providerUid == other.m_providerUid; } bool operator!=(const Channel& other) const { return !(*this == other); } @@ -60,6 +60,9 @@ class Channel : public Entity const std::string& GetIcon() const { return m_icon; } void SetIcon(const std::string& icon) { m_icon = icon; } + int32_t GetProviderUid() const { return m_providerUid; } + void SetProviderUid(int32_t providerUid) { m_providerUid = providerUid; } + private: uint32_t m_num; uint32_t m_numMinor; @@ -67,6 +70,7 @@ class Channel : public Entity uint32_t m_caid; std::string m_name; std::string m_icon; + int32_t m_providerUid{PVR_PROVIDER_INVALID_UID}; }; } // namespace entity } // namespace tvheadend diff --git a/src/tvheadend/entity/Provider.h b/src/tvheadend/entity/Provider.h new file mode 100644 index 00000000..bd3d9a6a --- /dev/null +++ b/src/tvheadend/entity/Provider.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include "Entity.h" + +#include +#include +#include +#include + +namespace tvheadend::entity +{ + +class Provider; +using ProviderMapEntry = std::pair; +using Providers = std::map; + +/** + * Represents a provider + */ +class Provider : public Entity +{ +public: + Provider() = default; + + bool operator==(const Provider& other) const + { + return m_id == other.m_id && m_name == other.m_name; + } + + bool operator!=(const Provider& other) const { return !(*this == other); } + + const std::string& GetName() const { return m_name; } + void SetName(const std::string& name) { m_name = name; } + +private: + std::string m_name; +}; +} // namespace tvheadend::entity diff --git a/src/tvheadend/utilities/Utilities.h b/src/tvheadend/utilities/Utilities.h index 31b6eaa0..8be29823 100644 --- a/src/tvheadend/utilities/Utilities.h +++ b/src/tvheadend/utilities/Utilities.h @@ -26,7 +26,26 @@ void erase_if(ContainerT& items, const PredicateT& predicate) else ++it; } -}; +} + +/** + * Simple hash function. Borrowed from: + * https://stackoverflow.com/questions/16075271/hashing-a-string-to-an-integer-in-c + */ +static int32_t hash_str_int32(const std::string& str) +{ + int32_t hash = 0x811c9dc5; + int32_t prime = 0x1000193; + + for (int i = 0; i < str.size(); ++i) + { + uint8_t value = str[i]; + hash = hash ^ value; + hash *= prime; + } + + return hash; +} } // namespace utilities } // namespace tvheadend