2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
21 #include "MythDirectory.h"
22 #include "MythSession.h"
23 #include "utils/URIUtils.h"
24 #include "DllLibCMyth.h"
25 #include "video/VideoInfoTag.h"
27 #include "settings/AdvancedSettings.h"
28 #include "settings/Settings.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"
38 #include "cmyth/include/cmyth/cmyth.h"
39 #include "cmyth/include/refmem/refmem.h"
42 using namespace XFILE;
45 CMythDirectory::CMythDirectory()
53 CMythDirectory::~CMythDirectory()
58 DIR_CACHE_TYPE CMythDirectory::GetCacheType(const CStdString& strPath) const
61 CStdString fileName = url.GetFileName();
62 URIUtils::RemoveSlashAtEnd(fileName);
65 * Always cache "All Recordings", "Guide" (top folder only), "Movies", and "TV Shows" (including
68 * Entire directory cache for myth:// is invalidated when the root directory is requested to
69 * ensure content is always up-to-date.
71 if (fileName == "recordings"
72 || fileName == "guide"
73 || fileName == "movies"
74 || fileName.Left(7) == "tvshows")
75 return DIR_CACHE_ALWAYS;
77 return DIR_CACHE_ONCE;
80 void CMythDirectory::Release()
84 m_dll->ref_release(m_recorder);
89 CMythSession::ReleaseSession(m_session);
95 bool CMythDirectory::GetGuide(const CStdString& base, CFileItemList &items)
97 cmyth_database_t db = m_session->GetDatabase();
101 cmyth_chanlist_t list = m_dll->mysql_get_chanlist(db);
104 CLog::Log(LOGERROR, "%s - Unable to get list of channels: %s", __FUNCTION__, base.c_str());
109 int count = m_dll->chanlist_get_count(list);
110 for (int i = 0; i < count; i++)
112 cmyth_channel_t channel = m_dll->chanlist_get_item(list, i);
115 if (!m_dll->channel_visible(channel))
117 m_dll->ref_release(channel);
121 int channum = m_dll->channel_channum(channel); // e.g. 3
122 CStdString name = GetValue(m_dll->channel_name(channel)); // e.g. TV3
125 CLog::Log(LOGDEBUG, "%s - Skipping channel number %d as <= 0: %s", __FUNCTION__, channum, name.c_str());
126 m_dll->ref_release(channel);
130 CLog::Log(LOGDEBUG, "%s - Adding channel number %d: %s", __FUNCTION__, channum, name.c_str());
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;
138 item->m_strTitle += " - " + name; // e.g. 3 - TV3
140 CStdString icon = GetValue(m_dll->channel_icon(channel));
143 url.SetFileName("files/channels/" + URIUtils::GetFileName(icon)); // e.g. files/channels/tv3.jpg
144 item->SetArt("thumb", url.Get());
149 m_dll->ref_release(channel);
153 items.AddSortMethod(SortByLabel, 551 /* Name */, LABEL_MASKS("", "", "%K", ""));
155 m_dll->ref_release(list);
159 bool CMythDirectory::GetGuideForChannel(const CStdString& base, CFileItemList &items, int channelNumber)
161 cmyth_database_t database = m_session->GetDatabase();
164 CLog::Log(LOGERROR, "%s - Could not get database", __FUNCTION__);
170 time_t end = now + (24 * 60 * 60); // How many seconds of EPG from now we should grab, 24 hours in seconds
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);
179 for (int i = 0; i < count; i++)
181 if (program[i].channum == channelNumber)
183 CFileItemPtr item(new CFileItem("", false)); // No path for guide entries
186 * Set the FileItem meta data.
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;
199 * Set the VideoInfoTag meta data so it matches the FileItem meta data where possible.
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
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.
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.
230 items.AddSortMethod(SortByNone, 552 /* Date */, LABEL_MASKS("%K", "%J")); // Still leave the date label
232 m_dll->ref_release(program);
236 bool CMythDirectory::GetRecordings(const CStdString& base, CFileItemList &items, enum FilterType type,
237 const CStdString& filter)
239 cmyth_proglist_t list = m_session->GetAllRecordedPrograms();
242 CLog::Log(LOGERROR, "%s - unable to get list of recordings", __FUNCTION__);
246 int count = m_dll->proglist_get_count(list);
247 for (int i = 0; i < count; i++)
249 cmyth_proginfo_t program = m_dll->proglist_get_item(list, i);
252 if (!IsVisible(program))
254 m_dll->ref_release(program);
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).
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.
266 url.SetHostName(GetValue(m_dll->proginfo_host(program)));
268 CStdString path = URIUtils::GetFileName(GetValue(m_dll->proginfo_pathname(program)));
269 CStdString name = GetValue(m_dll->proginfo_title(program));
274 if (!IsMovie(program))
276 m_dll->ref_release(program);
279 url.SetFileName("movies/" + path);
282 if (filter.CompareNoCase(name))
284 m_dll->ref_release(program);
287 url.SetFileName("tvshows/" + name + "/" + path);
290 url.SetFileName("recordings/" + path);
294 CFileItemPtr item(new CFileItem(url.Get(), false));
295 m_session->SetFileItemMetaData(*item, program);
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.
305 * Adding the production year, if available, to the label for Movies to aid in scraper
308 CStdString label(item->m_strTitle);
309 unsigned short year = m_dll->proginfo_year(program);
311 label.AppendFormat(" (%d)", year);
312 item->SetLabel(label);
313 item->SetLabelPreformated(true);
317 m_dll->ref_release(program);
320 m_dll->ref_release(list);
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.
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"));
335 * \brief Gets a list of folders for recorded TV shows
337 bool CMythDirectory::GetTvShowFolders(const CStdString& base, CFileItemList &items)
339 cmyth_proglist_t list = m_session->GetAllRecordedPrograms();
342 CLog::Log(LOGERROR, "%s - unable to get list of recordings", __FUNCTION__);
346 int count = m_dll->proglist_get_count(list);
347 for (int i = 0; i < count; i++)
349 cmyth_proginfo_t program = m_dll->proglist_get_item(list, i);
352 if (!IsVisible(program))
354 m_dll->ref_release(program);
358 if (!IsTvShow(program))
360 m_dll->ref_release(program);
364 CStdString title = GetValue(m_dll->proginfo_title(program));
365 CStdString path = base + "/" + title + "/";
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.
372 if (items.Contains(path))
374 CFileItemPtr item = items.Get(path);
375 item->m_dateTime = GetValue(m_dll->proginfo_rec_start(program));
379 CFileItemPtr item(new CFileItem(path, true));
380 item->m_dateTime = GetValue(m_dll->proginfo_rec_start(program));
381 item->SetLabel(title);
384 m_dll->ref_release(program);
388 m_dll->ref_release(list);
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"));
396 bool CMythDirectory::GetChannels(const CStdString& base, CFileItemList &items)
398 cmyth_conn_t control = m_session->GetControl();
402 vector<cmyth_proginfo_t> channels;
403 for (unsigned i = 0; i < 16; i++)
405 cmyth_recorder_t recorder = m_dll->conn_get_recorder_from_num(control, i);
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);
414 m_dll->ref_release(m_recorder);
418 long startchan = m_dll->proginfo_chan_id(program);
420 while (startchan != currchan)
423 for (j = 0; j < channels.size(); j++)
425 if (m_dll->proginfo_compare(program, channels[j]) == 0)
429 if (j == channels.size())
430 channels.push_back(program);
432 program = m_dll->recorder_get_next_proginfo(recorder, program, BROWSE_DIRECTION_UP);
436 currchan = m_dll->proginfo_chan_id(program);
438 m_dll->ref_release(recorder);
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.
447 for (unsigned i = 0; i < channels.size(); i++)
449 cmyth_proginfo_t program = channels[i];
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);
456 m_dll->ref_release(program);
459 items.AddSortMethod(SortByLabel, 551 /* Name */, LABEL_MASKS("%K", "%B"));
462 * Video sort title is set to the channel number.
464 items.AddSortMethod(SortBySortTitle, 556 /* Title */, LABEL_MASKS("%K", "%B"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
469 bool CMythDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
471 m_session = CMythSession::AquireSession(strPath);
475 m_dll = m_session->GetLibrary();
479 CStdString base(strPath);
480 URIUtils::RemoveSlashAtEnd(base);
483 CStdString fileName = url.GetFileName();
484 URIUtils::RemoveSlashAtEnd(fileName);
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.
494 cmyth_conn_t control = m_session->GetControl();
500 item.reset(new CFileItem(base + "/recordings/", true));
501 item->SetLabel(g_localizeStrings.Get(22015)); // All recordings
504 item.reset(new CFileItem(base + "/tvshows/", true));
505 item->SetLabel(g_localizeStrings.Get(20343)); // TV shows
508 item.reset(new CFileItem(base + "/movies/", true));
509 item->SetLabel(g_localizeStrings.Get(20342)); // Movies
512 item.reset(new CFileItem(base + "/channels/", true));
513 item->SetLabel(g_localizeStrings.Get(22018)); // Live channels
516 item.reset(new CFileItem(base + "/guide/", true));
517 item->SetLabel(g_localizeStrings.Get(22020)); // Guide
520 items.AddSortMethod(SortByNone, 564 /* Type */, LABEL_MASKS("", "", "%L", "")); // No sorting, as added to list.
523 * Clear the directory cache so the cached sub-folders are guaranteed to be accurate.
525 g_directoryCache.ClearSubPaths(base);
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));
546 bool CMythDirectory::Exists(const char* strPath)
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.
554 CStdString fileName = url.GetFileName();
555 URIUtils::RemoveSlashAtEnd(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/")
570 bool CMythDirectory::IsVisible(const cmyth_proginfo_t program)
572 CStdString group = GetValue(m_dll->proginfo_recgroup(program));
573 unsigned long flags = m_dll->proginfo_flags(program);
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.
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.
586 return !(group.Equals("LiveTV") || group.Equals("Deleted") || flags & 0x00000080);
589 bool CMythDirectory::IsMovie(const cmyth_proginfo_t program)
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
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.
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.
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
612 return GetValue(m_dll->proginfo_programid(program)).Left(2) == "MV";
615 bool CMythDirectory::IsTvShow(const cmyth_proginfo_t program)
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.
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
625 return !IsMovie(program);
628 bool CMythDirectory::SupportsWriteFileOperations(const CStdString& strPath)
631 CStdString filename = url.GetFileName();
632 URIUtils::RemoveSlashAtEnd(filename);
634 * TV Shows directory has sub-folders so extra check is included so only files get the file
637 return filename.Left(11) == "recordings/" ||
638 filename.Left(7) == "movies/" ||
639 (filename.Left(8) == "tvshows/" && URIUtils::HasExtension(filename));
642 bool CMythDirectory::IsLiveTV(const CStdString& strPath)
645 return url.GetFileName().Left(9) == "channels/";