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