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 while (!m_bStop && !g_application.m_bStop)
264 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
266 CSingleLock lock(m_critSection);
267 bUpdateEpg = (iNow >= m_iNextEpgUpdate);
271 if (!InterruptUpdate() && bUpdateEpg && g_PVRManager.EpgsCreated() && UpdateEPG())
272 m_bIsInitialising = false;
274 /* clean up old entries */
275 if (!m_bStop && iNow >= m_iLastEpgCleanup)
278 /* check for pending manual EPG updates */
282 CSingleLock lock(m_critSection);
283 bHasPendingUpdates = m_bHasPendingUpdates;
286 if (bHasPendingUpdates)
290 /* check for updated active tag */
292 CheckPlayingEvents();
294 /* check for changes that need to be saved every 60 seconds */
295 if (iNow - iLastSave > 60)
305 CEpg *CEpgContainer::GetById(int iEpgId) const
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;
315 CEpg *CEpgContainer::GetByChannel(const CPVRChannel &channel) const
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())
325 void CEpgContainer::InsertFromDatabase(int iEpgID, const CStdString &strName, const CStdString &strScraperName)
327 // table might already have been created when pvr channels were loaded
328 CEpg* epg = GetById(iEpgID);
331 if (!epg->Name().Equals(strName) || !epg->ScraperName().Equals(strScraperName))
333 // current table data differs from the info in the db
340 // create a new epg table
341 epg = new CEpg(iEpgID, strName, strScraperName, true);
344 m_epgs.insert(make_pair(iEpgID, epg));
346 epg->RegisterObserver(this);
351 CEpg *CEpgContainer::CreateChannelEpg(CPVRChannelPtr channel)
356 WaitForUpdateFinish(true);
360 if (channel->EpgID() > 0)
361 epg = GetById(channel->EpgID());
365 channel->SetEpgID(NextEpgId());
366 epg = new CEpg(channel, false);
368 CSingleLock lock(m_critSection);
369 m_epgs.insert(make_pair((unsigned int)epg->EpgID(), epg));
371 epg->RegisterObserver(this);
374 epg->SetChannel(channel);
377 CSingleLock lock(m_critSection);
378 m_bPreventUpdates = false;
379 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
382 NotifyObservers(ObservableMessageEpgContainer);
387 bool CEpgContainer::LoadSettings(void)
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;
396 bool CEpgContainer::RemoveOldEntries(void)
398 CDateTime now = CDateTime::GetUTCDateTime() -
399 CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
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);
405 /* remove the old entries from the database */
406 if (!m_bIgnoreDbForClient && m_database.IsOpen())
407 m_database.DeleteOldEpgEntries();
409 CSingleLock lock(m_critSection);
410 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iLastEpgCleanup);
411 m_iLastEpgCleanup += g_advancedSettings.m_iEpgCleanupInterval;
416 bool CEpgContainer::DeleteEpg(const CEpg &epg, bool bDeleteFromDatabase /* = false */)
421 CSingleLock lock(m_critSection);
423 map<unsigned int, CEpg *>::iterator it = m_epgs.find((unsigned int)epg.EpgID());
424 if (it == m_epgs.end())
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);
431 it->second->UnregisterObserver(this);
438 void CEpgContainer::CloseProgressDialog(void)
440 if (m_progressHandle)
442 m_progressHandle->MarkFinished();
443 m_progressHandle = NULL;
447 void CEpgContainer::ShowProgressDialog(bool bUpdating /* = true */)
449 if (!m_progressHandle)
451 CGUIDialogExtendedProgressBar *progressDialog = (CGUIDialogExtendedProgressBar *)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
453 m_progressHandle = progressDialog->GetHandle(bUpdating ? g_localizeStrings.Get(19004) : g_localizeStrings.Get(19250));
457 void CEpgContainer::UpdateProgressDialog(int iCurrent, int iMax, const CStdString &strText)
459 if (!m_progressHandle)
460 ShowProgressDialog();
462 if (m_progressHandle)
464 m_progressHandle->SetProgress(iCurrent, iMax);
465 m_progressHandle->SetText(strText);
469 bool CEpgContainer::InterruptUpdate(void) const
472 CSingleLock lock(m_critSection);
473 bReturn = g_application.m_bStop || m_bStop || m_bPreventUpdates;
477 (CSettings::Get().GetBool("epg.preventupdateswhileplayingtv") &&
478 g_PVRManager.IsStarted() &&
479 g_PVRManager.IsPlaying());
482 void CEpgContainer::WaitForUpdateFinish(bool bInterrupt /* = true */)
485 CSingleLock lock(m_critSection);
487 m_bPreventUpdates = true;
492 m_updateEvent.Reset();
495 m_updateEvent.Wait();
498 bool CEpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
500 bool bInterrupted(false);
501 unsigned int iUpdatedTables(0);
502 bool bShowProgress(false);
504 /* set start and end time */
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);
513 CSingleLock lock(m_critSection);
514 if (m_bIsUpdating || InterruptUpdate())
516 m_bIsUpdating = true;
519 if (bShowProgress && !bOnlyPending)
520 ShowProgressDialog();
522 if (!m_bIgnoreDbForClient && !m_database.IsOpen())
524 CLog::Log(LOGERROR, "EpgContainer - %s - could not open the database", __FUNCTION__);
526 CSingleLock lock(m_critSection);
527 m_bIsUpdating = false;
530 if (bShowProgress && !bOnlyPending)
531 CloseProgressDialog();
536 vector<CEpg*> invalidTables;
538 /* load or update all EPG tables */
540 unsigned int iCounter(0);
541 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
543 if (InterruptUpdate())
553 if (bShowProgress && !bOnlyPending)
554 UpdateProgressDialog(++iCounter, m_epgs.size(), epg->Name());
556 // we currently only support update via pvr add-ons. skip update when the pvr manager isn't started
557 if (!g_PVRManager.IsStarted())
560 // check the pvr manager when the channel pointer isn't set
563 CPVRChannelPtr channel = g_PVRChannelGroups->GetChannelByEpgId(epg->EpgID());
565 epg->SetChannel(channel);
568 if ((!bOnlyPending || epg->UpdatePending()) && epg->Update(start, end, m_iUpdateTime, bOnlyPending))
570 else if (!epg->IsValid())
571 invalidTables.push_back(epg);
574 for (vector<CEpg*>::iterator it = invalidTables.begin(); it != invalidTables.end(); it++)
575 DeleteEpg(**it, true);
579 /* the update has been interrupted. try again later */
581 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
582 m_iNextEpgUpdate = iNow + g_advancedSettings.m_iEpgRetryInterruptedUpdateInterval;
586 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(m_iNextEpgUpdate);
587 m_iNextEpgUpdate += g_advancedSettings.m_iEpgUpdateCheckInterval;
588 m_bHasPendingUpdates = false;
591 if (bShowProgress && !bOnlyPending)
592 CloseProgressDialog();
594 /* notify observers */
595 if (iUpdatedTables > 0)
598 NotifyObservers(ObservableMessageEpgContainer);
601 CSingleLock lock(m_critSection);
602 m_bIsUpdating = false;
605 return !bInterrupted;
608 int CEpgContainer::GetEPGAll(CFileItemList &results)
610 int iInitialSize = results.Size();
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);
616 return results.Size() - iInitialSize;
619 const CDateTime CEpgContainer::GetFirstEPGDate(void)
621 CDateTime returnValue;
623 CSingleLock lock(m_critSection);
624 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
627 CDateTime entry = it->second->GetFirstDate();
628 if (entry.IsValid() && (!returnValue.IsValid() || entry < returnValue))
636 const CDateTime CEpgContainer::GetLastEPGDate(void)
638 CDateTime returnValue;
640 CSingleLock lock(m_critSection);
641 for (map<unsigned int, CEpg *>::iterator it = m_epgs.begin(); it != m_epgs.end(); it++)
644 CDateTime entry = it->second->GetLastDate();
645 if (entry.IsValid() && (!returnValue.IsValid() || entry > returnValue))
653 int CEpgContainer::GetEPGSearch(CFileItemList &results, const EpgSearchFilter &filter)
655 int iInitialSize = results.Size();
657 /* get filtered results from all tables */
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);
664 /* remove duplicate entries */
665 if (filter.m_bPreventRepeats)
666 EpgSearchFilter::RemoveDuplicates(results);
668 return results.Size() - iInitialSize;
671 bool CEpgContainer::CheckPlayingEvents(void)
675 CSingleLock lock(m_critSection);
677 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
678 if (iNow >= m_iNextEpgActiveTagCheck)
680 bool bFoundChanges(false);
681 CSingleLock lock(m_critSection);
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;
691 NotifyObservers(ObservableMessageEpgActiveItem);
694 /* pvr tags always start on the full minute */
695 if (g_PVRManager.IsStarted())
696 m_iNextEpgActiveTagCheck -= m_iNextEpgActiveTagCheck % 60;
704 bool CEpgContainer::IsInitialising(void) const
706 CSingleLock lock(m_critSection);
707 return m_bIsInitialising;
710 void CEpgContainer::SetHasPendingUpdates(bool bHasPendingUpdates /* = true */)
712 CSingleLock lock(m_critSection);
713 m_bHasPendingUpdates = bHasPendingUpdates;