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 "guilib/LocalizeStrings.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/Settings.h"
24 #include "threads/SingleLock.h"
25 #include "utils/log.h"
26 #include "utils/TimeUtils.h"
28 #include "EpgDatabase.h"
29 #include "EpgContainer.h"
30 #include "pvr/PVRManager.h"
31 #include "pvr/addons/PVRClients.h"
32 #include "pvr/channels/PVRChannelGroupsContainer.h"
33 #include "utils/StringUtils.h"
35 #include "../addons/include/xbmc_epg_types.h"
41 CEpg::CEpg(int iEpgID, const CStdString &strName /* = "" */, const CStdString &strScraperName /* = "" */, bool bLoadedFromDb /* = false */) :
42 m_bChanged(!bLoadedFromDb),
43 m_bTagsChanged(false),
45 m_bUpdatePending(false),
48 m_strScraperName(strScraperName),
49 m_bUpdateLastScanTime(false)
55 CEpg::CEpg(CPVRChannelPtr channel, bool bLoadedFromDb /* = false */) :
56 m_bChanged(!bLoadedFromDb),
57 m_bTagsChanged(false),
59 m_bUpdatePending(false),
60 m_iEpgID(channel->EpgID()),
61 m_strName(channel->ChannelName()),
62 m_strScraperName(channel->EPGScraper()),
63 m_pvrChannel(channel),
64 m_bUpdateLastScanTime(false)
70 m_bTagsChanged(false),
72 m_bUpdatePending(false),
74 m_bUpdateLastScanTime(false)
85 CEpg &CEpg::operator =(const CEpg &right)
87 m_bChanged = right.m_bChanged;
88 m_bTagsChanged = right.m_bTagsChanged;
89 m_bLoaded = right.m_bLoaded;
90 m_bUpdatePending = right.m_bUpdatePending;
91 m_iEpgID = right.m_iEpgID;
92 m_strName = right.m_strName;
93 m_strScraperName = right.m_strScraperName;
94 m_nowActiveStart = right.m_nowActiveStart;
95 m_lastScanTime = right.m_lastScanTime;
96 m_pvrChannel = right.m_pvrChannel;
98 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = right.m_tags.begin(); it != right.m_tags.end(); it++)
100 CEpgInfoTagPtr EITPtr (new CEpgInfoTag(*it->second));
101 m_tags.insert(make_pair(it->first, EITPtr));
107 /** @name Public methods */
110 void CEpg::SetName(const CStdString &strName)
112 CSingleLock lock(m_critSection);
114 if (!m_strName.Equals(strName))
121 void CEpg::SetScraperName(const CStdString &strScraperName)
123 CSingleLock lock(m_critSection);
125 if (!m_strScraperName.Equals(strScraperName))
128 m_strScraperName = strScraperName;
132 void CEpg::SetUpdatePending(bool bUpdatePending /* = true */)
135 CSingleLock lock(m_critSection);
136 m_bUpdatePending = bUpdatePending;
140 g_EpgContainer.SetHasPendingUpdates(true);
143 void CEpg::ForceUpdate(void)
148 bool CEpg::HasValidEntries(void) const
150 CSingleLock lock(m_critSection);
152 return (m_iEpgID > 0 && /* valid EPG ID */
153 !m_tags.empty() && /* contains at least 1 tag */
154 m_tags.rbegin()->second->EndAsUTC() >= CDateTime::GetCurrentDateTime().GetAsUTCDateTime()); /* the last end time hasn't passed yet */
157 void CEpg::Clear(void)
159 CSingleLock lock(m_critSection);
163 void CEpg::Cleanup(void)
165 CDateTime cleanupTime = CDateTime::GetCurrentDateTime().GetAsUTCDateTime() -
166 CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
167 Cleanup(cleanupTime);
170 void CEpg::Cleanup(const CDateTime &Time)
172 CSingleLock lock(m_critSection);
173 for (map<CDateTime, CEpgInfoTagPtr>::iterator it = m_tags.begin(); it != m_tags.end(); it != m_tags.end() ? it++ : it)
175 if (it->second->EndAsUTC() < Time)
177 if (m_nowActiveStart == it->first)
178 m_nowActiveStart.SetValid(false);
180 it->second->ClearTimer();
186 bool CEpg::InfoTagNow(CEpgInfoTag &tag, bool bUpdateIfNeeded /* = true */)
188 CSingleLock lock(m_critSection);
189 if (m_nowActiveStart.IsValid())
191 map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(m_nowActiveStart);
192 if (it != m_tags.end() && it->second->IsActive())
201 CDateTime lastActiveTag;
203 /* one of the first items will always match if the list is sorted */
204 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
206 if (it->second->IsActive())
208 m_nowActiveStart = it->first;
212 else if (it->second->WasActive())
213 lastActiveTag = it->first;
216 /* there might be a gap between the last and next event. just return the last if found */
217 map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(lastActiveTag);
218 if (it != m_tags.end())
228 bool CEpg::InfoTagNext(CEpgInfoTag &tag)
231 if (InfoTagNow(nowTag))
233 CSingleLock lock(m_critSection);
234 map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(nowTag.StartAsUTC());
235 if (it != m_tags.end() && ++it != m_tags.end())
243 /* return the first event that is in the future */
244 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
246 if (it->second->InTheFuture())
257 bool CEpg::CheckPlayingEvent(void)
260 CEpgInfoTag previousTag, newTag;
261 bool bGotPreviousTag = InfoTagNow(previousTag, false);
262 bool bGotCurrentTag = InfoTagNow(newTag);
264 bool bTagChanged = bGotCurrentTag && (!bGotPreviousTag || previousTag != newTag);
265 bool bTagRemoved = !bGotCurrentTag && bGotPreviousTag;
266 if (bTagChanged || bTagRemoved)
268 NotifyObservers(ObservableMessageEpgActiveItem);
275 CEpgInfoTagPtr CEpg::GetTag(const CDateTime &StartTime) const
277 CSingleLock lock(m_critSection);
278 map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(StartTime);
279 if (it != m_tags.end())
284 CEpgInfoTagPtr empty;
288 CEpgInfoTagPtr CEpg::GetTagBetween(const CDateTime &beginTime, const CDateTime &endTime) const
290 CSingleLock lock(m_critSection);
291 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
293 if (it->second->StartAsUTC() >= beginTime && it->second->EndAsUTC() <= endTime)
297 CEpgInfoTagPtr retVal;
301 CEpgInfoTagPtr CEpg::GetTagAround(const CDateTime &time) const
303 CSingleLock lock(m_critSection);
304 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
306 if ((it->second->StartAsUTC() < time) && (it->second->EndAsUTC() > time))
310 CEpgInfoTagPtr retVal;
314 void CEpg::AddEntry(const CEpgInfoTag &tag)
316 CEpgInfoTagPtr newTag;
317 CSingleLock lock(m_critSection);
318 map<CDateTime, CEpgInfoTagPtr>::iterator itr = m_tags.find(tag.StartAsUTC());
319 if (itr != m_tags.end())
320 newTag = itr->second;
323 newTag = CEpgInfoTagPtr(new CEpgInfoTag(this, m_pvrChannel, m_strName, m_pvrChannel ? m_pvrChannel->IconPath() : StringUtils::EmptyString));
324 m_tags.insert(make_pair(tag.StartAsUTC(), newTag));
330 newTag->SetPVRChannel(m_pvrChannel);
331 newTag->m_epg = this;
332 newTag->m_bChanged = false;
336 bool CEpg::UpdateEntry(const CEpgInfoTag &tag, bool bUpdateDatabase /* = false */, bool bSort /* = true */)
338 CEpgInfoTagPtr infoTag;
339 CSingleLock lock(m_critSection);
340 map<CDateTime, CEpgInfoTagPtr>::iterator it = m_tags.find(tag.StartAsUTC());
342 if (it != m_tags.end())
344 infoTag = it->second;
348 /* create a new tag if no tag with this ID exists */
349 infoTag = CEpgInfoTagPtr(new CEpgInfoTag(this, m_pvrChannel, m_strName, m_pvrChannel ? m_pvrChannel->IconPath() : StringUtils::EmptyString));
350 infoTag->SetUniqueBroadcastID(tag.UniqueBroadcastID());
351 m_tags.insert(make_pair(tag.StartAsUTC(), infoTag));
355 infoTag->Update(tag, bNewTag);
356 infoTag->m_epg = this;
357 infoTag->m_pvrChannel = m_pvrChannel;
360 m_changedTags.insert(make_pair(infoTag->UniqueBroadcastID(), infoTag));
365 bool CEpg::Load(void)
368 CEpgDatabase *database = g_EpgContainer.GetDatabase();
370 if (!database || !database->IsOpen())
372 CLog::Log(LOGERROR, "EPG - %s - could not open the database", __FUNCTION__);
376 CSingleLock lock(m_critSection);
377 int iEntriesLoaded = database->Get(*this);
378 if (iEntriesLoaded <= 0)
380 CLog::Log(LOGDEBUG, "EPG - %s - no database entries found for table '%s'.", __FUNCTION__, m_strName.c_str());
384 m_lastScanTime = GetLastScanTime();
386 CLog::Log(LOGDEBUG, "EPG - %s - %d entries loaded for table '%s'.", __FUNCTION__, (int) m_tags.size(), m_strName.c_str());
396 bool CEpg::UpdateEntries(const CEpg &epg, bool bStoreInDb /* = true */)
398 CSingleLock lock(m_critSection);
400 CLog::Log(LOGDEBUG, "EPG - %s - %zu entries in memory before merging", __FUNCTION__, m_tags.size());
403 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = epg.m_tags.begin(); it != epg.m_tags.end(); it++)
404 UpdateEntry(*it->second, bStoreInDb, false);
407 CLog::Log(LOGDEBUG, "EPG - %s - %zu entries in memory after merging and before fixing", __FUNCTION__, m_tags.size());
409 FixOverlappingEvents(bStoreInDb);
412 CLog::Log(LOGDEBUG, "EPG - %s - %zu entries in memory after fixing", __FUNCTION__, m_tags.size());
414 /* update the last scan time of this table */
415 m_lastScanTime = CDateTime::GetCurrentDateTime().GetAsUTCDateTime();
416 m_bUpdateLastScanTime = true;
418 NotifyObservers(ObservableMessageEpg);
423 CDateTime CEpg::GetLastScanTime(void)
425 CDateTime lastScanTime;
427 CSingleLock lock(m_critSection);
429 if (!m_lastScanTime.IsValid())
431 if (!CSettings::Get().GetBool("epg.ignoredbforclient"))
433 CEpgDatabase *database = g_EpgContainer.GetDatabase();
434 CDateTime dtReturn; dtReturn.SetValid(false);
436 if (database && database->IsOpen())
437 database->GetLastEpgScanTime(m_iEpgID, &m_lastScanTime);
440 if (!m_lastScanTime.IsValid())
442 m_lastScanTime.SetDateTime(0, 0, 0, 0, 0, 0);
443 m_lastScanTime.SetValid(true);
446 lastScanTime = m_lastScanTime;
449 return m_lastScanTime;
452 bool CEpg::Update(const time_t start, const time_t end, int iUpdateTime, bool bForceUpdate /* = false */)
454 bool bGrabSuccess(true);
457 /* load the entries from the db first */
458 if (!m_bLoaded && !g_EpgContainer.IgnoreDB())
461 /* clean up if needed */
465 /* get the last update time from the database */
466 CDateTime lastScanTime = GetLastScanTime();
468 /* enforce advanced settings update interval override for TV Channels with no EPG data */
469 if (m_tags.empty() && !bUpdate && ChannelID() > 0 && !Channel()->IsRadio())
470 iUpdateTime = g_advancedSettings.m_iEpgUpdateEmptyTagsInterval;
474 /* check if we have to update */
476 time_t iLastUpdate = 0;
477 CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
478 lastScanTime.GetAsTime(iLastUpdate);
479 bUpdate = (iNow > iLastUpdate + iUpdateTime);
485 bGrabSuccess = LoadFromClients(start, end);
489 CPVRChannelPtr channel;
490 if (g_PVRManager.GetCurrentChannel(channel) &&
491 channel->EpgID() == m_iEpgID)
492 g_PVRManager.ResetPlayingTag();
496 CLog::Log(LOGERROR, "EPG - %s - failed to update table '%s'", __FUNCTION__, Name().c_str());
498 CSingleLock lock(m_critSection);
499 m_bUpdatePending = false;
504 int CEpg::Get(CFileItemList &results) const
506 int iInitialSize = results.Size();
508 CSingleLock lock(m_critSection);
510 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
511 results.Add(CFileItemPtr(new CFileItem(*it->second)));
513 return results.Size() - iInitialSize;
516 int CEpg::Get(CFileItemList &results, const EpgSearchFilter &filter) const
518 int iInitialSize = results.Size();
520 if (!HasValidEntries())
523 CSingleLock lock(m_critSection);
525 for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
527 if (filter.FilterEntry(*it->second))
528 results.Add(CFileItemPtr(new CFileItem(*it->second)));
531 return results.Size() - iInitialSize;
534 bool CEpg::Persist(void)
536 if (CSettings::Get().GetBool("epg.ignoredbforclient") || !NeedsSave())
540 CLog::Log(LOGDEBUG, "persist table '%s' (#%d) changed=%d deleted=%d", Name().c_str(), m_iEpgID, m_changedTags.size(), m_deletedTags.size());
543 CEpgDatabase *database = g_EpgContainer.GetDatabase();
544 if (!database || !database->IsOpen())
546 CLog::Log(LOGERROR, "EPG - %s - could not open the database", __FUNCTION__);
551 CSingleLock lock(m_critSection);
552 if (m_iEpgID <= 0 || m_bChanged)
554 int iId = database->Persist(*this, m_iEpgID > 0);
559 for (std::map<int, CEpgInfoTagPtr>::iterator it = m_deletedTags.begin(); it != m_deletedTags.end(); it++)
560 database->Delete(*it->second);
562 for (std::map<int, CEpgInfoTagPtr>::iterator it = m_changedTags.begin(); it != m_changedTags.end(); it++)
563 it->second->Persist(false);
565 if (m_bUpdateLastScanTime)
566 database->PersistLastEpgScanTime(m_iEpgID, true);
568 m_deletedTags.clear();
569 m_changedTags.clear();
571 m_bTagsChanged = false;
572 m_bUpdateLastScanTime = false;
575 return database->CommitInsertQueries();
578 CDateTime CEpg::GetFirstDate(void) const
582 CSingleLock lock(m_critSection);
584 first = m_tags.begin()->second->StartAsUTC();
589 CDateTime CEpg::GetLastDate(void) const
593 CSingleLock lock(m_critSection);
595 last = m_tags.rbegin()->second->StartAsUTC();
602 /** @name Private methods */
605 bool CEpg::FixOverlappingEvents(bool bUpdateDb /* = false */)
608 CEpgInfoTagPtr previousTag, currentTag;
610 for (map<CDateTime, CEpgInfoTagPtr>::iterator it = m_tags.begin(); it != m_tags.end(); it != m_tags.end() ? it++ : it)
614 previousTag = it->second;
617 currentTag = it->second;
619 if (previousTag->EndAsUTC() >= currentTag->EndAsUTC())
621 // delete the current tag. it's completely overlapped
623 m_deletedTags.insert(make_pair(currentTag->UniqueBroadcastID(), currentTag));
625 if (m_nowActiveStart == it->first)
626 m_nowActiveStart.SetValid(false);
628 it->second->ClearTimer();
631 else if (previousTag->EndAsUTC() != currentTag->StartAsUTC())
633 previousTag->SetEndFromUTC(currentTag->StartAsUTC());
635 m_changedTags.insert(make_pair(previousTag->UniqueBroadcastID(), previousTag));
637 previousTag = it->second;
641 previousTag = it->second;
648 bool CEpg::UpdateFromScraper(time_t start, time_t end)
650 bool bGrabSuccess = false;
651 if (ScraperName() == "client")
653 CPVRChannelPtr channel = Channel();
656 CLog::Log(LOGWARNING, "EPG - %s - channel not found, can't update", __FUNCTION__);
658 else if (!channel->EPGEnabled())
661 CLog::Log(LOGDEBUG, "EPG - %s - EPG updating disabled in the channel configuration", __FUNCTION__);
665 else if (channel->IsHidden())
668 CLog::Log(LOGDEBUG, "EPG - %s - channel '%s' on client '%i' is hidden", __FUNCTION__, channel->ChannelName().c_str(), channel->ClientID());
672 else if (!g_PVRClients->SupportsEPG(channel->ClientID()))
674 CLog::Log(LOGDEBUG, "EPG - %s - the backend for channel '%s' on client '%i' does not support EPGs", __FUNCTION__, channel->ChannelName().c_str(), channel->ClientID());
678 CLog::Log(LOGDEBUG, "EPG - %s - updating EPG for channel '%s' from client '%i'", __FUNCTION__, channel->ChannelName().c_str(), channel->ClientID());
679 bGrabSuccess = (g_PVRClients->GetEPGForChannel(*channel, this, start, end) == PVR_ERROR_NO_ERROR);
682 else if (m_strScraperName.IsEmpty()) /* no grabber defined */
683 CLog::Log(LOGWARNING, "EPG - %s - no EPG scraper defined for table '%s'", __FUNCTION__, m_strName.c_str());
686 CLog::Log(LOGINFO, "EPG - %s - updating EPG table '%s' with scraper '%s'", __FUNCTION__, m_strName.c_str(), m_strScraperName.c_str());
687 CLog::Log(LOGWARNING, "loading the EPG via scraper has not been implemented yet");
688 // TODO: Add Support for Web EPG Scrapers here
696 const CStdString &CEpg::ConvertGenreIdToString(int iID, int iSubID)
698 unsigned int iLabelId = 19499;
701 case EPG_EVENT_CONTENTMASK_MOVIEDRAMA:
702 iLabelId = (iSubID <= 8) ? 19500 + iSubID : 19500;
704 case EPG_EVENT_CONTENTMASK_NEWSCURRENTAFFAIRS:
705 iLabelId = (iSubID <= 4) ? 19516 + iSubID : 19516;
707 case EPG_EVENT_CONTENTMASK_SHOW:
708 iLabelId = (iSubID <= 3) ? 19532 + iSubID : 19532;
710 case EPG_EVENT_CONTENTMASK_SPORTS:
711 iLabelId = (iSubID <= 11) ? 19548 + iSubID : 19548;
713 case EPG_EVENT_CONTENTMASK_CHILDRENYOUTH:
714 iLabelId = (iSubID <= 5) ? 19564 + iSubID : 19564;
716 case EPG_EVENT_CONTENTMASK_MUSICBALLETDANCE:
717 iLabelId = (iSubID <= 6) ? 19580 + iSubID : 19580;
719 case EPG_EVENT_CONTENTMASK_ARTSCULTURE:
720 iLabelId = (iSubID <= 11) ? 19596 + iSubID : 19596;
722 case EPG_EVENT_CONTENTMASK_SOCIALPOLITICALECONOMICS:
723 iLabelId = (iSubID <= 3) ? 19612 + iSubID : 19612;
725 case EPG_EVENT_CONTENTMASK_EDUCATIONALSCIENCE:
726 iLabelId = (iSubID <= 7) ? 19628 + iSubID : 19628;
728 case EPG_EVENT_CONTENTMASK_LEISUREHOBBIES:
729 iLabelId = (iSubID <= 7) ? 19644 + iSubID : 19644;
731 case EPG_EVENT_CONTENTMASK_SPECIAL:
732 iLabelId = (iSubID <= 3) ? 19660 + iSubID : 19660;
734 case EPG_EVENT_CONTENTMASK_USERDEFINED:
735 iLabelId = (iSubID <= 8) ? 19676 + iSubID : 19676;
741 return g_localizeStrings.Get(iLabelId);
744 bool CEpg::UpdateEntry(const EPG_TAG *data, bool bUpdateDatabase /* = false */)
749 CEpgInfoTag tag(*data);
750 return UpdateEntry(tag, bUpdateDatabase);
753 bool CEpg::IsRadio(void) const
755 CPVRChannelPtr channel = Channel();
756 return channel ? channel->IsRadio() : false;
759 bool CEpg::IsRemovableTag(const CEpgInfoTag &tag) const
761 return !tag.HasTimer();
764 bool CEpg::LoadFromClients(time_t start, time_t end)
767 CPVRChannelPtr channel = Channel();
770 CEpg tmpEpg(channel);
771 if (tmpEpg.UpdateFromScraper(start, end))
772 bReturn = UpdateEntries(tmpEpg, !CSettings::Get().GetBool("epg.ignoredbforclient"));
776 CEpg tmpEpg(m_iEpgID, m_strName, m_strScraperName);
777 if (tmpEpg.UpdateFromScraper(start, end))
778 bReturn = UpdateEntries(tmpEpg, !CSettings::Get().GetBool("epg.ignoredbforclient"));
784 CEpgInfoTagPtr CEpg::GetNextEvent(const CEpgInfoTag& tag) const
786 CSingleLock lock(m_critSection);
787 map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(tag.StartAsUTC());
788 if (it != m_tags.end() && ++it != m_tags.end())
791 CEpgInfoTagPtr retVal;
795 CEpgInfoTagPtr CEpg::GetPreviousEvent(const CEpgInfoTag& tag) const
797 CSingleLock lock(m_critSection);
798 map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(tag.StartAsUTC());
799 if (it != m_tags.end() && it != m_tags.begin())
805 CEpgInfoTagPtr retVal;
809 CPVRChannelPtr CEpg::Channel(void) const
811 CSingleLock lock(m_critSection);
815 int CEpg::ChannelID(void) const
817 CSingleLock lock(m_critSection);
818 return m_pvrChannel ? m_pvrChannel->ChannelID() : -1;
821 int CEpg::ChannelNumber(void) const
823 CSingleLock lock(m_critSection);
824 return m_pvrChannel ? m_pvrChannel->ChannelNumber() : -1;
827 void CEpg::SetChannel(PVR::CPVRChannelPtr channel)
829 CSingleLock lock(m_critSection);
830 if (m_pvrChannel != channel)
834 SetName(channel->ChannelName());
835 channel->SetEpgID(m_iEpgID);
837 m_pvrChannel = channel;
838 for (map<CDateTime, CEpgInfoTagPtr>::iterator it = m_tags.begin(); it != m_tags.end(); it++)
839 it->second->SetPVRChannel(m_pvrChannel);
843 bool CEpg::HasPVRChannel(void) const
845 CSingleLock lock(m_critSection);
849 bool CEpg::UpdatePending(void) const
851 CSingleLock lock(m_critSection);
852 return m_bUpdatePending;
855 size_t CEpg::Size(void) const
857 CSingleLock lock(m_critSection);
858 return m_tags.size();
861 bool CEpg::NeedsSave(void) const
863 CSingleLock lock(m_critSection);
864 return !m_changedTags.empty() || !m_deletedTags.empty() || m_bChanged;
867 bool CEpg::IsValid(void) const
869 CSingleLock lock(m_critSection);
870 if (ScraperName() == "client")
871 return Channel().get() != NULL;