Merge pull request #3111 from janbar/epg_timeslot
[vuplus_xbmc] / xbmc / epg / Epg.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
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"
27
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"
34
35 #include "../addons/include/xbmc_epg_types.h"
36
37 using namespace PVR;
38 using namespace EPG;
39 using namespace std;
40
41 CEpg::CEpg(int iEpgID, const CStdString &strName /* = "" */, const CStdString &strScraperName /* = "" */, bool bLoadedFromDb /* = false */) :
42     m_bChanged(!bLoadedFromDb),
43     m_bTagsChanged(false),
44     m_bLoaded(false),
45     m_bUpdatePending(false),
46     m_iEpgID(iEpgID),
47     m_strName(strName),
48     m_strScraperName(strScraperName),
49     m_bUpdateLastScanTime(false)
50 {
51   CPVRChannelPtr empty;
52   m_pvrChannel = empty;
53 }
54
55 CEpg::CEpg(CPVRChannelPtr channel, bool bLoadedFromDb /* = false */) :
56     m_bChanged(!bLoadedFromDb),
57     m_bTagsChanged(false),
58     m_bLoaded(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)
65 {
66 }
67
68 CEpg::CEpg(void) :
69     m_bChanged(false),
70     m_bTagsChanged(false),
71     m_bLoaded(false),
72     m_bUpdatePending(false),
73     m_iEpgID(0),
74     m_bUpdateLastScanTime(false)
75 {
76   CPVRChannelPtr empty;
77   m_pvrChannel = empty;
78 }
79
80 CEpg::~CEpg(void)
81 {
82   Clear();
83 }
84
85 CEpg &CEpg::operator =(const CEpg &right)
86 {
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;
97
98   for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = right.m_tags.begin(); it != right.m_tags.end(); it++)
99   {
100     CEpgInfoTagPtr EITPtr (new CEpgInfoTag(*it->second));
101     m_tags.insert(make_pair(it->first, EITPtr));
102   }
103
104   return *this;
105 }
106
107 /** @name Public methods */
108 //@{
109
110 void CEpg::SetName(const CStdString &strName)
111 {
112   CSingleLock lock(m_critSection);
113
114   if (!m_strName.Equals(strName))
115   {
116     m_bChanged = true;
117     m_strName = strName;
118   }
119 }
120
121 void CEpg::SetScraperName(const CStdString &strScraperName)
122 {
123   CSingleLock lock(m_critSection);
124
125   if (!m_strScraperName.Equals(strScraperName))
126   {
127     m_bChanged = true;
128     m_strScraperName = strScraperName;
129   }
130 }
131
132 void CEpg::SetUpdatePending(bool bUpdatePending /* = true */)
133 {
134   {
135     CSingleLock lock(m_critSection);
136     m_bUpdatePending = bUpdatePending;
137   }
138
139   if (bUpdatePending)
140     g_EpgContainer.SetHasPendingUpdates(true);
141 }
142
143 void CEpg::ForceUpdate(void)
144 {
145   SetUpdatePending();
146 }
147
148 bool CEpg::HasValidEntries(void) const
149 {
150   CSingleLock lock(m_critSection);
151
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 */
155 }
156
157 void CEpg::Clear(void)
158 {
159   CSingleLock lock(m_critSection);
160   m_tags.clear();
161 }
162
163 void CEpg::Cleanup(void)
164 {
165   CDateTime cleanupTime = CDateTime::GetCurrentDateTime().GetAsUTCDateTime() -
166       CDateTimeSpan(0, g_advancedSettings.m_iEpgLingerTime / 60, g_advancedSettings.m_iEpgLingerTime % 60, 0);
167   Cleanup(cleanupTime);
168 }
169
170 void CEpg::Cleanup(const CDateTime &Time)
171 {
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)
174   {
175     if (it->second->EndAsUTC() < Time)
176     {
177       if (m_nowActiveStart == it->first)
178         m_nowActiveStart.SetValid(false);
179
180       it->second->ClearTimer();
181       m_tags.erase(it++);
182     }
183   }
184 }
185
186 bool CEpg::InfoTagNow(CEpgInfoTag &tag, bool bUpdateIfNeeded /* = true */)
187 {
188   CSingleLock lock(m_critSection);
189   if (m_nowActiveStart.IsValid())
190   {
191     map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(m_nowActiveStart);
192     if (it != m_tags.end() && it->second->IsActive())
193     {
194       tag = *it->second;
195       return true;
196     }
197   }
198
199   if (bUpdateIfNeeded)
200   {
201     CDateTime lastActiveTag;
202
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++)
205     {
206       if (it->second->IsActive())
207       {
208         m_nowActiveStart = it->first;
209         tag = *it->second;
210         return true;
211       }
212       else if (it->second->WasActive())
213         lastActiveTag = it->first;
214     }
215
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())
219     {
220       tag = *it->second;
221       return true;
222     }
223   }
224
225   return false;
226 }
227
228 bool CEpg::InfoTagNext(CEpgInfoTag &tag)
229 {
230   CEpgInfoTag nowTag;
231   if (InfoTagNow(nowTag))
232   {
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())
236     {
237       tag = *it->second;
238       return true;
239     }
240   }
241   else if (Size() > 0)
242   {
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++)
245     {
246       if (it->second->InTheFuture())
247       {
248         tag = *it->second;
249         return true;
250       }
251     }
252   }
253
254   return false;
255 }
256
257 bool CEpg::CheckPlayingEvent(void)
258 {
259   bool bReturn(false);
260   CEpgInfoTag previousTag, newTag;
261   bool bGotPreviousTag = InfoTagNow(previousTag, false);
262   bool bGotCurrentTag = InfoTagNow(newTag);
263
264   bool bTagChanged = bGotCurrentTag && (!bGotPreviousTag || previousTag != newTag);
265   bool bTagRemoved = !bGotCurrentTag && bGotPreviousTag;
266   if (bTagChanged || bTagRemoved)
267   {
268     NotifyObservers(ObservableMessageEpgActiveItem);
269     bReturn = true;
270   }
271
272   return bReturn;
273 }
274
275 CEpgInfoTagPtr CEpg::GetTag(const CDateTime &StartTime) const
276 {
277   CSingleLock lock(m_critSection);
278   map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.find(StartTime);
279   if (it != m_tags.end())
280   {
281     return it->second;
282   }
283
284   CEpgInfoTagPtr empty;
285   return empty;
286 }
287
288 CEpgInfoTagPtr CEpg::GetTagBetween(const CDateTime &beginTime, const CDateTime &endTime) const
289 {
290   CSingleLock lock(m_critSection);
291   for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
292   {
293     if (it->second->StartAsUTC() >= beginTime && it->second->EndAsUTC() <= endTime)
294       return it->second;
295   }
296
297   CEpgInfoTagPtr retVal;
298   return retVal;
299 }
300
301 CEpgInfoTagPtr CEpg::GetTagAround(const CDateTime &time) const
302 {
303   CSingleLock lock(m_critSection);
304   for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
305   {
306     if ((it->second->StartAsUTC() < time) && (it->second->EndAsUTC() > time))
307       return it->second;
308   }
309
310   CEpgInfoTagPtr retVal;
311   return retVal;
312 }
313
314 void CEpg::AddEntry(const CEpgInfoTag &tag)
315 {
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;
321   else
322   {
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));
325   }
326
327   if (newTag)
328   {
329     newTag->Update(tag);
330     newTag->SetPVRChannel(m_pvrChannel);
331     newTag->m_epg          = this;
332     newTag->m_bChanged     = false;
333   }
334 }
335
336 bool CEpg::UpdateEntry(const CEpgInfoTag &tag, bool bUpdateDatabase /* = false */, bool bSort /* = true */)
337 {
338   CEpgInfoTagPtr infoTag;
339   CSingleLock lock(m_critSection);
340   map<CDateTime, CEpgInfoTagPtr>::iterator it = m_tags.find(tag.StartAsUTC());
341   bool bNewTag(false);
342   if (it != m_tags.end())
343   {
344     infoTag = it->second;
345   }
346   else
347   {
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));
352     bNewTag = true;
353   }
354
355   infoTag->Update(tag, bNewTag);
356   infoTag->m_epg          = this;
357   infoTag->m_pvrChannel   = m_pvrChannel;
358
359   if (bUpdateDatabase)
360     m_changedTags.insert(make_pair(infoTag->UniqueBroadcastID(), infoTag));
361
362   return true;
363 }
364
365 bool CEpg::Load(void)
366 {
367   bool bReturn(false);
368   CEpgDatabase *database = g_EpgContainer.GetDatabase();
369
370   if (!database || !database->IsOpen())
371   {
372     CLog::Log(LOGERROR, "EPG - %s - could not open the database", __FUNCTION__);
373     return bReturn;
374   }
375
376   CSingleLock lock(m_critSection);
377   int iEntriesLoaded = database->Get(*this);
378   if (iEntriesLoaded <= 0)
379   {
380     CLog::Log(LOGDEBUG, "EPG - %s - no database entries found for table '%s'.", __FUNCTION__, m_strName.c_str());
381   }
382   else
383   {
384     m_lastScanTime = GetLastScanTime();
385 #if EPG_DEBUGGING
386     CLog::Log(LOGDEBUG, "EPG - %s - %d entries loaded for table '%s'.", __FUNCTION__, (int) m_tags.size(), m_strName.c_str());
387 #endif
388     bReturn = true;
389   }
390
391   m_bLoaded = true;
392
393   return bReturn;
394 }
395
396 bool CEpg::UpdateEntries(const CEpg &epg, bool bStoreInDb /* = true */)
397 {
398   CSingleLock lock(m_critSection);
399 #if EPG_DEBUGGING
400   CLog::Log(LOGDEBUG, "EPG - %s - %zu entries in memory before merging", __FUNCTION__, m_tags.size());
401 #endif
402   /* copy over tags */
403   for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = epg.m_tags.begin(); it != epg.m_tags.end(); it++)
404     UpdateEntry(*it->second, bStoreInDb, false);
405
406 #if EPG_DEBUGGING
407   CLog::Log(LOGDEBUG, "EPG - %s - %zu entries in memory after merging and before fixing", __FUNCTION__, m_tags.size());
408 #endif
409   FixOverlappingEvents(bStoreInDb);
410
411 #if EPG_DEBUGGING
412   CLog::Log(LOGDEBUG, "EPG - %s - %zu entries in memory after fixing", __FUNCTION__, m_tags.size());
413 #endif
414   /* update the last scan time of this table */
415   m_lastScanTime = CDateTime::GetCurrentDateTime().GetAsUTCDateTime();
416   m_bUpdateLastScanTime = true;
417
418   NotifyObservers(ObservableMessageEpg);
419
420   return true;
421 }
422
423 CDateTime CEpg::GetLastScanTime(void)
424 {
425   CDateTime lastScanTime;
426   {
427     CSingleLock lock(m_critSection);
428
429     if (!m_lastScanTime.IsValid())
430     {
431       if (!CSettings::Get().GetBool("epg.ignoredbforclient"))
432       {
433         CEpgDatabase *database = g_EpgContainer.GetDatabase();
434         CDateTime dtReturn; dtReturn.SetValid(false);
435
436         if (database && database->IsOpen())
437           database->GetLastEpgScanTime(m_iEpgID, &m_lastScanTime);
438       }
439
440       if (!m_lastScanTime.IsValid())
441       {
442         m_lastScanTime.SetDateTime(0, 0, 0, 0, 0, 0);
443         m_lastScanTime.SetValid(true);
444       }
445     }
446     lastScanTime = m_lastScanTime;
447   }
448
449   return m_lastScanTime;
450 }
451
452 bool CEpg::Update(const time_t start, const time_t end, int iUpdateTime, bool bForceUpdate /* = false */)
453 {
454   bool bGrabSuccess(true);
455   bool bUpdate(false);
456
457   /* load the entries from the db first */
458   if (!m_bLoaded && !g_EpgContainer.IgnoreDB())
459     Load();
460
461   /* clean up if needed */
462   if (m_bLoaded)
463     Cleanup();
464
465   /* get the last update time from the database */
466   CDateTime lastScanTime = GetLastScanTime();
467
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;
471
472   if (!bForceUpdate)
473   {
474     /* check if we have to update */
475     time_t iNow = 0;
476     time_t iLastUpdate = 0;
477     CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(iNow);
478     lastScanTime.GetAsTime(iLastUpdate);
479     bUpdate = (iNow > iLastUpdate + iUpdateTime);
480   }
481   else
482     bUpdate = true;
483
484   if (bUpdate)
485     bGrabSuccess = LoadFromClients(start, end);
486
487   if (bGrabSuccess)
488   {
489     CPVRChannelPtr channel;
490     if (g_PVRManager.GetCurrentChannel(channel) &&
491         channel->EpgID() == m_iEpgID)
492       g_PVRManager.ResetPlayingTag();
493     m_bLoaded = true;
494   }
495   else
496     CLog::Log(LOGERROR, "EPG - %s - failed to update table '%s'", __FUNCTION__, Name().c_str());
497
498   CSingleLock lock(m_critSection);
499   m_bUpdatePending = false;
500
501   return bGrabSuccess;
502 }
503
504 int CEpg::Get(CFileItemList &results) const
505 {
506   int iInitialSize = results.Size();
507
508   CSingleLock lock(m_critSection);
509
510   for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
511     results.Add(CFileItemPtr(new CFileItem(*it->second)));
512
513   return results.Size() - iInitialSize;
514 }
515
516 int CEpg::Get(CFileItemList &results, const EpgSearchFilter &filter) const
517 {
518   int iInitialSize = results.Size();
519
520   if (!HasValidEntries())
521     return -1;
522
523   CSingleLock lock(m_critSection);
524
525   for (map<CDateTime, CEpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); it++)
526   {
527     if (filter.FilterEntry(*it->second))
528       results.Add(CFileItemPtr(new CFileItem(*it->second)));
529   }
530
531   return results.Size() - iInitialSize;
532 }
533
534 bool CEpg::Persist(void)
535 {
536   if (CSettings::Get().GetBool("epg.ignoredbforclient") || !NeedsSave())
537     return true;
538
539 #if EPG_DEBUGGING
540   CLog::Log(LOGDEBUG, "persist table '%s' (#%d) changed=%d deleted=%d", Name().c_str(), m_iEpgID, m_changedTags.size(), m_deletedTags.size());
541 #endif
542
543   CEpgDatabase *database = g_EpgContainer.GetDatabase();
544   if (!database || !database->IsOpen())
545   {
546     CLog::Log(LOGERROR, "EPG - %s - could not open the database", __FUNCTION__);
547     return false;
548   }
549
550   {
551     CSingleLock lock(m_critSection);
552     if (m_iEpgID <= 0 || m_bChanged)
553     {
554       int iId = database->Persist(*this, m_iEpgID > 0);
555       if (iId > 0)
556         m_iEpgID = iId;
557     }
558
559     for (std::map<int, CEpgInfoTagPtr>::iterator it = m_deletedTags.begin(); it != m_deletedTags.end(); it++)
560       database->Delete(*it->second);
561
562     for (std::map<int, CEpgInfoTagPtr>::iterator it = m_changedTags.begin(); it != m_changedTags.end(); it++)
563       it->second->Persist(false);
564
565     if (m_bUpdateLastScanTime)
566       database->PersistLastEpgScanTime(m_iEpgID, true);
567
568     m_deletedTags.clear();
569     m_changedTags.clear();
570     m_bChanged            = false;
571     m_bTagsChanged        = false;
572     m_bUpdateLastScanTime = false;
573   }
574
575   return database->CommitInsertQueries();
576 }
577
578 CDateTime CEpg::GetFirstDate(void) const
579 {
580   CDateTime first;
581
582   CSingleLock lock(m_critSection);
583   if (!m_tags.empty())
584     first = m_tags.begin()->second->StartAsUTC();
585
586   return first;
587 }
588
589 CDateTime CEpg::GetLastDate(void) const
590 {
591   CDateTime last;
592
593   CSingleLock lock(m_critSection);
594   if (!m_tags.empty())
595     last = m_tags.rbegin()->second->StartAsUTC();
596
597   return last;
598 }
599
600 //@}
601
602 /** @name Private methods */
603 //@{
604
605 bool CEpg::FixOverlappingEvents(bool bUpdateDb /* = false */)
606 {
607   bool bReturn(true);
608   CEpgInfoTagPtr previousTag, currentTag;
609
610   for (map<CDateTime, CEpgInfoTagPtr>::iterator it = m_tags.begin(); it != m_tags.end(); it != m_tags.end() ? it++ : it)
611   {
612     if (!previousTag)
613     {
614       previousTag = it->second;
615       continue;
616     }
617     currentTag = it->second;
618
619     if (previousTag->EndAsUTC() >= currentTag->EndAsUTC())
620     {
621       // delete the current tag. it's completely overlapped
622       if (bUpdateDb)
623         m_deletedTags.insert(make_pair(currentTag->UniqueBroadcastID(), currentTag));
624
625       if (m_nowActiveStart == it->first)
626         m_nowActiveStart.SetValid(false);
627
628       it->second->ClearTimer();
629       m_tags.erase(it++);
630     }
631     else if (previousTag->EndAsUTC() != currentTag->StartAsUTC())
632     {
633       previousTag->SetEndFromUTC(currentTag->StartAsUTC());
634       if (bUpdateDb)
635         m_changedTags.insert(make_pair(previousTag->UniqueBroadcastID(), previousTag));
636
637       previousTag = it->second;
638     }
639     else
640     {
641       previousTag = it->second;
642     }
643   }
644
645   return bReturn;
646 }
647
648 bool CEpg::UpdateFromScraper(time_t start, time_t end)
649 {
650   bool bGrabSuccess = false;
651   if (ScraperName() == "client")
652   {
653     CPVRChannelPtr channel = Channel();
654     if (!channel)
655     {
656       CLog::Log(LOGWARNING, "EPG - %s - channel not found, can't update", __FUNCTION__);
657     }
658     else if (!channel->EPGEnabled())
659     {
660 #if EPG_DEBUGGING
661       CLog::Log(LOGDEBUG, "EPG - %s - EPG updating disabled in the channel configuration", __FUNCTION__);
662 #endif
663       bGrabSuccess = true;
664     }
665     else if (channel->IsHidden())
666     {
667 #if EPG_DEBUGGING
668       CLog::Log(LOGDEBUG, "EPG - %s - channel '%s' on client '%i' is hidden", __FUNCTION__, channel->ChannelName().c_str(), channel->ClientID());
669 #endif
670       bGrabSuccess = true;
671     }
672     else if (!g_PVRClients->SupportsEPG(channel->ClientID()))
673     {
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());
675     }
676     else
677     {
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);
680     }
681   }
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());
684   else
685   {
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
689   }
690
691   return bGrabSuccess;
692 }
693
694 //@}
695
696 const CStdString &CEpg::ConvertGenreIdToString(int iID, int iSubID)
697 {
698   unsigned int iLabelId = 19499;
699   switch (iID)
700   {
701     case EPG_EVENT_CONTENTMASK_MOVIEDRAMA:
702       iLabelId = (iSubID <= 8) ? 19500 + iSubID : 19500;
703       break;
704     case EPG_EVENT_CONTENTMASK_NEWSCURRENTAFFAIRS:
705       iLabelId = (iSubID <= 4) ? 19516 + iSubID : 19516;
706       break;
707     case EPG_EVENT_CONTENTMASK_SHOW:
708       iLabelId = (iSubID <= 3) ? 19532 + iSubID : 19532;
709       break;
710     case EPG_EVENT_CONTENTMASK_SPORTS:
711       iLabelId = (iSubID <= 11) ? 19548 + iSubID : 19548;
712       break;
713     case EPG_EVENT_CONTENTMASK_CHILDRENYOUTH:
714       iLabelId = (iSubID <= 5) ? 19564 + iSubID : 19564;
715       break;
716     case EPG_EVENT_CONTENTMASK_MUSICBALLETDANCE:
717       iLabelId = (iSubID <= 6) ? 19580 + iSubID : 19580;
718       break;
719     case EPG_EVENT_CONTENTMASK_ARTSCULTURE:
720       iLabelId = (iSubID <= 11) ? 19596 + iSubID : 19596;
721       break;
722     case EPG_EVENT_CONTENTMASK_SOCIALPOLITICALECONOMICS:
723       iLabelId = (iSubID <= 3) ? 19612 + iSubID : 19612;
724       break;
725     case EPG_EVENT_CONTENTMASK_EDUCATIONALSCIENCE:
726       iLabelId = (iSubID <= 7) ? 19628 + iSubID : 19628;
727       break;
728     case EPG_EVENT_CONTENTMASK_LEISUREHOBBIES:
729       iLabelId = (iSubID <= 7) ? 19644 + iSubID : 19644;
730       break;
731     case EPG_EVENT_CONTENTMASK_SPECIAL:
732       iLabelId = (iSubID <= 3) ? 19660 + iSubID : 19660;
733       break;
734     case EPG_EVENT_CONTENTMASK_USERDEFINED:
735       iLabelId = (iSubID <= 8) ? 19676 + iSubID : 19676;
736       break;
737     default:
738       break;
739   }
740
741   return g_localizeStrings.Get(iLabelId);
742 }
743
744 bool CEpg::UpdateEntry(const EPG_TAG *data, bool bUpdateDatabase /* = false */)
745 {
746   if (!data)
747     return false;
748
749   CEpgInfoTag tag(*data);
750   return UpdateEntry(tag, bUpdateDatabase);
751 }
752
753 bool CEpg::IsRadio(void) const
754 {
755   CPVRChannelPtr channel = Channel();
756   return channel ? channel->IsRadio() : false;
757 }
758
759 bool CEpg::IsRemovableTag(const CEpgInfoTag &tag) const
760 {
761   return !tag.HasTimer();
762 }
763
764 bool CEpg::LoadFromClients(time_t start, time_t end)
765 {
766   bool bReturn(false);
767   CPVRChannelPtr channel = Channel();
768   if (channel)
769   {
770     CEpg tmpEpg(channel);
771     if (tmpEpg.UpdateFromScraper(start, end))
772       bReturn = UpdateEntries(tmpEpg, !CSettings::Get().GetBool("epg.ignoredbforclient"));
773   }
774   else
775   {
776     CEpg tmpEpg(m_iEpgID, m_strName, m_strScraperName);
777     if (tmpEpg.UpdateFromScraper(start, end))
778       bReturn = UpdateEntries(tmpEpg, !CSettings::Get().GetBool("epg.ignoredbforclient"));
779   }
780
781   return bReturn;
782 }
783
784 CEpgInfoTagPtr CEpg::GetNextEvent(const CEpgInfoTag& tag) const
785 {
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())
789     return it->second;
790
791   CEpgInfoTagPtr retVal;
792   return retVal;
793 }
794
795 CEpgInfoTagPtr CEpg::GetPreviousEvent(const CEpgInfoTag& tag) const
796 {
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())
800   {
801     it--;
802     return it->second;
803   }
804
805   CEpgInfoTagPtr retVal;
806   return retVal;
807 }
808
809 CPVRChannelPtr CEpg::Channel(void) const
810 {
811   CSingleLock lock(m_critSection);
812   return m_pvrChannel;
813 }
814
815 int CEpg::ChannelID(void) const
816 {
817   CSingleLock lock(m_critSection);
818   return m_pvrChannel ? m_pvrChannel->ChannelID() : -1;
819 }
820
821 int CEpg::ChannelNumber(void) const
822 {
823   CSingleLock lock(m_critSection);
824   return m_pvrChannel ? m_pvrChannel->ChannelNumber() : -1;
825 }
826
827 void CEpg::SetChannel(PVR::CPVRChannelPtr channel)
828 {
829   CSingleLock lock(m_critSection);
830   if (m_pvrChannel != channel)
831   {
832     if (channel)
833     {
834       SetName(channel->ChannelName());
835       channel->SetEpgID(m_iEpgID);
836     }
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);
840   }
841 }
842
843 bool CEpg::HasPVRChannel(void) const
844 {
845   CSingleLock lock(m_critSection);
846   return m_pvrChannel;
847 }
848
849 bool CEpg::UpdatePending(void) const
850 {
851   CSingleLock lock(m_critSection);
852   return m_bUpdatePending;
853 }
854
855 size_t CEpg::Size(void) const
856 {
857   CSingleLock lock(m_critSection);
858   return m_tags.size();
859 }
860
861 bool CEpg::NeedsSave(void) const
862 {
863   CSingleLock lock(m_critSection);
864   return !m_changedTags.empty() || !m_deletedTags.empty() || m_bChanged;
865 }
866
867 bool CEpg::IsValid(void) const
868 {
869   CSingleLock lock(m_critSection);
870   if (ScraperName() == "client")
871     return Channel().get() != NULL;
872   return true;
873 }