diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/FileItem.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/FileItem.cpp')
-rw-r--r-- | xbmc/FileItem.cpp | 4036 |
1 files changed, 4036 insertions, 0 deletions
diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp new file mode 100644 index 0000000..2eb5eb0 --- /dev/null +++ b/xbmc/FileItem.cpp @@ -0,0 +1,4036 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileItem.h" + +#include "CueDocument.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "events/IEvent.h" +#include "filesystem/CurlFile.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/MusicDatabaseDirectory.h" +#include "filesystem/StackDirectory.h" +#include "filesystem/VideoDatabaseDirectory.h" +#include "filesystem/VideoDatabaseDirectory/QueryParams.h" +#include "games/GameUtils.h" +#include "games/tags/GameInfoTag.h" +#include "guilib/LocalizeStrings.h" +#include "media/MediaLockState.h" +#include "music/Album.h" +#include "music/Artist.h" +#include "music/MusicDatabase.h" +#include "music/tags/MusicInfoTag.h" +#include "music/tags/MusicInfoTagLoaderFactory.h" +#include "pictures/PictureInfoTag.h" +#include "playlists/PlayListFactory.h" +#include "pvr/PVRManager.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/epg/EpgSearchFilter.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/Archive.h" +#include "utils/Crc32.h" +#include "utils/FileExtensionProvider.h" +#include "utils/Mime.h" +#include "utils/Random.h" +#include "utils/RegExp.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/Bookmark.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" + +#include <algorithm> +#include <cstdlib> +#include <mutex> + +using namespace KODI; +using namespace XFILE; +using namespace PLAYLIST; +using namespace MUSIC_INFO; +using namespace PVR; +using namespace GAME; + +CFileItem::CFileItem(const CSong& song) +{ + Initialize(); + SetFromSong(song); +} + +CFileItem::CFileItem(const CSong& song, const CMusicInfoTag& music) +{ + Initialize(); + SetFromSong(song); + *GetMusicInfoTag() = music; +} + +CFileItem::CFileItem(const CURL &url, const CAlbum& album) +{ + Initialize(); + + m_strPath = url.Get(); + URIUtils::AddSlashAtEnd(m_strPath); + SetFromAlbum(album); +} + +CFileItem::CFileItem(const std::string &path, const CAlbum& album) +{ + Initialize(); + + m_strPath = path; + URIUtils::AddSlashAtEnd(m_strPath); + SetFromAlbum(album); +} + +CFileItem::CFileItem(const CMusicInfoTag& music) +{ + Initialize(); + SetLabel(music.GetTitle()); + m_strPath = music.GetURL(); + m_bIsFolder = URIUtils::HasSlashAtEnd(m_strPath); + *GetMusicInfoTag() = music; + FillInDefaultIcon(); + FillInMimeType(false); +} + +CFileItem::CFileItem(const CVideoInfoTag& movie) +{ + Initialize(); + SetFromVideoInfoTag(movie); +} + +namespace +{ + std::string GetEpgTagTitle(const std::shared_ptr<CPVREpgInfoTag>& epgTag) + { + if (CServiceBroker::GetPVRManager().IsParentalLocked(epgTag)) + return g_localizeStrings.Get(19266); // Parental locked + else if (epgTag->Title().empty() && + !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_EPG_HIDENOINFOAVAILABLE)) + return g_localizeStrings.Get(19055); // no information available + else + return epgTag->Title(); + } +} // unnamed namespace + +void CFileItem::FillMusicInfoTag(const std::shared_ptr<CPVREpgInfoTag>& tag) +{ + CMusicInfoTag* musictag = GetMusicInfoTag(); // create (!) the music tag. + + if (tag) + { + musictag->SetTitle(GetEpgTagTitle(tag)); + musictag->SetGenre(tag->Genre()); + musictag->SetDuration(tag->GetDuration()); + musictag->SetURL(tag->Path()); + } + else if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_EPG_HIDENOINFOAVAILABLE)) + { + musictag->SetTitle(g_localizeStrings.Get(19055)); // no information available + } + + musictag->SetLoaded(true); +} + +CFileItem::CFileItem(const std::shared_ptr<CPVREpgInfoTag>& tag) +{ + Initialize(); + + m_bIsFolder = false; + m_epgInfoTag = tag; + m_strPath = tag->Path(); + m_bCanQueue = false; + SetLabel(GetEpgTagTitle(tag)); + m_dateTime = tag->StartAsLocalTime(); + + if (!tag->IconPath().empty()) + { + SetArt("icon", tag->IconPath()); + } + else + { + const std::string iconPath = tag->ChannelIconPath(); + if (!iconPath.empty()) + SetArt("icon", iconPath); + else if (tag->IsRadio()) + SetArt("icon", "DefaultMusicSongs.png"); + else + SetArt("icon", "DefaultTVShows.png"); + } + + // Speedup FillInDefaultIcon() + SetProperty("icon_never_overlay", true); + + if (tag->IsRadio() && !HasMusicInfoTag()) + FillMusicInfoTag(tag); + + FillInMimeType(false); +} + +CFileItem::CFileItem(const std::shared_ptr<PVR::CPVREpgSearchFilter>& filter) +{ + Initialize(); + + m_bIsFolder = true; + m_epgSearchFilter = filter; + m_strPath = filter->GetPath(); + m_bCanQueue = false; + SetLabel(filter->GetTitle()); + + const CDateTime lastExec = filter->GetLastExecutedDateTime(); + if (lastExec.IsValid()) + m_dateTime.SetFromUTCDateTime(lastExec); + + SetArt("icon", "DefaultPVRSearch.png"); + + // Speedup FillInDefaultIcon() + SetProperty("icon_never_overlay", true); + + FillInMimeType(false); +} + +CFileItem::CFileItem(const std::shared_ptr<CPVRChannelGroupMember>& channelGroupMember) +{ + Initialize(); + + const std::shared_ptr<CPVRChannel> channel = channelGroupMember->Channel(); + + m_pvrChannelGroupMemberInfoTag = channelGroupMember; + + m_strPath = channelGroupMember->Path(); + m_bIsFolder = false; + m_bCanQueue = false; + SetLabel(channel->ChannelName()); + + if (!channel->IconPath().empty()) + SetArt("icon", channel->IconPath()); + else if (channel->IsRadio()) + SetArt("icon", "DefaultMusicSongs.png"); + else + SetArt("icon", "DefaultTVShows.png"); + + SetProperty("channelid", channel->ChannelID()); + SetProperty("path", channelGroupMember->Path()); + SetArt("thumb", channel->IconPath()); + + // Speedup FillInDefaultIcon() + SetProperty("icon_never_overlay", true); + + if (channel->IsRadio() && !HasMusicInfoTag()) + { + const std::shared_ptr<CPVREpgInfoTag> epgNow = channel->GetEPGNow(); + FillMusicInfoTag(epgNow); + } + FillInMimeType(false); +} + +CFileItem::CFileItem(const std::shared_ptr<CPVRRecording>& record) +{ + Initialize(); + + m_bIsFolder = false; + m_pvrRecordingInfoTag = record; + m_strPath = record->m_strFileNameAndPath; + SetLabel(record->m_strTitle); + m_dateTime = record->RecordingTimeAsLocalTime(); + m_dwSize = record->GetSizeInBytes(); + m_bCanQueue = true; + + // Set art + if (!record->IconPath().empty()) + SetArt("icon", record->IconPath()); + else + { + const std::shared_ptr<CPVRChannel> channel = record->Channel(); + if (channel && !channel->IconPath().empty()) + SetArt("icon", channel->IconPath()); + else if (record->IsRadio()) + SetArt("icon", "DefaultMusicSongs.png"); + else + SetArt("icon", "DefaultTVShows.png"); + } + + if (!record->ThumbnailPath().empty()) + SetArt("thumb", record->ThumbnailPath()); + + if (!record->FanartPath().empty()) + SetArt("fanart", record->FanartPath()); + + // Speedup FillInDefaultIcon() + SetProperty("icon_never_overlay", true); + + FillInMimeType(false); +} + +CFileItem::CFileItem(const std::shared_ptr<CPVRTimerInfoTag>& timer) +{ + Initialize(); + + m_bIsFolder = timer->IsTimerRule(); + m_pvrTimerInfoTag = timer; + m_strPath = timer->Path(); + SetLabel(timer->Title()); + m_dateTime = timer->StartAsLocalTime(); + m_bCanQueue = false; + + if (!timer->ChannelIcon().empty()) + SetArt("icon", timer->ChannelIcon()); + else if (timer->IsRadio()) + SetArt("icon", "DefaultMusicSongs.png"); + else + SetArt("icon", "DefaultTVShows.png"); + + // Speedup FillInDefaultIcon() + SetProperty("icon_never_overlay", true); + + FillInMimeType(false); +} + +CFileItem::CFileItem(const CArtist& artist) +{ + Initialize(); + SetLabel(artist.strArtist); + m_strPath = artist.strArtist; + m_bIsFolder = true; + URIUtils::AddSlashAtEnd(m_strPath); + GetMusicInfoTag()->SetArtist(artist); + FillInMimeType(false); +} + +CFileItem::CFileItem(const CGenre& genre) +{ + Initialize(); + SetLabel(genre.strGenre); + m_strPath = genre.strGenre; + m_bIsFolder = true; + URIUtils::AddSlashAtEnd(m_strPath); + GetMusicInfoTag()->SetGenre(genre.strGenre); + FillInMimeType(false); +} + +CFileItem::CFileItem(const CFileItem& item) + : CGUIListItem(item), + m_musicInfoTag(NULL), + m_videoInfoTag(NULL), + m_pictureInfoTag(NULL), + m_gameInfoTag(NULL) +{ + *this = item; +} + +CFileItem::CFileItem(const CGUIListItem& item) +{ + Initialize(); + // not particularly pretty, but it gets around the issue of Initialize() defaulting + // parameters in the CGUIListItem base class. + *static_cast<CGUIListItem*>(this) = item; + + FillInMimeType(false); +} + +CFileItem::CFileItem(void) +{ + Initialize(); +} + +CFileItem::CFileItem(const std::string& strLabel) +{ + Initialize(); + SetLabel(strLabel); +} + +CFileItem::CFileItem(const char* strLabel) +{ + Initialize(); + SetLabel(std::string(strLabel)); +} + +CFileItem::CFileItem(const CURL& path, bool bIsFolder) +{ + Initialize(); + m_strPath = path.Get(); + m_bIsFolder = bIsFolder; + if (m_bIsFolder && !m_strPath.empty() && !IsFileFolder()) + URIUtils::AddSlashAtEnd(m_strPath); + FillInMimeType(false); +} + +CFileItem::CFileItem(const std::string& strPath, bool bIsFolder) +{ + Initialize(); + m_strPath = strPath; + m_bIsFolder = bIsFolder; + if (m_bIsFolder && !m_strPath.empty() && !IsFileFolder()) + URIUtils::AddSlashAtEnd(m_strPath); + FillInMimeType(false); +} + +CFileItem::CFileItem(const CMediaSource& share) +{ + Initialize(); + m_bIsFolder = true; + m_bIsShareOrDrive = true; + m_strPath = share.strPath; + if (!IsRSS()) // no slash at end for rss feeds + URIUtils::AddSlashAtEnd(m_strPath); + std::string label = share.strName; + if (!share.strStatus.empty()) + label = StringUtils::Format("{} ({})", share.strName, share.strStatus); + SetLabel(label); + m_iLockMode = share.m_iLockMode; + m_strLockCode = share.m_strLockCode; + m_iHasLock = share.m_iHasLock; + m_iBadPwdCount = share.m_iBadPwdCount; + m_iDriveType = share.m_iDriveType; + SetArt("thumb", share.m_strThumbnailImage); + SetLabelPreformatted(true); + if (IsDVD()) + GetVideoInfoTag()->m_strFileNameAndPath = share.strDiskUniqueId; // share.strDiskUniqueId contains disc unique id + FillInMimeType(false); +} + +CFileItem::CFileItem(std::shared_ptr<const ADDON::IAddon> addonInfo) : m_addonInfo(std::move(addonInfo)) +{ + Initialize(); +} + +CFileItem::CFileItem(const EventPtr& eventLogEntry) +{ + Initialize(); + + m_eventLogEntry = eventLogEntry; + SetLabel(eventLogEntry->GetLabel()); + m_dateTime = eventLogEntry->GetDateTime(); + if (!eventLogEntry->GetIcon().empty()) + SetArt("icon", eventLogEntry->GetIcon()); +} + +CFileItem::~CFileItem(void) +{ + delete m_musicInfoTag; + delete m_videoInfoTag; + delete m_pictureInfoTag; + delete m_gameInfoTag; + + m_musicInfoTag = NULL; + m_videoInfoTag = NULL; + m_pictureInfoTag = NULL; + m_gameInfoTag = NULL; +} + +CFileItem& CFileItem::operator=(const CFileItem& item) +{ + if (this == &item) + return *this; + + CGUIListItem::operator=(item); + m_bLabelPreformatted=item.m_bLabelPreformatted; + FreeMemory(); + m_strPath = item.m_strPath; + m_strDynPath = item.m_strDynPath; + m_bIsParentFolder = item.m_bIsParentFolder; + m_iDriveType = item.m_iDriveType; + m_bIsShareOrDrive = item.m_bIsShareOrDrive; + m_dateTime = item.m_dateTime; + m_dwSize = item.m_dwSize; + + if (item.m_musicInfoTag) + { + if (m_musicInfoTag) + *m_musicInfoTag = *item.m_musicInfoTag; + else + m_musicInfoTag = new MUSIC_INFO::CMusicInfoTag(*item.m_musicInfoTag); + } + else + { + delete m_musicInfoTag; + m_musicInfoTag = NULL; + } + + if (item.m_videoInfoTag) + { + if (m_videoInfoTag) + *m_videoInfoTag = *item.m_videoInfoTag; + else + m_videoInfoTag = new CVideoInfoTag(*item.m_videoInfoTag); + } + else + { + delete m_videoInfoTag; + m_videoInfoTag = NULL; + } + + if (item.m_pictureInfoTag) + { + if (m_pictureInfoTag) + *m_pictureInfoTag = *item.m_pictureInfoTag; + else + m_pictureInfoTag = new CPictureInfoTag(*item.m_pictureInfoTag); + } + else + { + delete m_pictureInfoTag; + m_pictureInfoTag = NULL; + } + + if (item.m_gameInfoTag) + { + if (m_gameInfoTag) + *m_gameInfoTag = *item.m_gameInfoTag; + else + m_gameInfoTag = new CGameInfoTag(*item.m_gameInfoTag); + } + else + { + delete m_gameInfoTag; + m_gameInfoTag = NULL; + } + + m_epgInfoTag = item.m_epgInfoTag; + m_epgSearchFilter = item.m_epgSearchFilter; + m_pvrChannelGroupMemberInfoTag = item.m_pvrChannelGroupMemberInfoTag; + m_pvrRecordingInfoTag = item.m_pvrRecordingInfoTag; + m_pvrTimerInfoTag = item.m_pvrTimerInfoTag; + m_addonInfo = item.m_addonInfo; + m_eventLogEntry = item.m_eventLogEntry; + + m_lStartOffset = item.m_lStartOffset; + m_lStartPartNumber = item.m_lStartPartNumber; + m_lEndOffset = item.m_lEndOffset; + m_strDVDLabel = item.m_strDVDLabel; + m_strTitle = item.m_strTitle; + m_iprogramCount = item.m_iprogramCount; + m_idepth = item.m_idepth; + m_iLockMode = item.m_iLockMode; + m_strLockCode = item.m_strLockCode; + m_iHasLock = item.m_iHasLock; + m_iBadPwdCount = item.m_iBadPwdCount; + m_bCanQueue=item.m_bCanQueue; + m_mimetype = item.m_mimetype; + m_extrainfo = item.m_extrainfo; + m_specialSort = item.m_specialSort; + m_bIsAlbum = item.m_bIsAlbum; + m_doContentLookup = item.m_doContentLookup; + return *this; +} + +void CFileItem::Initialize() +{ + m_musicInfoTag = NULL; + m_videoInfoTag = NULL; + m_pictureInfoTag = NULL; + m_gameInfoTag = NULL; + m_bLabelPreformatted = false; + m_bIsAlbum = false; + m_dwSize = 0; + m_bIsParentFolder = false; + m_bIsShareOrDrive = false; + m_iDriveType = CMediaSource::SOURCE_TYPE_UNKNOWN; + m_lStartOffset = 0; + m_lStartPartNumber = 1; + m_lEndOffset = 0; + m_iprogramCount = 0; + m_idepth = 1; + m_iLockMode = LOCK_MODE_EVERYONE; + m_iBadPwdCount = 0; + m_iHasLock = LOCK_STATE_NO_LOCK; + m_bCanQueue = true; + m_specialSort = SortSpecialNone; + m_doContentLookup = true; +} + +void CFileItem::Reset() +{ + // CGUIListItem members... + m_strLabel2.clear(); + SetLabel(""); + FreeIcons(); + m_overlayIcon = ICON_OVERLAY_NONE; + m_bSelected = false; + m_bIsFolder = false; + + m_strDVDLabel.clear(); + m_strTitle.clear(); + m_strPath.clear(); + m_strDynPath.clear(); + m_dateTime.Reset(); + m_strLockCode.clear(); + m_mimetype.clear(); + delete m_musicInfoTag; + m_musicInfoTag=NULL; + delete m_videoInfoTag; + m_videoInfoTag=NULL; + m_epgInfoTag.reset(); + m_epgSearchFilter.reset(); + m_pvrChannelGroupMemberInfoTag.reset(); + m_pvrRecordingInfoTag.reset(); + m_pvrTimerInfoTag.reset(); + delete m_pictureInfoTag; + m_pictureInfoTag=NULL; + delete m_gameInfoTag; + m_gameInfoTag = NULL; + m_extrainfo.clear(); + ClearProperties(); + m_eventLogEntry.reset(); + + Initialize(); + SetInvalid(); +} + +// do not archive dynamic path +void CFileItem::Archive(CArchive& ar) +{ + CGUIListItem::Archive(ar); + + if (ar.IsStoring()) + { + ar << m_bIsParentFolder; + ar << m_bLabelPreformatted; + ar << m_strPath; + ar << m_bIsShareOrDrive; + ar << m_iDriveType; + ar << m_dateTime; + ar << m_dwSize; + ar << m_strDVDLabel; + ar << m_strTitle; + ar << m_iprogramCount; + ar << m_idepth; + ar << m_lStartOffset; + ar << m_lStartPartNumber; + ar << m_lEndOffset; + ar << m_iLockMode; + ar << m_strLockCode; + ar << m_iBadPwdCount; + + ar << m_bCanQueue; + ar << m_mimetype; + ar << m_extrainfo; + ar << m_specialSort; + ar << m_doContentLookup; + + if (m_musicInfoTag) + { + ar << 1; + ar << *m_musicInfoTag; + } + else + ar << 0; + if (m_videoInfoTag) + { + ar << 1; + ar << *m_videoInfoTag; + } + else + ar << 0; + if (m_pictureInfoTag) + { + ar << 1; + ar << *m_pictureInfoTag; + } + else + ar << 0; + if (m_gameInfoTag) + { + ar << 1; + ar << *m_gameInfoTag; + } + else + ar << 0; + } + else + { + ar >> m_bIsParentFolder; + ar >> m_bLabelPreformatted; + ar >> m_strPath; + ar >> m_bIsShareOrDrive; + ar >> m_iDriveType; + ar >> m_dateTime; + ar >> m_dwSize; + ar >> m_strDVDLabel; + ar >> m_strTitle; + ar >> m_iprogramCount; + ar >> m_idepth; + ar >> m_lStartOffset; + ar >> m_lStartPartNumber; + ar >> m_lEndOffset; + int temp; + ar >> temp; + m_iLockMode = (LockType)temp; + ar >> m_strLockCode; + ar >> m_iBadPwdCount; + + ar >> m_bCanQueue; + ar >> m_mimetype; + ar >> m_extrainfo; + ar >> temp; + m_specialSort = (SortSpecial)temp; + ar >> m_doContentLookup; + + int iType; + ar >> iType; + if (iType == 1) + ar >> *GetMusicInfoTag(); + ar >> iType; + if (iType == 1) + ar >> *GetVideoInfoTag(); + ar >> iType; + if (iType == 1) + ar >> *GetPictureInfoTag(); + ar >> iType; + if (iType == 1) + ar >> *GetGameInfoTag(); + + SetInvalid(); + } +} + +void CFileItem::Serialize(CVariant& value) const +{ + //CGUIListItem::Serialize(value["CGUIListItem"]); + + value["strPath"] = m_strPath; + value["dateTime"] = (m_dateTime.IsValid()) ? m_dateTime.GetAsRFC1123DateTime() : ""; + value["lastmodified"] = m_dateTime.IsValid() ? m_dateTime.GetAsDBDateTime() : ""; + value["size"] = m_dwSize; + value["DVDLabel"] = m_strDVDLabel; + value["title"] = m_strTitle; + value["mimetype"] = m_mimetype; + value["extrainfo"] = m_extrainfo; + + if (m_musicInfoTag) + (*m_musicInfoTag).Serialize(value["musicInfoTag"]); + + if (m_videoInfoTag) + (*m_videoInfoTag).Serialize(value["videoInfoTag"]); + + if (m_pictureInfoTag) + (*m_pictureInfoTag).Serialize(value["pictureInfoTag"]); + + if (m_gameInfoTag) + (*m_gameInfoTag).Serialize(value["gameInfoTag"]); + + if (!m_mapProperties.empty()) + { + auto& customProperties = value["customproperties"]; + for (const auto& prop : m_mapProperties) + customProperties[prop.first] = prop.second; + } +} + +void CFileItem::ToSortable(SortItem &sortable, Field field) const +{ + switch (field) + { + case FieldPath: + sortable[FieldPath] = m_strPath; + break; + case FieldDate: + sortable[FieldDate] = (m_dateTime.IsValid()) ? m_dateTime.GetAsDBDateTime() : ""; + break; + case FieldSize: + sortable[FieldSize] = m_dwSize; + break; + case FieldDriveType: + sortable[FieldDriveType] = m_iDriveType; + break; + case FieldStartOffset: + sortable[FieldStartOffset] = m_lStartOffset; + break; + case FieldEndOffset: + sortable[FieldEndOffset] = m_lEndOffset; + break; + case FieldProgramCount: + sortable[FieldProgramCount] = m_iprogramCount; + break; + case FieldBitrate: + sortable[FieldBitrate] = m_dwSize; + break; + case FieldTitle: + sortable[FieldTitle] = m_strTitle; + break; + + // If there's ever a need to convert more properties from CGUIListItem it might be + // worth to make CGUIListItem implement ISortable as well and call it from here + + default: + break; + } + + if (HasMusicInfoTag()) + GetMusicInfoTag()->ToSortable(sortable, field); + + if (HasVideoInfoTag()) + GetVideoInfoTag()->ToSortable(sortable, field); + + if (HasPictureInfoTag()) + GetPictureInfoTag()->ToSortable(sortable, field); + + if (HasPVRChannelInfoTag()) + GetPVRChannelInfoTag()->ToSortable(sortable, field); + + if (HasPVRChannelGroupMemberInfoTag()) + GetPVRChannelGroupMemberInfoTag()->ToSortable(sortable, field); + + if (HasAddonInfo()) + { + switch (field) + { + case FieldInstallDate: + sortable[FieldInstallDate] = GetAddonInfo()->InstallDate().GetAsDBDateTime(); + break; + case FieldLastUpdated: + sortable[FieldLastUpdated] = GetAddonInfo()->LastUpdated().GetAsDBDateTime(); + break; + case FieldLastUsed: + sortable[FieldLastUsed] = GetAddonInfo()->LastUsed().GetAsDBDateTime(); + break; + default: + break; + } + } + + if (HasGameInfoTag()) + GetGameInfoTag()->ToSortable(sortable, field); + + if (m_eventLogEntry) + m_eventLogEntry->ToSortable(sortable, field); + + if (IsFavourite()) + { + if (field == FieldUserPreference) + sortable[FieldUserPreference] = GetProperty("favourite.index").asString(); + } +} + +void CFileItem::ToSortable(SortItem &sortable, const Fields &fields) const +{ + Fields::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) + ToSortable(sortable, *it); + + /* FieldLabel is used as a fallback by all sorters and therefore has to be present as well */ + sortable[FieldLabel] = GetLabel(); + /* FieldSortSpecial and FieldFolder are required in conjunction with all other sorters as well */ + sortable[FieldSortSpecial] = m_specialSort; + sortable[FieldFolder] = m_bIsFolder; +} + +bool CFileItem::Exists(bool bUseCache /* = true */) const +{ + if (m_strPath.empty() + || IsPath("add") + || IsInternetStream() + || IsParentFolder() + || IsVirtualDirectoryRoot() + || IsPlugin() + || IsPVR()) + return true; + + if (IsVideoDb() && HasVideoInfoTag()) + { + CFileItem dbItem(m_bIsFolder ? GetVideoInfoTag()->m_strPath : GetVideoInfoTag()->m_strFileNameAndPath, m_bIsFolder); + return dbItem.Exists(); + } + + std::string strPath = m_strPath; + + if (URIUtils::IsMultiPath(strPath)) + strPath = CMultiPathDirectory::GetFirstPath(strPath); + + if (URIUtils::IsStack(strPath)) + strPath = CStackDirectory::GetFirstStackedFile(strPath); + + if (m_bIsFolder) + return CDirectory::Exists(strPath, bUseCache); + else + return CFile::Exists(strPath, bUseCache); + + return false; +} + +bool CFileItem::IsVideo() const +{ + /* check preset mime type */ + if(StringUtils::StartsWithNoCase(m_mimetype, "video/")) + return true; + + if (HasVideoInfoTag()) + return true; + + if (HasGameInfoTag()) + return false; + + if (HasMusicInfoTag()) + return false; + + if (HasPictureInfoTag()) + return false; + + // only tv recordings are videos... + if (IsPVRRecording()) + return !GetPVRRecordingInfoTag()->IsRadio(); + + // ... all other PVR items are not. + if (IsPVR()) + return false; + + if (URIUtils::IsDVD(m_strPath)) + return true; + + std::string extension; + if(StringUtils::StartsWithNoCase(m_mimetype, "application/")) + { /* check for some standard types */ + extension = m_mimetype.substr(12); + if( StringUtils::EqualsNoCase(extension, "ogg") + || StringUtils::EqualsNoCase(extension, "mp4") + || StringUtils::EqualsNoCase(extension, "mxf") ) + return true; + } + + //! @todo If the file is a zip file, ask the game clients if any support this + // file before assuming it is video. + + return URIUtils::HasExtension(m_strPath, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions()); +} + +bool CFileItem::IsEPG() const +{ + return HasEPGInfoTag(); +} + +bool CFileItem::IsPVRChannel() const +{ + return HasPVRChannelInfoTag(); +} + +bool CFileItem::IsPVRChannelGroup() const +{ + return URIUtils::IsPVRChannelGroup(m_strPath); +} + +bool CFileItem::IsPVRRecording() const +{ + return HasPVRRecordingInfoTag(); +} + +bool CFileItem::IsUsablePVRRecording() const +{ + return (m_pvrRecordingInfoTag && !m_pvrRecordingInfoTag->IsDeleted()); +} + +bool CFileItem::IsDeletedPVRRecording() const +{ + return (m_pvrRecordingInfoTag && m_pvrRecordingInfoTag->IsDeleted()); +} + +bool CFileItem::IsInProgressPVRRecording() const +{ + return (m_pvrRecordingInfoTag && m_pvrRecordingInfoTag->IsInProgress()); +} + +bool CFileItem::IsPVRTimer() const +{ + return HasPVRTimerInfoTag(); +} + +bool CFileItem::IsDiscStub() const +{ + if (IsVideoDb() && HasVideoInfoTag()) + { + CFileItem dbItem(m_bIsFolder ? GetVideoInfoTag()->m_strPath : GetVideoInfoTag()->m_strFileNameAndPath, m_bIsFolder); + return dbItem.IsDiscStub(); + } + + return URIUtils::HasExtension(m_strPath, CServiceBroker::GetFileExtensionProvider().GetDiscStubExtensions()); +} + +bool CFileItem::IsAudio() const +{ + /* check preset mime type */ + if(StringUtils::StartsWithNoCase(m_mimetype, "audio/")) + return true; + + if (HasMusicInfoTag()) + return true; + + if (HasVideoInfoTag()) + return false; + + if (HasPictureInfoTag()) + return false; + + if (HasGameInfoTag()) + return false; + + if (IsCDDA()) + return true; + + if(StringUtils::StartsWithNoCase(m_mimetype, "application/")) + { /* check for some standard types */ + std::string extension = m_mimetype.substr(12); + if( StringUtils::EqualsNoCase(extension, "ogg") + || StringUtils::EqualsNoCase(extension, "mp4") + || StringUtils::EqualsNoCase(extension, "mxf") ) + return true; + } + + //! @todo If the file is a zip file, ask the game clients if any support this + // file before assuming it is audio + + return URIUtils::HasExtension(m_strPath, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions()); +} + +bool CFileItem::IsDeleted() const +{ + if (HasPVRRecordingInfoTag()) + return GetPVRRecordingInfoTag()->IsDeleted(); + + return false; +} + +bool CFileItem::IsAudioBook() const +{ + return IsType(".m4b") || IsType(".mka"); +} + +bool CFileItem::IsGame() const +{ + if (HasGameInfoTag()) + return true; + + if (HasVideoInfoTag()) + return false; + + if (HasMusicInfoTag()) + return false; + + if (HasPictureInfoTag()) + return false; + + if (IsPVR()) + return false; + + if (HasAddonInfo()) + return CGameUtils::IsStandaloneGame(std::const_pointer_cast<ADDON::IAddon>(GetAddonInfo())); + + return CGameUtils::HasGameExtension(m_strPath); +} + +bool CFileItem::IsPicture() const +{ + if (StringUtils::StartsWithNoCase(m_mimetype, "image/")) + return true; + + if (HasPictureInfoTag()) + return true; + + if (HasGameInfoTag()) + return false; + + if (HasMusicInfoTag()) + return false; + + if (HasVideoInfoTag()) + return false; + + if (HasPVRTimerInfoTag() || HasPVRChannelInfoTag() || HasPVRChannelGroupMemberInfoTag() || + HasPVRRecordingInfoTag() || HasEPGInfoTag() || HasEPGSearchFilter()) + return false; + + if (!m_strPath.empty()) + return CUtil::IsPicture(m_strPath); + + return false; +} + +bool CFileItem::IsLyrics() const +{ + return URIUtils::HasExtension(m_strPath, ".cdg|.lrc"); +} + +bool CFileItem::IsSubtitle() const +{ + return URIUtils::HasExtension(m_strPath, CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions()); +} + +bool CFileItem::IsCUESheet() const +{ + return URIUtils::HasExtension(m_strPath, ".cue"); +} + +bool CFileItem::IsInternetStream(const bool bStrictCheck /* = false */) const +{ + if (HasProperty("IsHTTPDirectory")) + return bStrictCheck; + + if (!m_strDynPath.empty()) + return URIUtils::IsInternetStream(m_strDynPath, bStrictCheck); + + return URIUtils::IsInternetStream(m_strPath, bStrictCheck); +} + +bool CFileItem::IsStreamedFilesystem() const +{ + if (!m_strDynPath.empty()) + return URIUtils::IsStreamedFilesystem(m_strDynPath); + + return URIUtils::IsStreamedFilesystem(m_strPath); +} + +bool CFileItem::IsFileFolder(EFileFolderType types) const +{ + EFileFolderType always_type = EFILEFOLDER_TYPE_ALWAYS; + + /* internet streams are not directly expanded */ + if(IsInternetStream()) + always_type = EFILEFOLDER_TYPE_ONCLICK; + + if(types & always_type) + { + if(IsSmartPlayList() + || (IsPlayList() && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) + || IsAPK() + || IsZIP() + || IsRAR() + || IsRSS() + || IsAudioBook() + || IsType(".ogg|.oga|.xbt") +#if defined(TARGET_ANDROID) + || IsType(".apk") +#endif + ) + return true; + } + + if (CServiceBroker::IsAddonInterfaceUp() && + IsType(CServiceBroker::GetFileExtensionProvider().GetFileFolderExtensions().c_str()) && + CServiceBroker::GetFileExtensionProvider().CanOperateExtension(m_strPath)) + return true; + + if(types & EFILEFOLDER_TYPE_ONBROWSE) + { + if((IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) + || IsDiscImage()) + return true; + } + + return false; +} + +bool CFileItem::IsSmartPlayList() const +{ + if (HasProperty("library.smartplaylist") && GetProperty("library.smartplaylist").asBoolean()) + return true; + + return URIUtils::HasExtension(m_strPath, ".xsp"); +} + +bool CFileItem::IsLibraryFolder() const +{ + if (HasProperty("library.filter") && GetProperty("library.filter").asBoolean()) + return true; + + return URIUtils::IsLibraryFolder(m_strPath); +} + +bool CFileItem::IsPlayList() const +{ + return CPlayListFactory::IsPlaylist(*this); +} + +bool CFileItem::IsPythonScript() const +{ + return URIUtils::HasExtension(m_strPath, ".py"); +} + +bool CFileItem::IsType(const char *ext) const +{ + if (!m_strDynPath.empty()) + return URIUtils::HasExtension(m_strDynPath, ext); + + return URIUtils::HasExtension(m_strPath, ext); +} + +bool CFileItem::IsNFO() const +{ + return URIUtils::HasExtension(m_strPath, ".nfo"); +} + +bool CFileItem::IsDiscImage() const +{ + return URIUtils::HasExtension(GetDynPath(), ".img|.iso|.nrg|.udf"); +} + +bool CFileItem::IsOpticalMediaFile() const +{ + if (IsDVDFile(false, true)) + return true; + + return IsBDFile(); +} + +bool CFileItem::IsDVDFile(bool bVobs /*= true*/, bool bIfos /*= true*/) const +{ + std::string strFileName = URIUtils::GetFileName(GetDynPath()); + if (bIfos) + { + if (StringUtils::EqualsNoCase(strFileName, "video_ts.ifo")) + return true; + if (StringUtils::StartsWithNoCase(strFileName, "vts_") && StringUtils::EndsWithNoCase(strFileName, "_0.ifo") && strFileName.length() == 12) + return true; + } + if (bVobs) + { + if (StringUtils::EqualsNoCase(strFileName, "video_ts.vob")) + return true; + if (StringUtils::StartsWithNoCase(strFileName, "vts_") && StringUtils::EndsWithNoCase(strFileName, ".vob")) + return true; + } + + return false; +} + +bool CFileItem::IsBDFile() const +{ + std::string strFileName = URIUtils::GetFileName(GetDynPath()); + return (StringUtils::EqualsNoCase(strFileName, "index.bdmv") || StringUtils::EqualsNoCase(strFileName, "MovieObject.bdmv") + || StringUtils::EqualsNoCase(strFileName, "INDEX.BDM") || StringUtils::EqualsNoCase(strFileName, "MOVIEOBJ.BDM")); +} + +bool CFileItem::IsRAR() const +{ + return URIUtils::IsRAR(m_strPath); +} + +bool CFileItem::IsAPK() const +{ + return URIUtils::IsAPK(m_strPath); +} + +bool CFileItem::IsZIP() const +{ + return URIUtils::IsZIP(m_strPath); +} + +bool CFileItem::IsCBZ() const +{ + return URIUtils::HasExtension(m_strPath, ".cbz"); +} + +bool CFileItem::IsCBR() const +{ + return URIUtils::HasExtension(m_strPath, ".cbr"); +} + +bool CFileItem::IsRSS() const +{ + return StringUtils::StartsWithNoCase(m_strPath, "rss://") || URIUtils::HasExtension(m_strPath, ".rss") + || StringUtils::StartsWithNoCase(m_strPath, "rsss://") + || m_mimetype == "application/rss+xml"; +} + +bool CFileItem::IsAndroidApp() const +{ + return URIUtils::IsAndroidApp(m_strPath); +} + +bool CFileItem::IsStack() const +{ + return URIUtils::IsStack(m_strPath); +} + +bool CFileItem::IsFavourite() const +{ + return URIUtils::IsFavourite(m_strPath); +} + +bool CFileItem::IsPlugin() const +{ + return URIUtils::IsPlugin(m_strPath); +} + +bool CFileItem::IsScript() const +{ + return URIUtils::IsScript(m_strPath); +} + +bool CFileItem::IsAddonsPath() const +{ + return URIUtils::IsAddonsPath(m_strPath); +} + +bool CFileItem::IsSourcesPath() const +{ + return URIUtils::IsSourcesPath(m_strPath); +} + +bool CFileItem::IsMultiPath() const +{ + return URIUtils::IsMultiPath(m_strPath); +} + +bool CFileItem::IsBluray() const +{ + if (URIUtils::IsBluray(m_strPath)) + return true; + + CFileItem item = CFileItem(GetOpticalMediaPath(), false); + + return item.IsBDFile(); +} + +bool CFileItem::IsProtectedBlurayDisc() const +{ + std::string path; + path = URIUtils::AddFileToFolder(GetPath(), "AACS", "Unit_Key_RO.inf"); + if (CFile::Exists(path)) + return true; + + return false; +} + +bool CFileItem::IsCDDA() const +{ + return URIUtils::IsCDDA(m_strPath); +} + +bool CFileItem::IsDVD() const +{ + return URIUtils::IsDVD(m_strPath) || m_iDriveType == CMediaSource::SOURCE_TYPE_DVD; +} + +bool CFileItem::IsOnDVD() const +{ + return URIUtils::IsOnDVD(m_strPath) || m_iDriveType == CMediaSource::SOURCE_TYPE_DVD; +} + +bool CFileItem::IsNfs() const +{ + return URIUtils::IsNfs(m_strPath); +} + +bool CFileItem::IsOnLAN() const +{ + return URIUtils::IsOnLAN(m_strPath); +} + +bool CFileItem::IsISO9660() const +{ + return URIUtils::IsISO9660(m_strPath); +} + +bool CFileItem::IsRemote() const +{ + return URIUtils::IsRemote(m_strPath); +} + +bool CFileItem::IsSmb() const +{ + return URIUtils::IsSmb(m_strPath); +} + +bool CFileItem::IsURL() const +{ + return URIUtils::IsURL(m_strPath); +} + +bool CFileItem::IsPVR() const +{ + return URIUtils::IsPVR(m_strPath); +} + +bool CFileItem::IsLiveTV() const +{ + return URIUtils::IsLiveTV(m_strPath); +} + +bool CFileItem::IsHD() const +{ + return URIUtils::IsHD(m_strPath); +} + +bool CFileItem::IsMusicDb() const +{ + return URIUtils::IsMusicDb(m_strPath); +} + +bool CFileItem::IsVideoDb() const +{ + return URIUtils::IsVideoDb(m_strPath); +} + +bool CFileItem::IsVirtualDirectoryRoot() const +{ + return (m_bIsFolder && m_strPath.empty()); +} + +bool CFileItem::IsRemovable() const +{ + return IsOnDVD() || IsCDDA() || m_iDriveType == CMediaSource::SOURCE_TYPE_REMOVABLE; +} + +bool CFileItem::IsReadOnly() const +{ + if (IsParentFolder()) + return true; + + if (m_bIsShareOrDrive) + return true; + + return !CUtil::SupportsWriteFileOperations(m_strPath); +} + +void CFileItem::FillInDefaultIcon() +{ + if (URIUtils::IsPVRGuideItem(m_strPath)) + { + // epg items never have a default icon. no need to execute this expensive method. + // when filling epg grid window, easily tens of thousands of epg items are processed. + return; + } + + //CLog::Log(LOGINFO, "FillInDefaultIcon({})", pItem->GetLabel()); + // find the default icon for a file or folder item + // for files this can be the (depending on the file type) + // default picture for photo's + // default picture for songs + // default picture for videos + // default picture for shortcuts + // default picture for playlists + // + // for folders + // for .. folders the default picture for parent folder + // for other folders the defaultFolder.png + + if (GetArt("icon").empty()) + { + if (!m_bIsFolder) + { + /* To reduce the average runtime of this code, this list should + * be ordered with most frequently seen types first. Also bear + * in mind the complexity of the code behind the check in the + * case of IsWhatever() returns false. + */ + if (IsPVRChannel()) + { + if (GetPVRChannelInfoTag()->IsRadio()) + SetArt("icon", "DefaultMusicSongs.png"); + else + SetArt("icon", "DefaultTVShows.png"); + } + else if ( IsLiveTV() ) + { + // Live TV Channel + SetArt("icon", "DefaultTVShows.png"); + } + else if ( URIUtils::IsArchive(m_strPath) ) + { // archive + SetArt("icon", "DefaultFile.png"); + } + else if ( IsUsablePVRRecording() ) + { + // PVR recording + SetArt("icon", "DefaultVideo.png"); + } + else if ( IsDeletedPVRRecording() ) + { + // PVR deleted recording + SetArt("icon", "DefaultVideoDeleted.png"); + } + else if ( IsAudio() ) + { + // audio + SetArt("icon", "DefaultAudio.png"); + } + else if ( IsVideo() ) + { + // video + SetArt("icon", "DefaultVideo.png"); + } + else if (IsPVRTimer()) + { + SetArt("icon", "DefaultVideo.png"); + } + else if ( IsPicture() ) + { + // picture + SetArt("icon", "DefaultPicture.png"); + } + else if ( IsPlayList() || IsSmartPlayList()) + { + SetArt("icon", "DefaultPlaylist.png"); + } + else if ( IsPythonScript() ) + { + SetArt("icon", "DefaultScript.png"); + } + else if (IsFavourite()) + { + SetArt("icon", "DefaultFavourites.png"); + } + else + { + // default icon for unknown file type + SetArt("icon", "DefaultFile.png"); + } + } + else + { + if ( IsPlayList() || IsSmartPlayList()) + { + SetArt("icon", "DefaultPlaylist.png"); + } + else if (IsParentFolder()) + { + SetArt("icon", "DefaultFolderBack.png"); + } + else + { + SetArt("icon", "DefaultFolder.png"); + } + } + } + // Set the icon overlays (if applicable) + if (!HasOverlay() && !HasProperty("icon_never_overlay")) + { + if (URIUtils::IsInRAR(m_strPath)) + SetOverlayImage(CGUIListItem::ICON_OVERLAY_RAR); + else if (URIUtils::IsInZIP(m_strPath)) + SetOverlayImage(CGUIListItem::ICON_OVERLAY_ZIP); + } +} + +void CFileItem::RemoveExtension() +{ + if (m_bIsFolder) + return; + + std::string strLabel = GetLabel(); + URIUtils::RemoveExtension(strLabel); + SetLabel(strLabel); +} + +void CFileItem::CleanString() +{ + if (IsLiveTV()) + return; + + std::string strLabel = GetLabel(); + std::string strTitle, strTitleAndYear, strYear; + CUtil::CleanString(strLabel, strTitle, strTitleAndYear, strYear, true); + SetLabel(strTitleAndYear); +} + +void CFileItem::SetLabel(const std::string &strLabel) +{ + if (strLabel == "..") + { + m_bIsParentFolder = true; + m_bIsFolder = true; + m_specialSort = SortSpecialOnTop; + SetLabelPreformatted(true); + } + CGUIListItem::SetLabel(strLabel); +} + +void CFileItem::SetFileSizeLabel() +{ + if(m_bIsFolder && m_dwSize == 0) + SetLabel2(""); + else + SetLabel2(StringUtils::SizeToString(m_dwSize)); +} + +bool CFileItem::CanQueue() const +{ + return m_bCanQueue; +} + +void CFileItem::SetCanQueue(bool bYesNo) +{ + m_bCanQueue = bYesNo; +} + +bool CFileItem::IsParentFolder() const +{ + return m_bIsParentFolder; +} + +void CFileItem::FillInMimeType(bool lookup /*= true*/) +{ + //! @todo adapt this to use CMime::GetMimeType() + if (m_mimetype.empty()) + { + if (m_bIsFolder) + m_mimetype = "x-directory/normal"; + else if (HasPVRChannelInfoTag()) + m_mimetype = GetPVRChannelInfoTag()->MimeType(); + else if (StringUtils::StartsWithNoCase(GetDynPath(), "shout://") || + StringUtils::StartsWithNoCase(GetDynPath(), "http://") || + StringUtils::StartsWithNoCase(GetDynPath(), "https://")) + { + // If lookup is false, bail out early to leave mime type empty + if (!lookup) + return; + + CCurlFile::GetMimeType(GetDynURL(), m_mimetype); + + // try to get mime-type again but with an NSPlayer User-Agent + // in order for server to provide correct mime-type. Allows us + // to properly detect an MMS stream + if (StringUtils::StartsWithNoCase(m_mimetype, "video/x-ms-")) + CCurlFile::GetMimeType(GetDynURL(), m_mimetype, "NSPlayer/11.00.6001.7000"); + + // make sure there are no options set in mime-type + // mime-type can look like "video/x-ms-asf ; charset=utf8" + size_t i = m_mimetype.find(';'); + if(i != std::string::npos) + m_mimetype.erase(i, m_mimetype.length() - i); + StringUtils::Trim(m_mimetype); + } + else + m_mimetype = CMime::GetMimeType(*this); + + // if it's still empty set to an unknown type + if (m_mimetype.empty()) + m_mimetype = "application/octet-stream"; + } + + // change protocol to mms for the following mime-type. Allows us to create proper FileMMS. + if(StringUtils::StartsWithNoCase(m_mimetype, "application/vnd.ms.wms-hdr.asfv1") || + StringUtils::StartsWithNoCase(m_mimetype, "application/x-mms-framed")) + { + if (m_strDynPath.empty()) + m_strDynPath = m_strPath; + + StringUtils::Replace(m_strDynPath, "http:", "mms:"); + } +} + +void CFileItem::SetMimeTypeForInternetFile() +{ + if (m_doContentLookup && IsInternetStream()) + { + SetMimeType(""); + FillInMimeType(true); + } +} + +bool CFileItem::IsSamePath(const CFileItem *item) const +{ + if (!item) + return false; + + if (!m_strPath.empty() && item->GetPath() == m_strPath) + { + if (item->HasProperty("item_start") || HasProperty("item_start")) + return (item->GetProperty("item_start") == GetProperty("item_start")); + return true; + } + if (HasMusicInfoTag() && item->HasMusicInfoTag()) + { + if (GetMusicInfoTag()->GetDatabaseId() != -1 && item->GetMusicInfoTag()->GetDatabaseId() != -1) + return ((GetMusicInfoTag()->GetDatabaseId() == item->GetMusicInfoTag()->GetDatabaseId()) && + (GetMusicInfoTag()->GetType() == item->GetMusicInfoTag()->GetType())); + } + if (HasVideoInfoTag() && item->HasVideoInfoTag()) + { + if (GetVideoInfoTag()->m_iDbId != -1 && item->GetVideoInfoTag()->m_iDbId != -1) + return ((GetVideoInfoTag()->m_iDbId == item->GetVideoInfoTag()->m_iDbId) && + (GetVideoInfoTag()->m_type == item->GetVideoInfoTag()->m_type)); + } + if (IsMusicDb() && HasMusicInfoTag()) + { + CFileItem dbItem(m_musicInfoTag->GetURL(), false); + if (HasProperty("item_start")) + dbItem.SetProperty("item_start", GetProperty("item_start")); + return dbItem.IsSamePath(item); + } + if (IsVideoDb() && HasVideoInfoTag()) + { + CFileItem dbItem(GetVideoInfoTag()->m_strFileNameAndPath, false); + if (HasProperty("item_start")) + dbItem.SetProperty("item_start", GetProperty("item_start")); + return dbItem.IsSamePath(item); + } + if (item->IsMusicDb() && item->HasMusicInfoTag()) + { + CFileItem dbItem(item->m_musicInfoTag->GetURL(), false); + if (item->HasProperty("item_start")) + dbItem.SetProperty("item_start", item->GetProperty("item_start")); + return IsSamePath(&dbItem); + } + if (item->IsVideoDb() && item->HasVideoInfoTag()) + { + CFileItem dbItem(item->GetVideoInfoTag()->m_strFileNameAndPath, false); + if (item->HasProperty("item_start")) + dbItem.SetProperty("item_start", item->GetProperty("item_start")); + return IsSamePath(&dbItem); + } + if (HasProperty("original_listitem_url")) + return (GetProperty("original_listitem_url") == item->GetPath()); + return false; +} + +bool CFileItem::IsAlbum() const +{ + return m_bIsAlbum; +} + +void CFileItem::UpdateInfo(const CFileItem &item, bool replaceLabels /*=true*/) +{ + if (item.HasVideoInfoTag()) + { // copy info across + //! @todo premiered info is normally stored in m_dateTime by the db + + if (item.m_videoInfoTag) + { + if (m_videoInfoTag) + *m_videoInfoTag = *item.m_videoInfoTag; + else + m_videoInfoTag = new CVideoInfoTag(*item.m_videoInfoTag); + } + else + { + if (m_videoInfoTag) + delete m_videoInfoTag; + + m_videoInfoTag = new CVideoInfoTag; + } + + m_pvrRecordingInfoTag = item.m_pvrRecordingInfoTag; + + SetOverlayImage(ICON_OVERLAY_UNWATCHED, GetVideoInfoTag()->GetPlayCount() > 0); + SetInvalid(); + } + if (item.HasMusicInfoTag()) + { + *GetMusicInfoTag() = *item.GetMusicInfoTag(); + SetInvalid(); + } + if (item.HasPictureInfoTag()) + { + *GetPictureInfoTag() = *item.GetPictureInfoTag(); + SetInvalid(); + } + if (item.HasGameInfoTag()) + { + *GetGameInfoTag() = *item.GetGameInfoTag(); + SetInvalid(); + } + SetDynPath(item.GetDynPath()); + if (replaceLabels && !item.GetLabel().empty()) + SetLabel(item.GetLabel()); + if (replaceLabels && !item.GetLabel2().empty()) + SetLabel2(item.GetLabel2()); + if (!item.GetArt().empty()) + SetArt(item.GetArt()); + AppendProperties(item); +} + +void CFileItem::MergeInfo(const CFileItem& item) +{ + // TODO: Currently merge the metadata/art info is implemented for video case only + if (item.HasVideoInfoTag()) + { + if (item.m_videoInfoTag) + { + if (m_videoInfoTag) + m_videoInfoTag->Merge(*item.m_videoInfoTag); + else + m_videoInfoTag = new CVideoInfoTag(*item.m_videoInfoTag); + } + + m_pvrRecordingInfoTag = item.m_pvrRecordingInfoTag; + + SetOverlayImage(ICON_OVERLAY_UNWATCHED, GetVideoInfoTag()->GetPlayCount() > 0); + SetInvalid(); + } + if (item.HasMusicInfoTag()) + { + *GetMusicInfoTag() = *item.GetMusicInfoTag(); + SetInvalid(); + } + if (item.HasPictureInfoTag()) + { + *GetPictureInfoTag() = *item.GetPictureInfoTag(); + SetInvalid(); + } + if (item.HasGameInfoTag()) + { + *GetGameInfoTag() = *item.GetGameInfoTag(); + SetInvalid(); + } + SetDynPath(item.GetDynPath()); + if (!item.GetLabel().empty()) + SetLabel(item.GetLabel()); + if (!item.GetLabel2().empty()) + SetLabel2(item.GetLabel2()); + if (!item.GetArt().empty()) + { + if (item.IsVideo()) + AppendArt(item.GetArt()); + else + SetArt(item.GetArt()); + } + AppendProperties(item); +} + +void CFileItem::SetFromVideoInfoTag(const CVideoInfoTag &video) +{ + if (!video.m_strTitle.empty()) + SetLabel(video.m_strTitle); + if (video.m_strFileNameAndPath.empty()) + { + m_strPath = video.m_strPath; + URIUtils::AddSlashAtEnd(m_strPath); + m_bIsFolder = true; + } + else + { + m_strPath = video.m_strFileNameAndPath; + m_bIsFolder = false; + } + + if (m_videoInfoTag) + *m_videoInfoTag = video; + else + m_videoInfoTag = new CVideoInfoTag(video); + + if (video.m_iSeason == 0) + SetProperty("isspecial", "true"); + FillInDefaultIcon(); + FillInMimeType(false); +} + +namespace +{ +class CPropertySaveHelper +{ +public: + CPropertySaveHelper(CFileItem& item, const std::string& property, const std::string& value) + : m_item(item), m_property(property), m_value(value) + { + } + + bool NeedsSave() const { return !m_value.empty() || m_item.HasProperty(m_property); } + + std::string GetValueToSave(const std::string& currentValue) const + { + std::string value; + + if (!m_value.empty()) + { + // Overwrite whatever we have; remember what we had originally. + if (!m_item.HasProperty(m_property)) + m_item.SetProperty(m_property, currentValue); + + value = m_value; + } + else if (m_item.HasProperty(m_property)) + { + // Restore original value + value = m_item.GetProperty(m_property).asString(); + m_item.ClearProperty(m_property); + } + + return value; + } + +private: + CFileItem& m_item; + const std::string m_property; + const std::string m_value; +}; +} // unnamed namespace + +void CFileItem::SetFromMusicInfoTag(const MUSIC_INFO::CMusicInfoTag& music) +{ + const std::string path = GetPath(); + if (path.empty()) + { + SetPath(music.GetURL()); + } + else + { + const CPropertySaveHelper dynpath(*this, "OriginalDynPath", music.GetURL()); + if (dynpath.NeedsSave()) + SetDynPath(dynpath.GetValueToSave(m_strDynPath)); + } + + const CPropertySaveHelper label(*this, "OriginalLabel", music.GetTitle()); + if (label.NeedsSave()) + SetLabel(label.GetValueToSave(GetLabel())); + + const CPropertySaveHelper thumb(*this, "OriginalThumb", music.GetStationArt()); + if (thumb.NeedsSave()) + SetArt("thumb", thumb.GetValueToSave(GetArt("thumb"))); + + *GetMusicInfoTag() = music; + FillInDefaultIcon(); + FillInMimeType(false); +} + +void CFileItem::SetFromAlbum(const CAlbum &album) +{ + if (!album.strAlbum.empty()) + SetLabel(album.strAlbum); + m_bIsFolder = true; + m_strLabel2 = album.GetAlbumArtistString(); + GetMusicInfoTag()->SetAlbum(album); + + if (album.art.empty()) + SetArt("icon", "DefaultAlbumCover.png"); + else + SetArt(album.art); + + m_bIsAlbum = true; + CMusicDatabase::SetPropertiesFromAlbum(*this,album); + FillInMimeType(false); +} + +void CFileItem::SetFromSong(const CSong &song) +{ + if (!song.strTitle.empty()) + SetLabel(song.strTitle); + if (song.idSong > 0) + { + std::string strExt = URIUtils::GetExtension(song.strFileName); + m_strPath = StringUtils::Format("musicdb://songs/{}{}", song.idSong, strExt); + } + else if (!song.strFileName.empty()) + m_strPath = song.strFileName; + GetMusicInfoTag()->SetSong(song); + m_lStartOffset = song.iStartOffset; + m_lStartPartNumber = 1; + SetProperty("item_start", song.iStartOffset); + m_lEndOffset = song.iEndOffset; + if (!song.strThumb.empty()) + SetArt("thumb", song.strThumb); + FillInMimeType(false); +} + +std::string CFileItem::GetOpticalMediaPath() const +{ + std::string path; + path = URIUtils::AddFileToFolder(GetPath(), "VIDEO_TS.IFO"); + if (CFile::Exists(path)) + return path; + + path = URIUtils::AddFileToFolder(GetPath(), "VIDEO_TS", "VIDEO_TS.IFO"); + if (CFile::Exists(path)) + return path; + +#ifdef HAVE_LIBBLURAY + path = URIUtils::AddFileToFolder(GetPath(), "index.bdmv"); + if (CFile::Exists(path)) + return path; + + path = URIUtils::AddFileToFolder(GetPath(), "BDMV", "index.bdmv"); + if (CFile::Exists(path)) + return path; + + path = URIUtils::AddFileToFolder(GetPath(), "INDEX.BDM"); + if (CFile::Exists(path)) + return path; + + path = URIUtils::AddFileToFolder(GetPath(), "BDMV", "INDEX.BDM"); + if (CFile::Exists(path)) + return path; +#endif + return std::string(); +} + +/** +* @todo Ideally this (and SetPath) would not be available outside of construction +* for CFileItem objects, or at least restricted to essentially be equivalent +* to construction. This would require re-formulating a bunch of CFileItem +* construction, and also allowing CFileItemList to have its own (public) +* SetURL() function, so for now we give direct access. +*/ +void CFileItem::SetURL(const CURL& url) +{ + m_strPath = url.Get(); +} + +const CURL CFileItem::GetURL() const +{ + CURL url(m_strPath); + return url; +} + +bool CFileItem::IsURL(const CURL& url) const +{ + return IsPath(url.Get()); +} + +bool CFileItem::IsPath(const std::string& path, bool ignoreURLOptions /* = false */) const +{ + return URIUtils::PathEquals(m_strPath, path, false, ignoreURLOptions); +} + +void CFileItem::SetDynURL(const CURL& url) +{ + m_strDynPath = url.Get(); +} + +const CURL CFileItem::GetDynURL() const +{ + if (!m_strDynPath.empty()) + { + CURL url(m_strDynPath); + return url; + } + else + { + CURL url(m_strPath); + return url; + } +} + +const std::string &CFileItem::GetDynPath() const +{ + if (!m_strDynPath.empty()) + return m_strDynPath; + else + return m_strPath; +} + +void CFileItem::SetDynPath(const std::string &path) +{ + m_strDynPath = path; +} + +void CFileItem::SetCueDocument(const CCueDocumentPtr& cuePtr) +{ + m_cueDocument = cuePtr; +} + +void CFileItem::LoadEmbeddedCue() +{ + CMusicInfoTag& tag = *GetMusicInfoTag(); + if (!tag.Loaded()) + return; + + const std::string embeddedCue = tag.GetCueSheet(); + if (!embeddedCue.empty()) + { + CCueDocumentPtr cuesheet(new CCueDocument); + if (cuesheet->ParseTag(embeddedCue)) + { + std::vector<std::string> MediaFileVec; + cuesheet->GetMediaFiles(MediaFileVec); + for (std::vector<std::string>::iterator itMedia = MediaFileVec.begin(); + itMedia != MediaFileVec.end(); ++itMedia) + cuesheet->UpdateMediaFile(*itMedia, GetPath()); + SetCueDocument(cuesheet); + } + // Clear cuesheet tag having added it to item + tag.SetCueSheet(""); + } +} + +bool CFileItem::HasCueDocument() const +{ + return (m_cueDocument.get() != nullptr); +} + +bool CFileItem::LoadTracksFromCueDocument(CFileItemList& scannedItems) +{ + if (!m_cueDocument) + return false; + + const CMusicInfoTag& tag = *GetMusicInfoTag(); + + VECSONGS tracks; + m_cueDocument->GetSongs(tracks); + + bool oneFilePerTrack = m_cueDocument->IsOneFilePerTrack(); + m_cueDocument.reset(); + + int tracksFound = 0; + for (VECSONGS::iterator it = tracks.begin(); it != tracks.end(); ++it) + { + CSong& song = *it; + if (song.strFileName == GetPath()) + { + if (tag.Loaded()) + { + if (song.strAlbum.empty() && !tag.GetAlbum().empty()) + song.strAlbum = tag.GetAlbum(); + //Pass album artist to final MusicInfoTag object via setting song album artist vector. + if (song.GetAlbumArtist().empty() && !tag.GetAlbumArtist().empty()) + song.SetAlbumArtist(tag.GetAlbumArtist()); + if (song.genre.empty() && !tag.GetGenre().empty()) + song.genre = tag.GetGenre(); + //Pass artist to final MusicInfoTag object via setting song artist description string only. + //Artist credits not used during loading from cue sheet. + if (song.strArtistDesc.empty() && !tag.GetArtistString().empty()) + song.strArtistDesc = tag.GetArtistString(); + if (tag.GetDiscNumber()) + song.iTrack |= (tag.GetDiscNumber() << 16); // see CMusicInfoTag::GetDiscNumber() + if (!tag.GetCueSheet().empty()) + song.strCueSheet = tag.GetCueSheet(); + + if (tag.GetYear()) + song.strReleaseDate = tag.GetReleaseDate(); + if (song.embeddedArt.Empty() && !tag.GetCoverArtInfo().Empty()) + song.embeddedArt = tag.GetCoverArtInfo(); + } + + if (!song.iDuration && tag.GetDuration() > 0) + { // must be the last song + song.iDuration = CUtil::ConvertMilliSecsToSecsIntRounded(CUtil::ConvertSecsToMilliSecs(tag.GetDuration()) - song.iStartOffset); + } + if ( tag.Loaded() && oneFilePerTrack && ! ( tag.GetAlbum().empty() || tag.GetArtist().empty() || tag.GetTitle().empty() ) ) + { + // If there are multiple files in a cue file, the tags from the files should be preferred if they exist. + scannedItems.Add(CFileItemPtr(new CFileItem(song, tag))); + } + else + { + scannedItems.Add(CFileItemPtr(new CFileItem(song))); + } + ++tracksFound; + } + } + return tracksFound != 0; +} + +///////////////////////////////////////////////////////////////////////////////// +///// +///// CFileItemList +///// +////////////////////////////////////////////////////////////////////////////////// + +CFileItemList::CFileItemList() +: CFileItem("", true) +{ +} + +CFileItemList::CFileItemList(const std::string& strPath) +: CFileItem(strPath, true) +{ +} + +CFileItemList::~CFileItemList() +{ + Clear(); +} + +CFileItemPtr CFileItemList::operator[] (int iItem) +{ + return Get(iItem); +} + +const CFileItemPtr CFileItemList::operator[] (int iItem) const +{ + return Get(iItem); +} + +CFileItemPtr CFileItemList::operator[] (const std::string& strPath) +{ + return Get(strPath); +} + +const CFileItemPtr CFileItemList::operator[] (const std::string& strPath) const +{ + return Get(strPath); +} + +void CFileItemList::SetIgnoreURLOptions(bool ignoreURLOptions) +{ + m_ignoreURLOptions = ignoreURLOptions; + + if (m_fastLookup) + { + m_fastLookup = false; // Force SetFastlookup to clear map + SetFastLookup(true); // and regenerate map + } +} + +void CFileItemList::SetFastLookup(bool fastLookup) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + if (fastLookup && !m_fastLookup) + { // generate the map + m_map.clear(); + for (unsigned int i=0; i < m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + m_map.insert(MAPFILEITEMSPAIR(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath(), pItem)); + } + } + if (!fastLookup && m_fastLookup) + m_map.clear(); + m_fastLookup = fastLookup; +} + +bool CFileItemList::Contains(const std::string& fileName) const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + if (m_fastLookup) + return m_map.find(m_ignoreURLOptions ? CURL(fileName).GetWithoutOptions() : fileName) != m_map.end(); + + // slow method... + for (unsigned int i = 0; i < m_items.size(); i++) + { + const CFileItemPtr pItem = m_items[i]; + if (pItem->IsPath(m_ignoreURLOptions ? CURL(fileName).GetWithoutOptions() : fileName)) + return true; + } + return false; +} + +void CFileItemList::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + ClearItems(); + m_sortDescription.sortBy = SortByNone; + m_sortDescription.sortOrder = SortOrderNone; + m_sortDescription.sortAttributes = SortAttributeNone; + m_sortIgnoreFolders = false; + m_cacheToDisc = CACHE_IF_SLOW; + m_sortDetails.clear(); + m_replaceListing = false; + m_content.clear(); +} + +void CFileItemList::ClearItems() +{ + std::unique_lock<CCriticalSection> lock(m_lock); + // make sure we free the memory of the items (these are GUIControls which may have allocated resources) + FreeMemory(); + for (unsigned int i = 0; i < m_items.size(); i++) + { + CFileItemPtr item = m_items[i]; + item->FreeMemory(); + } + m_items.clear(); + m_map.clear(); +} + +void CFileItemList::Add(CFileItemPtr pItem) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + if (m_fastLookup) + m_map.insert(MAPFILEITEMSPAIR(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath(), pItem)); + m_items.emplace_back(std::move(pItem)); +} + +void CFileItemList::Add(CFileItem&& item) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + auto ptr = std::make_shared<CFileItem>(std::move(item)); + if (m_fastLookup) + m_map.insert(MAPFILEITEMSPAIR(m_ignoreURLOptions ? CURL(ptr->GetPath()).GetWithoutOptions() : ptr->GetPath(), ptr)); + m_items.emplace_back(std::move(ptr)); +} + +void CFileItemList::AddFront(const CFileItemPtr &pItem, int itemPosition) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + if (itemPosition >= 0) + { + m_items.insert(m_items.begin()+itemPosition, pItem); + } + else + { + m_items.insert(m_items.begin()+(m_items.size()+itemPosition), pItem); + } + if (m_fastLookup) + { + m_map.insert(MAPFILEITEMSPAIR(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath(), pItem)); + } +} + +void CFileItemList::Remove(CFileItem* pItem) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + for (IVECFILEITEMS it = m_items.begin(); it != m_items.end(); ++it) + { + if (pItem == it->get()) + { + m_items.erase(it); + if (m_fastLookup) + { + m_map.erase(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath()); + } + break; + } + } +} + +VECFILEITEMS::iterator CFileItemList::erase(VECFILEITEMS::iterator first, + VECFILEITEMS::iterator last) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + return m_items.erase(first, last); +} + +void CFileItemList::Remove(int iItem) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + if (iItem >= 0 && iItem < Size()) + { + CFileItemPtr pItem = *(m_items.begin() + iItem); + if (m_fastLookup) + { + m_map.erase(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath()); + } + m_items.erase(m_items.begin() + iItem); + } +} + +void CFileItemList::Append(const CFileItemList& itemlist) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + for (int i = 0; i < itemlist.Size(); ++i) + Add(itemlist[i]); +} + +void CFileItemList::Assign(const CFileItemList& itemlist, bool append) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + if (!append) + Clear(); + Append(itemlist); + SetPath(itemlist.GetPath()); + SetLabel(itemlist.GetLabel()); + m_sortDetails = itemlist.m_sortDetails; + m_sortDescription = itemlist.m_sortDescription; + m_replaceListing = itemlist.m_replaceListing; + m_content = itemlist.m_content; + m_mapProperties = itemlist.m_mapProperties; + m_cacheToDisc = itemlist.m_cacheToDisc; +} + +bool CFileItemList::Copy(const CFileItemList& items, bool copyItems /* = true */) +{ + // assign all CFileItem parts + *static_cast<CFileItem*>(this) = static_cast<const CFileItem&>(items); + + // assign the rest of the CFileItemList properties + m_replaceListing = items.m_replaceListing; + m_content = items.m_content; + m_mapProperties = items.m_mapProperties; + m_cacheToDisc = items.m_cacheToDisc; + m_sortDetails = items.m_sortDetails; + m_sortDescription = items.m_sortDescription; + m_sortIgnoreFolders = items.m_sortIgnoreFolders; + + if (copyItems) + { + // make a copy of each item + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr newItem(new CFileItem(*items[i])); + Add(newItem); + } + } + + return true; +} + +CFileItemPtr CFileItemList::Get(int iItem) const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + if (iItem > -1 && iItem < (int)m_items.size()) + return m_items[iItem]; + + return CFileItemPtr(); +} + +CFileItemPtr CFileItemList::Get(const std::string& strPath) const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + if (m_fastLookup) + { + MAPFILEITEMS::const_iterator it = + m_map.find(m_ignoreURLOptions ? CURL(strPath).GetWithoutOptions() : strPath); + if (it != m_map.end()) + return it->second; + + return CFileItemPtr(); + } + // slow method... + for (unsigned int i = 0; i < m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + if (pItem->IsPath(m_ignoreURLOptions ? CURL(strPath).GetWithoutOptions() : strPath)) + return pItem; + } + + return CFileItemPtr(); +} + +int CFileItemList::Size() const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + return (int)m_items.size(); +} + +bool CFileItemList::IsEmpty() const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + return m_items.empty(); +} + +void CFileItemList::Reserve(size_t iCount) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + m_items.reserve(iCount); +} + +void CFileItemList::Sort(FILEITEMLISTCOMPARISONFUNC func) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + std::stable_sort(m_items.begin(), m_items.end(), func); +} + +void CFileItemList::FillSortFields(FILEITEMFILLFUNC func) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + std::for_each(m_items.begin(), m_items.end(), func); +} + +void CFileItemList::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute sortAttributes /* = SortAttributeNone */) +{ + if (sortBy == SortByNone || + (m_sortDescription.sortBy == sortBy && m_sortDescription.sortOrder == sortOrder && + m_sortDescription.sortAttributes == sortAttributes)) + return; + + SortDescription sorting; + sorting.sortBy = sortBy; + sorting.sortOrder = sortOrder; + sorting.sortAttributes = sortAttributes; + + Sort(sorting); + m_sortDescription = sorting; +} + +void CFileItemList::Sort(SortDescription sortDescription) +{ + if (sortDescription.sortBy == SortByFile || sortDescription.sortBy == SortBySortTitle || + sortDescription.sortBy == SortByOriginalTitle || sortDescription.sortBy == SortByDateAdded || + sortDescription.sortBy == SortByRating || sortDescription.sortBy == SortByYear || + sortDescription.sortBy == SortByPlaylistOrder || sortDescription.sortBy == SortByLastPlayed || + sortDescription.sortBy == SortByPlaycount) + sortDescription.sortAttributes = (SortAttribute)((int)sortDescription.sortAttributes | SortAttributeIgnoreFolders); + + if (sortDescription.sortBy == SortByNone || + (m_sortDescription.sortBy == sortDescription.sortBy && m_sortDescription.sortOrder == sortDescription.sortOrder && + m_sortDescription.sortAttributes == sortDescription.sortAttributes)) + return; + + if (m_sortIgnoreFolders) + sortDescription.sortAttributes = (SortAttribute)((int)sortDescription.sortAttributes | SortAttributeIgnoreFolders); + + const Fields fields = SortUtils::GetFieldsForSorting(sortDescription.sortBy); + SortItems sortItems((size_t)Size()); + for (int index = 0; index < Size(); index++) + { + sortItems[index] = std::shared_ptr<SortItem>(new SortItem); + m_items[index]->ToSortable(*sortItems[index], fields); + (*sortItems[index])[FieldId] = index; + } + + // do the sorting + SortUtils::Sort(sortDescription, sortItems); + + // apply the new order to the existing CFileItems + VECFILEITEMS sortedFileItems; + sortedFileItems.reserve(Size()); + for (SortItems::const_iterator it = sortItems.begin(); it != sortItems.end(); ++it) + { + CFileItemPtr item = m_items[(int)(*it)->at(FieldId).asInteger()]; + // Set the sort label in the CFileItem + item->SetSortLabel((*it)->at(FieldSort).asWideString()); + + sortedFileItems.push_back(item); + } + + // replace the current list with the re-ordered one + m_items = std::move(sortedFileItems); +} + +void CFileItemList::Randomize() +{ + std::unique_lock<CCriticalSection> lock(m_lock); + KODI::UTILS::RandomShuffle(m_items.begin(), m_items.end()); +} + +void CFileItemList::Archive(CArchive& ar) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + if (ar.IsStoring()) + { + CFileItem::Archive(ar); + + int i = 0; + if (!m_items.empty() && m_items[0]->IsParentFolder()) + i = 1; + + ar << (int)(m_items.size() - i); + + ar << m_ignoreURLOptions; + + ar << m_fastLookup; + + ar << (int)m_sortDescription.sortBy; + ar << (int)m_sortDescription.sortOrder; + ar << (int)m_sortDescription.sortAttributes; + ar << m_sortIgnoreFolders; + ar << (int)m_cacheToDisc; + + ar << (int)m_sortDetails.size(); + for (unsigned int j = 0; j < m_sortDetails.size(); ++j) + { + const GUIViewSortDetails &details = m_sortDetails[j]; + ar << (int)details.m_sortDescription.sortBy; + ar << (int)details.m_sortDescription.sortOrder; + ar << (int)details.m_sortDescription.sortAttributes; + ar << details.m_buttonLabel; + ar << details.m_labelMasks.m_strLabelFile; + ar << details.m_labelMasks.m_strLabelFolder; + ar << details.m_labelMasks.m_strLabel2File; + ar << details.m_labelMasks.m_strLabel2Folder; + } + + ar << m_content; + + for (; i < (int)m_items.size(); ++i) + { + CFileItemPtr pItem = m_items[i]; + ar << *pItem; + } + } + else + { + CFileItemPtr pParent; + if (!IsEmpty()) + { + CFileItemPtr pItem=m_items[0]; + if (pItem->IsParentFolder()) + pParent.reset(new CFileItem(*pItem)); + } + + SetIgnoreURLOptions(false); + SetFastLookup(false); + Clear(); + + CFileItem::Archive(ar); + + int iSize = 0; + ar >> iSize; + if (iSize <= 0) + return ; + + if (pParent) + { + m_items.reserve(iSize + 1); + m_items.push_back(pParent); + } + else + m_items.reserve(iSize); + + bool ignoreURLOptions = false; + ar >> ignoreURLOptions; + + bool fastLookup = false; + ar >> fastLookup; + + int tempint; + ar >> tempint; + m_sortDescription.sortBy = (SortBy)tempint; + ar >> tempint; + m_sortDescription.sortOrder = (SortOrder)tempint; + ar >> tempint; + m_sortDescription.sortAttributes = (SortAttribute)tempint; + ar >> m_sortIgnoreFolders; + ar >> tempint; + m_cacheToDisc = CACHE_TYPE(tempint); + + unsigned int detailSize = 0; + ar >> detailSize; + for (unsigned int j = 0; j < detailSize; ++j) + { + GUIViewSortDetails details; + ar >> tempint; + details.m_sortDescription.sortBy = (SortBy)tempint; + ar >> tempint; + details.m_sortDescription.sortOrder = (SortOrder)tempint; + ar >> tempint; + details.m_sortDescription.sortAttributes = (SortAttribute)tempint; + ar >> details.m_buttonLabel; + ar >> details.m_labelMasks.m_strLabelFile; + ar >> details.m_labelMasks.m_strLabelFolder; + ar >> details.m_labelMasks.m_strLabel2File; + ar >> details.m_labelMasks.m_strLabel2Folder; + m_sortDetails.push_back(details); + } + + ar >> m_content; + + for (int i = 0; i < iSize; ++i) + { + CFileItemPtr pItem(new CFileItem); + ar >> *pItem; + Add(pItem); + } + + SetIgnoreURLOptions(ignoreURLOptions); + SetFastLookup(fastLookup); + } +} + +void CFileItemList::FillInDefaultIcons() +{ + std::unique_lock<CCriticalSection> lock(m_lock); + for (int i = 0; i < (int)m_items.size(); ++i) + { + CFileItemPtr pItem = m_items[i]; + pItem->FillInDefaultIcon(); + } +} + +int CFileItemList::GetFolderCount() const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + int nFolderCount = 0; + for (int i = 0; i < (int)m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + if (pItem->m_bIsFolder) + nFolderCount++; + } + + return nFolderCount; +} + +int CFileItemList::GetObjectCount() const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + int numObjects = (int)m_items.size(); + if (numObjects && m_items[0]->IsParentFolder()) + numObjects--; + + return numObjects; +} + +int CFileItemList::GetFileCount() const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + int nFileCount = 0; + for (int i = 0; i < (int)m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + if (!pItem->m_bIsFolder) + nFileCount++; + } + + return nFileCount; +} + +int CFileItemList::GetSelectedCount() const +{ + std::unique_lock<CCriticalSection> lock(m_lock); + int count = 0; + for (int i = 0; i < (int)m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + if (pItem->IsSelected()) + count++; + } + + return count; +} + +void CFileItemList::FilterCueItems() +{ + std::unique_lock<CCriticalSection> lock(m_lock); + // Handle .CUE sheet files... + std::vector<std::string> itemstodelete; + for (int i = 0; i < (int)m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + if (!pItem->m_bIsFolder) + { // see if it's a .CUE sheet + if (pItem->IsCUESheet()) + { + CCueDocumentPtr cuesheet(new CCueDocument); + if (cuesheet->ParseFile(pItem->GetPath())) + { + std::vector<std::string> MediaFileVec; + cuesheet->GetMediaFiles(MediaFileVec); + + // queue the cue sheet and the underlying media file for deletion + for (std::vector<std::string>::iterator itMedia = MediaFileVec.begin(); + itMedia != MediaFileVec.end(); ++itMedia) + { + std::string strMediaFile = *itMedia; + std::string fileFromCue = strMediaFile; // save the file from the cue we're matching against, + // as we're going to search for others here... + bool bFoundMediaFile = CFile::Exists(strMediaFile); + if (!bFoundMediaFile) + { + // try file in same dir, not matching case... + if (Contains(strMediaFile)) + { + bFoundMediaFile = true; + } + else + { + // try removing the .cue extension... + strMediaFile = pItem->GetPath(); + URIUtils::RemoveExtension(strMediaFile); + CFileItem item(strMediaFile, false); + if (item.IsAudio() && Contains(strMediaFile)) + { + bFoundMediaFile = true; + } + else + { // try replacing the extension with one of our allowed ones. + std::vector<std::string> extensions = StringUtils::Split(CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), "|"); + for (std::vector<std::string>::const_iterator i = extensions.begin(); i != extensions.end(); ++i) + { + strMediaFile = URIUtils::ReplaceExtension(pItem->GetPath(), *i); + CFileItem item(strMediaFile, false); + if (!item.IsCUESheet() && !item.IsPlayList() && Contains(strMediaFile)) + { + bFoundMediaFile = true; + break; + } + } + } + } + } + if (bFoundMediaFile) + { + cuesheet->UpdateMediaFile(fileFromCue, strMediaFile); + // apply CUE for later processing + for (int j = 0; j < (int)m_items.size(); j++) + { + CFileItemPtr pItem = m_items[j]; + if (StringUtils::CompareNoCase(pItem->GetPath(), strMediaFile) == 0) + pItem->SetCueDocument(cuesheet); + } + } + } + } + itemstodelete.push_back(pItem->GetPath()); + } + } + } + // now delete the .CUE files. + for (int i = 0; i < (int)itemstodelete.size(); i++) + { + for (int j = 0; j < (int)m_items.size(); j++) + { + CFileItemPtr pItem = m_items[j]; + if (StringUtils::CompareNoCase(pItem->GetPath(), itemstodelete[i]) == 0) + { // delete this item + m_items.erase(m_items.begin() + j); + break; + } + } + } +} + +// Remove the extensions from the filenames +void CFileItemList::RemoveExtensions() +{ + std::unique_lock<CCriticalSection> lock(m_lock); + for (int i = 0; i < Size(); ++i) + m_items[i]->RemoveExtension(); +} + +void CFileItemList::Stack(bool stackFiles /* = true */) +{ + std::unique_lock<CCriticalSection> lock(m_lock); + + // not allowed here + if (IsVirtualDirectoryRoot() || + IsLiveTV() || + IsSourcesPath() || + IsLibraryFolder()) + return; + + SetProperty("isstacked", true); + + // items needs to be sorted for stuff below to work properly + Sort(SortByLabel, SortOrderAscending); + + StackFolders(); + + if (stackFiles) + StackFiles(); +} + +void CFileItemList::StackFolders() +{ + // Precompile our REs + VECCREGEXP folderRegExps; + CRegExp folderRegExp(true, CRegExp::autoUtf8); + const std::vector<std::string>& strFolderRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_folderStackRegExps; + + std::vector<std::string>::const_iterator strExpression = strFolderRegExps.begin(); + while (strExpression != strFolderRegExps.end()) + { + if (!folderRegExp.RegComp(*strExpression)) + CLog::Log(LOGERROR, "{}: Invalid folder stack RegExp:'{}'", __FUNCTION__, + strExpression->c_str()); + else + folderRegExps.push_back(folderRegExp); + + ++strExpression; + } + + if (!folderRegExp.IsCompiled()) + { + CLog::Log(LOGDEBUG, "{}: No stack expressions available. Skipping folder stacking", + __FUNCTION__); + return; + } + + // stack folders + for (int i = 0; i < Size(); i++) + { + CFileItemPtr item = Get(i); + // combined the folder checks + if (item->m_bIsFolder) + { + // only check known fast sources? + // NOTES: + // 1. rars and zips may be on slow sources? is this supposed to be allowed? + if( !item->IsRemote() + || item->IsSmb() + || item->IsNfs() + || URIUtils::IsInRAR(item->GetPath()) + || URIUtils::IsInZIP(item->GetPath()) + || URIUtils::IsOnLAN(item->GetPath()) + ) + { + // stack cd# folders if contains only a single video file + + bool bMatch(false); + + VECCREGEXP::iterator expr = folderRegExps.begin(); + while (!bMatch && expr != folderRegExps.end()) + { + //CLog::Log(LOGDEBUG,"{}: Running expression {} on {}", __FUNCTION__, expr->GetPattern(), item->GetLabel()); + bMatch = (expr->RegFind(item->GetLabel().c_str()) != -1); + if (bMatch) + { + CFileItemList items; + CDirectory::GetDirectory(item->GetPath(), items, + CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), + DIR_FLAG_DEFAULTS); + // optimized to only traverse listing once by checking for filecount + // and recording last file item for later use + int nFiles = 0; + int index = -1; + for (int j = 0; j < items.Size(); j++) + { + if (!items[j]->m_bIsFolder) + { + nFiles++; + index = j; + } + + if (nFiles > 1) + break; + } + + if (nFiles == 1) + *item = *items[index]; + } + ++expr; + } + + // check for dvd folders + if (!bMatch) + { + std::string dvdPath = item->GetOpticalMediaPath(); + + if (!dvdPath.empty()) + { + // NOTE: should this be done for the CD# folders too? + item->m_bIsFolder = false; + item->SetPath(dvdPath); + item->SetLabel2(""); + item->SetLabelPreformatted(true); + m_sortDescription.sortBy = SortByNone; /* sorting is now broken */ + } + } + } + } + } +} + +void CFileItemList::StackFiles() +{ + // Precompile our REs + VECCREGEXP stackRegExps; + CRegExp tmpRegExp(true, CRegExp::autoUtf8); + const std::vector<std::string>& strStackRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoStackRegExps; + std::vector<std::string>::const_iterator strRegExp = strStackRegExps.begin(); + while (strRegExp != strStackRegExps.end()) + { + if (tmpRegExp.RegComp(*strRegExp)) + { + if (tmpRegExp.GetCaptureTotal() == 4) + stackRegExps.push_back(tmpRegExp); + else + CLog::Log(LOGERROR, "Invalid video stack RE ({}). Must have 4 captures.", + strRegExp->c_str()); + } + ++strRegExp; + } + + // now stack the files, some of which may be from the previous stack iteration + int i = 0; + while (i < Size()) + { + CFileItemPtr item1 = Get(i); + + // skip folders, nfo files, playlists + if (item1->m_bIsFolder + || item1->IsParentFolder() + || item1->IsNFO() + || item1->IsPlayList() + ) + { + // increment index + i++; + continue; + } + + int64_t size = 0; + size_t offset = 0; + std::string stackName; + std::string file1; + std::string filePath; + std::vector<int> stack; + VECCREGEXP::iterator expr = stackRegExps.begin(); + + URIUtils::Split(item1->GetPath(), filePath, file1); + if (URIUtils::HasEncodedFilename(CURL(filePath))) + file1 = CURL::Decode(file1); + + int j; + while (expr != stackRegExps.end()) + { + if (expr->RegFind(file1, offset) != -1) + { + std::string Title1 = expr->GetMatch(1), + Volume1 = expr->GetMatch(2), + Ignore1 = expr->GetMatch(3), + Extension1 = expr->GetMatch(4); + if (offset) + Title1 = file1.substr(0, expr->GetSubStart(2)); + j = i + 1; + while (j < Size()) + { + CFileItemPtr item2 = Get(j); + + // skip folders, nfo files, playlists + if (item2->m_bIsFolder + || item2->IsParentFolder() + || item2->IsNFO() + || item2->IsPlayList() + ) + { + // increment index + j++; + continue; + } + + std::string file2, filePath2; + URIUtils::Split(item2->GetPath(), filePath2, file2); + if (URIUtils::HasEncodedFilename(CURL(filePath2)) ) + file2 = CURL::Decode(file2); + + if (expr->RegFind(file2, offset) != -1) + { + std::string Title2 = expr->GetMatch(1), + Volume2 = expr->GetMatch(2), + Ignore2 = expr->GetMatch(3), + Extension2 = expr->GetMatch(4); + if (offset) + Title2 = file2.substr(0, expr->GetSubStart(2)); + if (StringUtils::EqualsNoCase(Title1, Title2)) + { + if (!StringUtils::EqualsNoCase(Volume1, Volume2)) + { + if (StringUtils::EqualsNoCase(Ignore1, Ignore2) && + StringUtils::EqualsNoCase(Extension1, Extension2)) + { + if (stack.empty()) + { + stackName = Title1 + Ignore1 + Extension1; + stack.push_back(i); + size += item1->m_dwSize; + } + stack.push_back(j); + size += item2->m_dwSize; + } + else // Sequel + { + offset = 0; + ++expr; + break; + } + } + else if (!StringUtils::EqualsNoCase(Ignore1, Ignore2)) // False positive, try again with offset + { + offset = expr->GetSubStart(3); + break; + } + else // Extension mismatch + { + offset = 0; + ++expr; + break; + } + } + else // Title mismatch + { + offset = 0; + ++expr; + break; + } + } + else // No match 2, next expression + { + offset = 0; + ++expr; + break; + } + j++; + } + if (j == Size()) + expr = stackRegExps.end(); + } + else // No match 1 + { + offset = 0; + ++expr; + } + if (stack.size() > 1) + { + // have a stack, remove the items and add the stacked item + // dont actually stack a multipart rar set, just remove all items but the first + std::string stackPath; + if (Get(stack[0])->IsRAR()) + stackPath = Get(stack[0])->GetPath(); + else + { + CStackDirectory dir; + stackPath = dir.ConstructStackPath(*this, stack); + } + item1->SetPath(stackPath); + // clean up list + for (unsigned k = 1; k < stack.size(); k++) + Remove(i+1); + // item->m_bIsFolder = true; // don't treat stacked files as folders + // the label may be in a different char set from the filename (eg over smb + // the label is converted from utf8, but the filename is not) + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS)) + URIUtils::RemoveExtension(stackName); + + item1->SetLabel(stackName); + item1->m_dwSize = size; + break; + } + } + i++; + } +} + +bool CFileItemList::Load(int windowID) +{ + CFile file; + auto path = GetDiscFileCache(windowID); + try + { + if (file.Open(path)) + { + CArchive ar(&file, CArchive::load); + ar >> *this; + CLog::Log(LOGDEBUG, "Loading items: {}, directory: {} sort method: {}, ascending: {}", Size(), + CURL::GetRedacted(GetPath()), m_sortDescription.sortBy, + m_sortDescription.sortOrder == SortOrderAscending ? "true" : "false"); + ar.Close(); + file.Close(); + return true; + } + } + catch(const std::out_of_range&) + { + CLog::Log(LOGERROR, "Corrupt archive: {}", CURL::GetRedacted(path)); + } + + return false; +} + +bool CFileItemList::Save(int windowID) +{ + int iSize = Size(); + if (iSize <= 0) + return false; + + CLog::Log(LOGDEBUG, "Saving fileitems [{}]", CURL::GetRedacted(GetPath())); + + CFile file; + std::string cachefile = GetDiscFileCache(windowID); + if (file.OpenForWrite(cachefile, true)) // overwrite always + { + // Before caching save simplified cache file name in every item so the cache file can be + // identifed and removed if the item is updated. List path and options (used for file + // name when list cached) can not be accurately derived from item path. + StringUtils::Replace(cachefile, "special://temp/archive_cache/", ""); + StringUtils::Replace(cachefile, ".fi", ""); + for (const auto& item : m_items) + item->SetProperty("cachefilename", cachefile); + + CArchive ar(&file, CArchive::store); + ar << *this; + CLog::Log(LOGDEBUG, " -- items: {}, sort method: {}, ascending: {}", iSize, + m_sortDescription.sortBy, + m_sortDescription.sortOrder == SortOrderAscending ? "true" : "false"); + ar.Close(); + file.Close(); + return true; + } + + return false; +} + +void CFileItemList::RemoveDiscCache(int windowID) const +{ + RemoveDiscCache(GetDiscFileCache(windowID)); +} + +void CFileItemList::RemoveDiscCache(const std::string& cacheFile) const +{ + if (CFile::Exists(cacheFile)) + { + CLog::Log(LOGDEBUG, "Clearing cached fileitems [{}]", CURL::GetRedacted(GetPath())); + CFile::Delete(cacheFile); + } +} + +void CFileItemList::RemoveDiscCacheCRC(const std::string& crc) const +{ + std::string cachefile = StringUtils::Format("special://temp/archive_cache/{}.fi", crc); + RemoveDiscCache(cachefile); +} + +std::string CFileItemList::GetDiscFileCache(int windowID) const +{ + std::string strPath(GetPath()); + URIUtils::RemoveSlashAtEnd(strPath); + + uint32_t crc = Crc32::ComputeFromLowerCase(strPath); + + if (IsCDDA() || IsOnDVD()) + return StringUtils::Format("special://temp/archive_cache/r-{:08x}.fi", crc); + + if (IsMusicDb()) + return StringUtils::Format("special://temp/archive_cache/mdb-{:08x}.fi", crc); + + if (IsVideoDb()) + return StringUtils::Format("special://temp/archive_cache/vdb-{:08x}.fi", crc); + + if (IsSmartPlayList()) + return StringUtils::Format("special://temp/archive_cache/sp-{:08x}.fi", crc); + + if (windowID) + return StringUtils::Format("special://temp/archive_cache/{}-{:08x}.fi", windowID, crc); + + return StringUtils::Format("special://temp/archive_cache/{:08x}.fi", crc); +} + +bool CFileItemList::AlwaysCache() const +{ + // some database folders are always cached + if (IsMusicDb()) + return CMusicDatabaseDirectory::CanCache(GetPath()); + if (IsVideoDb()) + return CVideoDatabaseDirectory::CanCache(GetPath()); + if (IsEPG()) + return true; // always cache + return false; +} + +std::string CFileItem::GetUserMusicThumb(bool alwaysCheckRemote /* = false */, bool fallbackToFolder /* = false */) const +{ + if (m_strPath.empty() + || StringUtils::StartsWithNoCase(m_strPath, "newsmartplaylist://") + || StringUtils::StartsWithNoCase(m_strPath, "newplaylist://") + || m_bIsShareOrDrive + || IsInternetStream() + || URIUtils::IsUPnP(m_strPath) + || (URIUtils::IsFTP(m_strPath) && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bFTPThumbs) + || IsPlugin() + || IsAddonsPath() + || IsLibraryFolder() + || IsParentFolder() + || IsMusicDb()) + return ""; + + // we first check for <filename>.tbn or <foldername>.tbn + std::string fileThumb(GetTBNFile()); + if (CFile::Exists(fileThumb)) + return fileThumb; + + // Fall back to folder thumb, if requested + if (!m_bIsFolder && fallbackToFolder) + { + CFileItem item(URIUtils::GetDirectory(m_strPath), true); + return item.GetUserMusicThumb(alwaysCheckRemote); + } + + // if a folder, check for folder.jpg + if (m_bIsFolder && !IsFileFolder() && (!IsRemote() || alwaysCheckRemote || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICFILES_FINDREMOTETHUMBS))) + { + std::vector<CVariant> thumbs = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList( + CSettings::SETTING_MUSICLIBRARY_MUSICTHUMBS); + for (const auto& i : thumbs) + { + std::string strFileName = i.asString(); + std::string folderThumb(GetFolderThumb(strFileName)); + if (CFile::Exists(folderThumb)) // folder.jpg + return folderThumb; + size_t period = strFileName.find_last_of('.'); + if (period != std::string::npos) + { + std::string ext; + std::string name = strFileName; + std::string folderThumb1 = folderThumb; + name.erase(period); + ext = strFileName.substr(period); + StringUtils::ToUpper(ext); + StringUtils::Replace(folderThumb1, strFileName, name + ext); + if (CFile::Exists(folderThumb1)) // folder.JPG + return folderThumb1; + + folderThumb1 = folderThumb; + std::string firstletter = name.substr(0, 1); + StringUtils::ToUpper(firstletter); + name.replace(0, 1, firstletter); + StringUtils::Replace(folderThumb1, strFileName, name + ext); + if (CFile::Exists(folderThumb1)) // Folder.JPG + return folderThumb1; + + folderThumb1 = folderThumb; + StringUtils::ToLower(ext); + StringUtils::Replace(folderThumb1, strFileName, name + ext); + if (CFile::Exists(folderThumb1)) // Folder.jpg + return folderThumb1; + } + } + } + // No thumb found + return ""; +} + +// Gets the .tbn filename from a file or folder name. +// <filename>.ext -> <filename>.tbn +// <foldername>/ -> <foldername>.tbn +std::string CFileItem::GetTBNFile() const +{ + std::string thumbFile; + std::string strFile = m_strPath; + + if (IsStack()) + { + std::string strPath, strReturn; + URIUtils::GetParentPath(m_strPath,strPath); + CFileItem item(CStackDirectory::GetFirstStackedFile(strFile),false); + std::string strTBNFile = item.GetTBNFile(); + strReturn = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); + if (CFile::Exists(strReturn)) + return strReturn; + + strFile = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(strFile))); + } + + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath,strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(m_strPath)); + } + + CURL url(strFile); + strFile = url.GetFileName(); + + if (m_bIsFolder && !IsFileFolder()) + URIUtils::RemoveSlashAtEnd(strFile); + + if (!strFile.empty()) + { + if (m_bIsFolder && !IsFileFolder()) + thumbFile = strFile + ".tbn"; // folder, so just add ".tbn" + else + thumbFile = URIUtils::ReplaceExtension(strFile, ".tbn"); + url.SetFileName(thumbFile); + thumbFile = url.Get(); + } + return thumbFile; +} + +bool CFileItem::SkipLocalArt() const +{ + return (m_strPath.empty() + || StringUtils::StartsWithNoCase(m_strPath, "newsmartplaylist://") + || StringUtils::StartsWithNoCase(m_strPath, "newplaylist://") + || m_bIsShareOrDrive + || IsInternetStream() + || URIUtils::IsUPnP(m_strPath) + || (URIUtils::IsFTP(m_strPath) && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bFTPThumbs) + || IsPlugin() + || IsAddonsPath() + || IsLibraryFolder() + || IsParentFolder() + || IsLiveTV() + || IsPVRRecording() + || IsDVD()); +} + +std::string CFileItem::GetThumbHideIfUnwatched(const CFileItem* item) const +{ + const std::shared_ptr<CSettingList> setting(std::dynamic_pointer_cast<CSettingList>( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting( + CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS))); + if (setting && item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_type == MediaTypeEpisode && + item->GetVideoInfoTag()->GetPlayCount() == 0 && + !CSettingUtils::FindIntInList(setting, + CSettings::VIDEOLIBRARY_THUMB_SHOW_UNWATCHED_EPISODE) && + item->HasArt("thumb")) + { + std::string fanArt = item->GetArt("fanart"); + if (fanArt.empty()) + return "OverlaySpoiler.png"; + else + return fanArt; + } + + return item->GetArt("thumb"); +} + +std::string CFileItem::FindLocalArt(const std::string &artFile, bool useFolder) const +{ + if (SkipLocalArt()) + return ""; + + std::string thumb; + if (!m_bIsFolder) + { + thumb = GetLocalArt(artFile, false); + if (!thumb.empty() && CFile::Exists(thumb)) + return thumb; + } + if ((useFolder || (m_bIsFolder && !IsFileFolder())) && !artFile.empty()) + { + std::string thumb2 = GetLocalArt(artFile, true); + if (!thumb2.empty() && thumb2 != thumb && CFile::Exists(thumb2)) + return thumb2; + } + return ""; +} + +std::string CFileItem::GetLocalArtBaseFilename() const +{ + bool useFolder = false; + return GetLocalArtBaseFilename(useFolder); +} + +std::string CFileItem::GetLocalArtBaseFilename(bool& useFolder) const +{ + std::string strFile = m_strPath; + if (IsStack()) + { + std::string strPath; + URIUtils::GetParentPath(m_strPath,strPath); + strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(strFile))); + } + + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath,strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(strFile)); + } + + if (IsMultiPath()) + strFile = CMultiPathDirectory::GetFirstPath(m_strPath); + + if (IsOpticalMediaFile()) + { // optical media files should be treated like folders + useFolder = true; + strFile = GetLocalMetadataPath(); + } + else if (useFolder && !(m_bIsFolder && !IsFileFolder())) + strFile = URIUtils::GetDirectory(strFile); + + return strFile; +} + +std::string CFileItem::GetLocalArt(const std::string& artFile, bool useFolder) const +{ + // no retrieving of empty art files from folders + if (useFolder && artFile.empty()) + return ""; + + std::string strFile = GetLocalArtBaseFilename(useFolder); + if (strFile.empty()) // empty filepath -> nothing to find + return ""; + + if (useFolder) + { + if (!artFile.empty()) + return URIUtils::AddFileToFolder(strFile, artFile); + } + else + { + if (artFile.empty()) // old thumbnail matching + return URIUtils::ReplaceExtension(strFile, ".tbn"); + else + return URIUtils::ReplaceExtension(strFile, "-" + artFile); + } + return ""; +} + +std::string CFileItem::GetFolderThumb(const std::string &folderJPG /* = "folder.jpg" */) const +{ + std::string strFolder = m_strPath; + + if (IsStack() || + URIUtils::IsInRAR(strFolder) || + URIUtils::IsInZIP(strFolder)) + { + URIUtils::GetParentPath(m_strPath,strFolder); + } + + if (IsMultiPath()) + strFolder = CMultiPathDirectory::GetFirstPath(m_strPath); + + if (IsPlugin()) + return ""; + + return URIUtils::AddFileToFolder(strFolder, folderJPG); +} + +std::string CFileItem::GetMovieName(bool bUseFolderNames /* = false */) const +{ + if (IsPlugin() && HasVideoInfoTag() && !GetVideoInfoTag()->m_strTitle.empty()) + return GetVideoInfoTag()->m_strTitle; + + if (IsLabelPreformatted()) + return GetLabel(); + + if (m_pvrRecordingInfoTag) + return m_pvrRecordingInfoTag->m_strTitle; + else if (URIUtils::IsPVRRecording(m_strPath)) + { + std::string title = CPVRRecording::GetTitleFromURL(m_strPath); + if (!title.empty()) + return title; + } + + std::string strMovieName; + if (URIUtils::IsStack(m_strPath)) + strMovieName = CStackDirectory::GetStackedTitlePath(m_strPath); + else + strMovieName = GetBaseMoviePath(bUseFolderNames); + + URIUtils::RemoveSlashAtEnd(strMovieName); + + return CURL::Decode(URIUtils::GetFileName(strMovieName)); +} + +std::string CFileItem::GetBaseMoviePath(bool bUseFolderNames) const +{ + std::string strMovieName = m_strPath; + + if (IsMultiPath()) + strMovieName = CMultiPathDirectory::GetFirstPath(m_strPath); + + if (IsOpticalMediaFile()) + return GetLocalMetadataPath(); + + if (bUseFolderNames && + (!m_bIsFolder || URIUtils::IsInArchive(m_strPath) || + (HasVideoInfoTag() && GetVideoInfoTag()->m_iDbId > 0 && !CMediaTypes::IsContainer(GetVideoInfoTag()->m_type)))) + { + std::string name2(strMovieName); + URIUtils::GetParentPath(name2,strMovieName); + if (URIUtils::IsInArchive(m_strPath)) + { + // Try to get archive itself, if empty take path before + name2 = CURL(m_strPath).GetHostName(); + if (name2.empty()) + name2 = strMovieName; + + URIUtils::GetParentPath(name2, strMovieName); + } + } + + return strMovieName; +} + +std::string CFileItem::GetLocalFanart() const +{ + if (IsVideoDb()) + { + if (!HasVideoInfoTag()) + return ""; // nothing can be done + CFileItem dbItem(m_bIsFolder ? GetVideoInfoTag()->m_strPath : GetVideoInfoTag()->m_strFileNameAndPath, m_bIsFolder); + return dbItem.GetLocalFanart(); + } + + std::string strFile2; + std::string strFile = m_strPath; + if (IsStack()) + { + std::string strPath; + URIUtils::GetParentPath(m_strPath,strPath); + CStackDirectory dir; + std::string strPath2; + strPath2 = dir.GetStackedTitlePath(strFile); + strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2)); + CFileItem item(dir.GetFirstStackedFile(m_strPath),false); + std::string strTBNFile(URIUtils::ReplaceExtension(item.GetTBNFile(), "-fanart")); + strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); + } + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath,strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(m_strPath)); + } + + // no local fanart available for these + if (IsInternetStream() + || URIUtils::IsUPnP(strFile) + || URIUtils::IsBluray(strFile) + || IsLiveTV() + || IsPlugin() + || IsAddonsPath() + || IsDVD() + || (URIUtils::IsFTP(strFile) && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bFTPThumbs) + || m_strPath.empty()) + return ""; + + std::string strDir = URIUtils::GetDirectory(strFile); + + if (strDir.empty()) + return ""; + + CFileItemList items; + CDirectory::GetDirectory(strDir, items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + if (IsOpticalMediaFile()) + { // grab from the optical media parent folder as well + CFileItemList moreItems; + CDirectory::GetDirectory(GetLocalMetadataPath(), moreItems, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + items.Append(moreItems); + } + + std::vector<std::string> fanarts = { "fanart" }; + + strFile = URIUtils::ReplaceExtension(strFile, "-fanart"); + fanarts.insert(m_bIsFolder ? fanarts.end() : fanarts.begin(), URIUtils::GetFileName(strFile)); + + if (!strFile2.empty()) + fanarts.insert(m_bIsFolder ? fanarts.end() : fanarts.begin(), URIUtils::GetFileName(strFile2)); + + for (std::vector<std::string>::const_iterator i = fanarts.begin(); i != fanarts.end(); ++i) + { + for (int j = 0; j < items.Size(); j++) + { + std::string strCandidate = URIUtils::GetFileName(items[j]->m_strPath); + URIUtils::RemoveExtension(strCandidate); + std::string strFanart = *i; + URIUtils::RemoveExtension(strFanart); + if (StringUtils::EqualsNoCase(strCandidate, strFanart)) + return items[j]->m_strPath; + } + } + + return ""; +} + +std::string CFileItem::GetLocalMetadataPath() const +{ + if (m_bIsFolder && !IsFileFolder()) + return m_strPath; + + std::string parent(URIUtils::GetParentPath(m_strPath)); + std::string parentFolder(parent); + URIUtils::RemoveSlashAtEnd(parentFolder); + parentFolder = URIUtils::GetFileName(parentFolder); + if (StringUtils::EqualsNoCase(parentFolder, "VIDEO_TS") || StringUtils::EqualsNoCase(parentFolder, "BDMV")) + { // go back up another one + parent = URIUtils::GetParentPath(parent); + } + return parent; +} + +bool CFileItem::LoadMusicTag() +{ + // not audio + if (!IsAudio()) + return false; + // already loaded? + if (HasMusicInfoTag() && m_musicInfoTag->Loaded()) + return true; + // check db + CMusicDatabase musicDatabase; + if (musicDatabase.Open()) + { + CSong song; + if (musicDatabase.GetSongByFileName(m_strPath, song)) + { + GetMusicInfoTag()->SetSong(song); + return true; + } + musicDatabase.Close(); + } + // load tag from file + CLog::Log(LOGDEBUG, "{}: loading tag information for file: {}", __FUNCTION__, m_strPath); + CMusicInfoTagLoaderFactory factory; + std::unique_ptr<IMusicInfoTagLoader> pLoader (factory.CreateLoader(*this)); + if (pLoader) + { + if (pLoader->Load(m_strPath, *GetMusicInfoTag())) + return true; + } + // no tag - try some other things + if (IsCDDA()) + { + // we have the tracknumber... + int iTrack = GetMusicInfoTag()->GetTrackNumber(); + if (iTrack >= 1) + { + std::string strText = g_localizeStrings.Get(554); // "Track" + if (!strText.empty() && strText[strText.size() - 1] != ' ') + strText += " "; + std::string strTrack = StringUtils::Format((strText + "{}"), iTrack); + GetMusicInfoTag()->SetTitle(strTrack); + GetMusicInfoTag()->SetLoaded(true); + return true; + } + } + else + { + std::string fileName = URIUtils::GetFileName(m_strPath); + URIUtils::RemoveExtension(fileName); + for (const std::string& fileFilter : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicTagsFromFileFilters) + { + CLabelFormatter formatter(fileFilter, ""); + if (formatter.FillMusicTag(fileName, GetMusicInfoTag())) + { + GetMusicInfoTag()->SetLoaded(true); + return true; + } + } + } + return false; +} + +bool CFileItem::LoadGameTag() +{ + // Already loaded? + if (HasGameInfoTag() && m_gameInfoTag->IsLoaded()) + return true; + + //! @todo + GetGameInfoTag(); + + m_gameInfoTag->SetLoaded(true); + + return false; +} + +bool CFileItem::LoadDetails() +{ + if (IsVideoDb()) + { + if (HasVideoInfoTag()) + return true; + + CVideoDatabase db; + if (!db.Open()) + return false; + + VIDEODATABASEDIRECTORY::CQueryParams params; + VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(GetPath(), params); + + if (params.GetMovieId() >= 0) + db.GetMovieInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetMovieId())); + else if (params.GetMVideoId() >= 0) + db.GetMusicVideoInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetMVideoId())); + else if (params.GetEpisodeId() >= 0) + db.GetEpisodeInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetEpisodeId())); + else if (params.GetSetId() >= 0) // movie set + db.GetSetInfo(static_cast<int>(params.GetSetId()), *GetVideoInfoTag(), this); + else if (params.GetTvShowId() >= 0) + { + if (params.GetSeason() >= 0) + { + const int idSeason = db.GetSeasonId(static_cast<int>(params.GetTvShowId()), + static_cast<int>(params.GetSeason())); + if (idSeason >= 0) + db.GetSeasonInfo(idSeason, *GetVideoInfoTag(), this); + } + else + db.GetTvShowInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetTvShowId()), + this); + } + else + { + db.Close(); + return false; + } + db.Close(); + return true; + } + + if (m_bIsFolder && URIUtils::IsPVRRecordingFileOrFolder(GetPath())) + { + if (HasProperty("watchedepisodes") || HasProperty("watched")) + return true; + + const std::string parentPath = URIUtils::GetParentPath(GetPath()); + + //! @todo optimize, find a way to set the details of the directory without loading its content. + CFileItemList items; + if (CDirectory::GetDirectory(parentPath, items, "", XFILE::DIR_FLAG_DEFAULTS)) + { + const std::string path = GetPath(); + const auto it = std::find_if(items.cbegin(), items.cend(), + [path](const auto& entry) { return entry->GetPath() == path; }); + if (it != items.cend()) + { + *this = *(*it); + return true; + } + } + + CLog::LogF(LOGERROR, "Error filling item details (path={})", GetPath()); + return false; + } + + //! @todo add support for other types on demand. + CLog::LogF(LOGDEBUG, "Unsupported item type (path={})", GetPath()); + return false; +} + +void CFileItemList::Swap(unsigned int item1, unsigned int item2) +{ + if (item1 != item2 && item1 < m_items.size() && item2 < m_items.size()) + std::swap(m_items[item1], m_items[item2]); +} + +bool CFileItemList::UpdateItem(const CFileItem *item) +{ + if (!item) + return false; + + std::unique_lock<CCriticalSection> lock(m_lock); + for (unsigned int i = 0; i < m_items.size(); i++) + { + CFileItemPtr pItem = m_items[i]; + if (pItem->IsSamePath(item)) + { + pItem->UpdateInfo(*item); + return true; + } + } + return false; +} + +void CFileItemList::AddSortMethod(SortBy sortBy, int buttonLabel, const LABEL_MASKS &labelMasks, SortAttribute sortAttributes /* = SortAttributeNone */) +{ + AddSortMethod(sortBy, sortAttributes, buttonLabel, labelMasks); +} + +void CFileItemList::AddSortMethod(SortBy sortBy, SortAttribute sortAttributes, int buttonLabel, const LABEL_MASKS &labelMasks) +{ + SortDescription sorting; + sorting.sortBy = sortBy; + sorting.sortAttributes = sortAttributes; + + AddSortMethod(sorting, buttonLabel, labelMasks); +} + +void CFileItemList::AddSortMethod(SortDescription sortDescription, int buttonLabel, const LABEL_MASKS &labelMasks) +{ + GUIViewSortDetails sort; + sort.m_sortDescription = sortDescription; + sort.m_buttonLabel = buttonLabel; + sort.m_labelMasks = labelMasks; + + m_sortDetails.push_back(sort); +} + +void CFileItemList::SetReplaceListing(bool replace) +{ + m_replaceListing = replace; +} + +void CFileItemList::ClearSortState() +{ + m_sortDescription.sortBy = SortByNone; + m_sortDescription.sortOrder = SortOrderNone; + m_sortDescription.sortAttributes = SortAttributeNone; +} + +bool CFileItem::HasVideoInfoTag() const +{ + // Note: CPVRRecording is derived from CVideoInfoTag + return m_pvrRecordingInfoTag.get() != nullptr || m_videoInfoTag != nullptr; +} + +CVideoInfoTag* CFileItem::GetVideoInfoTag() +{ + // Note: CPVRRecording is derived from CVideoInfoTag + if (m_pvrRecordingInfoTag) + return m_pvrRecordingInfoTag.get(); + else if (!m_videoInfoTag) + m_videoInfoTag = new CVideoInfoTag; + + return m_videoInfoTag; +} + +const CVideoInfoTag* CFileItem::GetVideoInfoTag() const +{ + // Note: CPVRRecording is derived from CVideoInfoTag + return m_pvrRecordingInfoTag ? m_pvrRecordingInfoTag.get() : m_videoInfoTag; +} + +CPictureInfoTag* CFileItem::GetPictureInfoTag() +{ + if (!m_pictureInfoTag) + m_pictureInfoTag = new CPictureInfoTag; + + return m_pictureInfoTag; +} + +MUSIC_INFO::CMusicInfoTag* CFileItem::GetMusicInfoTag() +{ + if (!m_musicInfoTag) + m_musicInfoTag = new MUSIC_INFO::CMusicInfoTag; + + return m_musicInfoTag; +} + +CGameInfoTag* CFileItem::GetGameInfoTag() +{ + if (!m_gameInfoTag) + m_gameInfoTag = new CGameInfoTag; + + return m_gameInfoTag; +} + +bool CFileItem::HasPVRChannelInfoTag() const +{ + return m_pvrChannelGroupMemberInfoTag && m_pvrChannelGroupMemberInfoTag->Channel() != nullptr; +} + +const std::shared_ptr<PVR::CPVRChannel> CFileItem::GetPVRChannelInfoTag() const +{ + return m_pvrChannelGroupMemberInfoTag ? m_pvrChannelGroupMemberInfoTag->Channel() + : std::shared_ptr<CPVRChannel>(); +} + +std::string CFileItem::FindTrailer() const +{ + std::string strFile2; + std::string strFile = m_strPath; + if (IsStack()) + { + std::string strPath; + URIUtils::GetParentPath(m_strPath,strPath); + CStackDirectory dir; + std::string strPath2; + strPath2 = dir.GetStackedTitlePath(strFile); + strFile = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strPath2)); + CFileItem item(dir.GetFirstStackedFile(m_strPath),false); + std::string strTBNFile(URIUtils::ReplaceExtension(item.GetTBNFile(), "-trailer")); + strFile2 = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strTBNFile)); + } + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath,strParent); + strFile = URIUtils::AddFileToFolder(strParent,URIUtils::GetFileName(m_strPath)); + } + + // no local trailer available for these + if (IsInternetStream() + || URIUtils::IsUPnP(strFile) + || URIUtils::IsBluray(strFile) + || IsLiveTV() + || IsPlugin() + || IsDVD()) + return ""; + + std::string strDir = URIUtils::GetDirectory(strFile); + CFileItemList items; + CDirectory::GetDirectory(strDir, items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO | DIR_FLAG_NO_FILE_DIRS); + URIUtils::RemoveExtension(strFile); + strFile += "-trailer"; + std::string strFile3 = URIUtils::AddFileToFolder(strDir, "movie-trailer"); + + // Precompile our REs + VECCREGEXP matchRegExps; + CRegExp tmpRegExp(true, CRegExp::autoUtf8); + const std::vector<std::string>& strMatchRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_trailerMatchRegExps; + + std::vector<std::string>::const_iterator strRegExp = strMatchRegExps.begin(); + while (strRegExp != strMatchRegExps.end()) + { + if (tmpRegExp.RegComp(*strRegExp)) + { + matchRegExps.push_back(tmpRegExp); + } + ++strRegExp; + } + + std::string strTrailer; + for (int i = 0; i < items.Size(); i++) + { + std::string strCandidate = items[i]->m_strPath; + URIUtils::RemoveExtension(strCandidate); + if (StringUtils::EqualsNoCase(strCandidate, strFile) || + StringUtils::EqualsNoCase(strCandidate, strFile2) || + StringUtils::EqualsNoCase(strCandidate, strFile3)) + { + strTrailer = items[i]->m_strPath; + break; + } + else + { + VECCREGEXP::iterator expr = matchRegExps.begin(); + + while (expr != matchRegExps.end()) + { + if (expr->RegFind(strCandidate) != -1) + { + strTrailer = items[i]->m_strPath; + i = items.Size(); + break; + } + ++expr; + } + } + } + + return strTrailer; +} + +VideoDbContentType CFileItem::GetVideoContentType() const +{ + VideoDbContentType type = VideoDbContentType::MOVIES; + if (HasVideoInfoTag() && GetVideoInfoTag()->m_type == MediaTypeTvShow) + type = VideoDbContentType::TVSHOWS; + if (HasVideoInfoTag() && GetVideoInfoTag()->m_type == MediaTypeEpisode) + return VideoDbContentType::EPISODES; + if (HasVideoInfoTag() && GetVideoInfoTag()->m_type == MediaTypeMusicVideo) + return VideoDbContentType::MUSICVIDEOS; + if (HasVideoInfoTag() && GetVideoInfoTag()->m_type == MediaTypeAlbum) + return VideoDbContentType::MUSICALBUMS; + + CVideoDatabaseDirectory dir; + VIDEODATABASEDIRECTORY::CQueryParams params; + dir.GetQueryParams(m_strPath, params); + if (params.GetSetId() != -1 && params.GetMovieId() == -1) // movie set + return VideoDbContentType::MOVIE_SETS; + + return type; +} + +CFileItem CFileItem::GetItemToPlay() const +{ + if (HasEPGInfoTag()) + { + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(*this); + if (groupMember) + return CFileItem(groupMember); + } + return *this; +} + +CBookmark CFileItem::GetResumePoint() const +{ + if (HasVideoInfoTag()) + return GetVideoInfoTag()->GetResumePoint(); + return CBookmark(); +} + +bool CFileItem::IsResumePointSet() const +{ + return GetResumePoint().IsSet(); +} + +double CFileItem::GetCurrentResumeTime() const +{ + return lrint(GetResumePoint().timeInSeconds); +} + +bool CFileItem::GetCurrentResumeTimeAndPartNumber(int64_t& startOffset, int& partNumber) const +{ + CBookmark resumePoint(GetResumePoint()); + if (resumePoint.IsSet()) + { + startOffset = llrint(resumePoint.timeInSeconds); + partNumber = resumePoint.partNumber; + return true; + } + return false; +} + +bool CFileItem::IsResumable() const +{ + return (!IsNFO() && !IsPlayList()) || IsType(".strm"); +} |