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 || StringUtils::StartsWith(fileName
, "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());
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
;
137 item
->m_strTitle
+= " - " + name
; // e.g. 3 - TV3
139 CStdString icon
= GetValue(m_dll
->channel_icon(channel
));
142 url
.SetFileName("files/channels/" + URIUtils::GetFileName(icon
)); // e.g. files/channels/tv3.jpg
143 item
->SetArt("thumb", url
.Get());
148 m_dll
->ref_release(channel
);
152 items
.AddSortMethod(SortByLabel
, 551 /* Name */, LABEL_MASKS("", "", "%K", ""));
154 m_dll
->ref_release(list
);
158 bool CMythDirectory::GetGuideForChannel(const CStdString
& base
, CFileItemList
&items
, int channelNumber
)
160 cmyth_database_t database
= m_session
->GetDatabase();
163 CLog::Log(LOGERROR
, "%s - Could not get database", __FUNCTION__
);
169 time_t end
= now
+ (24 * 60 * 60); // How many seconds of EPG from now we should grab, 24 hours in seconds
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
);
178 for (int i
= 0; i
< count
; i
++)
180 if (program
[i
].channum
== channelNumber
)
182 CFileItemPtr
item(new CFileItem("", false)); // No path for guide entries
185 * Set the FileItem meta data.
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
.empty())
196 item
->m_strTitle
+= " - \"" + subtitle
+ "\""; // e.g. 20:30 - Mythbusters - "The Pirate Special"
197 item
->m_dateTime
= localstart
;
200 * Set the VideoInfoTag meta data so it matches the FileItem meta data where possible.
202 CVideoInfoTag
* tag
= item
->GetVideoInfoTag();
203 tag
->m_strTitle
= title
;
204 if (!subtitle
.empty())
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
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.
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.
231 items
.AddSortMethod(SortByNone
, 552 /* Date */, LABEL_MASKS("%K", "%J")); // Still leave the date label
233 m_dll
->ref_release(program
);
237 bool CMythDirectory::GetRecordings(const CStdString
& base
, CFileItemList
&items
, enum FilterType type
,
238 const CStdString
& filter
)
240 cmyth_proglist_t list
= m_session
->GetAllRecordedPrograms();
243 CLog::Log(LOGERROR
, "%s - unable to get list of recordings", __FUNCTION__
);
247 int count
= m_dll
->proglist_get_count(list
);
248 for (int i
= 0; i
< count
; i
++)
250 cmyth_proginfo_t program
= m_dll
->proglist_get_item(list
, i
);
253 if (!IsVisible(program
))
255 m_dll
->ref_release(program
);
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).
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.
267 url
.SetHostName(GetValue(m_dll
->proginfo_host(program
)));
269 CStdString path
= URIUtils::GetFileName(GetValue(m_dll
->proginfo_pathname(program
)));
270 CStdString name
= GetValue(m_dll
->proginfo_title(program
));
275 if (!IsMovie(program
))
277 m_dll
->ref_release(program
);
280 url
.SetFileName("movies/" + path
);
283 if (!StringUtils::EqualsNoCase(filter
, name
))
285 m_dll
->ref_release(program
);
288 url
.SetFileName("tvshows/" + name
+ "/" + path
);
291 url
.SetFileName("recordings/" + path
);
295 CFileItemPtr
item(new CFileItem(url
.Get(), false));
296 m_session
->SetFileItemMetaData(*item
, program
);
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.
306 * Adding the production year, if available, to the label for Movies to aid in scraper
309 CStdString
label(item
->m_strTitle
);
310 unsigned short year
= m_dll
->proginfo_year(program
);
312 label
+= StringUtils::Format(" (%d)", year
);
313 item
->SetLabel(label
);
314 item
->SetLabelPreformated(true);
318 m_dll
->ref_release(program
);
321 m_dll
->ref_release(list
);
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.
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"));
336 * \brief Gets a list of folders for recorded TV shows
338 bool CMythDirectory::GetTvShowFolders(const CStdString
& base
, CFileItemList
&items
)
340 cmyth_proglist_t list
= m_session
->GetAllRecordedPrograms();
343 CLog::Log(LOGERROR
, "%s - unable to get list of recordings", __FUNCTION__
);
347 int count
= m_dll
->proglist_get_count(list
);
348 for (int i
= 0; i
< count
; i
++)
350 cmyth_proginfo_t program
= m_dll
->proglist_get_item(list
, i
);
353 if (!IsVisible(program
))
355 m_dll
->ref_release(program
);
359 if (!IsTvShow(program
))
361 m_dll
->ref_release(program
);
365 CStdString title
= GetValue(m_dll
->proginfo_title(program
));
366 CStdString path
= base
+ "/" + title
+ "/";
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.
373 if (items
.Contains(path
))
375 CFileItemPtr item
= items
.Get(path
);
376 item
->m_dateTime
= GetValue(m_dll
->proginfo_rec_start(program
));
380 CFileItemPtr
item(new CFileItem(path
, true));
381 item
->m_dateTime
= GetValue(m_dll
->proginfo_rec_start(program
));
382 item
->SetLabel(title
);
385 m_dll
->ref_release(program
);
389 m_dll
->ref_release(list
);
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"));
397 bool CMythDirectory::GetChannels(const CStdString
& base
, CFileItemList
&items
)
399 cmyth_conn_t control
= m_session
->GetControl();
403 vector
<cmyth_proginfo_t
> channels
;
404 for (unsigned i
= 0; i
< 16; i
++)
406 cmyth_recorder_t recorder
= m_dll
->conn_get_recorder_from_num(control
, i
);
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
);
415 m_dll
->ref_release(m_recorder
);
419 long startchan
= m_dll
->proginfo_chan_id(program
);
421 while (startchan
!= currchan
)
424 for (j
= 0; j
< channels
.size(); j
++)
426 if (m_dll
->proginfo_compare(program
, channels
[j
]) == 0)
430 if (j
== channels
.size())
431 channels
.push_back(program
);
433 program
= m_dll
->recorder_get_next_proginfo(recorder
, program
, BROWSE_DIRECTION_UP
);
437 currchan
= m_dll
->proginfo_chan_id(program
);
439 m_dll
->ref_release(recorder
);
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.
448 for (unsigned i
= 0; i
< channels
.size(); i
++)
450 cmyth_proginfo_t program
= channels
[i
];
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
);
457 m_dll
->ref_release(program
);
460 items
.AddSortMethod(SortByLabel
, 551 /* Name */, LABEL_MASKS("%K", "%B"));
463 * Video sort title is set to the channel number.
465 items
.AddSortMethod(SortBySortTitle
, 556 /* Title */, LABEL_MASKS("%K", "%B"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle
: SortAttributeNone
);
470 bool CMythDirectory::GetDirectory(const CStdString
& strPath
, CFileItemList
&items
)
472 m_session
= CMythSession::AquireSession(strPath
);
476 m_dll
= m_session
->GetLibrary();
480 CStdString
base(strPath
);
481 URIUtils::RemoveSlashAtEnd(base
);
484 CStdString fileName
= url
.GetFileName();
485 URIUtils::RemoveSlashAtEnd(fileName
);
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.
495 cmyth_conn_t control
= m_session
->GetControl();
501 item
.reset(new CFileItem(base
+ "/recordings/", true));
502 item
->SetLabel(g_localizeStrings
.Get(22015)); // All recordings
505 item
.reset(new CFileItem(base
+ "/tvshows/", true));
506 item
->SetLabel(g_localizeStrings
.Get(20343)); // TV shows
509 item
.reset(new CFileItem(base
+ "/movies/", true));
510 item
->SetLabel(g_localizeStrings
.Get(20342)); // Movies
513 item
.reset(new CFileItem(base
+ "/channels/", true));
514 item
->SetLabel(g_localizeStrings
.Get(22018)); // Live channels
517 item
.reset(new CFileItem(base
+ "/guide/", true));
518 item
->SetLabel(g_localizeStrings
.Get(22020)); // Guide
521 items
.AddSortMethod(SortByNone
, 564 /* Type */, LABEL_MASKS("", "", "%L", "")); // No sorting, as added to list.
524 * Clear the directory cache so the cached sub-folders are guaranteed to be accurate.
526 g_directoryCache
.ClearSubPaths(base
);
530 else if (fileName
== "channels")
531 return GetChannels(base
, items
);
532 else if (fileName
== "guide")
533 return GetGuide(base
, items
);
534 else if (StringUtils::StartsWith(fileName
, "guide/"))
535 return GetGuideForChannel(base
, items
, atoi(fileName
.substr(6).c_str()));
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 (StringUtils::StartsWith(fileName
, "tvshows/"))
543 return GetRecordings(base
, items
, TV_SHOWS
, fileName
.substr(8).c_str());
547 bool CMythDirectory::Exists(const char* strPath
)
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.
555 CStdString fileName
= url
.GetFileName();
556 URIUtils::RemoveSlashAtEnd(fileName
);
559 || fileName
== "channels"
560 || fileName
== "guide"
561 || StringUtils::StartsWith(fileName
, "guide/")
562 || fileName
== "movies"
563 || fileName
== "recordings"
564 || fileName
== "tvshows"
565 || StringUtils::StartsWith(fileName
, "tvshows/"))
571 bool CMythDirectory::IsVisible(const cmyth_proginfo_t program
)
573 CStdString group
= GetValue(m_dll
->proginfo_recgroup(program
));
574 unsigned long flags
= m_dll
->proginfo_flags(program
);
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.
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.
587 return !(group
.Equals("LiveTV") || group
.Equals("Deleted") || flags
& 0x00000080);
590 bool CMythDirectory::IsMovie(const cmyth_proginfo_t program
)
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
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.
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.
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 StringUtils::StartsWith(GetValue(m_dll
->proginfo_programid(program
)), "MV")
611 || m_dll
->proginfo_length_sec(program
) > iMovieLength
* 60; // Minutes to seconds
613 return StringUtils::StartsWith(GetValue(m_dll
->proginfo_programid(program
)), "MV");
616 bool CMythDirectory::IsTvShow(const cmyth_proginfo_t program
)
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.
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
626 return !IsMovie(program
);
629 bool CMythDirectory::SupportsWriteFileOperations(const CStdString
& strPath
)
632 CStdString filename
= url
.GetFileName();
633 URIUtils::RemoveSlashAtEnd(filename
);
635 * TV Shows directory has sub-folders so extra check is included so only files get the file
638 return StringUtils::StartsWith(filename
, "recordings/") ||
639 StringUtils::StartsWith(filename
, "movies/") ||
640 (StringUtils::StartsWith(filename
, "tvshows/") && URIUtils::HasExtension(filename
));
643 bool CMythDirectory::IsLiveTV(const CStdString
& strPath
)
646 return StringUtils::StartsWith(url
.GetFileName(), "channels/");