[osx/ios] remove unused function
[vuplus_xbmc] / xbmc / filesystem / MythSession.cpp
1 /*
2  *      Copyright (C) 2005-2008 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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "DllLibCMyth.h"
23 #include "MythSession.h"
24 #include "video/VideoInfoTag.h"
25 #include "settings/AdvancedSettings.h"
26 #include "DateTime.h"
27 #include "FileItem.h"
28 #include "URL.h"
29 #include "utils/URIUtils.h"
30 #include "utils/StringUtils.h"
31 #include "threads/SingleLock.h"
32 #include "utils/log.h"
33 #include "utils/TimeUtils.h"
34
35 extern "C"
36 {
37 #include "cmyth/include/cmyth/cmyth.h"
38 #include "cmyth/include/refmem/refmem.h"
39 }
40
41 using namespace XFILE;
42 using namespace std;
43
44 #define MYTH_DEFAULT_PORT     6543
45 #define MYTH_DEFAULT_USERNAME "mythtv"
46 #define MYTH_DEFAULT_PASSWORD "mythtv"
47 #define MYTH_DEFAULT_DATABASE "mythconverg"
48
49 #define MYTH_IDLE_TIMEOUT     5 * 60 // 5 minutes in seconds
50
51 CCriticalSection       CMythSession::m_section_session;
52 vector<CMythSession*>  CMythSession::m_sessions;
53
54 void CMythSession::CheckIdle()
55 {
56   CSingleLock lock(m_section_session);
57
58   vector<CMythSession*>::iterator it;
59   for (it = m_sessions.begin(); it != m_sessions.end(); )
60   {
61     CMythSession* session = *it;
62     if (session->m_timestamp + (MYTH_IDLE_TIMEOUT * 1000) < CTimeUtils::GetTimeMS())
63     {
64       CLog::Log(LOGINFO, "%s - closing idle connection to MythTV backend: %s", __FUNCTION__, session->m_hostname.c_str());
65       delete session;
66       it = m_sessions.erase(it);
67     }
68     else
69     {
70       it++;
71     }
72   }
73 }
74
75 CMythSession* CMythSession::AquireSession(const CURL& url)
76 {
77   CSingleLock lock(m_section_session);
78
79   vector<CMythSession*>::iterator it;
80   for (it = m_sessions.begin(); it != m_sessions.end(); it++)
81   {
82     CMythSession* session = *it;
83     if (session->CanSupport(url))
84     {
85       m_sessions.erase(it);
86       CLog::Log(LOGDEBUG, "%s - Aquired existing MythTV session: %p", __FUNCTION__, session);
87       return session;
88     }
89   }
90   CMythSession* session = new CMythSession(url);
91   CLog::Log(LOGINFO, "%s - Aquired new MythTV session for %s: %p", __FUNCTION__,
92             url.GetWithoutUserDetails().c_str(), session);
93   return session;
94 }
95
96 void CMythSession::ReleaseSession(CMythSession* session)
97 {
98   CLog::Log(LOGDEBUG, "%s - Releasing MythTV session: %p", __FUNCTION__, session);
99   session->SetListener(NULL);
100   session->m_timestamp = CTimeUtils::GetTimeMS();
101   CSingleLock lock(m_section_session);
102   m_sessions.push_back(session);
103 }
104
105 CDateTime CMythSession::GetValue(cmyth_timestamp_t t)
106 {
107   CDateTime result;
108   if (t)
109   {
110     time_t time = m_dll->timestamp_to_unixtime(t); // Returns NULL if error
111     if (time)
112       result = CTimeUtils::GetLocalTime(time);
113     else
114       result = CTimeUtils::GetLocalTime(0);
115     m_dll->ref_release(t);
116   }
117   else // Return epoch so 0 and NULL behave the same.
118     result = CTimeUtils::GetLocalTime(0);
119
120   return result;
121 }
122
123 CStdString CMythSession::GetValue(char *str)
124 {
125   CStdString result;
126   if (str)
127   {
128     result = str;
129     m_dll->ref_release(str);
130     result.Trim();
131   }
132   return result;
133 }
134
135 void CMythSession::SetFileItemMetaData(CFileItem &item, cmyth_proginfo_t program)
136 {
137   if (!program)
138     return;
139
140   /*
141    * Set the FileItem meta-data.
142    */
143   CStdString title        = GetValue(m_dll->proginfo_title(program)); // e.g. Mythbusters
144   CStdString subtitle     = GetValue(m_dll->proginfo_subtitle(program)); // e.g. The Pirate Special
145   item.m_strTitle         = title;
146   if (!subtitle.IsEmpty())
147     item.m_strTitle      += " - \"" + subtitle + "\""; // e.g. Mythbusters - "The Pirate Special"
148   item.m_dateTime         = GetValue(m_dll->proginfo_rec_start(program));
149   item.m_dwSize           = m_dll->proginfo_length(program); // size in bytes
150
151   /*
152    * Set the VideoInfoTag meta-data so it matches the FileItem meta-data where possible.
153    */
154   CVideoInfoTag* tag      = item.GetVideoInfoTag();
155   tag->m_strTitle         = subtitle; // The title is just supposed to be the episode title.
156   tag->m_strShowTitle     = title;
157   tag->m_strOriginalTitle = title;
158   tag->m_strPlotOutline   = subtitle;
159   tag->m_strPlot          = GetValue(m_dll->proginfo_description(program));
160   /*
161    * TODO: Strip out the subtitle from the description if it is present at the start? OR add the
162    * subtitle to the start of the plot if not already as it used to? Seems strange, should be
163    * handled by skin?
164    *
165   if (tag->m_strPlot.Left(tag->m_strPlotOutline.length()) != tag->m_strPlotOutline && !tag->m_strPlotOutline.IsEmpty())
166     tag->m_strPlot = tag->m_strPlotOutline + '\n' + tag->m_strPlot;
167    */
168   tag->m_strGenre         = GetValue(m_dll->proginfo_category(program)); // e.g. Sports
169   tag->m_strAlbum         = GetValue(m_dll->proginfo_chansign(program)); // e.g. TV3
170   tag->m_strRuntime       = StringUtils::SecondsToTimeString(m_dll->proginfo_length_sec(program));
171   
172   SetSeasonAndEpisode(program, &tag->m_iSeason, &tag->m_iEpisode);
173
174   /*
175    * Original air date is used by the VideoInfoScanner to scrape the TV Show information into the
176    * Video Library. If the original air date is empty the date returned will be the epoch.
177    */
178   CStdString originalairdate = GetValue(m_dll->proginfo_originalairdate(program)).GetAsDBDate();
179   if (originalairdate != "1970-01-01"
180   &&  originalairdate != "1969-12-31")
181     tag->m_strFirstAired = originalairdate;
182
183   /*
184    * Video sort title is the raw title with the date appended on the end in a sortable format so
185    * when the "All Recordings" listing is sorted by "Name" rather than "Date", all of the episodes
186    * for a given show are still presented in date order (even though some may have a subtitle that
187    * would cause it to be shown in a different position if it was indeed strictly sorting by
188    * what is displayed in the list).
189    */
190   tag->m_strSortTitle = title + " " + item.m_dateTime.GetAsDBDateTime(); // e.g. Mythbusters 2009-12-13 12:23:14
191
192   /*
193    * Set further FileItem and VideoInfoTag meta-data based on whether it is LiveTV or not.
194    */
195   CURL url(item.m_strPath);
196   if (url.GetFileName().Left(9) == "channels/")
197   {
198     /*
199      * Prepend the channel number onto the FileItem title for the listing so it's clear what is
200      * playing on each channel without using up as much room as the channel name.
201      */
202     CStdString number = GetValue(m_dll->proginfo_chanstr(program));
203     item.m_strTitle = number + " - " + item.m_strTitle;
204
205     /*
206      * Append the channel name onto the end of the tag title for the OSD so it's clear what LiveTV
207      * channel is currently being watched to give some context for Next or Previous channel. Added
208      * to the end so sorting by title will work, and it's not really as important as the title
209      * within the OSD.
210      */
211     CStdString name = GetValue(m_dll->proginfo_chansign(program));
212     if (!name.IsEmpty())
213       tag->m_strTitle += " - " + name;
214
215     /*
216      * Set the sort title to be the channel number.
217      */
218     tag->m_strSortTitle = number;
219
220     /*
221      * Set the status so XBMC treats the content as LiveTV.
222      */
223     tag->m_strStatus = "livetv";
224
225     /*
226      * Update the path and channel icon for LiveTV in case the channel has changed through
227      * NextChannel(), PreviousChannel() or SetChannel().
228      */
229     if (!number.IsEmpty())
230     {
231       url.SetFileName("channels/" + number + ".ts"); // e.g. channels/3.ts
232       item.m_strPath = url.Get();
233     }
234     CStdString chanicon = GetValue(m_dll->proginfo_chanicon(program));
235     if (!chanicon.IsEmpty())
236     {
237       url.SetFileName("files/channels/" + URIUtils::GetFileName(chanicon)); // e.g. files/channels/tv3.jpg
238       item.SetThumbnailImage(url.Get());
239     }
240   }
241   else
242   {
243     /*
244      * MythTV thumbnails aren't generated until a program has finished recording.
245      */
246     if (m_dll->proginfo_rec_status(program) == RS_RECORDED)
247     {
248       url.SetFileName("files/" + URIUtils::GetFileName(GetValue(m_dll->proginfo_pathname(program))) + ".png");
249       item.SetThumbnailImage(url.Get());
250     }
251   }
252 }
253
254 void CMythSession::SetSeasonAndEpisode(const cmyth_proginfo_t &program, int *season, int *episode) {
255   /*
256    * A valid programid generated from an XMLTV source should look like:
257    * [EP|MV|SH|SP][seriesid][episode][season]([partnumber][parttotal])
258    * mythtv/trunk/programs/mytfilldatabaseline/xmltvparser.cpp - Line 522 onwards.
259    * 
260    * Season changed to a base36 character for XMLTV in Myth 0.24. http://svn.mythtv.org/trac/changeset/24724
261    * 
262    * A valid SchedulesDirect programid appears to have a similar format to the XMLTV programid but
263    * doesn't have any obvious way to parse out the season and episode information. The number at the
264    * end of the programid could possibly be the completely sequential number for the episode, but
265    * even that doesn't seem to match up with TVDB. SchedulesDirect data does seem to have a valid
266    * original air date though, so if we identify a SchedulesDirect programid, leave the season and
267    * episode as 0. 
268    */
269   CStdString programid = GetValue(m_dll->proginfo_programid(program));
270   CStdString seriesid = GetValue(m_dll->proginfo_seriesid(program));
271
272   /*
273    * Default the season and episode to 0 so XBMC treats the content as an episode and displays tag
274    * information. If the season and episode can be parsed from the programid these will be
275    * overwritten.
276    */
277   *season  = 0;
278   *episode = 0;
279   
280   if (programid.IsEmpty() // Can't do anything if the program ID is empty
281   ||  seriesid.IsEmpty()) // Can't figure out the end parsing if the series ID is empty  {
282     return;
283   
284   CStdString category = programid.Left(2); // Valid for both XMLTV and SchedulesDirect sources
285   if (category != "MV"  // Movie
286   &&  category != "EP"  // Series
287   &&  category != "SH"  // TV Show
288   &&  category != "SP") // Sports
289     return;
290   
291   if (programid.Mid(category.length(), seriesid.length()) != seriesid) // Series ID does not follow the category
292     return;
293   
294   CStdString remainder = programid.Mid(category.length() + seriesid.length()); // Whatever is after series ID
295   
296   /*
297    * All SchedulesDirect remainders appear to be 4 characters and start with a 0. If the assumption
298    * is correct that the number somehow relates to the sequential episode number across all seasons
299    * then we can ignore remainders that start with 0. It will be very unlikely for a sequential
300    * episode number for a series to be > 999.
301    */
302   if (remainder.length() == 4     // All SchedulesDirect codes seem to be 4 characters
303   &&  remainder.Left(0)  == "0")  // Padded with 0's for low number. No valid XMLTV remainder will start with 0.
304     return;
305   
306   /*
307    * If the remainder is more than 5 characters, it must include the optional part number and total
308    * number of parts. Strip off the last 2 characters assuming that there are ridiculously few
309    * cases where the number of parts for a single episode is > 9.
310    */
311   if (remainder.length() >= 5) // Must include optional part number and total number of parts
312     remainder = remainder.Left(remainder.length() - 2); // Assumes part number and total are both < 10
313   
314   /*
315    * Now for some heuristic black magic.
316    */
317   if (remainder.length() == 2)  // Single character season and episode.
318   {
319     *season = atoi(remainder.Right(1)); // TODO: Fix for base 36 in Myth 0.24. Assume season < 10
320     *episode = atoi(remainder.Left(1));
321   }
322   else if (remainder.length() == 3) // Ambiguous in Myth 0.23. Single character season in Myth 0.24
323   {
324     /*
325      * Following heuristics are intended to work with largest possible number of cases. It won't be
326      * perfect, but way better than just assuming the season is < 10.
327      */
328     if (remainder.Right(1) == "0") // e.g. 610. Unlikely to have a season of 0 (specials) with more than 9 special episodes.
329     {
330       *season = atoi(remainder.Right(2));
331       *episode = atoi(remainder.Left(1));
332     }
333     else if (remainder.Mid(2, 1) == "0") // e.g. 203. Can't have a season start with 0. Must be end of episode.
334     {
335       *season = atoi(remainder.Right(1)); // TODO: Fix for base 36 in Myth 0.24. Assume season < 10
336       *episode = atoi(remainder.Left(2));
337     }
338     else if (atoi(remainder.Left(1)) > 3) // e.g. 412. Very unlikely to have more than 39 episodes per season if season > 9.
339     {
340       /*
341        * TODO: See if a check for > 2 is better, e.g. is it still unlike to have more than 29 episodes
342        * per season if season > 9?
343        */
344       *season = atoi(remainder.Right(2));
345       *episode = atoi(remainder.Left(1));
346     }
347     else // e.g. 129. Assume season is < 10 or Myth 0.24 Base 36 season.
348     {
349       *season = atoi(remainder.Right(1)); // TODO: Fix for base 36 in Myth 0.24. Assume season < 10
350       *episode = atoi(remainder.Left(2));
351     }
352   }
353   else if (remainder.length() == 4) // Double digit season and episode in Myth 0.23 OR TODO: has part number and total number of parts
354   {
355     *season = atoi(remainder.Right(2));
356     *episode = atoi(remainder.Left(2));
357   }
358   return;
359 }
360
361 CMythSession::CMythSession(const CURL& url)
362 {
363   m_control   = NULL;
364   m_event     = NULL;
365   m_database  = NULL;
366   m_hostname  = url.GetHostName();
367   m_username  = url.GetUserName() == "" ? MYTH_DEFAULT_USERNAME : url.GetUserName();
368   m_password  = url.GetPassWord() == "" ? MYTH_DEFAULT_PASSWORD : url.GetPassWord();
369   m_port      = url.HasPort() ? url.GetPort() : MYTH_DEFAULT_PORT;
370   m_timestamp = CTimeUtils::GetTimeMS();
371   m_dll = new DllLibCMyth;
372   m_dll->Load();
373   if (m_dll->IsLoaded())
374   {
375     m_dll->set_dbg_msgcallback(&CMythSession::LogCMyth);
376     if (g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG_SAMBA)
377       m_dll->dbg_level(CMYTH_DBG_ALL);
378     else if (g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG)
379       m_dll->dbg_level(CMYTH_DBG_DETAIL);
380     else
381       m_dll->dbg_level(CMYTH_DBG_ERROR);
382   }
383   m_all_recorded = NULL;
384 }
385
386 CMythSession::~CMythSession()
387 {
388   Disconnect();
389   delete m_dll;
390 }
391
392 bool CMythSession::CanSupport(const CURL& url)
393 {
394   if (m_hostname != url.GetHostName())
395     return false;
396
397   if (m_port != (url.HasPort() ? url.GetPort() : MYTH_DEFAULT_PORT))
398     return false;
399
400   if (m_username != (url.GetUserName() == "" ? MYTH_DEFAULT_USERNAME : url.GetUserName()))
401     return false;
402
403   if (m_password != (url.GetPassWord() == "" ? MYTH_DEFAULT_PASSWORD : url.GetPassWord()))
404     return false;
405
406   return true;
407 }
408
409 void CMythSession::Process()
410 {
411   char buf[128];
412   int  next;
413
414   struct timeval to;
415
416   while (!m_bStop)
417   {
418     /* check if there are any new events */
419     to.tv_sec = 0;
420     to.tv_usec = 100000;
421     if (m_dll->event_select(m_event, &to) <= 0)
422       continue;
423
424     next = m_dll->event_get(m_event, buf, sizeof(buf));
425     buf[sizeof(buf) - 1] = 0;
426
427     switch (next)
428     {
429     case CMYTH_EVENT_UNKNOWN:
430       CLog::Log(LOGDEBUG, "%s - MythTV event UNKNOWN (error?)", __FUNCTION__);
431       break;
432     case CMYTH_EVENT_CLOSE:
433       CLog::Log(LOGDEBUG, "%s - MythTV event EVENT_CLOSE", __FUNCTION__);
434       break;
435     case CMYTH_EVENT_RECORDING_LIST_CHANGE:
436       CLog::Log(LOGDEBUG, "%s - MythTV event RECORDING_LIST_CHANGE", __FUNCTION__);
437       GetAllRecordedPrograms(true);
438       break;
439     case CMYTH_EVENT_RECORDING_LIST_CHANGE_ADD:
440       CLog::Log(LOGDEBUG, "%s - MythTV event RECORDING_LIST_CHANGE_ADD: %s", __FUNCTION__, buf);
441       GetAllRecordedPrograms(true);
442       break;
443     case CMYTH_EVENT_RECORDING_LIST_CHANGE_UPDATE:
444       CLog::Log(LOGDEBUG, "%s - MythTV event RECORDING_LIST_CHANGE_UPDATE", __FUNCTION__);
445       GetAllRecordedPrograms(true);
446       break;
447     case CMYTH_EVENT_RECORDING_LIST_CHANGE_DELETE:
448       CLog::Log(LOGDEBUG, "%s - MythTV event RECORDING_LIST_CHANGE_DELETE: %s", __FUNCTION__, buf);
449       GetAllRecordedPrograms(true);
450       break;
451     case CMYTH_EVENT_SCHEDULE_CHANGE:
452       CLog::Log(LOGDEBUG, "%s - MythTV event SCHEDULE_CHANGE", __FUNCTION__);
453       break;
454     case CMYTH_EVENT_DONE_RECORDING:
455       CLog::Log(LOGDEBUG, "%s - MythTV event DONE_RECORDING", __FUNCTION__);
456       break;
457     case CMYTH_EVENT_QUIT_LIVETV:
458       CLog::Log(LOGDEBUG, "%s - MythTV event QUIT_LIVETV", __FUNCTION__);
459       break;
460     case CMYTH_EVENT_WATCH_LIVETV:
461       CLog::Log(LOGDEBUG, "%s - MythTV event LIVETV_WATCH", __FUNCTION__);
462       break;
463     case CMYTH_EVENT_LIVETV_CHAIN_UPDATE:
464       CLog::Log(LOGDEBUG, "%s - MythTV event LIVETV_CHAIN_UPDATE: %s", __FUNCTION__, buf);
465       break;
466     case CMYTH_EVENT_SIGNAL:
467       CLog::Log(LOGDEBUG, "%s - MythTV event SIGNAL", __FUNCTION__);
468       break;
469     case CMYTH_EVENT_ASK_RECORDING:
470       CLog::Log(LOGDEBUG, "%s - MythTV event ASK_RECORDING", __FUNCTION__);
471       break;
472     case CMYTH_EVENT_SYSTEM_EVENT:
473       CLog::Log(LOGDEBUG, "%s - MythTV event SYSTEM_EVENT: %s", __FUNCTION__, buf);
474       break;
475     case CMYTH_EVENT_UPDATE_FILE_SIZE:
476       CLog::Log(LOGDEBUG, "%s - MythTV event UPDATE_FILE_SIZE: %s", __FUNCTION__, buf);
477       break;
478     case CMYTH_EVENT_GENERATED_PIXMAP:
479       CLog::Log(LOGDEBUG, "%s - MythTV event GENERATED_PIXMAP: %s", __FUNCTION__, buf);
480       break;
481     case CMYTH_EVENT_CLEAR_SETTINGS_CACHE:
482       CLog::Log(LOGDEBUG, "%s - MythTV event CLEAR_SETTINGS_CACHE", __FUNCTION__);
483       break;
484     }
485
486     {
487       CSingleLock lock(m_section);
488       if (m_listener)
489         m_listener->OnEvent(next, buf);
490     }
491   }
492 }
493
494 void CMythSession::Disconnect()
495 {
496   if (!m_dll || !m_dll->IsLoaded())
497     return;
498
499   StopThread();
500
501   if (m_control)
502     m_dll->ref_release(m_control);
503   if (m_event)
504     m_dll->ref_release(m_event);
505   if (m_database)
506     m_dll->ref_release(m_database);
507   if (m_all_recorded)
508     m_dll->ref_release(m_all_recorded);
509 }
510
511 cmyth_conn_t CMythSession::GetControl()
512 {
513   if (!m_control)
514   {
515     if (!m_dll->IsLoaded())
516       return false;
517
518     m_control = m_dll->conn_connect_ctrl((char*)m_hostname.c_str(), m_port, 16*1024, 4096);
519     if (!m_control)
520       CLog::Log(LOGERROR, "%s - unable to connect to server on %s:%d", __FUNCTION__, m_hostname.c_str(), m_port);
521   }
522   return m_control;
523 }
524
525 cmyth_database_t CMythSession::GetDatabase()
526 {
527   if (!m_database)
528   {
529     if (!m_dll->IsLoaded())
530       return false;
531
532     m_database = m_dll->database_init((char*)m_hostname.c_str(), (char*)MYTH_DEFAULT_DATABASE,
533                                       (char*)m_username.c_str(), (char*)m_password.c_str());
534     if (!m_database)
535       CLog::Log(LOGERROR, "%s - unable to connect to database on %s:%d", __FUNCTION__, m_hostname.c_str(), m_port);
536   }
537   return m_database;
538 }
539
540 bool CMythSession::SetListener(IEventListener *listener)
541 {
542   if (!m_event && listener)
543   {
544     if (!m_dll->IsLoaded())
545       return false;
546
547     m_event = m_dll->conn_connect_event((char*)m_hostname.c_str(), m_port, 16*1024 , 4096);
548     if (!m_event)
549     {
550       CLog::Log(LOGERROR, "%s - unable to connect to server on %s:%d", __FUNCTION__, m_hostname.c_str(), m_port);
551       return false;
552     }
553     /* start event handler thread */
554     CThread::Create(false, THREAD_MINSTACKSIZE);
555   }
556   CSingleLock lock(m_section);
557   m_listener = listener;
558   return true;
559 }
560
561 DllLibCMyth* CMythSession::GetLibrary()
562 {
563   if (m_dll->IsLoaded())
564     return m_dll;
565   return NULL;
566 }
567
568 cmyth_proglist_t CMythSession::GetAllRecordedPrograms(bool force)
569 {
570   if (!m_all_recorded || force)
571   {
572     CSingleLock lock(m_section);
573     if (m_all_recorded)
574     {
575       m_dll->ref_release(m_all_recorded);
576       m_all_recorded = NULL;
577     }
578     cmyth_conn_t control = GetControl();
579     if (!control)
580       return NULL;
581
582     m_all_recorded = m_dll->proglist_get_all_recorded(control);
583   }
584   return m_all_recorded;
585 }
586
587 void CMythSession::LogCMyth(int level, char *msg)
588 {
589   int xbmc_lvl = -1;
590   switch (level)
591   {
592     case CMYTH_DBG_NONE:  break;
593     case CMYTH_DBG_ERROR: xbmc_lvl = LOGERROR;   break;
594     case CMYTH_DBG_WARN:  xbmc_lvl = LOGWARNING; break;
595     case CMYTH_DBG_INFO:  xbmc_lvl = LOGINFO;    break;
596     default:              xbmc_lvl = LOGDEBUG;   break;
597   }
598   if (xbmc_lvl >= 0) {
599     CLog::Log(xbmc_lvl, "%s", msg);
600   }
601 }