Skip to content

Commit

Permalink
Channel list update
Browse files Browse the repository at this point in the history
Reload changed channel  lists during EPG updates
  • Loading branch information
emveepee committed Nov 29, 2024
1 parent 9bf83f5 commit f5bebb5
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 51 deletions.
153 changes: 105 additions & 48 deletions src/Channels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "utilities/XMLUtils.h"
#include "pvrclient-nextpvr.h"

#include <kodi/General.h>
#include <kodi/tools/StringUtils.h>
#include "zlib.h"

Expand All @@ -26,11 +27,12 @@ Channels::Channels(const std::shared_ptr<InstanceSettings>& settings, Request& r
int Channels::GetNumChannels()
{
// Kodi polls this while recordings are open avoid calls to backend
std::lock_guard<std::recursive_mutex> lock(m_channelMutex);
int channelCount = m_channelDetails.size();
if (channelCount == 0)
{
tinyxml2::XMLDocument doc;
if (ReadCachedChannelList(doc) == tinyxml2::XML_SUCCESS)
if (GetChannelList(doc) == tinyxml2::XML_SUCCESS)
{
tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels");
tinyxml2::XMLNode* pChannelNode;
Expand Down Expand Up @@ -91,17 +93,9 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r
return PVR_ERROR_NO_ERROR;
PVR_ERROR returnValue = PVR_ERROR_NO_ERROR;
std::string stream;
std::map<int, std::pair<bool, bool>>::iterator itr = m_channelDetails.begin();
while (itr != m_channelDetails.end())
{
if (itr->second.second == (radio == true))
itr = m_channelDetails.erase(itr);
else
++itr;
}

tinyxml2::XMLDocument doc;
if (ReadCachedChannelList(doc) == tinyxml2::XML_SUCCESS)
if (GetChannelList(doc) == tinyxml2::XML_SUCCESS)
{
tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels");
tinyxml2::XMLNode* pChannelNode;
Expand Down Expand Up @@ -149,14 +143,6 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r
if (iconFile.length() > 0)
tag.SetIconPath(iconFile);
}

// V5 has the EPG source type info.
std::string epg;
if (XMLUtils::GetString(pChannelNode, "epg", epg))
m_channelDetails[tag.GetUniqueId()] = std::make_pair(epg == "None", tag.GetIsRadio());
else
m_channelDetails[tag.GetUniqueId()] = std::make_pair(false, tag.GetIsRadio());

// transfer channel to XBMC
results.Add(tag);
}
Expand All @@ -168,6 +154,44 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r
return returnValue;
}

bool Channels::ResetChannelList(time_t updateTime)
{
// normally GetChannels loads the internal channel list;
// force update in case GetChannels is not called before EPG triggers
std::string checksum = m_checksumChannelList;
CacheChannelList(updateTime);
if (checksum != m_checksumChannelList)
{
std::lock_guard<std::recursive_mutex> lock(m_channelMutex);
m_channelDetails.clear();
tinyxml2::XMLDocument doc;
if (GetChannelList(doc) == tinyxml2::XML_SUCCESS)
{
tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels");
tinyxml2::XMLNode* pChannelNode;
for (pChannelNode = channelsNode->FirstChildElement("channel"); pChannelNode; pChannelNode = pChannelNode->NextSiblingElement())
{
std::string buffer;
bool isRadio = false;
XMLUtils::GetString(pChannelNode, "type", buffer);
if (buffer == "0xa")
{
if (!m_settings->m_showRadio)
continue;
isRadio = true;
}
std::string epg;
if (XMLUtils::GetString(pChannelNode, "epg", epg))
m_channelDetails[XMLUtils::GetUIntValue(pChannelNode, "id")] = std::make_pair(epg == "None", isRadio);
else
m_channelDetails[XMLUtils::GetUIntValue(pChannelNode, "id")] = std::make_pair(false, isRadio);
}
return true;
}
}
return false;
}


/************************************************************/
/** Channel group handling **/
Expand All @@ -182,6 +206,7 @@ PVR_ERROR Channels::GetChannelGroupsAmount(int& amount)
PVR_RECORDING_CHANNEL_TYPE Channels::GetChannelType(unsigned int uid)
{
// when uid is invalid we assume TV because Kodi will
std::lock_guard<std::recursive_mutex> lock(m_channelMutex);
if (m_channelDetails.count(uid) > 0 && m_channelDetails[uid].second == true)
return PVR_RECORDING_CHANNEL_TYPE_RADIO;

Expand All @@ -192,7 +217,7 @@ PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsRe
{
if (radio && !m_settings->m_showRadio)
return PVR_ERROR_NO_ERROR;

std::lock_guard<std::recursive_mutex> lock(m_channelMutex);
PVR_ERROR returnValue = PVR_ERROR_NO_ERROR;
int priority = 1;

Expand All @@ -201,7 +226,7 @@ PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsRe
selectedGroups.clear();
bool hasAllChannels = false;
tinyxml2::XMLDocument doc;
if (ReadCachedChannelList(doc) == tinyxml2::XML_SUCCESS)
if (GetChannelList(doc) == tinyxml2::XML_SUCCESS)
{
tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels");
tinyxml2::XMLNode* pChannelNode;
Expand Down Expand Up @@ -289,7 +314,7 @@ PVR_ERROR Channels::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& g
tinyxml2::XMLError retCode;
if (group.GetGroupName() == GetAllChannelsGroupName(group.GetIsRadio()))
{
retCode = ReadCachedChannelList(doc);
retCode = GetChannelList(doc);
}
else
{
Expand All @@ -299,6 +324,7 @@ PVR_ERROR Channels::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& g

if (retCode == tinyxml2::XML_SUCCESS)
{
std::lock_guard<std::recursive_mutex> lock(m_channelMutex);
tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels");
tinyxml2::XMLNode* pChannelNode;
for (pChannelNode = channelsNode->FirstChildElement("channel"); pChannelNode; pChannelNode = pChannelNode->NextSiblingElement())
Expand Down Expand Up @@ -389,50 +415,81 @@ void Channels::LoadLiveStreams()
}
}
}
bool Channels::CacheAllChannels(time_t updateTime)
bool Channels::CacheChannelList(time_t updateTime)
{
std::string response;
if (updateTime == ReadChannelListCache(response))
return true;
m_checksumChannelList.clear();
return WriteChannelListCache(response,updateTime);
}

time_t Channels::ReadChannelListCache(std::string& response)
{
time_t rc = 0;
const std::string filename = kodi::tools::StringUtils::Format("%s%s", m_settings->m_instanceDirectory.c_str(), "channel.cache");
gzFile gz_file;
struct { time_t update; unsigned long size; } header{0,0};
if (kodi::vfs::FileExists(filename))
{
gzFile gz_file;
CacheHeader header{ 0,0 };
gz_file = gzopen(kodi::vfs::TranslateSpecialProtocol(filename).c_str(), "rb");
gzread(gz_file, (void*)&header, sizeof(header));
gzclose(gz_file);
if (updateTime == header.update)
if (gz_file != NULL)
{
return true;
int gzReturn = gzread(gz_file, (void*)&header, sizeof(CacheHeader));
if (gzReturn == sizeof(CacheHeader))
{
response.resize(header.size / sizeof(char));
if (gzread(gz_file, (void*)response.data(), header.size) == header.size)
{
m_checksumChannelList = kodi::GetMD5(response);
rc = header.updateTime;
}
}
gzclose(gz_file);
}
}
return rc;
}

bool Channels::WriteChannelListCache(std::string& response, time_t updateTime)
{
const std::string filename = kodi::tools::StringUtils::Format("%s%s", m_settings->m_instanceDirectory.c_str(), "channel.cache");
gzFile gz_file;
CacheHeader header{ 0,0 };
if (m_request.DoRequest("/service?method=channel.list&extras=true", response) == HTTP_OK)
{
gz_file = gzopen(kodi::vfs::TranslateSpecialProtocol(filename).c_str(), "wb");
header.size = sizeof(char) * response.size();
header.update = updateTime - m_settings->m_serverTimeOffset;
gzwrite(gz_file, (void*)&header, sizeof(header));
gzwrite(gz_file, (void*)(response.c_str()), header.size);
gzclose(gz_file);
return true;
if (gz_file != NULL)
{
header.size = sizeof(char) * response.size();
updateTime = updateTime - m_settings->m_serverTimeOffset;
header.updateTime = updateTime;
gzwrite(gz_file, (void*)&header, sizeof(CacheHeader));
gzwrite(gz_file, (void*)(response.c_str()), header.size);
gzclose(gz_file);
m_checksumChannelList = kodi::GetMD5(response);
return true;
}
}
return false;
}

tinyxml2::XMLError Channels::ReadCachedChannelList(tinyxml2::XMLDocument& doc)


tinyxml2::XMLError Channels::GetChannelList(tinyxml2::XMLDocument& doc)
{
auto start = std::chrono::steady_clock::now();
std::string response;
const std::string filename = kodi::tools::StringUtils::Format("%s%s", m_settings->m_instanceDirectory.c_str(), "channel.cache");
struct { time_t update; unsigned long size; } header{0,0};
gzFile gz_file = gzopen(kodi::vfs::TranslateSpecialProtocol(filename).c_str(), "rb");
gzread(gz_file, (void*)&header, sizeof(header));
response.resize(header.size / sizeof(char));
gzread(gz_file, (void*)response.data(), header.size);
gzclose(gz_file);
tinyxml2::XMLError xmlCheck = doc.Parse(response.c_str());
if (doc.Parse(response.c_str()) != tinyxml2::XML_SUCCESS)
return m_request.DoMethodRequest("channel.list&extras=true", doc);
int milliseconds = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count());
kodi::Log(ADDON_LOG_DEBUG, "ReadCachedChannelList %d %d %d %d", m_settings->m_instanceNumber, xmlCheck, response.length(), milliseconds);
return xmlCheck;
if (ReadChannelListCache(response) != 0)
{
tinyxml2::XMLError xmlCheck = doc.Parse(response.c_str());
if (xmlCheck == tinyxml2::XML_SUCCESS)
{
int milliseconds = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count());
kodi::Log(ADDON_LOG_DEBUG, "GetChannelList %d %d %d %d", m_settings->m_instanceNumber, xmlCheck, response.length(), milliseconds);
return xmlCheck;
}
}
kodi::Log(ADDON_LOG_DEBUG, "Cannot use channel cache");
return m_request.DoMethodRequest("channel.list&extras=true", doc);
}
10 changes: 8 additions & 2 deletions src/Channels.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace NextPVR
{
typedef struct { time_t updateTime; unsigned long size; } CacheHeader;

class ATTR_DLL_LOCAL Channels
{
Expand All @@ -25,9 +26,10 @@ namespace NextPVR
/* Channel handling */
int GetNumChannels();

bool CacheAllChannels(time_t updateTime);
bool CacheChannelList(time_t updateTime);

PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results);
bool ResetChannelList(time_t updateTime);
/* Channel group handling */
PVR_ERROR GetChannelGroupsAmount(int& amount);
PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results);
Expand All @@ -43,6 +45,7 @@ namespace NextPVR
std::map<int, std::pair<bool, bool>> m_channelDetails;
std::unordered_set<std::string> m_tvGroups;
std::unordered_set<std::string> m_radioGroups;
mutable std::recursive_mutex m_channelMutex;

private:
Channels() = default;
Expand All @@ -53,6 +56,9 @@ namespace NextPVR
std::string GetChannelIcon(int channelID);
const std::shared_ptr<InstanceSettings> m_settings;
Request& m_request;
tinyxml2::XMLError ReadCachedChannelList(tinyxml2::XMLDocument& doc);
tinyxml2::XMLError GetChannelList(tinyxml2::XMLDocument& doc);
time_t ReadChannelListCache(std::string& response);
bool WriteChannelListCache(std::string& response, time_t updateTime);
std::string m_checksumChannelList;
};
} // namespace NextPVR
2 changes: 2 additions & 0 deletions src/EPG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ EPG::EPG(const std::shared_ptr<InstanceSettings>& settings, Request& request, Re
PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::addon::PVREPGTagsResultSet& results)
{
std::pair<bool, bool> channelDetail;
std::lock_guard<std::recursive_mutex> lock(m_channels.m_channelMutex);
channelDetail = m_channels.m_channelDetails[channelUid];
std::lock_guard<std::recursive_mutex> unlock(m_channels.m_channelMutex);
if (channelDetail.first == true)
{
kodi::Log(ADDON_LOG_DEBUG, "Skipping %d", channelUid);
Expand Down
1 change: 1 addition & 0 deletions src/Timers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ PVR_ERROR Timers::GetTimers(kodi::addon::PVRTimersResultSet& results)
int timerCount = 0;
// first add the recurring recordings
tinyxml2::XMLDocument doc;
std::lock_guard<std::recursive_mutex> lock(m_channels.m_channelMutex);
if (m_request.DoMethodRequest("recording.recurring.list", doc) == tinyxml2::XML_SUCCESS)
{
tinyxml2::XMLNode* recurringsNode = doc.RootElement()->FirstChildElement("recurrings");
Expand Down
10 changes: 9 additions & 1 deletion src/pvrclient-nextpvr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ void cPVRClientNextPVR::ConfigurePostConnectionOptions()
if (m_lastEPGUpdateTime == 0)
m_request.GetLastUpdate("system.epg.summary", m_lastEPGUpdateTime);

m_channels.CacheAllChannels(m_lastEPGUpdateTime);
m_channels.ResetChannelList(m_lastEPGUpdateTime);
}

/* IsUp()
Expand Down Expand Up @@ -335,6 +335,14 @@ bool cPVRClientNextPVR::IsUp()
{
if (lastUpdate > m_lastEPGUpdateTime)
{
// if channel list changed trigger channel updates
if (m_channels.ResetChannelList(lastUpdate))
{
kodi::Log(ADDON_LOG_DEBUG, "Trigger Channel update start");
TriggerChannelUpdate();
kodi::Log(ADDON_LOG_DEBUG, "Trigger Channel Groups update start");
TriggerChannelGroupsUpdate();
}
// trigger EPG updates for all channels with a guide source
kodi::Log(ADDON_LOG_DEBUG, "Trigger EPG update start");
int channels = 0;
Expand Down

0 comments on commit f5bebb5

Please sign in to comment.