f83bf6373d15e5c86c69567047562e606e9ff9f3
[vuplus_xbmc] / xbmc / filesystem / MythDirectory.cpp
1 /*
2  *      Copyright (C) 2005-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 "MythDirectory.h"
22 #include "MythSession.h"
23 #include "utils/URIUtils.h"
24 #include "DllLibCMyth.h"
25 #include "video/VideoInfoTag.h"
26 #include "URL.h"
27 #include "settings/AdvancedSettings.h"
28 #include "settings/Settings.h"
29 #include "FileItem.h"
30 #include "utils/StringUtils.h"
31 #include "guilib/LocalizeStrings.h"
32 #include "utils/log.h"
33 #include "DirectoryCache.h"
34 #include "utils/TimeUtils.h"
35
36 extern "C"
37 {
38 #include "cmyth/include/cmyth/cmyth.h"
39 #include "cmyth/include/refmem/refmem.h"
40 }
41
42 using namespace XFILE;
43 using namespace std;
44
45 CMythDirectory::CMythDirectory()
46 {
47   m_session  = NULL;
48   m_dll      = NULL;
49   m_database = NULL;
50   m_recorder = NULL;
51 }
52
53 CMythDirectory::~CMythDirectory()
54 {
55   Release();
56 }
57
58 DIR_CACHE_TYPE CMythDirectory::GetCacheType(const CStdString& strPath) const
59 {
60   CURL url(strPath);
61   CStdString fileName = url.GetFileName();
62   URIUtils::RemoveSlashAtEnd(fileName);
63
64   /*
65    * Always cache "All Recordings", "Guide" (top folder only), "Movies", and "TV Shows" (including
66    * sub folders).
67    *
68    * Entire directory cache for myth:// is invalidated when the root directory is requested to
69    * ensure content is always up-to-date.
70    */
71   if (fileName == "recordings"
72   ||  fileName == "guide"
73   ||  fileName == "movies"
74   ||  fileName.Left(7) == "tvshows")
75     return DIR_CACHE_ALWAYS;
76
77   return DIR_CACHE_ONCE;
78 }
79
80 void CMythDirectory::Release()
81 {
82   if (m_recorder)
83   {
84     m_dll->ref_release(m_recorder);
85     m_recorder = NULL;
86   }
87   if (m_session)
88   {
89     CMythSession::ReleaseSession(m_session);
90     m_session = NULL;
91   }
92   m_dll = NULL;
93 }
94
95 bool CMythDirectory::GetGuide(const CStdString& base, CFileItemList &items)
96 {
97   cmyth_database_t db = m_session->GetDatabase();
98   if (!db)
99     return false;
100
101   cmyth_chanlist_t list = m_dll->mysql_get_chanlist(db);
102   if (!list)
103   {
104     CLog::Log(LOGERROR, "%s - Unable to get list of channels: %s", __FUNCTION__, base.c_str());
105     return false;
106   }
107   CURL url(base);
108
109   int count = m_dll->chanlist_get_count(list);
110   for (int i = 0; i < count; i++)
111   {
112     cmyth_channel_t channel = m_dll->chanlist_get_item(list, i);
113     if (channel)
114     {
115       if (!m_dll->channel_visible(channel))
116       {
117         m_dll->ref_release(channel);
118         continue;
119       }
120
121       int channum = m_dll->channel_channum(channel); // e.g. 3
122       CStdString name = GetValue(m_dll->channel_name(channel)); // e.g. TV3
123       if (channum <= 0)
124       {
125         CLog::Log(LOGDEBUG, "%s - Skipping channel number %d as <= 0: %s", __FUNCTION__, channum, name.c_str());
126         m_dll->ref_release(channel);
127         continue;
128       }
129
130       CLog::Log(LOGDEBUG, "%s - Adding channel number %d: %s", __FUNCTION__, channum, name.c_str());
131
132       CStdString number;
133       number.Format("%d", channum); // CStdString easier for string manipulation than int.
134       url.SetFileName("guide/" + number);
135       CFileItemPtr item(new CFileItem(url.Get(), true));
136       item->m_strTitle = number;
137       if (!name.IsEmpty())
138         item->m_strTitle += " - " + name; // e.g. 3 - TV3
139
140       CStdString icon = GetValue(m_dll->channel_icon(channel));
141       if (!icon.IsEmpty())
142       {
143         url.SetFileName("files/channels/" + URIUtils::GetFileName(icon)); // e.g. files/channels/tv3.jpg
144         item->SetArt("thumb", url.Get());
145       }
146
147       items.Add(item);
148
149       m_dll->ref_release(channel);
150     }
151   }
152
153   items.AddSortMethod(SortByLabel, 551 /* Name */, LABEL_MASKS("", "", "%K", ""));
154
155   m_dll->ref_release(list);
156   return true;
157 }
158
159 bool CMythDirectory::GetGuideForChannel(const CStdString& base, CFileItemList &items, int channelNumber)
160 {
161   cmyth_database_t database = m_session->GetDatabase();
162   if (!database)
163   {
164     CLog::Log(LOGERROR, "%s - Could not get database", __FUNCTION__);
165     return false;
166   }
167
168   time_t now;
169   time(&now);
170   time_t end = now + (24 * 60 * 60); // How many seconds of EPG from now we should grab, 24 hours in seconds
171
172   cmyth_program_t *program = NULL;
173   // TODO: See if there is a way to just get the entries for the chosen channel rather than ALL
174   int count = m_dll->mysql_get_guide(database, &program, now, end);
175   CLog::Log(LOGDEBUG, "%s - %i entries in guide data", __FUNCTION__, count);
176   if (count <= 0)
177     return false;
178
179   for (int i = 0; i < count; i++)
180   {
181     if (program[i].channum == channelNumber)
182     {
183       CFileItemPtr item(new CFileItem("", false)); // No path for guide entries
184
185       /*
186        * Set the FileItem meta data.
187        */
188       CStdString title        = program[i].title; // e.g. Mythbusters
189       CStdString subtitle     = program[i].subtitle; // e.g. The Pirate Special
190       CDateTime localstart;
191       if (program[i].starttime)
192         localstart = CTimeUtils::GetLocalTime(program[i].starttime);
193       item->m_strTitle.Format("%s - %s", localstart.GetAsLocalizedTime("HH:mm", false), title); // e.g. 20:30 - Mythbusters
194       if (!subtitle.IsEmpty())
195         item->m_strTitle     += " - \"" + subtitle + "\""; // e.g. 20:30 - Mythbusters - "The Pirate Special"
196       item->m_dateTime        = localstart;
197
198       /*
199        * Set the VideoInfoTag meta data so it matches the FileItem meta data where possible.
200        */
201       CVideoInfoTag* tag      = item->GetVideoInfoTag();
202       tag->m_strTitle         = title;
203       if (!subtitle.IsEmpty())
204         tag->m_strTitle      += " - \"" + subtitle + "\""; // e.g. Mythbusters - "The Pirate Special"
205       tag->m_strShowTitle     = title;
206       tag->m_strOriginalTitle = title;
207       tag->m_strPlotOutline   = subtitle;
208       tag->m_strPlot          = program[i].description;
209       // TODO: Strip out the subtitle from the description if it is present at the start?
210       // TODO: Do we need to add the subtitle to the start of the plot if not already as it used to? Seems strange, should be handled by skin?
211       tag->m_genre            = StringUtils::Split(program[i].category, g_advancedSettings.m_videoItemSeparator); // e.g. Sports
212       tag->m_strAlbum         = program[i].callsign; // e.g. TV3
213
214       CDateTime start(program[i].starttime);
215       CDateTime end(program[i].endtime);
216       CDateTimeSpan runtime = end - start;
217       tag->m_duration         = runtime.GetSeconds() + runtime.GetMinutes() * 60 + runtime.GetHours() * 3600;
218       tag->m_iSeason          = 0; // So XBMC treats the content as an episode and displays tag information.
219       tag->m_iEpisode         = 0;
220
221       items.Add(item);
222     }
223   }
224
225   /*
226    * Items are sorted as added to the list (in ascending date order). Specifying sorting by date can
227    * result in the guide being shown in the wrong order for skins that sort by date in descending
228    * order by default with no option to change to ascending, e.g. Confluence.
229    */
230   items.AddSortMethod(SortByNone, 552 /* Date */, LABEL_MASKS("%K", "%J")); // Still leave the date label
231
232   m_dll->ref_release(program);
233   return true;
234 }
235
236 bool CMythDirectory::GetRecordings(const CStdString& base, CFileItemList &items, enum FilterType type,
237                                     const CStdString& filter)
238 {
239   cmyth_proglist_t list = m_session->GetAllRecordedPrograms();
240   if (!list)
241   {
242     CLog::Log(LOGERROR, "%s - unable to get list of recordings", __FUNCTION__);
243     return false;
244   }
245
246   int count = m_dll->proglist_get_count(list);
247   for (int i = 0; i < count; i++)
248   {
249     cmyth_proginfo_t program = m_dll->proglist_get_item(list, i);
250     if (program)
251     {
252       if (!IsVisible(program))
253       {
254         m_dll->ref_release(program);
255         continue;
256       }
257
258       CURL url(base);
259       /*
260        * The base is the URL used to connect to the master server. The hostname in this may not
261        * appropriate for all items as MythTV supports multiple backends (master + slaves).
262        *
263        * The appropriate host for playback is contained in the program information sent back from
264        * the master. The same username and password are used in the URL as for the master.
265        */
266       url.SetHostName(GetValue(m_dll->proginfo_host(program)));
267
268       CStdString path = URIUtils::GetFileName(GetValue(m_dll->proginfo_pathname(program)));
269       CStdString name = GetValue(m_dll->proginfo_title(program));
270
271       switch (type)
272       {
273       case MOVIES:
274         if (!IsMovie(program))
275         {
276           m_dll->ref_release(program);
277           continue;
278         }
279         url.SetFileName("movies/" + path);
280         break;
281       case TV_SHOWS:
282         if (filter.CompareNoCase(name))
283         {
284           m_dll->ref_release(program);
285           continue;
286         }
287         url.SetFileName("tvshows/" + name + "/" + path);
288         break;
289       case ALL:
290         url.SetFileName("recordings/" + path);
291         break;
292       }
293
294       CFileItemPtr item(new CFileItem(url.Get(), false));
295       m_session->SetFileItemMetaData(*item, program);
296
297       /*
298        * If MOVIES, set the label and specify as pre-formatted so any scraper lookup will use the
299        * label rather than the filename. Don't set as pre-formatted for any other types as this
300        * prevents the display of the title changing depending on what the list is being sorted by.
301        */
302       if (type == MOVIES)
303       {
304         /*
305          * Adding the production year, if available, to the label for Movies to aid in scraper
306          * lookups.
307          */
308         CStdString label(item->m_strTitle);
309         unsigned short year = m_dll->proginfo_year(program);
310         if (year > 0)
311           label.AppendFormat(" (%d)", year);
312         item->SetLabel(label);
313         item->SetLabelPreformated(true);
314       }
315
316       items.Add(item);
317       m_dll->ref_release(program);
318     }
319   }
320   m_dll->ref_release(list);
321
322   /*
323    * Don't sort by name for TV_SHOWS as they all have the same name, so only date sort is useful.
324    * Since the subtitle has been added to the TV Show name, the video sort title sort is used so
325    * the subtitle doesn't influence the sort order and they are sorted by date.
326    */
327   if (type != TV_SHOWS)
328     items.AddSortMethod(SortBySortTitle, 556 /* Name */, LABEL_MASKS("%K", "%J"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
329   items.AddSortMethod(SortByDate, 552 /* Date */, LABEL_MASKS("%K", "%J"));
330
331   return true;
332 }
333
334 /**
335  * \brief Gets a list of folders for recorded TV shows
336  */
337 bool CMythDirectory::GetTvShowFolders(const CStdString& base, CFileItemList &items)
338 {
339   cmyth_proglist_t list = m_session->GetAllRecordedPrograms();
340   if (!list)
341   {
342     CLog::Log(LOGERROR, "%s - unable to get list of recordings", __FUNCTION__);
343     return false;
344   }
345
346   int count = m_dll->proglist_get_count(list);
347   for (int i = 0; i < count; i++)
348   {
349     cmyth_proginfo_t program = m_dll->proglist_get_item(list, i);
350     if (program)
351     {
352       if (!IsVisible(program))
353       {
354         m_dll->ref_release(program);
355         continue;
356       }
357
358       if (!IsTvShow(program))
359       {
360         m_dll->ref_release(program);
361         continue;
362       }
363
364       CStdString title = GetValue(m_dll->proginfo_title(program));
365       CStdString path = base + "/" + title + "/";
366
367       /*
368        * Only add each TV show once. If the TV show is already in the list, update the date for the
369        * folder to be the date of the last recorded TV show as the programs are returned in the
370        * order they were recorded.
371        */
372       if (items.Contains(path))
373       {
374         CFileItemPtr item = items.Get(path);
375         item->m_dateTime = GetValue(m_dll->proginfo_rec_start(program));
376       }
377       else
378       {
379         CFileItemPtr item(new CFileItem(path, true));
380         item->m_dateTime = GetValue(m_dll->proginfo_rec_start(program));
381         item->SetLabel(title);
382         items.Add(item);
383       }
384       m_dll->ref_release(program);
385     }
386
387   }
388   m_dll->ref_release(list);
389
390   items.AddSortMethod(SortByLabel, 551 /* Name */, LABEL_MASKS("", "", "%L", "%J"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
391   items.AddSortMethod(SortByDate, 552 /* Date */, LABEL_MASKS("", "", "%L", "%J"));
392
393   return true;
394 }
395
396 bool CMythDirectory::GetChannels(const CStdString& base, CFileItemList &items)
397 {
398   cmyth_conn_t control = m_session->GetControl();
399   if (!control)
400     return false;
401
402   vector<cmyth_proginfo_t> channels;
403   for (unsigned i = 0; i < 16; i++)
404   {
405     cmyth_recorder_t recorder = m_dll->conn_get_recorder_from_num(control, i);
406     if (!recorder)
407       continue;
408
409     cmyth_proginfo_t program;
410     program = m_dll->recorder_get_cur_proginfo(recorder);
411     program = m_dll->recorder_get_next_proginfo(recorder, program, BROWSE_DIRECTION_UP);
412     if (!program)
413     {
414       m_dll->ref_release(m_recorder);
415       continue;
416     }
417
418     long startchan = m_dll->proginfo_chan_id(program);
419     long currchan  = -1;
420     while (startchan != currchan)
421     {
422       unsigned j;
423       for (j = 0; j < channels.size(); j++)
424       {
425         if (m_dll->proginfo_compare(program, channels[j]) == 0)
426           break;
427       }
428
429       if (j == channels.size())
430         channels.push_back(program);
431
432       program = m_dll->recorder_get_next_proginfo(recorder, program, BROWSE_DIRECTION_UP);
433       if (!program)
434         break;
435
436       currchan = m_dll->proginfo_chan_id(program);
437     }
438     m_dll->ref_release(recorder);
439   }
440
441   CURL url(base);
442   /*
443    * The content of the cmyth_proginfo_t struct retrieved and stored in channels[] above does not
444    * contain the host so the URL cannot be modified to support both master and slave servers.
445    */
446
447   for (unsigned i = 0; i < channels.size(); i++)
448   {
449     cmyth_proginfo_t program = channels[i];
450
451     url.SetFileName("channels/" + GetValue(m_dll->proginfo_chanstr(program)) + ".ts"); // e.g. 3.ts
452     CFileItemPtr item(new CFileItem(url.Get(), false));
453     m_session->SetFileItemMetaData(*item, program);
454
455     items.Add(item);
456     m_dll->ref_release(program);
457   }
458
459   items.AddSortMethod(SortByLabel, 551 /* Name */, LABEL_MASKS("%K", "%B"));
460
461   /*
462    * Video sort title is set to the channel number.
463    */
464   items.AddSortMethod(SortBySortTitle, 556 /* Title */, LABEL_MASKS("%K", "%B"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
465
466   return true;
467 }
468
469 bool CMythDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
470 {
471   m_session = CMythSession::AquireSession(strPath);
472   if (!m_session)
473     return false;
474
475   m_dll = m_session->GetLibrary();
476   if (!m_dll)
477     return false;
478
479   CStdString base(strPath);
480   URIUtils::RemoveSlashAtEnd(base);
481
482   CURL url(strPath);
483   CStdString fileName = url.GetFileName();
484   URIUtils::RemoveSlashAtEnd(fileName);
485
486   if (fileName == "")
487   {
488     /*
489      * If we can't get the control then we can't connect to the backend. Don't even show any of the
490      * virtual folders as none of them will work. Without this check the "Browse" functionality
491      * when adding a myth:// source is way confusing as it shows folders so it looks like it has
492      * connected successfully when it in fact hasn't.
493      */
494     cmyth_conn_t control = m_session->GetControl();
495     if (!control)
496       return false;
497
498     CFileItemPtr item;
499
500     item.reset(new CFileItem(base + "/recordings/", true));
501     item->SetLabel(g_localizeStrings.Get(22015)); // All recordings
502     items.Add(item);
503
504     item.reset(new CFileItem(base + "/tvshows/", true));
505     item->SetLabel(g_localizeStrings.Get(20343)); // TV shows
506     items.Add(item);
507
508     item.reset(new CFileItem(base + "/movies/", true));
509     item->SetLabel(g_localizeStrings.Get(20342)); // Movies
510     items.Add(item);
511
512     item.reset(new CFileItem(base + "/channels/", true));
513     item->SetLabel(g_localizeStrings.Get(22018)); // Live channels
514     items.Add(item);
515
516     item.reset(new CFileItem(base + "/guide/", true));
517     item->SetLabel(g_localizeStrings.Get(22020)); // Guide
518     items.Add(item);
519
520     items.AddSortMethod(SortByNone, 564 /* Type */, LABEL_MASKS("", "", "%L", "")); // No sorting, as added to list.
521
522     /*
523      * Clear the directory cache so the cached sub-folders are guaranteed to be accurate.
524      */
525     g_directoryCache.ClearSubPaths(base);
526
527     return true;
528   }
529   else if (fileName == "channels")
530     return GetChannels(base, items);
531   else if (fileName == "guide")
532     return GetGuide(base, items);
533   else if (fileName.Left(6) == "guide/")
534     return GetGuideForChannel(base, items, atoi(fileName.Mid(6)));
535   else if (fileName == "movies")
536     return GetRecordings(base, items, MOVIES);
537   else if (fileName == "recordings")
538     return GetRecordings(base, items);
539   else if (fileName == "tvshows")
540     return GetTvShowFolders(base, items);
541   else if (fileName.Left(8) == "tvshows/")
542     return GetRecordings(base, items, TV_SHOWS, fileName.Mid(8));
543   return false;
544 }
545
546 bool CMythDirectory::Exists(const char* strPath)
547 {
548   /*
549    * Return true for any virtual folders that are known to exist. Don't check for explicit
550    * existence using GetDirectory() as most methods will return true with empty content due to the
551    * way they are implemented - by iterating over all programs and filtering out content.
552    */
553   CURL url(strPath);
554   CStdString fileName = url.GetFileName();
555   URIUtils::RemoveSlashAtEnd(fileName);
556
557   if (fileName == ""
558   ||  fileName == "channels"
559   ||  fileName == "guide"
560   ||  fileName.Left(6) == "guide/"
561   ||  fileName == "movies"
562   ||  fileName == "recordings"
563   ||  fileName == "tvshows"
564   ||  fileName.Left(8) == "tvshows/")
565     return true;
566
567   return false;
568 }
569
570 bool CMythDirectory::IsVisible(const cmyth_proginfo_t program)
571 {
572   CStdString group = GetValue(m_dll->proginfo_recgroup(program));
573   unsigned long flags = m_dll->proginfo_flags(program);
574
575   /*
576    * Ignore programs that were recorded using "LiveTV" or that have been deleted via the
577    * "Auto Expire Instead of Delete Recording" option, which places the recording in the
578    * "Deleted" recording group for x days rather than deleting straight away.
579    *
580    * As of 0.24, when a recording is deleted using the Myth Protocol it is marked as "pending delete"
581    * using the program flags mask. It is then scheduled to be physically deleted in a detached
582    * thread. This means that a deleted recording can still appear in the list of all recordings.
583    * Recordings that are "pending delete" will have a program flag mask that matches
584    * FL_DELETEPENDING = 0x00000080.
585    */
586   return !(group.Equals("LiveTV") || group.Equals("Deleted") || flags & 0x00000080);
587 }
588
589 bool CMythDirectory::IsMovie(const cmyth_proginfo_t program)
590 {
591   /*
592    * The mythconverg.recordedprogram.programid field (if it exists) is a combination key where the first 2 characters map
593    * to the category_type and the rest is the key. From MythTV/release-0-21-fixes/mythtv/libs/libmythtv/programinfo.cpp
594    * "MV" = movie
595    * "EP" = series
596    * "SP" = sports
597    * "SH" = tvshow
598    *
599    * Based on MythTV usage it appears that the programid is only filled in for Movies though. Shame, could have used
600    * it for the other categories as well.
601    *
602    * mythconverg.recordedprogram.category_type contains the exact information that is needed. However, category_type
603    * isn't available through the libcmyth API. Since there is a direct correlation between the programid starting
604    * with "MV" and the category_type being "movie" that should work fine.
605    */
606
607   const int iMovieLength = g_advancedSettings.m_iMythMovieLength; // Minutes
608   if (iMovieLength > 0) // Use hack to identify movie based on length (used if EPG is dubious).
609     return GetValue(m_dll->proginfo_programid(program)).Left(2) == "MV"
610         || m_dll->proginfo_length_sec(program) > iMovieLength * 60; // Minutes to seconds
611   else
612     return GetValue(m_dll->proginfo_programid(program)).Left(2) == "MV";
613 }
614
615 bool CMythDirectory::IsTvShow(const cmyth_proginfo_t program)
616 {
617   /*
618    * There isn't enough information exposed by libcmyth to distinguish between an episode in a series and a
619    * one off TV show. See comment in IsMovie for more information.
620    *
621    * Return anything that isn't a movie as per any advanced setting override. This may result in a
622    * recorded TV Show only being shown in the Movies directory if it's something like a double
623    * episode.
624    */
625   return !IsMovie(program);
626 }
627
628 bool CMythDirectory::SupportsWriteFileOperations(const CStdString& strPath)
629 {
630   CURL url(strPath);
631   CStdString filename = url.GetFileName();
632   URIUtils::RemoveSlashAtEnd(filename);
633   /*
634    * TV Shows directory has sub-folders so extra check is included so only files get the file
635    * operations.
636    */
637   return filename.Left(11) == "recordings/" ||
638          filename.Left(7)  == "movies/" ||
639         (filename.Left(8)  == "tvshows/" && URIUtils::HasExtension(filename));
640 }
641
642 bool CMythDirectory::IsLiveTV(const CStdString& strPath)
643 {
644   CURL url(strPath);
645   return url.GetFileName().Left(9) == "channels/";
646 }