Merge pull request #3124 from koying/fixplugingetdirlock
[vuplus_xbmc] / xbmc / epg / EpgContainer.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "Application.h"
22 #include "threads/SingleLock.h"
23 #include "settings/AdvancedSettings.h"
24 #include "settings/Setting.h"
25 #include "settings/Settings.h"
26 #include "dialogs/GUIDialogExtendedProgressBar.h"
27 #include "dialogs/GUIDialogProgress.h"
28 #include "guilib/GUIWindowManager.h"
29 #include "guilib/LocalizeStrings.h"
30 #include "utils/log.h"
31 #include "pvr/PVRManager.h"
32 #include "pvr/channels/PVRChannelGroupsContainer.h"
33 #include "pvr/timers/PVRTimers.h"
34
35 #include "EpgContainer.h"
36 #include "Epg.h"
37 #include "EpgInfoTag.h"
38 #include "EpgSearchFilter.h"
39
40 using namespace std;
41 using namespace EPG;
42 using namespace PVR;
43
44 typedef std::map<int, CEpg*>::iterator EPGITR;
45
46 CEpgContainer::CEpgContainer(void) :
47     CThread("EPGUpdater")
48 {
49   m_progressHandle = NULL;
50   m_bStop = true;
51   m_bIsUpdating = false;
52   m_bIsInitialising = true;
53   m_iNextEpgId = 0;
54   m_bPreventUpdates = false;
55   m_updateEvent.Reset();
56   m_bStarted = false;
57   m_bLoaded = false;
58   m_bHasPendingUpdates = false;
59 }
60
61 CEpgContainer::~CEpgContainer(void)
62 {
63   Unload();
64 }
65
66 CEpgContainer &CEpgContainer::Get(void)
67 {
68   static CEpgContainer epgInstance;
69   return epgInstance;
70 }
71
72 void CEpgContainer::Unload(void)
73 {
74   Stop();
75   Clear(false);
76 }
77
78 bool CEpgContainer::IsStarted(void) const
79 {
80   CSingleLock lock(m_critSection);
81   return m_bStarted;
82 }
83
84 unsigned int CEpgContainer::NextEpgId(void)
85 {
86   CSingleLock lock(m_critSection);
87   return ++m_iNextEpgId;
88 }
89
90 void CEpgContainer::Clear(bool bClearDb /* = false */)
91 {
92   /* make sure the update thread is stopped */
93   bool bThreadRunning = !m_bStop;
94   if (bThreadRunning && !Stop())
95   {
96     CLog::Log(LOGERROR, "%s - cannot stop the update thread", __FUNCTION__);
97     return;
98   }
99
100   {
101     CSingleLock lock(m_critSection);
102     /* clear all epg tables and remove pointers to epg tables on channels */
103     for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
104     {
105       it->second->UnregisterObserver(this);
106       delete it->second;
107     }
108     m_epgs.clear();
109     m_iNextEpgUpdate  = 0;
110     m_bStarted = false;
111     m_bIsInitialising = true;
112     m_iNextEpgId = 0;
113   }
114
115   /* clear the database entries */
116   if (bClearDb && !m_bIgnoreDbForClient)
117   {
118     if (!m_database.IsOpen())
119       m_database.Open();
120
121     if (m_database.IsOpen())
122       m_database.DeleteEpg();
123   }
124
125   SetChanged();
126   NotifyObservers(ObservableMessageEpgContainer);
127
128   if (bThreadRunning)
129     Start();
130 }
131
132 void CEpgContainer::Start(void)
133 {
134   Stop();
135
136   {
137     CSingleLock lock(m_critSection);
138
139     if (!m_database.IsOpen())
140       m_database.Open();
141
142     m_bIsInitialising = true;
143     m_bStop = false;
144     LoadSettings();
145
146     m_iNextEpgUpdate  = 0;
147     m_iNextEpgActiveTagCheck = 0;
148   }
149
150   LoadFromDB();
151
152   CSingleLock lock(m_critSection);
153   if (!m_bStop)
154   {
155     CheckPlayingEvents();
156
157     Create();
158     SetPriority(-1);
159
160     m_bStarted = true;
161
162     CLog::Log(LOGNOTICE, "%s - EPG thread started", __FUNCTION__);
163   }
164 }
165
166 bool CEpgContainer::Stop(void)
167 {
168   StopThread();
169
170   if (m_database.IsOpen())
171     m_database.Close();
172
173   CSingleLock lock(m_critSection);
174   m_bStarted = false;
175
176   return true;
177 }
178
179 void CEpgContainer::Notify(const Observable &obs, const ObservableMessage msg)
180 {
181   SetChanged();
182   NotifyObservers(msg);
183 }
184
185 void CEpgContainer::OnSettingChanged(const CSetting *setting)
186 {
187   if (setting == NULL)
188     return;
189
190   const std::string &settingId = setting->GetId();
191   if (settingId == "epg.ignoredbforclient" || settingId == "epg.epgupdate" ||
192       settingId == "epg.daystodisplay")
193     LoadSettings();
194 }
195
196 void CEpgContainer::LoadFromDB(void)
197 {
198   CSingleLock lock(m_critSection);
199
200   if (m_bLoaded || m_bIgnoreDbForClient)
201     return;
202
203   if (!m_database.IsOpen())
204     m_database.Open();
205
206   m_iNextEpgId = m_database.GetLastEPGId();
207
208   bool bLoaded(true);
209   unsigned int iCounter(0);
210   if (m_database.IsOpen())
211   {
212     ShowProgressDialog(false);
213
214     m_database.DeleteOldEpgEntries();
215     m_database.Get(*this);
216
217     for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
218     {
219       if (m_bStop)
220         break;
221       UpdateProgressDialog(++iCounter, m_epgs.size(), it->second->Name());
222       lock.Leave();
223       it->second->Load();
224       lock.Enter();
225     }
226
227     CloseProgressDialog();
228   }
229
230   m_bLoaded = bLoaded;
231 }
232
233 bool CEpgContainer::PersistTables(void)
234 {
235   return m_database.Persist(*this);
236 }
237
238 bool CEpgContainer::PersistAll(void)
239 {
240   bool bReturn(true);
241   CSingleLock lock(m_critSection);
242   for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end() && !m_bStop; it++)
243   {
244     CEpg *epg = it->second;
245     if (epg && epg->NeedsSave())
246     {
247       lock.Leave();
248       bReturn &= epg->Persist();
249       lock.Enter();
250     }
251   }
252
253   return bReturn;
254 }
255
256 void CEpgContainer::Process(void)
257 {
258   time_t iNow(0), iLastSave(0);
259   bool bUpdateEpg(true);
260   bool bHasPendingUpdates(false);
261
262   while (!m_bStop && !g_application.m_bStop)
263   {
264     CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
265     {
266       CSingleLock lock(m_critSection);
267       bUpdateEpg = (iNow >= m_iNextEpgUpdate);
268     }
269
270     /* update the EPG */
271     if (!InterruptUpdate() && bUpdateEpg && g_PVRManager.EpgsCreated() && UpdateEPG())
272       m_bIsInitialising = false;
273
274     /* clean up old entries */
275     if (!m_bStop && iNow >= m_iLastEpgCleanup)
276       RemoveOldEntries();
277
278     /* check for pending manual EPG updates */
279     if (!m_bStop)
280     {
281       {
282         CSingleLock lock(m_critSection);
283         bHasPendingUpdates = m_bHasPendingUpdates;
284       }
285
286       if (bHasPendingUpdates)
287         UpdateEPG(true);
288     }
289
290     /* check for updated active tag */
291     if (!m_bStop)
292       CheckPlayingEvents();
293
294     /* check for changes that need to be saved every 60 seconds */
295     if (iNow - iLastSave > 60)
296     {
297       PersistAll();
298       iLastSave = iNow;
299     }
300
301     Sleep(1000);
302   }
303 }
304
305 CEpg *CEpgContainer::GetById(int iEpgId) const
306 {
307   if (iEpgId < 0)
308     return NULL;
309
310   CSingleLock lock(m_critSection);
311   map<unsigned int, CEpg *>::const_iterator it = m_epgs.find((unsigned int) iEpgId);
312   return it != m_epgs.end() ? it->second : NULL;
313 }
314
315 CEpg *CEpgContainer::GetByChannel(const CPVRChannel &channel) const
316 {
317   CSingleLock lock(m_critSection);
318   for (map<unsigned int, CEpg *>::const_iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
319     if (channel.ChannelID() == it->second->ChannelID())
320       return it->second;
321
322   return NULL;
323 }
324
325 void CEpgContainer::InsertFromDatabase(int iEpgID, const CStdString &strName, const CStdString &strScraperName)
326 {
327   // table might already have been created when pvr channels were loaded
328   CEpg* epg = GetById(iEpgID);
329   if (epg)
330   {
331     if (!epg->Name().Equals(strName) || !epg->ScraperName().Equals(strScraperName))
332     {
333       // current table data differs from the info in the db
334       epg->SetChanged();
335       SetChanged();
336     }
337   }
338   else
339   {
340     // create a new epg table
341     epg = new CEpg(iEpgID, strName, strScraperName, true);
342     if (epg)
343     {
344       m_epgs.insert(make_pair(iEpgID, epg));
345       SetChanged();
346       epg->RegisterObserver(this);
347     }
348   }
349 }
350
351 CEpg *CEpgContainer::CreateChannelEpg(CPVRChannelPtr channel)
352 {
353   if (!channel)
354     return NULL;
355
356   WaitForUpdateFinish(true);
357   LoadFromDB();
358
359   CEpg *epg(NULL);
360   if (channel->EpgID() > 0)
361     epg = GetById(channel->EpgID());
362
363   if (!epg)
364   {
365     channel->SetEpgID(NextEpgId());
366     epg = new CEpg(channel, false);
367
368     CSingleLock lock(m_critSection);
369     m_epgs.insert(make_pair((unsigned int)epg->EpgID(), epg));
370     SetChanged();
371     epg->RegisterObserver(this);
372   }
373
374   epg->SetChannel(channel);
375
376   {
377     CSingleLock lock(m_critSection);
378     m_bPreventUpdates = false;
379     CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
380   }
381
382   NotifyObservers(ObservableMessageEpgContainer);
383
384   return epg;
385 }
386
387 bool CEpgContainer::LoadSettings(void)
388 {
389   m_bIgnoreDbForClient = CSettings::Get().GetBool("epg.ignoredbforclient");
390   m_iUpdateTime        = CSettings::Get().GetInt ("epg.epgupdate") * 60;
391   m_iDisplayTime       = CSettings::Get().GetInt ("epg.daystodisplay") * 24 * 60 * 60;
392
393   return true;
394 }
395
396 bool CEpgContainer::RemoveOldEntries(void)
397 {
398   CDateTime now = CDateTime::GetUTCDateTime() -
399       CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
400
401   /* call Cleanup() on all known EPG tables */
402   for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
403     it->second->Cleanup(now);
404
405   /* remove the old entries from the database */
406   if (!m_bIgnoreDbForClient && m_database.IsOpen())
407     m_database.DeleteOldEpgEntries();
408
409   CSingleLock lock(m_critSection);
410   CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup);
411   m_iLastEpgCleanup += g_advancedSettings.m_iEpgCleanupInterval;
412
413   return true;
414 }
415
416 bool CEpgContainer::DeleteEpg(const CEpg &epg, bool bDeleteFromDatabase /* = false */)
417 {
418   if (epg.EpgID() < 0)
419     return false;
420
421   CSingleLock lock(m_critSection);
422
423   map<unsigned int, CEpg *>::iterator it = m_epgs.find((unsigned int)epg.EpgID());
424   if (it == m_epgs.end())
425     return false;
426
427   CLog::Log(LOGDEBUG, "deleting EPG table %s (%d)", epg.Name().c_str(), epg.EpgID());
428   if (bDeleteFromDatabase && !m_bIgnoreDbForClient && m_database.IsOpen())
429     m_database.Delete(*it->second);
430
431   it->second->UnregisterObserver(this);
432   delete it->second;
433   m_epgs.erase(it);
434
435   return true;
436 }
437
438 void CEpgContainer::CloseProgressDialog(void)
439 {
440   if (m_progressHandle)
441   {
442     m_progressHandle->MarkFinished();
443     m_progressHandle = NULL;
444   }
445 }
446
447 void CEpgContainer::ShowProgressDialog(bool bUpdating /* = true */)
448 {
449   if (!m_progressHandle)
450   {
451     CGUIDialogExtendedProgressBar *progressDialog = (CGUIDialogExtendedProgressBar *)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
452     if (progressDialog)
453       m_progressHandle = progressDialog->GetHandle(bUpdating ? g_localizeStrings.Get(19004) : g_localizeStrings.Get(19250));
454   }
455 }
456
457 void CEpgContainer::UpdateProgressDialog(int iCurrent, int iMax, const CStdString &strText)
458 {
459   if (!m_progressHandle)
460     ShowProgressDialog();
461
462   if (m_progressHandle)
463   {
464     m_progressHandle->SetProgress(iCurrent, iMax);
465     m_progressHandle->SetText(strText);
466   }
467 }
468
469 bool CEpgContainer::InterruptUpdate(void) const
470 {
471   bool bReturn(false);
472   CSingleLock lock(m_critSection);
473   bReturn = g_application.m_bStop || m_bStop || m_bPreventUpdates;
474   lock.Leave();
475
476   return bReturn ||
477     (CSettings::Get().GetBool("epg.preventupdateswhileplayingtv") &&
478      g_PVRManager.IsStarted() &&
479      g_PVRManager.IsPlaying());
480 }
481
482 void CEpgContainer::WaitForUpdateFinish(bool bInterrupt /* = true */)
483 {
484   {
485     CSingleLock lock(m_critSection);
486     if (bInterrupt)
487       m_bPreventUpdates = true;
488
489     if (!m_bIsUpdating)
490       return;
491
492     m_updateEvent.Reset();
493   }
494
495   m_updateEvent.Wait();
496 }
497
498 bool CEpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
499 {
500   bool bInterrupted(false);
501   unsigned int iUpdatedTables(0);
502   bool bShowProgress(false);
503
504   /* set start and end time */
505   time_t start;
506   time_t end;
507   CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(start);
508   end = start + m_iDisplayTime;
509   start -= g_advancedSettings.m_iEpgLingerTime * 60;
510   bShowProgress = g_advancedSettings.m_bEpgDisplayUpdatePopup && (m_bIsInitialising || g_advancedSettings.m_bEpgDisplayIncrementalUpdatePopup);
511
512   {
513     CSingleLock lock(m_critSection);
514     if (m_bIsUpdating || InterruptUpdate())
515       return false;
516     m_bIsUpdating = true;
517   }
518
519   if (bShowProgress && !bOnlyPending)
520     ShowProgressDialog();
521
522   if (!m_bIgnoreDbForClient && !m_database.IsOpen())
523   {
524     CLog::Log(LOGERROR, "EpgContainer - %s - could not open the database", __FUNCTION__);
525
526     CSingleLock lock(m_critSection);
527     m_bIsUpdating = false;
528     m_updateEvent.Set();
529
530     if (bShowProgress && !bOnlyPending)
531       CloseProgressDialog();
532
533     return false;
534   }
535
536   vector<CEpg*> invalidTables;
537
538   /* load or update all EPG tables */
539   CEpg *epg;
540   unsigned int iCounter(0);
541   for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
542   {
543     if (InterruptUpdate())
544     {
545       bInterrupted = true;
546       break;
547     }
548
549     epg = it->second;
550     if (!epg)
551       continue;
552
553     if (bShowProgress && !bOnlyPending)
554       UpdateProgressDialog(++iCounter, m_epgs.size(), epg->Name());
555
556     // we currently only support update via pvr add-ons. skip update when the pvr manager isn't started
557     if (!g_PVRManager.IsStarted())
558       continue;
559
560     // check the pvr manager when the channel pointer isn't set
561     if (!epg->Channel())
562     {
563       CPVRChannelPtr channel = g_PVRChannelGroups->GetChannelByEpgId(epg->EpgID());
564       if (channel)
565         epg->SetChannel(channel);
566     }
567
568     if ((!bOnlyPending || epg->UpdatePending()) && epg->Update(start, end, m_iUpdateTime, bOnlyPending))
569       iUpdatedTables++;
570     else if (!epg->IsValid())
571       invalidTables.push_back(epg);
572   }
573
574   for (vector<CEpg*>::iterator it = invalidTables.begin(); it != invalidTables.end(); it++)
575     DeleteEpg(**it, true);
576
577   if (bInterrupted)
578   {
579     /* the update has been interrupted. try again later */
580     time_t iNow;
581     CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
582     m_iNextEpgUpdate = iNow + g_advancedSettings.m_iEpgRetryInterruptedUpdateInterval;
583   }
584   else
585   {
586     CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
587     m_iNextEpgUpdate += g_advancedSettings.m_iEpgUpdateCheckInterval;
588     m_bHasPendingUpdates = false;
589   }
590
591   if (bShowProgress && !bOnlyPending)
592     CloseProgressDialog();
593
594   /* notify observers */
595   if (iUpdatedTables > 0)
596   {
597     SetChanged();
598     NotifyObservers(ObservableMessageEpgContainer);
599   }
600
601   CSingleLock lock(m_critSection);
602   m_bIsUpdating = false;
603   m_updateEvent.Set();
604
605   return !bInterrupted;
606 }
607
608 int CEpgContainer::GetEPGAll(CFileItemList &results)
609 {
610   int iInitialSize = results.Size();
611
612   CSingleLock lock(m_critSection);
613   for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
614     it->second->Get(results);
615
616   return results.Size() - iInitialSize;
617 }
618
619 const CDateTime CEpgContainer::GetFirstEPGDate(void)
620 {
621   CDateTime returnValue;
622
623   CSingleLock lock(m_critSection);
624   for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
625   {
626     lock.Leave();
627     CDateTime entry = it->second->GetFirstDate();
628     if (entry.IsValid() && (!returnValue.IsValid() || entry < returnValue))
629       returnValue = entry;
630     lock.Enter();
631   }
632
633   return returnValue;
634 }
635
636 const CDateTime CEpgContainer::GetLastEPGDate(void)
637 {
638   CDateTime returnValue;
639
640   CSingleLock lock(m_critSection);
641   for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
642   {
643     lock.Leave();
644     CDateTime entry = it->second->GetLastDate();
645     if (entry.IsValid() && (!returnValue.IsValid() || entry > returnValue))
646       returnValue = entry;
647     lock.Enter();
648   }
649
650   return returnValue;
651 }
652
653 int CEpgContainer::GetEPGSearch(CFileItemList &results, const EpgSearchFilter &filter)
654 {
655   int iInitialSize = results.Size();
656
657   /* get filtered results from all tables */
658   {
659     CSingleLock lock(m_critSection);
660     for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
661       it->second->Get(results, filter);
662   }
663
664   /* remove duplicate entries */
665   if (filter.m_bPreventRepeats)
666     EpgSearchFilter::RemoveDuplicates(results);
667
668   return results.Size() - iInitialSize;
669 }
670
671 bool CEpgContainer::CheckPlayingEvents(void)
672 {
673   bool bReturn(false);
674   time_t iNow;
675   CSingleLock lock(m_critSection);
676
677   CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
678   if (iNow >= m_iNextEpgActiveTagCheck)
679   {
680     bool bFoundChanges(false);
681     CSingleLock lock(m_critSection);
682
683     for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
684       bFoundChanges = it->second->CheckPlayingEvent() || bFoundChanges;
685     CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgActiveTagCheck);
686     m_iNextEpgActiveTagCheck += g_advancedSettings.m_iEpgActiveTagCheckInterval;
687
688     if (bFoundChanges)
689     {
690       SetChanged();
691       NotifyObservers(ObservableMessageEpgActiveItem);
692     }
693
694     /* pvr tags always start on the full minute */
695     if (g_PVRManager.IsStarted())
696       m_iNextEpgActiveTagCheck -= m_iNextEpgActiveTagCheck % 60;
697
698     bReturn = true;
699   }
700
701   return bReturn;
702 }
703
704 bool CEpgContainer::IsInitialising(void) const
705 {
706   CSingleLock lock(m_critSection);
707   return m_bIsInitialising;
708 }
709
710 void CEpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */)
711 {
712   CSingleLock lock(m_critSection);
713   m_bHasPendingUpdates = bHasPendingUpdates;
714 }