1 /*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "EpgContainer.h"
10
11 #include "ServiceBroker.h"
12 #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID
13 #include "guilib/LocalizeStrings.h"
14 #include "pvr/PVRManager.h"
15 #include "pvr/epg/Epg.h"
16 #include "pvr/epg/EpgChannelData.h"
17 #include "pvr/epg/EpgContainer.h"
18 #include "pvr/epg/EpgDatabase.h"
19 #include "pvr/epg/EpgInfoTag.h"
20 #include "pvr/guilib/PVRGUIProgressHandler.h"
21 #include "settings/AdvancedSettings.h"
22 #include "settings/Settings.h"
23 #include "settings/SettingsComponent.h"
24 #include "threads/SingleLock.h"
25 #include "utils/log.h"
26
27 #include <memory>
28 #include <utility>
29 #include <vector>
30
31 namespace PVR
32 {
33
34 class CEpgUpdateRequest
35 {
36 public:
CEpgUpdateRequest()37 CEpgUpdateRequest() : CEpgUpdateRequest(-1, PVR_CHANNEL_INVALID_UID) {}
CEpgUpdateRequest(int iClientID,int iUniqueChannelID)38 CEpgUpdateRequest(int iClientID, int iUniqueChannelID) : m_iClientID(iClientID), m_iUniqueChannelID(iUniqueChannelID) {}
39
40 void Deliver();
41
42 private:
43 int m_iClientID;
44 int m_iUniqueChannelID;
45 };
46
Deliver()47 void CEpgUpdateRequest::Deliver()
48 {
49 const std::shared_ptr<CPVREpg> epg = CServiceBroker::GetPVRManager().EpgContainer().GetByChannelUid(m_iClientID, m_iUniqueChannelID);
50 if (!epg)
51 {
52 CLog::LogF(LOGERROR,
53 "Unable to obtain EPG for client {} and channel {}! Unable to deliver the epg "
54 "update request!",
55 m_iClientID, m_iUniqueChannelID);
56 return;
57 }
58
59 epg->ForceUpdate();
60 }
61
62 class CEpgTagStateChange
63 {
64 public:
65 CEpgTagStateChange() = default;
CEpgTagStateChange(const std::shared_ptr<CPVREpgInfoTag> & tag,EPG_EVENT_STATE eNewState)66 CEpgTagStateChange(const std::shared_ptr<CPVREpgInfoTag>& tag, EPG_EVENT_STATE eNewState) : m_epgtag(tag), m_state(eNewState) {}
67
68 void Deliver();
69
70 private:
71 std::shared_ptr<CPVREpgInfoTag> m_epgtag;
72 EPG_EVENT_STATE m_state = EPG_EVENT_CREATED;
73 };
74
Deliver()75 void CEpgTagStateChange::Deliver()
76 {
77 CPVREpgContainer& epgContainer = CServiceBroker::GetPVRManager().EpgContainer();
78
79 const std::shared_ptr<CPVREpg> epg = epgContainer.GetByChannelUid(m_epgtag->ClientID(), m_epgtag->UniqueChannelID());
80 if (!epg)
81 {
82 CLog::LogF(LOGERROR,
83 "Unable to obtain EPG for client {} and channel {}! Unable to deliver state change "
84 "for tag '{}'!",
85 m_epgtag->ClientID(), m_epgtag->UniqueChannelID(), m_epgtag->UniqueBroadcastID());
86 return;
87 }
88
89 if (m_epgtag->EpgID() < 0)
90 {
91 // now that we have the epg instance, fully initialize the tag
92 m_epgtag->SetEpgID(epg->EpgID());
93 m_epgtag->SetChannelData(epg->GetChannelData());
94 }
95
96 epg->UpdateEntry(m_epgtag, m_state);
97 }
98
CPVREpgContainer()99 CPVREpgContainer::CPVREpgContainer() :
100 CThread("EPGUpdater"),
101 m_database(new CPVREpgDatabase),
102 m_settings({
103 CSettings::SETTING_EPG_EPGUPDATE,
104 CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY,
105 CSettings::SETTING_EPG_PAST_DAYSTODISPLAY,
106 CSettings::SETTING_EPG_PREVENTUPDATESWHILEPLAYINGTV
107 })
108 {
109 m_bStop = true; // base class member
110 m_updateEvent.Reset();
111 }
112
~CPVREpgContainer()113 CPVREpgContainer::~CPVREpgContainer()
114 {
115 Stop();
116 Unload();
117 }
118
GetEpgDatabase() const119 std::shared_ptr<CPVREpgDatabase> CPVREpgContainer::GetEpgDatabase() const
120 {
121 CSingleLock lock(m_critSection);
122
123 if (!m_database->IsOpen())
124 m_database->Open();
125
126 return m_database;
127 }
128
IsStarted() const129 bool CPVREpgContainer::IsStarted() const
130 {
131 CSingleLock lock(m_critSection);
132 return m_bStarted;
133 }
134
NextEpgId()135 int CPVREpgContainer::NextEpgId()
136 {
137 CSingleLock lock(m_critSection);
138 return ++m_iNextEpgId;
139 }
140
Start()141 void CPVREpgContainer::Start()
142 {
143 Stop();
144
145 {
146 CSingleLock lock(m_critSection);
147 m_bIsInitialising = true;
148
149 CheckPlayingEvents();
150
151 Create();
152 SetPriority(-1);
153
154 m_bStarted = true;
155 }
156 }
157
Stop()158 void CPVREpgContainer::Stop()
159 {
160 StopThread();
161
162 {
163 CSingleLock lock(m_critSection);
164 m_bStarted = false;
165 }
166 }
167
Load()168 bool CPVREpgContainer::Load()
169 {
170 // EPGs must be loaded via PVR Manager -> channel groups -> EPG container to associate the
171 // channels with the right EPG.
172 CServiceBroker::GetPVRManager().TriggerEpgsCreate();
173 return true;
174 }
175
Unload()176 void CPVREpgContainer::Unload()
177 {
178 {
179 CSingleLock lock(m_updateRequestsLock);
180 m_updateRequests.clear();
181 }
182
183 {
184 CSingleLock lock(m_epgTagChangesLock);
185 m_epgTagChanges.clear();
186 }
187
188 std::vector<std::shared_ptr<CPVREpg>> epgs;
189 {
190 CSingleLock lock(m_critSection);
191
192 /* clear all epg tables and remove pointers to epg tables on channels */
193 for (const auto& epgEntry : m_epgIdToEpgMap)
194 epgs.emplace_back(epgEntry.second);
195
196 m_epgIdToEpgMap.clear();
197 m_channelUidToEpgMap.clear();
198
199 m_iNextEpgUpdate = 0;
200 m_iNextEpgId = 0;
201 m_iNextEpgActiveTagCheck = 0;
202 m_bUpdateNotificationPending = false;
203 m_bLoaded = false;
204
205 m_database->Close();
206 }
207
208 for (const auto& epg : epgs)
209 {
210 epg->Events().Unsubscribe(this);
211 epg->RemovedFromContainer();
212 }
213
214 m_events.Publish(PVREvent::EpgContainer);
215 }
216
Notify(const PVREvent & event)217 void CPVREpgContainer::Notify(const PVREvent& event)
218 {
219 if (event == PVREvent::EpgItemUpdate)
220 {
221 // there can be many of these notifications during short time period. Thus, announce async and not every event.
222 CSingleLock lock(m_critSection);
223 m_bUpdateNotificationPending = true;
224 return;
225 }
226 else if (event == PVREvent::EpgUpdatePending)
227 {
228 SetHasPendingUpdates(true);
229 return;
230 }
231
232 m_events.Publish(event);
233 }
234
LoadFromDB()235 void CPVREpgContainer::LoadFromDB()
236 {
237 CSingleLock lock(m_critSection);
238
239 if (m_bLoaded)
240 return;
241
242 const std::shared_ptr<CPVREpgDatabase> database = GetEpgDatabase();
243 database->Lock();
244 m_iNextEpgId = database->GetLastEPGId();
245 const std::vector<std::shared_ptr<CPVREpg>> result = database->GetAll();
246 database->Unlock();
247
248 for (const auto& entry : result)
249 InsertFromDB(entry);
250
251 m_bLoaded = true;
252 }
253
PersistAll(unsigned int iMaxTimeslice) const254 bool CPVREpgContainer::PersistAll(unsigned int iMaxTimeslice) const
255 {
256 const std::shared_ptr<CPVREpgDatabase> database = GetEpgDatabase();
257 if (!database)
258 {
259 CLog::LogF(LOGERROR, "No EPG database");
260 return false;
261 }
262
263 std::vector<std::shared_ptr<CPVREpg>> changedEpgs;
264 {
265 CSingleLock lock(m_critSection);
266 for (const auto& epg : m_epgIdToEpgMap)
267 {
268 if (epg.second && epg.second->NeedsSave())
269 {
270 // Note: We need to obtain a lock for every epg instance before we can lock
271 // the epg db. This order is important. Otherwise deadlocks may occur.
272 epg.second->Lock();
273 changedEpgs.emplace_back(epg.second);
274 }
275 }
276 }
277
278 bool bReturn = true;
279
280 if (!changedEpgs.empty())
281 {
282 // Note: We must lock the db the whole time, otherwise races may occur.
283 database->Lock();
284
285 XbmcThreads::EndTime processTimeslice(iMaxTimeslice);
286 for (const auto& epg : changedEpgs)
287 {
288 if (!processTimeslice.IsTimePast())
289 {
290 CLog::LogFC(LOGDEBUG, LOGEPG, "EPG Container: Persisting events for channel '{}'...",
291 epg->GetChannelData()->ChannelName());
292
293 bReturn &= epg->QueuePersistQuery(database);
294
295 size_t queryCount = database->GetInsertQueriesCount() + database->GetDeleteQueriesCount();
296 if (queryCount > EPG_COMMIT_QUERY_COUNT_LIMIT)
297 {
298 CLog::LogFC(LOGDEBUG, LOGEPG, "EPG Container: committing {} queries in loop.",
299 queryCount);
300 database->CommitDeleteQueries();
301 database->CommitInsertQueries();
302 CLog::LogFC(LOGDEBUG, LOGEPG, "EPG Container: committed {} queries in loop.", queryCount);
303 }
304 }
305
306 epg->Unlock();
307 }
308
309 if (bReturn)
310 {
311 database->CommitDeleteQueries();
312 database->CommitInsertQueries();
313 }
314
315 database->Unlock();
316 }
317
318 return bReturn;
319 }
320
Process()321 void CPVREpgContainer::Process()
322 {
323 time_t iNow = 0;
324 time_t iLastSave = 0;
325 time_t iLastEpgCleanup = 0;
326 bool bUpdateEpg = true;
327 bool bHasPendingUpdates = false;
328
329 SetPriority(GetMinPriority());
330
331 while (!m_bStop)
332 {
333 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
334 {
335 CSingleLock lock(m_critSection);
336 bUpdateEpg = (iNow >= m_iNextEpgUpdate) && !m_bSuspended;
337 iLastEpgCleanup = m_iLastEpgCleanup;
338 }
339
340 /* update the EPG */
341 if (!InterruptUpdate() && bUpdateEpg && CServiceBroker::GetPVRManager().EpgsCreated() && UpdateEPG())
342 m_bIsInitialising = false;
343
344 /* clean up old entries */
345 if (!m_bStop && !m_bSuspended &&
346 iNow >= iLastEpgCleanup + CServiceBroker::GetSettingsComponent()
347 ->GetAdvancedSettings()
348 ->m_iEpgCleanupInterval)
349 RemoveOldEntries();
350
351 /* check for pending manual EPG updates */
352
353 while (!m_bStop && !m_bSuspended)
354 {
355 CEpgUpdateRequest request;
356 {
357 CSingleLock lock(m_updateRequestsLock);
358 if (m_updateRequests.empty())
359 break;
360
361 request = m_updateRequests.front();
362 m_updateRequests.pop_front();
363 }
364
365 // do the update
366 request.Deliver();
367 }
368
369 /* check for pending EPG tag changes */
370
371 // during Kodi startup, addons may push updates very early, even before EPGs are ready to use.
372 if (!m_bStop && !m_bSuspended && CServiceBroker::GetPVRManager().EpgsCreated())
373 {
374 unsigned int iProcessed = 0;
375 XbmcThreads::EndTime processTimeslice(1000); // max 1 sec per cycle, regardless of how many events are in the queue
376
377 while (!InterruptUpdate())
378 {
379 CEpgTagStateChange change;
380 {
381 CSingleLock lock(m_epgTagChangesLock);
382 if (processTimeslice.IsTimePast() || m_epgTagChanges.empty())
383 {
384 if (iProcessed > 0)
385 CLog::LogFC(LOGDEBUG, LOGEPG, "Processed {} queued epg event changes.", iProcessed);
386
387 break;
388 }
389
390 change = m_epgTagChanges.front();
391 m_epgTagChanges.pop_front();
392 }
393
394 iProcessed++;
395
396 // deliver the updated tag to the respective epg
397 change.Deliver();
398 }
399 }
400
401 if (!m_bStop && !m_bSuspended)
402 {
403 {
404 CSingleLock lock(m_critSection);
405 bHasPendingUpdates = (m_pendingUpdates > 0);
406 }
407
408 if (bHasPendingUpdates)
409 UpdateEPG(true);
410 }
411
412 /* check for updated active tag */
413 if (!m_bStop)
414 CheckPlayingEvents();
415
416 /* check for pending update notifications */
417 if (!m_bStop)
418 {
419 CSingleLock lock(m_critSection);
420 if (m_bUpdateNotificationPending)
421 {
422 m_bUpdateNotificationPending = false;
423 m_events.Publish(PVREvent::Epg);
424 }
425 }
426
427 /* check for changes that need to be saved every 60 seconds */
428 if ((iNow - iLastSave > 60) && !InterruptUpdate())
429 {
430 PersistAll(1000);
431 iLastSave = iNow;
432 }
433
434 CThread::Sleep(1000);
435 }
436
437 // store data on exit
438 CLog::Log(LOGINFO, "EPG Container: Persisting unsaved events...");
439 PersistAll(XbmcThreads::EndTime::InfiniteValue);
440 CLog::Log(LOGINFO, "EPG Container: Persisting events done");
441 }
442
GetAllEpgs() const443 std::vector<std::shared_ptr<CPVREpg>> CPVREpgContainer::GetAllEpgs() const
444 {
445 std::vector<std::shared_ptr<CPVREpg>> epgs;
446
447 CSingleLock lock(m_critSection);
448 for (const auto& epg : m_epgIdToEpgMap)
449 {
450 epgs.emplace_back(epg.second);
451 }
452
453 return epgs;
454 }
455
GetById(int iEpgId) const456 std::shared_ptr<CPVREpg> CPVREpgContainer::GetById(int iEpgId) const
457 {
458 std::shared_ptr<CPVREpg> retval;
459
460 if (iEpgId < 0)
461 return retval;
462
463 CSingleLock lock(m_critSection);
464 const auto& epgEntry = m_epgIdToEpgMap.find(iEpgId);
465 if (epgEntry != m_epgIdToEpgMap.end())
466 retval = epgEntry->second;
467
468 return retval;
469 }
470
GetByChannelUid(int iClientId,int iChannelUid) const471 std::shared_ptr<CPVREpg> CPVREpgContainer::GetByChannelUid(int iClientId, int iChannelUid) const
472 {
473 std::shared_ptr<CPVREpg> epg;
474
475 if (iClientId < 0 || iChannelUid < 0)
476 return epg;
477
478 CSingleLock lock(m_critSection);
479 const auto& epgEntry = m_channelUidToEpgMap.find(std::pair<int, int>(iClientId, iChannelUid));
480 if (epgEntry != m_channelUidToEpgMap.end())
481 epg = epgEntry->second;
482
483 return epg;
484 }
485
GetTagById(const std::shared_ptr<CPVREpg> & epg,unsigned int iBroadcastId) const486 std::shared_ptr<CPVREpgInfoTag> CPVREpgContainer::GetTagById(const std::shared_ptr<CPVREpg>& epg, unsigned int iBroadcastId) const
487 {
488 std::shared_ptr<CPVREpgInfoTag> retval;
489
490 if (iBroadcastId == EPG_TAG_INVALID_UID)
491 return retval;
492
493 if (epg)
494 {
495 retval = epg->GetTagByBroadcastId(iBroadcastId);
496 }
497
498 return retval;
499 }
500
GetTagByDatabaseId(int iDatabaseId) const501 std::shared_ptr<CPVREpgInfoTag> CPVREpgContainer::GetTagByDatabaseId(int iDatabaseId) const
502 {
503 std::shared_ptr<CPVREpgInfoTag> retval;
504
505 if (iDatabaseId <= 0)
506 return retval;
507
508 m_critSection.lock();
509 const auto epgs = m_epgIdToEpgMap;
510 m_critSection.unlock();
511
512 for (const auto& epgEntry : epgs)
513 {
514 retval = epgEntry.second->GetTagByDatabaseId(iDatabaseId);
515 if (retval)
516 break;
517 }
518
519 return retval;
520 }
521
GetTags(const PVREpgSearchData & searchData) const522 std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgContainer::GetTags(
523 const PVREpgSearchData& searchData) const
524 {
525 // make sure we have up-to-date data in the database.
526 PersistAll(XbmcThreads::EndTime::InfiniteValue);
527
528 const std::shared_ptr<CPVREpgDatabase> database = GetEpgDatabase();
529 std::vector<std::shared_ptr<CPVREpgInfoTag>> results = database->GetEpgTags(searchData);
530
531 CSingleLock lock(m_critSection);
532 for (const auto& tag : results)
533 {
534 const auto& it = m_epgIdToEpgMap.find(tag->EpgID());
535 if (it != m_epgIdToEpgMap.cend())
536 tag->SetChannelData((*it).second->GetChannelData());
537 }
538
539 return results;
540 }
541
InsertFromDB(const std::shared_ptr<CPVREpg> & newEpg)542 void CPVREpgContainer::InsertFromDB(const std::shared_ptr<CPVREpg>& newEpg)
543 {
544 CSingleLock lock(m_critSection);
545
546 // table might already have been created when pvr channels were loaded
547 std::shared_ptr<CPVREpg> epg = GetById(newEpg->EpgID());
548 if (!epg)
549 {
550 // create a new epg table
551 epg = newEpg;
552 m_epgIdToEpgMap.insert({epg->EpgID(), epg});
553 epg->Events().Subscribe(this, &CPVREpgContainer::Notify);
554 }
555 }
556
CreateChannelEpg(int iEpgId,const std::string & strScraperName,const std::shared_ptr<CPVREpgChannelData> & channelData)557 std::shared_ptr<CPVREpg> CPVREpgContainer::CreateChannelEpg(int iEpgId, const std::string& strScraperName, const std::shared_ptr<CPVREpgChannelData>& channelData)
558 {
559 std::shared_ptr<CPVREpg> epg;
560
561 WaitForUpdateFinish();
562 LoadFromDB();
563
564 if (iEpgId > 0)
565 epg = GetById(iEpgId);
566
567 if (!epg)
568 {
569 if (iEpgId <= 0)
570 iEpgId = NextEpgId();
571
572 epg.reset(new CPVREpg(iEpgId, channelData->ChannelName(), strScraperName, channelData,
573 GetEpgDatabase()));
574
575 CSingleLock lock(m_critSection);
576 m_epgIdToEpgMap.insert({iEpgId, epg});
577 m_channelUidToEpgMap.insert({{channelData->ClientId(), channelData->UniqueClientChannelId()}, epg});
578 epg->Events().Subscribe(this, &CPVREpgContainer::Notify);
579 }
580 else if (epg->ChannelID() == -1)
581 {
582 CSingleLock lock(m_critSection);
583 m_channelUidToEpgMap.insert({{channelData->ClientId(), channelData->UniqueClientChannelId()}, epg});
584 epg->SetChannelData(channelData);
585 }
586
587 {
588 CSingleLock lock(m_critSection);
589 m_bPreventUpdates = false;
590 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
591 }
592
593 m_events.Publish(PVREvent::EpgContainer);
594
595 return epg;
596 }
597
RemoveOldEntries()598 bool CPVREpgContainer::RemoveOldEntries()
599 {
600 const CDateTime cleanupTime(CDateTime::GetUTCDateTime() - CDateTimeSpan(GetPastDaysToDisplay(), 0, 0, 0));
601
602 m_critSection.lock();
603 const auto epgs = m_epgIdToEpgMap;
604 m_critSection.unlock();
605
606 for (const auto& epgEntry : epgs)
607 epgEntry.second->Cleanup(cleanupTime);
608
609 CSingleLock lock(m_critSection);
610 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup);
611
612 return true;
613 }
614
QueueDeleteEpgs(const std::vector<std::shared_ptr<CPVREpg>> & epgs)615 bool CPVREpgContainer::QueueDeleteEpgs(const std::vector<std::shared_ptr<CPVREpg>>& epgs)
616 {
617 if (epgs.empty())
618 return true;
619
620 const std::shared_ptr<CPVREpgDatabase> database = GetEpgDatabase();
621 if (!database)
622 {
623 CLog::LogF(LOGERROR, "No EPG database");
624 return false;
625 }
626
627 for (const auto& epg : epgs)
628 {
629 // Note: We need to obtain a lock for every epg instance before we can lock
630 // the epg db. This order is important. Otherwise deadlocks may occur.
631 epg->Lock();
632 }
633
634 database->Lock();
635 for (const auto& epg : epgs)
636 {
637 QueueDeleteEpg(epg);
638 epg->Unlock();
639
640 size_t queryCount = database->GetDeleteQueriesCount();
641 if (queryCount > EPG_COMMIT_QUERY_COUNT_LIMIT)
642 database->CommitDeleteQueries();
643 }
644 database->CommitDeleteQueries();
645 database->Unlock();
646
647 return true;
648 }
649
QueueDeleteEpg(const std::shared_ptr<CPVREpg> & epg)650 bool CPVREpgContainer::QueueDeleteEpg(const std::shared_ptr<CPVREpg>& epg)
651 {
652 if (!epg || epg->EpgID() < 0)
653 return false;
654
655 const std::shared_ptr<CPVREpgDatabase> database = GetEpgDatabase();
656 if (!database)
657 {
658 CLog::LogF(LOGERROR, "No EPG database");
659 return false;
660 }
661
662 std::shared_ptr<CPVREpg> epgToDelete;
663 {
664 CSingleLock lock(m_critSection);
665
666 const auto& epgEntry = m_epgIdToEpgMap.find(epg->EpgID());
667 if (epgEntry == m_epgIdToEpgMap.end())
668 return false;
669
670 const auto& epgEntry1 = m_channelUidToEpgMap.find(std::make_pair(
671 epg->GetChannelData()->ClientId(), epg->GetChannelData()->UniqueClientChannelId()));
672 if (epgEntry1 != m_channelUidToEpgMap.end())
673 m_channelUidToEpgMap.erase(epgEntry1);
674
675 CLog::LogFC(LOGDEBUG, LOGEPG, "Deleting EPG table {} ({})", epg->Name(), epg->EpgID());
676 epgEntry->second->QueueDeleteQueries(database);
677
678 epgToDelete = epgEntry->second;
679 m_epgIdToEpgMap.erase(epgEntry);
680 }
681
682 epgToDelete->Events().Unsubscribe(this);
683 epgToDelete->RemovedFromContainer();
684 return true;
685 }
686
InterruptUpdate() const687 bool CPVREpgContainer::InterruptUpdate() const
688 {
689 CSingleLock lock(m_critSection);
690 return m_bStop ||
691 m_bPreventUpdates ||
692 (m_bPlaying && m_settings.GetBoolValue(CSettings::SETTING_EPG_PREVENTUPDATESWHILEPLAYINGTV));
693 }
694
WaitForUpdateFinish()695 void CPVREpgContainer::WaitForUpdateFinish()
696 {
697 {
698 CSingleLock lock(m_critSection);
699 m_bPreventUpdates = true;
700
701 if (!m_bIsUpdating)
702 return;
703
704 m_updateEvent.Reset();
705 }
706
707 m_updateEvent.Wait();
708 }
709
UpdateEPG(bool bOnlyPending)710 bool CPVREpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
711 {
712 bool bInterrupted = false;
713 unsigned int iUpdatedTables = 0;
714 const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
715
716 /* set start and end time */
717 time_t start;
718 time_t end;
719 CDateTime::GetUTCDateTime().GetAsTime(start);
720 end = start + GetFutureDaysToDisplay() * 24 * 60 * 60;
721 start -= GetPastDaysToDisplay() * 24 * 60 * 60;
722
723 bool bShowProgress = (m_bIsInitialising || advancedSettings->m_bEpgDisplayIncrementalUpdatePopup) &&
724 advancedSettings->m_bEpgDisplayUpdatePopup;
725 int pendingUpdates = 0;
726
727 {
728 CSingleLock lock(m_critSection);
729 if (m_bIsUpdating || InterruptUpdate())
730 return false;
731
732 m_bIsUpdating = true;
733 pendingUpdates = m_pendingUpdates;
734 }
735
736 std::vector<std::shared_ptr<CPVREpg>> invalidTables;
737
738 CPVRGUIProgressHandler* progressHandler = nullptr;
739 if (bShowProgress && !bOnlyPending)
740 progressHandler = new CPVRGUIProgressHandler(g_localizeStrings.Get(19004)); // Importing guide from clients
741
742 /* load or update all EPG tables */
743 unsigned int iCounter = 0;
744 const std::shared_ptr<CPVREpgDatabase> database = GetEpgDatabase();
745
746 m_critSection.lock();
747 const auto epgsToUpdate = m_epgIdToEpgMap;
748 m_critSection.unlock();
749
750 for (const auto& epgEntry : epgsToUpdate)
751 {
752 if (InterruptUpdate())
753 {
754 bInterrupted = true;
755 break;
756 }
757
758 const std::shared_ptr<CPVREpg> epg = epgEntry.second;
759 if (!epg)
760 continue;
761
762 if (bShowProgress && !bOnlyPending)
763 progressHandler->UpdateProgress(epg->GetChannelData()->ChannelName(), ++iCounter,
764 epgsToUpdate.size());
765
766 if ((!bOnlyPending || epg->UpdatePending()) &&
767 epg->Update(start,
768 end,
769 m_settings.GetIntValue(CSettings::SETTING_EPG_EPGUPDATE) * 60,
770 m_settings.GetIntValue(CSettings::SETTING_EPG_PAST_DAYSTODISPLAY),
771 database,
772 bOnlyPending))
773 {
774 iUpdatedTables++;
775 }
776 else if (!epg->IsValid())
777 {
778 invalidTables.push_back(epg);
779 }
780 }
781
782 if (bShowProgress && !bOnlyPending)
783 progressHandler->DestroyProgress();
784
785 QueueDeleteEpgs(invalidTables);
786
787 if (bInterrupted)
788 {
789 /* the update has been interrupted. try again later */
790 time_t iNow;
791 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
792
793 CSingleLock lock(m_critSection);
794 m_iNextEpgUpdate = iNow + advancedSettings->m_iEpgRetryInterruptedUpdateInterval;
795 }
796 else
797 {
798 CSingleLock lock(m_critSection);
799 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
800 m_iNextEpgUpdate += advancedSettings->m_iEpgUpdateCheckInterval;
801 if (m_pendingUpdates == pendingUpdates)
802 m_pendingUpdates = 0;
803 }
804
805 if (iUpdatedTables > 0)
806 m_events.Publish(PVREvent::EpgContainer);
807
808 CSingleLock lock(m_critSection);
809 m_bIsUpdating = false;
810 m_updateEvent.Set();
811
812 return !bInterrupted;
813 }
814
GetFirstEPGDate()815 const CDateTime CPVREpgContainer::GetFirstEPGDate()
816 {
817 CDateTime returnValue;
818
819 m_critSection.lock();
820 const auto epgs = m_epgIdToEpgMap;
821 m_critSection.unlock();
822
823 for (const auto& epgEntry : epgs)
824 {
825 CDateTime entry = epgEntry.second->GetFirstDate();
826 if (entry.IsValid() && (!returnValue.IsValid() || entry < returnValue))
827 returnValue = entry;
828 }
829
830 return returnValue;
831 }
832
GetLastEPGDate()833 const CDateTime CPVREpgContainer::GetLastEPGDate()
834 {
835 CDateTime returnValue;
836
837 m_critSection.lock();
838 const auto epgs = m_epgIdToEpgMap;
839 m_critSection.unlock();
840
841 for (const auto& epgEntry : epgs)
842 {
843 CDateTime entry = epgEntry.second->GetLastDate();
844 if (entry.IsValid() && (!returnValue.IsValid() || entry > returnValue))
845 returnValue = entry;
846 }
847
848 return returnValue;
849 }
850
CheckPlayingEvents()851 bool CPVREpgContainer::CheckPlayingEvents()
852 {
853 bool bReturn = false;
854 bool bFoundChanges = false;
855
856 m_critSection.lock();
857 const auto epgs = m_epgIdToEpgMap;
858 time_t iNextEpgActiveTagCheck = m_iNextEpgActiveTagCheck;
859 m_critSection.unlock();
860
861 time_t iNow;
862 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
863 if (iNow >= iNextEpgActiveTagCheck)
864 {
865 for (const auto& epgEntry : epgs)
866 bFoundChanges = epgEntry.second->CheckPlayingEvent() || bFoundChanges;
867
868 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNextEpgActiveTagCheck);
869 iNextEpgActiveTagCheck += CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEpgActiveTagCheckInterval;
870
871 /* pvr tags always start on the full minute */
872 if (CServiceBroker::GetPVRManager().IsStarted())
873 iNextEpgActiveTagCheck -= iNextEpgActiveTagCheck % 60;
874
875 bReturn = true;
876 }
877
878 if (bReturn)
879 {
880 CSingleLock lock(m_critSection);
881 m_iNextEpgActiveTagCheck = iNextEpgActiveTagCheck;
882 }
883
884 if (bFoundChanges)
885 m_events.Publish(PVREvent::EpgActiveItem);
886
887 return bReturn;
888 }
889
SetHasPendingUpdates(bool bHasPendingUpdates)890 void CPVREpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */)
891 {
892 CSingleLock lock(m_critSection);
893 if (bHasPendingUpdates)
894 m_pendingUpdates++;
895 else
896 m_pendingUpdates = 0;
897 }
898
UpdateRequest(int iClientID,int iUniqueChannelID)899 void CPVREpgContainer::UpdateRequest(int iClientID, int iUniqueChannelID)
900 {
901 CSingleLock lock(m_updateRequestsLock);
902 m_updateRequests.emplace_back(CEpgUpdateRequest(iClientID, iUniqueChannelID));
903 }
904
UpdateFromClient(const std::shared_ptr<CPVREpgInfoTag> & tag,EPG_EVENT_STATE eNewState)905 void CPVREpgContainer::UpdateFromClient(const std::shared_ptr<CPVREpgInfoTag>& tag, EPG_EVENT_STATE eNewState)
906 {
907 CSingleLock lock(m_epgTagChangesLock);
908 m_epgTagChanges.emplace_back(CEpgTagStateChange(tag, eNewState));
909 }
910
GetPastDaysToDisplay() const911 int CPVREpgContainer::GetPastDaysToDisplay() const
912 {
913 return m_settings.GetIntValue(CSettings::SETTING_EPG_PAST_DAYSTODISPLAY);
914 }
915
GetFutureDaysToDisplay() const916 int CPVREpgContainer::GetFutureDaysToDisplay() const
917 {
918 return m_settings.GetIntValue(CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY);
919 }
920
OnPlaybackStarted()921 void CPVREpgContainer::OnPlaybackStarted()
922 {
923 CSingleLock lock(m_critSection);
924 m_bPlaying = true;
925 }
926
OnPlaybackStopped()927 void CPVREpgContainer::OnPlaybackStopped()
928 {
929 CSingleLock lock(m_critSection);
930 m_bPlaying = false;
931 }
932
OnSystemSleep()933 void CPVREpgContainer::OnSystemSleep()
934 {
935 m_bSuspended = true;
936 }
937
OnSystemWake()938 void CPVREpgContainer::OnSystemWake()
939 {
940 m_bSuspended = false;
941 }
942
943 } // namespace PVR
944