2 * Copyright (C) 2012-2013 Team XBMC
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)
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.
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/>.
21 #include "Application.h"
22 #include "threads/SingleLock.h"
23 #include "settings/AdvancedSettings.h"
24 #include "settings/GUISettings.h"
25 #include "dialogs/GUIDialogExtendedProgressBar.h"
26 #include "dialogs/GUIDialogProgress.h"
27 #include "guilib/GUIWindowManager.h"
28 #include "guilib/LocalizeStrings.h"
29 #include "utils/log.h"
30 #include "pvr/PVRManager.h"
31 #include "pvr/channels/PVRChannelGroupsContainer.h"
32 #include "pvr/timers/PVRTimers.h"
34 #include "EpgContainer.h"
36 #include "EpgInfoTag.h"
37 #include "EpgSearchFilter.h"
43 typedef std::map<int, CEpg*>::iterator EPGITR;
45 CEpgContainer::CEpgContainer(void) :
46 CThread("EPG updater")
48 m_progressHandle = NULL;
50 m_bIsUpdating = false;
51 m_bIsInitialising = true;
53 m_bPreventUpdates = false;
54 m_updateEvent.Reset();
56 m_bHasPendingUpdates = false;
59 CEpgContainer::~CEpgContainer(void)
64 CEpgContainer &CEpgContainer::Get(void)
66 static CEpgContainer epgInstance;
70 void CEpgContainer::Unload(void)
76 unsigned int CEpgContainer::NextEpgId(void)
78 CSingleLock lock(m_critSection);
79 return ++m_iNextEpgId;
82 void CEpgContainer::Clear(bool bClearDb /* = false */)
84 /* make sure the update thread is stopped */
85 bool bThreadRunning = !m_bStop;
86 if (bThreadRunning && !Stop())
88 CLog::Log(LOGERROR, "%s - cannot stop the update thread", __FUNCTION__);
93 CSingleLock lock(m_critSection);
94 /* clear all epg tables and remove pointers to epg tables on channels */
95 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
97 it->second->UnregisterObserver(this);
101 m_iNextEpgUpdate = 0;
102 m_bIsInitialising = true;
106 /* clear the database entries */
107 if (bClearDb && !m_bIgnoreDbForClient)
109 if (!m_database.IsOpen())
112 if (m_database.IsOpen())
113 m_database.DeleteEpg();
117 NotifyObservers(ObservableMessageEpgContainer);
123 void CEpgContainer::Start(void)
127 CSingleLock lock(m_critSection);
129 if (!m_database.IsOpen())
132 m_bIsInitialising = true;
134 g_guiSettings.RegisterObserver(this);
137 m_iNextEpgUpdate = 0;
138 m_iNextEpgActiveTagCheck = 0;
141 CheckPlayingEvents();
145 CLog::Log(LOGNOTICE, "%s - EPG thread started", __FUNCTION__);
148 bool CEpgContainer::Stop(void)
152 if (m_database.IsOpen())
158 void CEpgContainer::Notify(const Observable &obs, const ObservableMessage msg)
160 /* settings were updated */
161 if (msg == ObservableMessageGuiSettings)
166 NotifyObservers(msg);
170 void CEpgContainer::LoadFromDB(void)
172 if (m_bLoaded || m_bIgnoreDbForClient)
175 if (!m_database.IsOpen())
178 m_iNextEpgId = m_database.GetLastEPGId();
181 unsigned int iCounter(0);
182 if (m_database.IsOpen())
184 ShowProgressDialog(false);
186 m_database.DeleteOldEpgEntries();
187 m_database.Get(*this);
189 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
191 UpdateProgressDialog(++iCounter, m_epgs.size(), it->second->Name());
195 CloseProgressDialog();
198 CSingleLock lock(m_critSection);
202 bool CEpgContainer::PersistTables(void)
204 return m_database.Persist(*this);
207 bool CEpgContainer::PersistAll(void)
210 CSingleLock lock(m_critSection);
211 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end() && !m_bStop; it++)
213 CEpg *epg = it->second;
214 if (epg && epg->NeedsSave())
217 bReturn &= epg->Persist();
225 void CEpgContainer::Process(void)
227 time_t iNow(0), iLastSave(0);
228 bool bUpdateEpg(true);
229 bool bHasPendingUpdates(false);
231 if (!CPVRManager::Get().WaitUntilInitialised())
233 CLog::Log(LOGDEBUG, "EPG - %s - pvr manager failed to load - exiting", __FUNCTION__);
237 while (!m_bStop && !g_application.m_bStop)
239 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
241 CSingleLock lock(m_critSection);
242 bUpdateEpg = (iNow >= m_iNextEpgUpdate);
246 if (!InterruptUpdate() && bUpdateEpg && UpdateEPG())
247 m_bIsInitialising = false;
249 /* clean up old entries */
250 if (!m_bStop && iNow >= m_iLastEpgCleanup)
253 /* check for pending manual EPG updates */
257 CSingleLock lock(m_critSection);
258 bHasPendingUpdates = m_bHasPendingUpdates;
261 if (bHasPendingUpdates)
265 /* check for updated active tag */
267 CheckPlayingEvents();
269 /* check for changes that need to be saved every 60 seconds */
270 if (iNow - iLastSave > 60)
279 g_guiSettings.UnregisterObserver(this);
282 CEpg *CEpgContainer::GetById(int iEpgId) const
287 CSingleLock lock(m_critSection);
288 map<unsigned int, CEpg *>::const_iterator it = m_epgs.find((unsigned int) iEpgId);
289 return it != m_epgs.end() ? it->second : NULL;
292 CEpg *CEpgContainer::GetByChannel(const CPVRChannel &channel) const
294 CSingleLock lock(m_critSection);
295 for (map<unsigned int, CEpg *>::const_iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
296 if (channel.ChannelID() == it->second->ChannelID())
302 void CEpgContainer::InsertFromDatabase(int iEpgID, const CStdString &strName, const CStdString &strScraperName)
304 // table might already have been created when pvr channels were loaded
305 CEpg* epg = GetById(iEpgID);
308 if (!epg->Name().Equals(strName) || !epg->ScraperName().Equals(strScraperName))
310 // current table data differs from the info in the db
317 // create a new epg table
318 epg = new CEpg(iEpgID, strName, strScraperName, true);
321 m_epgs.insert(make_pair(iEpgID, epg));
323 epg->RegisterObserver(this);
328 CEpg *CEpgContainer::CreateChannelEpg(CPVRChannelPtr channel)
333 WaitForUpdateFinish(true);
334 CSingleLock lock(m_critSection);
338 if (channel->EpgID() > 0)
339 epg = GetById(channel->EpgID());
343 channel->SetEpgID(NextEpgId());
344 epg = new CEpg(channel, false);
345 m_epgs.insert(make_pair((unsigned int)epg->EpgID(), epg));
347 epg->RegisterObserver(this);
350 epg->SetChannel(channel);
352 m_bPreventUpdates = false;
353 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
355 NotifyObservers(ObservableMessageEpgContainer);
360 bool CEpgContainer::LoadSettings(void)
362 m_bIgnoreDbForClient = g_guiSettings.GetBool("epg.ignoredbforclient");
363 m_iUpdateTime = g_guiSettings.GetInt ("epg.epgupdate") * 60;
364 m_iDisplayTime = g_guiSettings.GetInt ("epg.daystodisplay") * 24 * 60 * 60;
369 bool CEpgContainer::RemoveOldEntries(void)
371 CDateTime now = CDateTime::GetUTCDateTime() -
372 CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
374 /* call Cleanup() on all known EPG tables */
375 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
376 it->second->Cleanup(now);
378 /* remove the old entries from the database */
379 if (!m_bIgnoreDbForClient && m_database.IsOpen())
380 m_database.DeleteOldEpgEntries();
382 CSingleLock lock(m_critSection);
383 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup);
384 m_iLastEpgCleanup += g_advancedSettings.m_iEpgCleanupInterval;
389 bool CEpgContainer::DeleteEpg(const CEpg &epg, bool bDeleteFromDatabase /* = false */)
394 CSingleLock lock(m_critSection);
396 map<unsigned int, CEpg *>::iterator it = m_epgs.find((unsigned int)epg.EpgID());
397 if (it == m_epgs.end())
400 CLog::Log(LOGDEBUG, "deleting EPG table %s (%d)", epg.Name().c_str(), epg.EpgID());
401 if (bDeleteFromDatabase && !m_bIgnoreDbForClient && m_database.IsOpen())
402 m_database.Delete(*it->second);
404 it->second->UnregisterObserver(this);
411 void CEpgContainer::CloseProgressDialog(void)
413 if (m_progressHandle)
415 m_progressHandle->MarkFinished();
416 m_progressHandle = NULL;
420 void CEpgContainer::ShowProgressDialog(bool bUpdating /* = true */)
422 if (!m_progressHandle)
424 CGUIDialogExtendedProgressBar *progressDialog = (CGUIDialogExtendedProgressBar *)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
426 m_progressHandle = progressDialog->GetHandle(bUpdating ? g_localizeStrings.Get(19004) : g_localizeStrings.Get(19250));
430 void CEpgContainer::UpdateProgressDialog(int iCurrent, int iMax, const CStdString &strText)
432 if (!m_progressHandle)
433 ShowProgressDialog();
435 if (m_progressHandle)
437 m_progressHandle->SetProgress(iCurrent, iMax);
438 m_progressHandle->SetText(strText);
442 bool CEpgContainer::InterruptUpdate(void) const
445 CSingleLock lock(m_critSection);
446 bReturn = g_application.m_bStop || m_bStop || m_bPreventUpdates;
450 (g_guiSettings.GetBool("epg.preventupdateswhileplayingtv") &&
451 g_PVRManager.IsStarted() &&
452 g_PVRManager.IsPlaying());
455 void CEpgContainer::WaitForUpdateFinish(bool bInterrupt /* = true */)
458 CSingleLock lock(m_critSection);
460 m_bPreventUpdates = true;
465 m_updateEvent.Reset();
468 m_updateEvent.Wait();
471 bool CEpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
473 bool bInterrupted(false);
474 unsigned int iUpdatedTables(0);
475 bool bShowProgress(false);
477 /* set start and end time */
480 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(start);
481 end = start + m_iDisplayTime;
482 start -= g_advancedSettings.m_iEpgLingerTime * 60;
483 bShowProgress = g_advancedSettings.m_bEpgDisplayUpdatePopup && (m_bIsInitialising || g_advancedSettings.m_bEpgDisplayIncrementalUpdatePopup);
486 CSingleLock lock(m_critSection);
487 if (m_bIsUpdating || InterruptUpdate())
489 m_bIsUpdating = true;
492 if (bShowProgress && !bOnlyPending)
493 ShowProgressDialog();
495 if (!m_bIgnoreDbForClient && !m_database.IsOpen())
497 CLog::Log(LOGERROR, "EpgContainer - %s - could not open the database", __FUNCTION__);
499 CSingleLock lock(m_critSection);
500 m_bIsUpdating = false;
503 if (bShowProgress && !bOnlyPending)
504 CloseProgressDialog();
509 vector<CEpg*> invalidTables;
511 /* load or update all EPG tables */
513 unsigned int iCounter(0);
514 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
516 if (InterruptUpdate())
526 if (bShowProgress && !bOnlyPending)
527 UpdateProgressDialog(++iCounter, m_epgs.size(), epg->Name());
529 // we currently only support update via pvr add-ons. skip update when the pvr manager isn't started
530 if (!g_PVRManager.IsStarted())
533 // check the pvr manager when the channel pointer isn't set
536 CPVRChannelPtr channel = g_PVRChannelGroups->GetChannelByEpgId(epg->EpgID());
538 epg->SetChannel(channel);
541 if ((!bOnlyPending || epg->UpdatePending()) && epg->Update(start, end, m_iUpdateTime, bOnlyPending))
543 else if (!epg->IsValid())
544 invalidTables.push_back(epg);
547 for (vector<CEpg*>::iterator it = invalidTables.begin(); it != invalidTables.end(); it++)
548 DeleteEpg(**it, true);
552 /* the update has been interrupted. try again later */
554 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
555 m_iNextEpgUpdate = iNow + g_advancedSettings.m_iEpgRetryInterruptedUpdateInterval;
559 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
560 m_iNextEpgUpdate += g_advancedSettings.m_iEpgUpdateCheckInterval;
561 m_bHasPendingUpdates = false;
564 if (bShowProgress && !bOnlyPending)
565 CloseProgressDialog();
567 /* notify observers */
568 if (iUpdatedTables > 0)
571 NotifyObservers(ObservableMessageEpgContainer);
574 CSingleLock lock(m_critSection);
575 m_bIsUpdating = false;
578 return !bInterrupted;
581 int CEpgContainer::GetEPGAll(CFileItemList &results)
583 int iInitialSize = results.Size();
585 CSingleLock lock(m_critSection);
586 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
587 it->second->Get(results);
589 return results.Size() - iInitialSize;
592 const CDateTime CEpgContainer::GetFirstEPGDate(void)
594 CDateTime returnValue;
596 CSingleLock lock(m_critSection);
597 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
600 CDateTime entry = it->second->GetFirstDate();
601 if (entry.IsValid() && (!returnValue.IsValid() || entry < returnValue))
609 const CDateTime CEpgContainer::GetLastEPGDate(void)
611 CDateTime returnValue;
613 CSingleLock lock(m_critSection);
614 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
617 CDateTime entry = it->second->GetLastDate();
618 if (entry.IsValid() && (!returnValue.IsValid() || entry > returnValue))
626 int CEpgContainer::GetEPGSearch(CFileItemList &results, const EpgSearchFilter &filter)
628 int iInitialSize = results.Size();
630 /* get filtered results from all tables */
632 CSingleLock lock(m_critSection);
633 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
634 it->second->Get(results, filter);
637 /* remove duplicate entries */
638 if (filter.m_bPreventRepeats)
639 EpgSearchFilter::RemoveDuplicates(results);
641 return results.Size() - iInitialSize;
644 bool CEpgContainer::CheckPlayingEvents(void)
648 CSingleLock lock(m_critSection);
650 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
651 if (iNow >= m_iNextEpgActiveTagCheck)
653 bool bFoundChanges(false);
654 CSingleLock lock(m_critSection);
656 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
657 bFoundChanges = it->second->CheckPlayingEvent() || bFoundChanges;
658 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgActiveTagCheck);
659 m_iNextEpgActiveTagCheck += g_advancedSettings.m_iEpgActiveTagCheckInterval;
664 NotifyObservers(ObservableMessageEpgActiveItem);
667 /* pvr tags always start on the full minute */
668 if (g_PVRManager.IsStarted())
669 m_iNextEpgActiveTagCheck -= m_iNextEpgActiveTagCheck % 60;
677 bool CEpgContainer::IsInitialising(void) const
679 CSingleLock lock(m_critSection);
680 return m_bIsInitialising;
683 void CEpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */)
685 CSingleLock lock(m_critSection);
686 m_bHasPendingUpdates = bHasPendingUpdates;