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