9
9
#include " utilities/XMLUtils.h"
10
10
#include " pvrclient-nextpvr.h"
11
11
12
+ #include < kodi/General.h>
12
13
#include < kodi/tools/StringUtils.h>
13
14
#include " zlib.h"
14
15
@@ -19,18 +20,20 @@ using namespace NextPVR::utilities;
19
20
20
21
Channels::Channels (const std::shared_ptr<InstanceSettings>& settings, Request& request) :
21
22
m_settings(settings),
22
- m_request(request)
23
+ m_request(request),
24
+ m_channelCacheFile(kodi::vfs::TranslateSpecialProtocol(kodi::tools::StringUtils::Format(" %s%s" , settings->m_instanceDirectory.c_str(), "channel.cache")))
23
25
{
24
26
}
25
27
26
28
int Channels::GetNumChannels ()
27
29
{
28
30
// Kodi polls this while recordings are open avoid calls to backend
31
+ std::lock_guard<std::recursive_mutex> lock (m_channelMutex);
29
32
int channelCount = m_channelDetails.size ();
30
33
if (channelCount == 0 )
31
34
{
32
35
tinyxml2::XMLDocument doc;
33
- if (ReadCachedChannelList (doc) == tinyxml2::XML_SUCCESS)
36
+ if (GetChannelList (doc) == tinyxml2::XML_SUCCESS)
34
37
{
35
38
tinyxml2::XMLNode* channelsNode = doc.RootElement ()->FirstChildElement (" channels" );
36
39
tinyxml2::XMLNode* pChannelNode;
@@ -91,17 +94,9 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r
91
94
return PVR_ERROR_NO_ERROR;
92
95
PVR_ERROR returnValue = PVR_ERROR_NO_ERROR;
93
96
std::string stream;
94
- std::map<int , std::pair<bool , bool >>::iterator itr = m_channelDetails.begin ();
95
- while (itr != m_channelDetails.end ())
96
- {
97
- if (itr->second .second == (radio == true ))
98
- itr = m_channelDetails.erase (itr);
99
- else
100
- ++itr;
101
- }
102
97
103
98
tinyxml2::XMLDocument doc;
104
- if (ReadCachedChannelList (doc) == tinyxml2::XML_SUCCESS)
99
+ if (GetChannelList (doc) == tinyxml2::XML_SUCCESS)
105
100
{
106
101
tinyxml2::XMLNode* channelsNode = doc.RootElement ()->FirstChildElement (" channels" );
107
102
tinyxml2::XMLNode* pChannelNode;
@@ -149,14 +144,6 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r
149
144
if (iconFile.length () > 0 )
150
145
tag.SetIconPath (iconFile);
151
146
}
152
-
153
- // V5 has the EPG source type info.
154
- std::string epg;
155
- if (XMLUtils::GetString (pChannelNode, " epg" , epg))
156
- m_channelDetails[tag.GetUniqueId ()] = std::make_pair (epg == " None" , tag.GetIsRadio ());
157
- else
158
- m_channelDetails[tag.GetUniqueId ()] = std::make_pair (false , tag.GetIsRadio ());
159
-
160
147
// transfer channel to XBMC
161
148
results.Add (tag);
162
149
}
@@ -168,7 +155,6 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r
168
155
return returnValue;
169
156
}
170
157
171
-
172
158
/* ***********************************************************/
173
159
/* * Channel group handling **/
174
160
@@ -182,6 +168,7 @@ PVR_ERROR Channels::GetChannelGroupsAmount(int& amount)
182
168
PVR_RECORDING_CHANNEL_TYPE Channels::GetChannelType (unsigned int uid)
183
169
{
184
170
// when uid is invalid we assume TV because Kodi will
171
+ std::lock_guard<std::recursive_mutex> lock (m_channelMutex);
185
172
if (m_channelDetails.count (uid) > 0 && m_channelDetails[uid].second == true )
186
173
return PVR_RECORDING_CHANNEL_TYPE_RADIO;
187
174
@@ -192,7 +179,7 @@ PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsRe
192
179
{
193
180
if (radio && !m_settings->m_showRadio )
194
181
return PVR_ERROR_NO_ERROR;
195
-
182
+ std::lock_guard<std::recursive_mutex> lock (m_channelMutex);
196
183
PVR_ERROR returnValue = PVR_ERROR_NO_ERROR;
197
184
int priority = 1 ;
198
185
@@ -201,7 +188,7 @@ PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsRe
201
188
selectedGroups.clear ();
202
189
bool hasAllChannels = false ;
203
190
tinyxml2::XMLDocument doc;
204
- if (ReadCachedChannelList (doc) == tinyxml2::XML_SUCCESS)
191
+ if (GetChannelList (doc) == tinyxml2::XML_SUCCESS)
205
192
{
206
193
tinyxml2::XMLNode* channelsNode = doc.RootElement ()->FirstChildElement (" channels" );
207
194
tinyxml2::XMLNode* pChannelNode;
@@ -289,7 +276,7 @@ PVR_ERROR Channels::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& g
289
276
tinyxml2::XMLError retCode;
290
277
if (group.GetGroupName () == GetAllChannelsGroupName (group.GetIsRadio ()))
291
278
{
292
- retCode = ReadCachedChannelList (doc);
279
+ retCode = GetChannelList (doc);
293
280
}
294
281
else
295
282
{
@@ -299,6 +286,7 @@ PVR_ERROR Channels::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& g
299
286
300
287
if (retCode == tinyxml2::XML_SUCCESS)
301
288
{
289
+ std::lock_guard<std::recursive_mutex> lock (m_channelMutex);
302
290
tinyxml2::XMLNode* channelsNode = doc.RootElement ()->FirstChildElement (" channels" );
303
291
tinyxml2::XMLNode* pChannelNode;
304
292
for (pChannelNode = channelsNode->FirstChildElement (" channel" ); pChannelNode; pChannelNode = pChannelNode->NextSiblingElement ())
@@ -389,50 +377,152 @@ void Channels::LoadLiveStreams()
389
377
}
390
378
}
391
379
}
392
- bool Channels::CacheAllChannels (time_t updateTime)
380
+ bool Channels::ChannelCacheChanged (time_t updateTime)
393
381
{
382
+ std::string checksum = m_checksumChannelList;
394
383
std::string response;
395
- const std::string filename = kodi::tools::StringUtils::Format (" %s%s" , m_settings->m_instanceDirectory .c_str (), " channel.cache" );
396
- gzFile gz_file;
397
- struct { time_t update; unsigned long size; } header{0 ,0 };
398
- if (kodi::vfs::FileExists (filename))
384
+ const time_t cacheTime = ReadChannelListCache (response);
385
+ // on first load need to cache details
386
+ if (cacheTime != 0 && m_channelDetails.empty ())
387
+ LoadChannelDetails ();
388
+
389
+ if (updateTime == cacheTime)
390
+ return false ;
391
+ else
399
392
{
400
- gz_file = gzopen (kodi::vfs::TranslateSpecialProtocol (filename).c_str (), " rb" );
401
- gzread (gz_file, (void *)&header, sizeof (header));
393
+ // change EPG updateTime
394
+ response.clear ();
395
+ // Get new channel list but return on error
396
+ if (!ReloadChannelListCache (response, updateTime))
397
+ return false ;
398
+ }
399
+ // checksum will be empty on first call
400
+ return checksum != m_checksumChannelList;
401
+ }
402
+
403
+ time_t Channels::ReadChannelListCache (std::string& response)
404
+ {
405
+ time_t updateTime = 0 ;
406
+ if (kodi::vfs::FileExists (m_channelCacheFile))
407
+ {
408
+ gzFile gz_file = gzopen (m_channelCacheFile.c_str (), " rb" );
409
+ if (gz_file != NULL )
410
+ {
411
+ CacheHeader header{ 0 ,0 };
412
+ if (gzread (gz_file, (void *)&header, sizeof (CacheHeader)) == sizeof (CacheHeader))
413
+ {
414
+ response.resize (header.size );
415
+ if (gzread (gz_file, (void *)response.data (), header.size ) == header.size )
416
+ {
417
+ m_checksumChannelList = kodi::GetMD5 (response);
418
+ updateTime = header.updateTime ;
419
+ }
420
+ }
421
+ }
402
422
gzclose (gz_file);
403
- if (updateTime == header. update )
423
+ if (updateTime == 0 )
404
424
{
405
- return true ;
425
+ kodi::Log (ADDON_LOG_WARNING, " Remove invalid cache file." );
426
+ kodi::vfs::DeleteFile (m_channelCacheFile);
406
427
}
407
428
}
429
+ return updateTime;
430
+ }
431
+
432
+ bool Channels::ReloadChannelListCache (std::string& response, time_t updateTime)
433
+ {
434
+ bool rc = false ;
435
+ gzFile gz_file;
436
+ m_checksumChannelList.clear ();
408
437
if (m_request.DoRequest (" /service?method=channel.list&extras=true" , response) == HTTP_OK)
409
438
{
410
- gz_file = gzopen (kodi::vfs::TranslateSpecialProtocol (filename).c_str (), " wb" );
411
- header.size = sizeof (char ) * response.size ();
412
- header.update = updateTime - m_settings->m_serverTimeOffset ;
413
- gzwrite (gz_file, (void *)&header, sizeof (header));
414
- gzwrite (gz_file, (void *)(response.c_str ()), header.size );
439
+ gz_file = gzopen (m_channelCacheFile.c_str (), " wb" );
440
+ if (gz_file != NULL )
441
+ {
442
+ CacheHeader header{ 0 ,0 };
443
+ header.size = response.size ();
444
+ header.updateTime = updateTime;
445
+ if (gzwrite (gz_file, (void *)&header, sizeof (CacheHeader)) == sizeof (CacheHeader))
446
+ {
447
+ if (gzwrite (gz_file, (void *)(response.c_str ()), header.size ) == header.size )
448
+ {
449
+ m_checksumChannelList = kodi::GetMD5 (response);
450
+ rc = true ;
451
+ }
452
+ }
453
+ }
415
454
gzclose (gz_file);
416
- return true ;
417
455
}
418
- return false ;
456
+ if (!rc)
457
+ kodi::Log (ADDON_LOG_ERROR, " Could not write channel cache" );
458
+
459
+ return rc;
419
460
}
420
461
421
- tinyxml2::XMLError Channels::ReadCachedChannelList (tinyxml2::XMLDocument& doc)
462
+ tinyxml2::XMLError Channels::GetChannelList (tinyxml2::XMLDocument& doc)
422
463
{
423
464
auto start = std::chrono::steady_clock::now ();
424
465
std::string response;
425
- const std::string filename = kodi::tools::StringUtils::Format (" %s%s" , m_settings->m_instanceDirectory .c_str (), " channel.cache" );
426
- struct { time_t update; unsigned long size; } header{0 ,0 };
427
- gzFile gz_file = gzopen (kodi::vfs::TranslateSpecialProtocol (filename).c_str (), " rb" );
428
- gzread (gz_file, (void *)&header, sizeof (header));
429
- response.resize (header.size / sizeof (char ));
430
- gzread (gz_file, (void *)response.data (), header.size );
431
- gzclose (gz_file);
432
- tinyxml2::XMLError xmlCheck = doc.Parse (response.c_str ());
433
- if (doc.Parse (response.c_str ()) != tinyxml2::XML_SUCCESS)
434
- return m_request.DoMethodRequest (" channel.list&extras=true" , doc);
435
- int milliseconds = static_cast <int >(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now () - start).count ());
436
- kodi::Log (ADDON_LOG_DEBUG, " ReadCachedChannelList %d %d %d %d" , m_settings->m_instanceNumber , xmlCheck, response.length (), milliseconds);
437
- return xmlCheck;
466
+ if (ReadChannelListCache (response) != 0 )
467
+ {
468
+ tinyxml2::XMLError xmlCheck = doc.Parse (response.c_str ());
469
+ if (xmlCheck == tinyxml2::XML_SUCCESS)
470
+ {
471
+ int milliseconds = static_cast <int >(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now () - start).count ());
472
+ kodi::Log (ADDON_LOG_DEBUG, " GetChannelList %d %d %d %d" , m_settings->m_instanceNumber , xmlCheck, response.length (), milliseconds);
473
+ return xmlCheck;
474
+ }
475
+ }
476
+ kodi::Log (ADDON_LOG_ERROR, " Cannot read channel cache" );
477
+ return m_request.DoMethodRequest (" channel.list&extras=true" , doc);
478
+ }
479
+
480
+ bool Channels::ResetChannelCache (time_t updateTime)
481
+ {
482
+ if (ChannelCacheChanged (updateTime) && !m_checksumChannelList.empty ())
483
+ {
484
+ // m_checksumChannelList will be empty on error
485
+ std::lock_guard<std::recursive_mutex> lock (m_channelMutex);
486
+ auto oldDetails = m_channelDetails;
487
+ m_channelDetails.clear ();
488
+ LoadChannelDetails ();
489
+ for ( const auto &details : oldDetails)
490
+ {
491
+ if (m_channelDetails.find (details.first ) == m_channelDetails.end ())
492
+ {
493
+ DeleteChannelIcon (details.first );
494
+ }
495
+ }
496
+ return true ;
497
+ }
498
+ return false ;
499
+ }
500
+
501
+ bool Channels::LoadChannelDetails ()
502
+ {
503
+ tinyxml2::XMLDocument doc;
504
+ if (GetChannelList (doc) == tinyxml2::XML_SUCCESS)
505
+ {
506
+ tinyxml2::XMLNode* channelsNode = doc.RootElement ()->FirstChildElement (" channels" );
507
+ tinyxml2::XMLNode* pChannelNode;
508
+ for (pChannelNode = channelsNode->FirstChildElement (" channel" ); pChannelNode; pChannelNode = pChannelNode->NextSiblingElement ())
509
+ {
510
+ std::string buffer;
511
+ bool isRadio = false ;
512
+ XMLUtils::GetString (pChannelNode, " type" , buffer);
513
+ if (buffer == " 0xa" )
514
+ {
515
+ if (!m_settings->m_showRadio )
516
+ continue ;
517
+ isRadio = true ;
518
+ }
519
+ std::string epg;
520
+ if (XMLUtils::GetString (pChannelNode, " epg" , epg))
521
+ m_channelDetails[XMLUtils::GetUIntValue (pChannelNode, " id" )] = std::make_pair (epg == " None" , isRadio);
522
+ else
523
+ m_channelDetails[XMLUtils::GetUIntValue (pChannelNode, " id" )] = std::make_pair (false , isRadio);
524
+ }
525
+ return true ;
526
+ }
527
+ return false ;
438
528
}
0 commit comments