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