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();
57 m_bHasPendingUpdates = false;
60 CEpgContainer::~CEpgContainer(void)
65 CEpgContainer &CEpgContainer::Get(void)
67 static CEpgContainer epgInstance;
71 void CEpgContainer::Unload(void)
77 unsigned int CEpgContainer::NextEpgId(void)
79 CSingleLock lock(m_critSection);
80 return ++m_iNextEpgId;
83 void CEpgContainer::Clear(bool bClearDb /* = false */)
85 /* make sure the update thread is stopped */
86 bool bThreadRunning = !m_bStop;
87 if (bThreadRunning && !Stop())
89 CLog::Log(LOGERROR, "%s - cannot stop the update thread", __FUNCTION__);
94 CSingleLock lock(m_critSection);
95 /* clear all epg tables and remove pointers to epg tables on channels */
96 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
98 it->second->UnregisterObserver(this);
102 m_iNextEpgUpdate = 0;
103 m_bIsInitialising = true;
107 /* clear the database entries */
108 if (bClearDb && !m_bIgnoreDbForClient)
110 if (!m_database.IsOpen())
113 if (m_database.IsOpen())
114 m_database.DeleteEpg();
118 NotifyObservers(ObservableMessageEpgContainer);
124 void CEpgContainer::Start(void)
128 CSingleLock lock(m_critSection);
130 if (!m_database.IsOpen())
133 m_bIsInitialising = true;
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)
161 NotifyObservers(msg);
164 void CEpgContainer::OnSettingChanged(const CSetting *setting)
169 const std::string &settingId = setting->GetId();
170 if (settingId == "epg.ignoredbforclient" || settingId == "epg.epgupdate" ||
171 settingId == "epg.daystodisplay")
175 void CEpgContainer::LoadFromDB(void)
177 if (m_bLoaded || m_bIgnoreDbForClient)
180 if (!m_database.IsOpen())
183 m_iNextEpgId = m_database.GetLastEPGId();
186 unsigned int iCounter(0);
187 if (m_database.IsOpen())
189 ShowProgressDialog(false);
191 m_database.DeleteOldEpgEntries();
192 m_database.Get(*this);
194 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
196 UpdateProgressDialog(++iCounter, m_epgs.size(), it->second->Name());
200 CloseProgressDialog();
203 CSingleLock lock(m_critSection);
207 bool CEpgContainer::PersistTables(void)
209 return m_database.Persist(*this);
212 bool CEpgContainer::PersistAll(void)
215 CSingleLock lock(m_critSection);
216 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end() && !m_bStop; it++)
218 CEpg *epg = it->second;
219 if (epg && epg->NeedsSave())
222 bReturn &= epg->Persist();
230 void CEpgContainer::Process(void)
232 time_t iNow(0), iLastSave(0);
233 bool bUpdateEpg(true);
234 bool bHasPendingUpdates(false);
236 if (!CPVRManager::Get().WaitUntilInitialised())
238 CLog::Log(LOGDEBUG, "EPG - %s - pvr manager failed to load - exiting", __FUNCTION__);
242 while (!m_bStop && !g_application.m_bStop)
244 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
246 CSingleLock lock(m_critSection);
247 bUpdateEpg = (iNow >= m_iNextEpgUpdate);
251 if (!InterruptUpdate() && bUpdateEpg && UpdateEPG())
252 m_bIsInitialising = false;
254 /* clean up old entries */
255 if (!m_bStop && iNow >= m_iLastEpgCleanup)
258 /* check for pending manual EPG updates */
262 CSingleLock lock(m_critSection);
263 bHasPendingUpdates = m_bHasPendingUpdates;
266 if (bHasPendingUpdates)
270 /* check for updated active tag */
272 CheckPlayingEvents();
274 /* check for changes that need to be saved every 60 seconds */
275 if (iNow - iLastSave > 60)
285 CEpg *CEpgContainer::GetById(int iEpgId) const
290 CSingleLock lock(m_critSection);
291 map<unsigned int, CEpg *>::const_iterator it = m_epgs.find((unsigned int) iEpgId);
292 return it != m_epgs.end() ? it->second : NULL;
295 CEpg *CEpgContainer::GetByChannel(const CPVRChannel &channel) const
297 CSingleLock lock(m_critSection);
298 for (map<unsigned int, CEpg *>::const_iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
299 if (channel.ChannelID() == it->second->ChannelID())
305 void CEpgContainer::InsertFromDatabase(int iEpgID, const CStdString &strName, const CStdString &strScraperName)
307 // table might already have been created when pvr channels were loaded
308 CEpg* epg = GetById(iEpgID);
311 if (!epg->Name().Equals(strName) || !epg->ScraperName().Equals(strScraperName))
313 // current table data differs from the info in the db
320 // create a new epg table
321 epg = new CEpg(iEpgID, strName, strScraperName, true);
324 m_epgs.insert(make_pair(iEpgID, epg));
326 epg->RegisterObserver(this);
331 CEpg *CEpgContainer::CreateChannelEpg(CPVRChannelPtr channel)
336 WaitForUpdateFinish(true);
337 CSingleLock lock(m_critSection);
341 if (channel->EpgID() > 0)
342 epg = GetById(channel->EpgID());
346 channel->SetEpgID(NextEpgId());
347 epg = new CEpg(channel, false);
348 m_epgs.insert(make_pair((unsigned int)epg->EpgID(), epg));
350 epg->RegisterObserver(this);
353 epg->SetChannel(channel);
355 m_bPreventUpdates = false;
356 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
358 NotifyObservers(ObservableMessageEpgContainer);
363 bool CEpgContainer::LoadSettings(void)
365 m_bIgnoreDbForClient = CSettings::Get().GetBool("epg.ignoredbforclient");
366 m_iUpdateTime = CSettings::Get().GetInt ("epg.epgupdate") * 60;
367 m_iDisplayTime = CSettings::Get().GetInt ("epg.daystodisplay") * 24 * 60 * 60;
372 bool CEpgContainer::RemoveOldEntries(void)
374 CDateTime now = CDateTime::GetUTCDateTime() -
375 CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
377 /* call Cleanup() on all known EPG tables */
378 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
379 it->second->Cleanup(now);
381 /* remove the old entries from the database */
382 if (!m_bIgnoreDbForClient && m_database.IsOpen())
383 m_database.DeleteOldEpgEntries();
385 CSingleLock lock(m_critSection);
386 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup);
387 m_iLastEpgCleanup += g_advancedSettings.m_iEpgCleanupInterval;
392 bool CEpgContainer::DeleteEpg(const CEpg &epg, bool bDeleteFromDatabase /* = false */)
397 CSingleLock lock(m_critSection);
399 map<unsigned int, CEpg *>::iterator it = m_epgs.find((unsigned int)epg.EpgID());
400 if (it == m_epgs.end())
403 CLog::Log(LOGDEBUG, "deleting EPG table %s (%d)", epg.Name().c_str(), epg.EpgID());
404 if (bDeleteFromDatabase && !m_bIgnoreDbForClient && m_database.IsOpen())
405 m_database.Delete(*it->second);
407 it->second->UnregisterObserver(this);
414 void CEpgContainer::CloseProgressDialog(void)
416 if (m_progressHandle)
418 m_progressHandle->MarkFinished();
419 m_progressHandle = NULL;
423 void CEpgContainer::ShowProgressDialog(bool bUpdating /* = true */)
425 if (!m_progressHandle)
427 CGUIDialogExtendedProgressBar *progressDialog = (CGUIDialogExtendedProgressBar *)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
429 m_progressHandle = progressDialog->GetHandle(bUpdating ? g_localizeStrings.Get(19004) : g_localizeStrings.Get(19250));
433 void CEpgContainer::UpdateProgressDialog(int iCurrent, int iMax, const CStdString &strText)
435 if (!m_progressHandle)
436 ShowProgressDialog();
438 if (m_progressHandle)
440 m_progressHandle->SetProgress(iCurrent, iMax);
441 m_progressHandle->SetText(strText);
445 bool CEpgContainer::InterruptUpdate(void) const
448 CSingleLock lock(m_critSection);
449 bReturn = g_application.m_bStop || m_bStop || m_bPreventUpdates;
453 (CSettings::Get().GetBool("epg.preventupdateswhileplayingtv") &&
454 g_PVRManager.IsStarted() &&
455 g_PVRManager.IsPlaying());
458 void CEpgContainer::WaitForUpdateFinish(bool bInterrupt /* = true */)
461 CSingleLock lock(m_critSection);
463 m_bPreventUpdates = true;
468 m_updateEvent.Reset();
471 m_updateEvent.Wait();
474 bool CEpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
476 bool bInterrupted(false);
477 unsigned int iUpdatedTables(0);
478 bool bShowProgress(false);
480 /* set start and end time */
483 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(start);
484 end = start + m_iDisplayTime;
485 start -= g_advancedSettings.m_iEpgLingerTime * 60;
486 bShowProgress = g_advancedSettings.m_bEpgDisplayUpdatePopup && (m_bIsInitialising || g_advancedSettings.m_bEpgDisplayIncrementalUpdatePopup);
489 CSingleLock lock(m_critSection);
490 if (m_bIsUpdating || InterruptUpdate())
492 m_bIsUpdating = true;
495 if (bShowProgress && !bOnlyPending)
496 ShowProgressDialog();
498 if (!m_bIgnoreDbForClient && !m_database.IsOpen())
500 CLog::Log(LOGERROR, "EpgContainer - %s - could not open the database", __FUNCTION__);
502 CSingleLock lock(m_critSection);
503 m_bIsUpdating = false;
506 if (bShowProgress && !bOnlyPending)
507 CloseProgressDialog();
512 vector<CEpg*> invalidTables;
514 /* load or update all EPG tables */
516 unsigned int iCounter(0);
517 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
519 if (InterruptUpdate())
529 if (bShowProgress && !bOnlyPending)
530 UpdateProgressDialog(++iCounter, m_epgs.size(), epg->Name());
532 // we currently only support update via pvr add-ons. skip update when the pvr manager isn't started
533 if (!g_PVRManager.IsStarted())
536 // check the pvr manager when the channel pointer isn't set
539 CPVRChannelPtr channel = g_PVRChannelGroups->GetChannelByEpgId(epg->EpgID());
541 epg->SetChannel(channel);
544 if ((!bOnlyPending || epg->UpdatePending()) && epg->Update(start, end, m_iUpdateTime, bOnlyPending))
546 else if (!epg->IsValid())
547 invalidTables.push_back(epg);
550 for (vector<CEpg*>::iterator it = invalidTables.begin(); it != invalidTables.end(); it++)
551 DeleteEpg(**it, true);
555 /* the update has been interrupted. try again later */
557 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
558 m_iNextEpgUpdate = iNow + g_advancedSettings.m_iEpgRetryInterruptedUpdateInterval;
562 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
563 m_iNextEpgUpdate += g_advancedSettings.m_iEpgUpdateCheckInterval;
564 m_bHasPendingUpdates = false;
567 if (bShowProgress && !bOnlyPending)
568 CloseProgressDialog();
570 /* notify observers */
571 if (iUpdatedTables > 0)
574 NotifyObservers(ObservableMessageEpgContainer);
577 CSingleLock lock(m_critSection);
578 m_bIsUpdating = false;
581 return !bInterrupted;
584 int CEpgContainer::GetEPGAll(CFileItemList &results)
586 int iInitialSize = results.Size();
588 CSingleLock lock(m_critSection);
589 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
590 it->second->Get(results);
592 return results.Size() - iInitialSize;
595 const CDateTime CEpgContainer::GetFirstEPGDate(void)
597 CDateTime returnValue;
599 CSingleLock lock(m_critSection);
600 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
603 CDateTime entry = it->second->GetFirstDate();
604 if (entry.IsValid() && (!returnValue.IsValid() || entry < returnValue))
612 const CDateTime CEpgContainer::GetLastEPGDate(void)
614 CDateTime returnValue;
616 CSingleLock lock(m_critSection);
617 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
620 CDateTime entry = it->second->GetLastDate();
621 if (entry.IsValid() && (!returnValue.IsValid() || entry > returnValue))
629 int CEpgContainer::GetEPGSearch(CFileItemList &results, const EpgSearchFilter &filter)
631 int iInitialSize = results.Size();
633 /* get filtered results from all tables */
635 CSingleLock lock(m_critSection);
636 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
637 it->second->Get(results, filter);
640 /* remove duplicate entries */
641 if (filter.m_bPreventRepeats)
642 EpgSearchFilter::RemoveDuplicates(results);
644 return results.Size() - iInitialSize;
647 bool CEpgContainer::CheckPlayingEvents(void)
651 CSingleLock lock(m_critSection);
653 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
654 if (iNow >= m_iNextEpgActiveTagCheck)
656 bool bFoundChanges(false);
657 CSingleLock lock(m_critSection);
659 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
660 bFoundChanges = it->second->CheckPlayingEvent() || bFoundChanges;
661 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgActiveTagCheck);
662 m_iNextEpgActiveTagCheck += g_advancedSettings.m_iEpgActiveTagCheckInterval;
667 NotifyObservers(ObservableMessageEpgActiveItem);
670 /* pvr tags always start on the full minute */
671 if (g_PVRManager.IsStarted())
672 m_iNextEpgActiveTagCheck -= m_iNextEpgActiveTagCheck % 60;
680 bool CEpgContainer::IsInitialising(void) const
682 CSingleLock lock(m_critSection);
683 return m_bIsInitialising;
686 void CEpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */)
688 CSingleLock lock(m_critSection);
689 m_bHasPendingUpdates = bHasPendingUpdates;