Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PVR Providers (HTSPv38+) #677

Merged
merged 1 commit into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pvr.hts/addon.xml.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="pvr.hts"
version="22.2.0"
version="22.3.0"
name="Tvheadend HTSP Client"
provider-name="Adam Sutton, Sam Stenvall, Lars Op den Kamp, Kai Sommerfeld">
<requires>@ADDON_DEPENDS@</requires>
Expand Down
3 changes: 3 additions & 0 deletions pvr.hts/changelog.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
111 changes: 109 additions & 2 deletions src/Tvheadend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<std::recursive_mutex> lock(m_mutex);
amount = m_providers.size();
return PVR_ERROR_NO_ERROR;
}

PVR_ERROR CTvheadend::GetProviders(kodi::addon::PVRProvidersResultSet& results)
{
std::vector<kodi::addon::PVRProvider> providers;
{
std::lock_guard<std::recursive_mutex> 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));
Jalle19 marked this conversation as resolved.
Show resolved Hide resolved
}
}

for (const auto& provider : providers)
{
/* Callback. */
results.Add(std::move(provider));
}

return PVR_ERROR_NO_ERROR;
}

/* **************************************************************************
* Tags
* *************************************************************************/
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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;
Expand All @@ -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));
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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{PVR_PROVIDER_INVALID_UID};
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)
Expand Down
6 changes: 6 additions & 0 deletions src/Tvheadend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -161,6 +165,7 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient,
/*
* Event handling
*/
void TriggerProviderUpdate();
void TriggerChannelGroupsUpdate();
void TriggerChannelUpdate();
void TriggerRecordingUpdate();
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/tvheadend/HTSPConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ using namespace tvheadend::utilities;

#define HTSP_MIN_SERVER_VERSION (26) // Server must support at least this htsp version
#define HTSP_CLIENT_VERSION \
(37) // Client uses HTSP features up to this version. If the respective \
(38) // Client uses HTSP features up to this version. If the respective \
// addon feature requires htsp features introduced after \
// HTSP_MIN_SERVER_VERSION this feature will only be available if the \
// actual server HTSP version matches (runtime htsp version check).
Expand Down
1 change: 1 addition & 0 deletions src/tvheadend/HTSPTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/tvheadend/entity/Channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand All @@ -60,13 +60,17 @@ 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;
uint32_t m_type;
uint32_t m_caid;
std::string m_name;
std::string m_icon;
int32_t m_providerUid{PVR_PROVIDER_INVALID_UID};
};
} // namespace entity
} // namespace tvheadend
45 changes: 45 additions & 0 deletions src/tvheadend/entity/Provider.h
Original file line number Diff line number Diff line change
@@ -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 <cstdint>
#include <map>
#include <string>
#include <utility>

namespace tvheadend::entity
{

class Provider;
using ProviderMapEntry = std::pair<int32_t, Provider>;
using Providers = std::map<int32_t, Provider>;

/**
* 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
24 changes: 23 additions & 1 deletion src/tvheadend/utilities/Utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,29 @@ 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 (size_t i = 0; i < str.size(); ++i)
{
uint8_t value = str[i];
hash = hash ^ value;
hash *= prime;
}

if (hash < 0)
hash = -hash;

return hash;
}

} // namespace utilities
} // namespace tvheadend