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/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"
35 #include "EpgContainer.h"
37 #include "EpgInfoTag.h"
38 #include "EpgSearchFilter.h"
44 typedef std::map<int, CEpg*>::iterator EPGITR;
46 CEpgContainer::CEpgContainer(void) :
49 m_progressHandle = NULL;
51 m_bIsUpdating = false;
52 m_bIsInitialising = true;
54 m_bPreventUpdates = false;
55 m_updateEvent.Reset();
58 m_bHasPendingUpdates = false;
61 CEpgContainer::~CEpgContainer(void)
66 CEpgContainer &CEpgContainer::Get(void)
68 static CEpgContainer epgInstance;
72 void CEpgContainer::Unload(void)
78 bool CEpgContainer::IsStarted(void) const
80 CSingleLock lock(m_critSection);
84 unsigned int CEpgContainer::NextEpgId(void)
86 CSingleLock lock(m_critSection);
87 return ++m_iNextEpgId;
90 void CEpgContainer::Clear(bool bClearDb /* = false */)
92 /* make sure the update thread is stopped */
93 bool bThreadRunning = !m_bStop;
94 if (bThreadRunning && !Stop())
96 CLog::Log(LOGERROR, "%s - cannot stop the update thread", __FUNCTION__);
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++)
105 it->second->UnregisterObserver(this);
109 m_iNextEpgUpdate = 0;
111 m_bIsInitialising = true;
115 /* clear the database entries */
116 if (bClearDb && !m_bIgnoreDbForClient)
118 if (!m_database.IsOpen())
121 if (m_database.IsOpen())
122 m_database.DeleteEpg();
126 NotifyObservers(ObservableMessageEpgContainer);
132 void CEpgContainer::Start(void)
137 CSingleLock lock(m_critSection);
139 if (!m_database.IsOpen())
142 m_bIsInitialising = true;
146 m_iNextEpgUpdate = 0;
147 m_iNextEpgActiveTagCheck = 0;
152 CSingleLock lock(m_critSection);
155 CheckPlayingEvents();
162 CLog::Log(LOGNOTICE, "%s - EPG thread started", __FUNCTION__);
166 bool CEpgContainer::Stop(void)
170 if (m_database.IsOpen())
173 CSingleLock lock(m_critSection);
179 void CEpgContainer::Notify(const Observable &obs, const ObservableMessage msg)
182 NotifyObservers(msg);
185 void CEpgContainer::OnSettingChanged(const CSetting *setting)
190 const std::string &settingId = setting->GetId();
191 if (settingId == "epg.ignoredbforclient" || settingId == "epg.epgupdate" ||
192 settingId == "epg.daystodisplay")
196 void CEpgContainer::LoadFromDB(void)
198 CSingleLock lock(m_critSection);
200 if (m_bLoaded || m_bIgnoreDbForClient)
203 if (!m_database.IsOpen())
206 m_iNextEpgId = m_database.GetLastEPGId();
209 unsigned int iCounter(0);
210 if (m_database.IsOpen())
212 ShowProgressDialog(false);
214 m_database.DeleteOldEpgEntries();
215 m_database.Get(*this);
217 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
221 UpdateProgressDialog(++iCounter, m_epgs.size(), it->second->Name());
227 CloseProgressDialog();
233 bool CEpgContainer::PersistTables(void)
235 return m_database.Persist(*this);
238 bool CEpgContainer::PersistAll(void)
241 CSingleLock lock(m_critSection);
242 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end() && !m_bStop; it++)
244 CEpg *epg = it->second;
245 if (epg && epg->NeedsSave())
248 bReturn &= epg->Persist();
256 void CEpgContainer::Process(void)
258 time_t iNow(0), iLastSave(0);
259 bool bUpdateEpg(true);
260 bool bHasPendingUpdates(false);
262 if (!CPVRManager::Get().WaitUntilInitialised())
264 CLog::Log(LOGDEBUG, "EPG - %s - pvr manager failed to load - exiting", __FUNCTION__);
268 while (!m_bStop && !g_application.m_bStop)
270 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
272 CSingleLock lock(m_critSection);
273 bUpdateEpg = (iNow >= m_iNextEpgUpdate);
277 if (!InterruptUpdate() && bUpdateEpg && g_PVRManager.EpgsCreated() && UpdateEPG())
278 m_bIsInitialising = false;
280 /* clean up old entries */
281 if (!m_bStop && iNow >= m_iLastEpgCleanup)
284 /* check for pending manual EPG updates */
288 CSingleLock lock(m_critSection);
289 bHasPendingUpdates = m_bHasPendingUpdates;
292 if (bHasPendingUpdates)
296 /* check for updated active tag */
298 CheckPlayingEvents();
300 /* check for changes that need to be saved every 60 seconds */
301 if (iNow - iLastSave > 60)
311 CEpg *CEpgContainer::GetById(int iEpgId) const
316 CSingleLock lock(m_critSection);
317 map<unsigned int, CEpg *>::const_iterator it = m_epgs.find((unsigned int) iEpgId);
318 return it != m_epgs.end() ? it->second : NULL;
321 CEpg *CEpgContainer::GetByChannel(const CPVRChannel &channel) const
323 CSingleLock lock(m_critSection);
324 for (map<unsigned int, CEpg *>::const_iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
325 if (channel.ChannelID() == it->second->ChannelID())
331 void CEpgContainer::InsertFromDatabase(int iEpgID, const CStdString &strName, const CStdString &strScraperName)
333 // table might already have been created when pvr channels were loaded
334 CEpg* epg = GetById(iEpgID);
337 if (!epg->Name().Equals(strName) || !epg->ScraperName().Equals(strScraperName))
339 // current table data differs from the info in the db
346 // create a new epg table
347 epg = new CEpg(iEpgID, strName, strScraperName, true);
350 m_epgs.insert(make_pair(iEpgID, epg));
352 epg->RegisterObserver(this);
357 CEpg *CEpgContainer::CreateChannelEpg(CPVRChannelPtr channel)
362 WaitForUpdateFinish(true);
366 if (channel->EpgID() > 0)
367 epg = GetById(channel->EpgID());
371 channel->SetEpgID(NextEpgId());
372 epg = new CEpg(channel, false);
374 CSingleLock lock(m_critSection);
375 m_epgs.insert(make_pair((unsigned int)epg->EpgID(), epg));
377 epg->RegisterObserver(this);
380 epg->SetChannel(channel);
383 CSingleLock lock(m_critSection);
384 m_bPreventUpdates = false;
385 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
388 NotifyObservers(ObservableMessageEpgContainer);
393 bool CEpgContainer::LoadSettings(void)
395 m_bIgnoreDbForClient = CSettings::Get().GetBool("epg.ignoredbforclient");
396 m_iUpdateTime = CSettings::Get().GetInt ("epg.epgupdate") * 60;
397 m_iDisplayTime = CSettings::Get().GetInt ("epg.daystodisplay") * 24 * 60 * 60;
402 bool CEpgContainer::RemoveOldEntries(void)
404 CDateTime now = CDateTime::GetUTCDateTime() -
405 CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
407 /* call Cleanup() on all known EPG tables */
408 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
409 it->second->Cleanup(now);
411 /* remove the old entries from the database */
412 if (!m_bIgnoreDbForClient && m_database.IsOpen())
413 m_database.DeleteOldEpgEntries();
415 CSingleLock lock(m_critSection);
416 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup);
417 m_iLastEpgCleanup += g_advancedSettings.m_iEpgCleanupInterval;
422 bool CEpgContainer::DeleteEpg(const CEpg &epg, bool bDeleteFromDatabase /* = false */)
427 CSingleLock lock(m_critSection);
429 map<unsigned int, CEpg *>::iterator it = m_epgs.find((unsigned int)epg.EpgID());
430 if (it == m_epgs.end())
433 CLog::Log(LOGDEBUG, "deleting EPG table %s (%d)", epg.Name().c_str(), epg.EpgID());
434 if (bDeleteFromDatabase && !m_bIgnoreDbForClient && m_database.IsOpen())
435 m_database.Delete(*it->second);
437 it->second->UnregisterObserver(this);
444 void CEpgContainer::CloseProgressDialog(void)
446 if (m_progressHandle)
448 m_progressHandle->MarkFinished();
449 m_progressHandle = NULL;
453 void CEpgContainer::ShowProgressDialog(bool bUpdating /* = true */)
455 if (!m_progressHandle)
457 CGUIDialogExtendedProgressBar *progressDialog = (CGUIDialogExtendedProgressBar *)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
459 m_progressHandle = progressDialog->GetHandle(bUpdating ? g_localizeStrings.Get(19004) : g_localizeStrings.Get(19250));
463 void CEpgContainer::UpdateProgressDialog(int iCurrent, int iMax, const CStdString &strText)
465 if (!m_progressHandle)
466 ShowProgressDialog();
468 if (m_progressHandle)
470 m_progressHandle->SetProgress(iCurrent, iMax);
471 m_progressHandle->SetText(strText);
475 bool CEpgContainer::InterruptUpdate(void) const
478 CSingleLock lock(m_critSection);
479 bReturn = g_application.m_bStop || m_bStop || m_bPreventUpdates;
483 (CSettings::Get().GetBool("epg.preventupdateswhileplayingtv") &&
484 g_PVRManager.IsStarted() &&
485 g_PVRManager.IsPlaying());
488 void CEpgContainer::WaitForUpdateFinish(bool bInterrupt /* = true */)
491 CSingleLock lock(m_critSection);
493 m_bPreventUpdates = true;
498 m_updateEvent.Reset();
501 m_updateEvent.Wait();
504 bool CEpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
506 bool bInterrupted(false);
507 unsigned int iUpdatedTables(0);
508 bool bShowProgress(false);
510 /* set start and end time */
513 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(start);
514 end = start + m_iDisplayTime;
515 start -= g_advancedSettings.m_iEpgLingerTime * 60;
516 bShowProgress = g_advancedSettings.m_bEpgDisplayUpdatePopup && (m_bIsInitialising || g_advancedSettings.m_bEpgDisplayIncrementalUpdatePopup);
519 CSingleLock lock(m_critSection);
520 if (m_bIsUpdating || InterruptUpdate())
522 m_bIsUpdating = true;
525 if (bShowProgress && !bOnlyPending)
526 ShowProgressDialog();
528 if (!m_bIgnoreDbForClient && !m_database.IsOpen())
530 CLog::Log(LOGERROR, "EpgContainer - %s - could not open the database", __FUNCTION__);
532 CSingleLock lock(m_critSection);
533 m_bIsUpdating = false;
536 if (bShowProgress && !bOnlyPending)
537 CloseProgressDialog();
542 vector<CEpg*> invalidTables;
544 /* load or update all EPG tables */
546 unsigned int iCounter(0);
547 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
549 if (InterruptUpdate())
559 if (bShowProgress && !bOnlyPending)
560 UpdateProgressDialog(++iCounter, m_epgs.size(), epg->Name());
562 // we currently only support update via pvr add-ons. skip update when the pvr manager isn't started
563 if (!g_PVRManager.IsStarted())
566 // check the pvr manager when the channel pointer isn't set
569 CPVRChannelPtr channel = g_PVRChannelGroups->GetChannelByEpgId(epg->EpgID());
571 epg->SetChannel(channel);
574 if ((!bOnlyPending || epg->UpdatePending()) && epg->Update(start, end, m_iUpdateTime, bOnlyPending))
576 else if (!epg->IsValid())
577 invalidTables.push_back(epg);
580 for (vector<CEpg*>::iterator it = invalidTables.begin(); it != invalidTables.end(); it++)
581 DeleteEpg(**it, true);
585 /* the update has been interrupted. try again later */
587 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
588 m_iNextEpgUpdate = iNow + g_advancedSettings.m_iEpgRetryInterruptedUpdateInterval;
592 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
593 m_iNextEpgUpdate += g_advancedSettings.m_iEpgUpdateCheckInterval;
594 m_bHasPendingUpdates = false;
597 if (bShowProgress && !bOnlyPending)
598 CloseProgressDialog();
600 /* notify observers */
601 if (iUpdatedTables > 0)
604 NotifyObservers(ObservableMessageEpgContainer);
607 CSingleLock lock(m_critSection);
608 m_bIsUpdating = false;
611 return !bInterrupted;
614 int CEpgContainer::GetEPGAll(CFileItemList &results)
616 int iInitialSize = results.Size();
618 CSingleLock lock(m_critSection);
619 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
620 it->second->Get(results);
622 return results.Size() - iInitialSize;
625 const CDateTime CEpgContainer::GetFirstEPGDate(void)
627 CDateTime returnValue;
629 CSingleLock lock(m_critSection);
630 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
633 CDateTime entry = it->second->GetFirstDate();
634 if (entry.IsValid() && (!returnValue.IsValid() || entry < returnValue))
642 const CDateTime CEpgContainer::GetLastEPGDate(void)
644 CDateTime returnValue;
646 CSingleLock lock(m_critSection);
647 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
650 CDateTime entry = it->second->GetLastDate();
651 if (entry.IsValid() && (!returnValue.IsValid() || entry > returnValue))
659 int CEpgContainer::GetEPGSearch(CFileItemList &results, const EpgSearchFilter &filter)
661 int iInitialSize = results.Size();
663 /* get filtered results from all tables */
665 CSingleLock lock(m_critSection);
666 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
667 it->second->Get(results, filter);
670 /* remove duplicate entries */
671 if (filter.m_bPreventRepeats)
672 EpgSearchFilter::RemoveDuplicates(results);
674 return results.Size() - iInitialSize;
677 bool CEpgContainer::CheckPlayingEvents(void)
681 CSingleLock lock(m_critSection);
683 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
684 if (iNow >= m_iNextEpgActiveTagCheck)
686 bool bFoundChanges(false);
687 CSingleLock lock(m_critSection);
689 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
690 bFoundChanges = it->second->CheckPlayingEvent() || bFoundChanges;
691 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgActiveTagCheck);
692 m_iNextEpgActiveTagCheck += g_advancedSettings.m_iEpgActiveTagCheckInterval;
697 NotifyObservers(ObservableMessageEpgActiveItem);
700 /* pvr tags always start on the full minute */
701 if (g_PVRManager.IsStarted())
702 m_iNextEpgActiveTagCheck -= m_iNextEpgActiveTagCheck % 60;
710 bool CEpgContainer::IsInitialising(void) const
712 CSingleLock lock(m_critSection);
713 return m_bIsInitialising;
716 void CEpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */)
718 CSingleLock lock(m_critSection);
719 m_bHasPendingUpdates = bHasPendingUpdates;