Fixed: Ticket #7555: segmentation fault when the background thumb loader accessed...
[xbmc:xbmc-antiquated.git] / xbmc / FileSystem / CMythDirectory.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 "CMythDirectory.h"
23 #include "CMythSession.h"
24 #include "Util.h"
25 #include "DllLibCMyth.h"
26 #include "VideoInfoTag.h"
27 #include "URL.h"
28 #include "GUISettings.h"
29 #include "AdvancedSettings.h"
30 #include "FileItem.h"
31 #include "StringUtils.h"
32 #include "LocalizeStrings.h"
33 #include "utils/log.h"
34
35 extern "C"
36 {
37 #include "lib/libcmyth/cmyth.h"
38 #include "lib/libcmyth/mvp_refmem.h"
39 }
40
41 using namespace DIRECTORY;
42 using namespace XFILE;
43 using namespace std;
44
45 CCMythDirectory::CCMythDirectory()
46 {
47   m_session  = NULL;
48   m_dll      = NULL;
49   m_database = NULL;
50   m_recorder = NULL;
51 }
52
53 CCMythDirectory::~CCMythDirectory()
54 {
55   Release();
56 }
57
58 void CCMythDirectory::Release()
59 {
60   if (m_recorder)
61   {
62     m_dll->ref_release(m_recorder);
63     m_recorder = NULL;
64   }
65   if (m_session)
66   {
67     CCMythSession::ReleaseSession(m_session);
68     m_session = NULL;
69   }
70   m_dll = NULL;
71 }
72
73 bool CCMythDirectory::GetGuide(const CStdString& base, CFileItemList &items)
74 {
75   cmyth_database_t db = m_session->GetDatabase();
76   if (!db)
77     return false;
78
79   cmyth_chanlist_t list = m_dll->mysql_get_chanlist(db);
80   if (!list)
81   {
82     CLog::Log(LOGERROR, "%s - unable to get list of channels with url %s", __FUNCTION__, base.c_str());
83     return false;
84   }
85   CURL url(base);
86
87   int count = m_dll->chanlist_get_count(list);
88   for (int i = 0; i < count; i++)
89   {
90     cmyth_channel_t channel = m_dll->chanlist_get_item(list, i);
91     if (channel)
92     {
93       CStdString name, path, icon;
94
95       if (!m_dll->channel_visible(channel))
96       {
97         m_dll->ref_release(channel);
98         continue;
99       }
100       int num = m_dll->channel_channum(channel);
101       char* str;
102       if ((str = m_dll->channel_name(channel)))
103       {
104         name.Format("%d - %s", num, str);
105         m_dll->ref_release(str);
106       }
107       else
108         name.Format("%d");
109
110       icon = GetValue(m_dll->channel_icon(channel));
111
112       if (num <= 0)
113       {
114         CLog::Log(LOGDEBUG, "%s - Channel '%s' Icon '%s' - Skipped", __FUNCTION__, name.c_str(), icon.c_str());
115       }
116       else
117       {
118         CLog::Log(LOGDEBUG, "%s - Channel '%s' Icon '%s'", __FUNCTION__, name.c_str(), icon.c_str());
119         path.Format("guide/%d/", num);
120         url.SetFileName(path);
121         CFileItemPtr item(new CFileItem(url.Get(), true));
122         item->SetLabel(name);
123         item->SetLabelPreformated(true);
124         if (icon.length() > 0)
125         {
126           url.SetFileName("files/channels/" + CUtil::GetFileName(icon));
127           item->SetThumbnailImage(url.Get());
128         }
129         items.Add(item);
130       }
131       m_dll->ref_release(channel);
132     }
133   }
134
135   // Sort by name only. Labels are preformated.
136   items.AddSortMethod(SORT_METHOD_LABEL, 551 /* Name */, LABEL_MASKS("%L", "", "%L", ""));
137
138   m_dll->ref_release(list);
139   return true;
140 }
141
142 bool CCMythDirectory::GetGuideForChannel(const CStdString& base, CFileItemList &items, int channelNumber)
143 {
144   cmyth_database_t database = m_session->GetDatabase();
145   if (!database)
146   {
147     CLog::Log(LOGERROR, "%s - Could not get database", __FUNCTION__);
148     return false;
149   }
150
151   time_t now;
152   time(&now);
153   // this sets how many seconds of EPG from now we should grab
154   time_t end = now + (1 * 24 * 60 * 60);
155
156   cmyth_program_t *program = NULL;
157
158   int count = m_dll->mysql_get_guide(database, &program, now, end);
159   CLog::Log(LOGDEBUG, "%s - %i entries of guide data", __FUNCTION__, count);
160   if (count <= 0)
161     return false;
162
163   for (int i = 0; i < count; i++)
164   {
165     if (program[i].channum == channelNumber)
166     {
167       CStdString path;
168       path.Format("%s%s", base.c_str(), program[i].title);
169
170       CDateTime starttime(program[i].starttime);
171       CDateTime endtime(program[i].endtime);
172
173       tm *local = localtime(&program[i].starttime);
174       CDateTime localstart = *local;
175       CStdString title;
176       title.Format("%s - %s", localstart.GetAsLocalizedTime("HH:mm", false), program[i].title);
177
178       CFileItemPtr item(new CFileItem(title, false));
179       item->SetLabel(title);
180       item->m_dateTime = localstart;
181
182       CVideoInfoTag* tag = item->GetVideoInfoTag();
183
184       tag->m_strAlbum       = program[i].callsign;
185       tag->m_strShowTitle   = program[i].title;
186       tag->m_strPlotOutline = program[i].subtitle;
187       tag->m_strPlot        = program[i].description;
188       tag->m_strGenre       = program[i].category;
189
190       if (tag->m_strPlot.Left(tag->m_strPlotOutline.length()) != tag->m_strPlotOutline && !tag->m_strPlotOutline.IsEmpty())
191         tag->m_strPlot = tag->m_strPlotOutline + '\n' + tag->m_strPlot;
192       tag->m_strOriginalTitle = tag->m_strShowTitle;
193
194       tag->m_strTitle = tag->m_strAlbum;
195       if (tag->m_strShowTitle.length() > 0)
196         tag->m_strTitle += " : " + tag->m_strShowTitle;
197
198       CDateTimeSpan runtime = endtime - starttime;
199       StringUtils::SecondsToTimeString( runtime.GetSeconds()
200                                       + runtime.GetMinutes() * 60
201                                       + runtime.GetHours() * 3600, tag->m_strRuntime);
202
203       tag->m_iSeason  = 0; /* set this so xbmc knows it's a tv show */
204       tag->m_iEpisode = 0;
205       tag->m_strStatus = program[i].rec_status;
206       items.Add(item);
207     }
208   }
209
210   // Sort by date only.
211   items.AddSortMethod(SORT_METHOD_DATE, 552 /* Date */, LABEL_MASKS("%L", "%J", "%L", ""));
212
213   m_dll->ref_release(program);
214   return true;
215 }
216
217 bool CCMythDirectory::GetRecordings(const CStdString& base, CFileItemList &items, enum FilterType type,
218                                     const CStdString& filter)
219 {
220   cmyth_conn_t control = m_session->GetControl();
221   if (!control)
222     return false;
223
224   cmyth_proglist_t list = m_dll->proglist_get_all_recorded(control);
225   if (!list)
226   {
227     CLog::Log(LOGERROR, "%s - unable to get list of recordings", __FUNCTION__);
228     return false;
229   }
230
231   int count = m_dll->proglist_get_count(list);
232   for (int i = 0; i < count; i++)
233   {
234     cmyth_proginfo_t program = m_dll->proglist_get_item(list, i);
235     if (program)
236     {
237       if (!IsVisible(program))
238       {
239         m_dll->ref_release(program);
240         continue;
241       }
242
243       CURL url(base);
244       /*
245        * The base is the URL used to connect to the master server. The hostname in this may not
246        * appropriate for all items as MythTV supports multiple backends (master + slaves).
247        *
248        * The appropriate host for playback is contained in the program information sent back from
249        * the master. The same username and password are used in the URL as for the master.
250        */
251       url.SetHostName(GetValue(m_dll->proginfo_host(program)));
252
253       CStdString path = CUtil::GetFileName(GetValue(m_dll->proginfo_pathname(program)));
254       CStdString name = GetValue(m_dll->proginfo_title(program));
255
256       switch (type)
257       {
258       case MOVIES:
259         if (!IsMovie(program))
260         {
261           m_dll->ref_release(program);
262           continue;
263         }
264         url.SetFileName("movies/" + path);
265         break;
266       case TV_SHOWS:
267         if (filter != name)
268         {
269           m_dll->ref_release(program);
270           continue;
271         }
272         url.SetFileName("tvshows/" + name + "/" + path);
273         break;
274       case ALL:
275         url.SetFileName("recordings/" + path);
276         break;
277       }
278
279       CFileItemPtr item(new CFileItem(url.Get(), false));
280       m_session->UpdateItem(*item, program);
281
282       url.SetFileName("files/" + path + ".png");
283       item->SetThumbnailImage(url.Get());
284
285       /*
286        * Don't adjust the name for MOVIES as additional information in the name will affect any scraper lookup.
287        */
288       if (type != MOVIES)
289       {
290         if (m_dll->proginfo_rec_status(program) == RS_RECORDING)
291         {
292           name += " (Recording)";
293           item->SetThumbnailImage("");
294         }
295         else
296           name += " (" + item->m_dateTime.GetAsLocalizedDateTime() + ")";
297       }
298
299       item->SetLabel(name);
300       /*
301        * Set the label as preformated for MOVIES so any scraper lookup will use
302        * the label rather than the filename. Don't set as preformated for other
303        * filter types as this prevents the display of the title changing 
304        * depending on what the list is being sorted by.
305        */
306       if (type == MOVIES)
307         item->SetLabelPreformated(true);
308
309       items.Add(item);
310       m_dll->ref_release(program);
311     }
312   }
313
314   /*
315    * Only add sort by name for the Movies and All Recordings directories. For TV Shows they all have
316    * the same name, so only date is useful.
317    */
318   if (type != TV_SHOWS)
319   {
320     if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
321       items.AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 551 /* Name */, LABEL_MASKS("%Z (%J)", "%Q", "%L", ""));
322     else
323       items.AddSortMethod(SORT_METHOD_LABEL, 551 /* Name */, LABEL_MASKS("%Z (%J)", "%Q", "%L", ""));
324   }
325   items.AddSortMethod(SORT_METHOD_DATE, 552 /* Date */, LABEL_MASKS("%Z", "%J", "%L", "%J"));
326
327   m_dll->ref_release(list);
328   return true;
329 }
330
331 /**
332  * \brief Gets a list of folders for recorded TV shows
333  */
334 bool CCMythDirectory::GetTvShowFolders(const CStdString& base, CFileItemList &items)
335 {
336   cmyth_conn_t control = m_session->GetControl();
337   if (!control)
338     return false;
339
340   cmyth_proglist_t list = m_dll->proglist_get_all_recorded(control);
341   if (!list)
342   {
343     CLog::Log(LOGERROR, "%s - unable to get list of recordings", __FUNCTION__);
344     return false;
345   }
346
347   int count = m_dll->proglist_get_count(list);
348   for (int i = 0; i < count; i++)
349   {
350     cmyth_proginfo_t program = m_dll->proglist_get_item(list, i);
351     if (program)
352     {
353       if (!IsVisible(program))
354       {
355         m_dll->ref_release(program);
356         continue;
357       }
358
359       if (!IsTvShow(program))
360       {
361         m_dll->ref_release(program);
362         continue;
363       }
364
365       CStdString title = GetValue(m_dll->proginfo_title(program));
366
367       // Only add each TV show once
368       if (items.Contains(base + "/" + title + "/"))
369       {
370         m_dll->ref_release(program);
371         continue;
372       }
373
374       CFileItemPtr item(new CFileItem(base + "/" + title + "/", true));
375       item->SetLabel(title);
376       item->SetLabelPreformated(true);
377
378       items.Add(item);
379       m_dll->ref_release(program);
380     }
381
382   }
383
384   // Sort by name only. Labels are preformated.
385   if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
386     items.AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 551 /* Name */, LABEL_MASKS("%L", "", "%L", ""));
387   else
388     items.AddSortMethod(SORT_METHOD_LABEL, 551 /* Name */, LABEL_MASKS("%L", "", "%L", ""));
389
390   m_dll->ref_release(list);
391   return true;
392 }
393
394 bool CCMythDirectory::GetChannels(const CStdString& base, CFileItemList &items)
395 {
396   cmyth_conn_t control = m_session->GetControl();
397   if (!control)
398     return false;
399
400   vector<cmyth_proginfo_t> channels;
401   for (unsigned i = 0; i < 16; i++)
402   {
403     cmyth_recorder_t recorder = m_dll->conn_get_recorder_from_num(control, i);
404     if (!recorder)
405       continue;
406
407     cmyth_proginfo_t program;
408     program = m_dll->recorder_get_cur_proginfo(recorder);
409     program = m_dll->recorder_get_next_proginfo(recorder, program, BROWSE_DIRECTION_UP);
410     if (!program)
411     {
412       m_dll->ref_release(m_recorder);
413       continue;
414     }
415
416     long startchan = m_dll->proginfo_chan_id(program);
417     long currchan  = -1;
418     while (startchan != currchan)
419     {
420       unsigned j;
421       for (j = 0; j < channels.size(); j++)
422       {
423         if (m_dll->proginfo_compare(program, channels[j]) == 0)
424           break;
425       }
426
427       if (j == channels.size())
428         channels.push_back(program);
429
430       program = m_dll->recorder_get_next_proginfo(recorder, program, BROWSE_DIRECTION_UP);
431       if (!program)
432         break;
433
434       currchan = m_dll->proginfo_chan_id(program);
435     }
436     m_dll->ref_release(recorder);
437   }
438
439   CURL url(base);
440   /*
441    * The content of the cmyth_proginfo_t struct retrieved and stored in channels[] above does not
442    * contain the host so the URL cannot be modified to support both master and slave servers.
443    */
444
445   for (unsigned i = 0; i < channels.size(); i++)
446   {
447     cmyth_proginfo_t program = channels[i];
448     CStdString num, progname, channame, icon, sign;
449
450     num   = GetValue(m_dll->proginfo_chanstr (program));
451     icon  = GetValue(m_dll->proginfo_chanicon(program));
452
453     url.SetFileName("channels/" + num + ".ts");
454     CFileItemPtr item(new CFileItem(url.Get(), false));
455     m_session->UpdateItem(*item, program);
456
457     item->SetLabel(GetValue(m_dll->proginfo_chansign(program)));
458
459     if (icon.length() > 0)
460     {
461       url.SetFileName("files/channels/" + CUtil::GetFileName(icon));
462       item->SetThumbnailImage(url.Get());
463     }
464
465     /* hack to get sorting working properly when sorting by show title */
466     if (item->GetVideoInfoTag()->m_strShowTitle.IsEmpty())
467       item->GetVideoInfoTag()->m_strShowTitle = " ";
468
469     items.Add(item);
470     m_dll->ref_release(program);
471   }
472
473   if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
474     items.AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 551 /* Name */, LABEL_MASKS("%K[ - %Z]", "%B", "%L", ""));
475   else
476     items.AddSortMethod(SORT_METHOD_LABEL, 551 /* Name */, LABEL_MASKS("%K[ - %Z]", "%B", "%L", ""));
477
478   if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
479     items.AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 20364 /* TV show */, LABEL_MASKS("%Z[ - %B]", "%K", "%L", ""));
480   else
481     items.AddSortMethod(SORT_METHOD_LABEL, 20364 /* TV show */, LABEL_MASKS("%Z[ - %B]", "%K", "%L", ""));
482
483   return true;
484 }
485
486 bool CCMythDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
487 {
488   m_session = CCMythSession::AquireSession(strPath);
489   if (!m_session)
490     return false;
491
492   m_dll = m_session->GetLibrary();
493   if (!m_dll)
494     return false;
495
496   CStdString base(strPath);
497   CUtil::RemoveSlashAtEnd(base);
498
499   CURL url(strPath);
500   CStdString fileName = url.GetFileName();
501   CUtil::RemoveSlashAtEnd(fileName);
502
503   if (fileName == "")
504   {
505     CFileItemPtr item;
506
507     item.reset(new CFileItem(base + "/channels/", true));
508     item->SetLabel(g_localizeStrings.Get(22018)); // Live channels
509     item->SetLabelPreformated(true);
510     items.Add(item);
511
512     item.reset(new CFileItem(base + "/guide/", true));
513     item->SetLabel(g_localizeStrings.Get(22020)); // Guide
514     item->SetLabelPreformated(true);
515     items.Add(item);
516
517     item.reset(new CFileItem(base + "/movies/", true));
518     item->SetLabel(g_localizeStrings.Get(20342)); // Movies
519     item->SetLabelPreformated(true);
520     items.Add(item);
521
522     item.reset(new CFileItem(base + "/recordings/", true));
523     item->SetLabel(g_localizeStrings.Get(22015)); // All recordings
524     item->SetLabelPreformated(true);
525     items.Add(item);
526
527     item.reset(new CFileItem(base + "/tvshows/", true));
528     item->SetLabel(g_localizeStrings.Get(20343)); // TV shows
529     item->SetLabelPreformated(true);
530     items.Add(item);
531
532     // Sort by name only. Labels are preformated.
533     items.AddSortMethod(SORT_METHOD_LABEL, 551 /* Name */, LABEL_MASKS("%L", "", "%L", ""));
534
535     return true;
536   }
537   else if (fileName == "channels")
538     return GetChannels(base, items);
539   else if (fileName == "guide")
540     return GetGuide(base, items);
541   else if (fileName.Left(6) == "guide/")
542     return GetGuideForChannel(base, items, atoi(fileName.Mid(6)));
543   else if (fileName == "movies")
544     return GetRecordings(base, items, MOVIES);
545   else if (fileName == "recordings")
546     return GetRecordings(base, items);
547   else if (fileName == "tvshows")
548     return GetTvShowFolders(base, items);
549   else if (fileName.Left(8) == "tvshows/")
550     return GetRecordings(base, items, TV_SHOWS, fileName.Mid(8));
551   return false;
552 }
553
554 bool CCMythDirectory::IsVisible(const cmyth_proginfo_t program)
555 {
556   CStdString group = GetValue(m_dll->proginfo_recgroup(program));
557   /*
558    * Ignore programs that were recorded using "LiveTV" or that have been deleted via the
559    * "Auto Expire Instead of Delete Recording" option, which places the recording in the
560    * "Deleted" recording group for x days rather than deleting straight away.
561    */
562   return !(group.Equals("LiveTV") || group.Equals("Deleted"));
563 }
564
565 bool CCMythDirectory::IsMovie(const cmyth_proginfo_t program)
566 {
567   /*
568    * The mythconverg.recordedprogram.programid field (if it exists) is a combination key where the first 2 characters map
569    * to the category_type and the rest is the key. From MythTV/release-0-21-fixes/mythtv/libs/libmythtv/programinfo.cpp
570    * "MV" = movie
571    * "EP" = series
572    * "SP" = sports
573    * "SH" = tvshow
574    *
575    * Based on MythTV usage it appears that the programid is only filled in for Movies though. Shame, could have used
576    * it for the other categories as well.
577    *
578    * mythconverg.recordedprogram.category_type contains the exact information that is needed. However, category_type
579    * isn't available through the libcmyth API. Since there is a direct correlation between the programid starting
580    * with "MV" and the category_type being "movie" that should work fine.
581    */
582
583   const int iMovieLength = g_advancedSettings.m_iMythMovieLength; // Minutes
584   if (iMovieLength > 0) // Use hack to identify movie based on length (used if EPG is dubious).
585     return GetValue(m_dll->proginfo_programid(program)).Left(2) == "MV"
586         || m_dll->proginfo_length_sec(program) > iMovieLength * 60; // Minutes to seconds
587   else
588     return GetValue(m_dll->proginfo_programid(program)).Left(2) == "MV";
589 }
590
591 bool CCMythDirectory::IsTvShow(const cmyth_proginfo_t program)
592 {
593   /*
594    * There isn't enough information exposed by libcmyth to distinguish between an episode in a series and a
595    * one off TV show. See comment in IsMovie for more information.
596    *
597    * Return anything that isn't a movie as per the program ID. This may result in a recording being
598    * in both the Movies and TV Shows folders if the advanced setting to choose a movie based on
599    * recording length is used, but means that at least all recorded TV Shows can be found in one
600    * place.
601    */
602   return GetValue(m_dll->proginfo_programid(program)).Left(2) != "MV";
603 }
604
605 bool CCMythDirectory::SupportsFileOperations(const CStdString& strPath)
606 {
607   CURL url(strPath);
608   CStdString filename = url.GetFileName();
609   CUtil::RemoveSlashAtEnd(filename);
610   /*
611    * TV Shows directory has sub-folders so extra check is included so only files get the file
612    * operations.
613    */
614   return filename.Left(11) == "recordings/" ||
615          filename.Left(7)  == "movies/" ||
616         (filename.Left(8)  == "tvshows/" && CUtil::GetExtension(filename) != "");
617 }
618
619 bool CCMythDirectory::IsLiveTV(const CStdString& strPath)
620 {
621   CURL url(strPath);
622   return url.GetFileName().Left(9) == "channels/";
623 }