/* * Copyright (C) 2005-2018 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 "PluginDirectory.h" #include "FileItem.h" #include "ServiceBroker.h" #include "URL.h" #include "addons/AddonInstaller.h" #include "addons/AddonManager.h" #include "addons/IAddon.h" #include "addons/PluginSource.h" #include "addons/addoninfo/AddonType.h" #include "interfaces/generic/RunningScriptObserver.h" #include "messaging/ApplicationMessenger.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/URIUtils.h" #include "utils/log.h" #include "video/VideoInfoTag.h" #include using namespace XFILE; using namespace ADDON; using namespace KODI::MESSAGING; namespace { const unsigned int maxPluginResolutions = 5; /*! \brief Get the plugin path from a CFileItem. \param item CFileItem where to get the path. \return The plugin path if found otherwise an empty string. */ std::string GetOriginalPluginPath(const CFileItem& item) { std::string currentPath = item.GetPath(); if (URIUtils::IsPlugin(currentPath)) return currentPath; currentPath = item.GetDynPath(); if (URIUtils::IsPlugin(currentPath)) return currentPath; return std::string(); } } // unnamed namespace CPluginDirectory::CPluginDirectory() : m_listItems(new CFileItemList), m_fileResult(new CFileItem), m_cancelled(false) { } CPluginDirectory::~CPluginDirectory(void) { } bool CPluginDirectory::StartScript(const std::string& strPath, bool resume) { CURL url(strPath); ADDON::AddonPtr addon; // try the plugin type first, and if not found, try an unknown type if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::PLUGIN, OnlyEnabled::CHOICE_YES) && !CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_YES) && !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon, InstallModalPrompt::CHOICE_YES)) { CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName()); return false; } // clear out our status variables m_fileResult->Reset(); m_listItems->Clear(); m_listItems->SetPath(strPath); m_listItems->SetLabel(addon->Name()); m_cancelled = false; m_success = false; m_totalItems = 0; // run the script return RunScript(this, addon, strPath, resume); } bool CPluginDirectory::GetResolvedPluginResult(CFileItem& resultItem) { std::string lastResolvedPath; if (resultItem.HasProperty("ForceResolvePlugin") && resultItem.GetProperty("ForceResolvePlugin").asBoolean()) { // ensures that a plugin have the callback to resolve the paths in any case // also when the same items in the playlist are played more times lastResolvedPath = GetOriginalPluginPath(resultItem); } else { lastResolvedPath = resultItem.GetDynPath(); } if (!lastResolvedPath.empty()) { // we try to resolve recursively up to n. (maxPluginResolutions) nested plugin paths // to avoid deadlocks (plugin:// paths can resolve to plugin:// paths) for (unsigned int i = 0; URIUtils::IsPlugin(lastResolvedPath) && i < maxPluginResolutions; ++i) { bool resume = resultItem.GetStartOffset() == STARTOFFSET_RESUME; // we modify the item so that it becomes a real URL if (!XFILE::CPluginDirectory::GetPluginResult(lastResolvedPath, resultItem, resume) || resultItem.GetDynPath() == resultItem.GetPath()) // GetPluginResult resolved to an empty path { return false; } lastResolvedPath = resultItem.GetDynPath(); } // if after the maximum allowed resolution attempts the item is still a plugin just return, it isn't playable if (URIUtils::IsPlugin(resultItem.GetDynPath())) return false; } return true; } bool CPluginDirectory::GetPluginResult(const std::string& strPath, CFileItem &resultItem, bool resume) { CURL url(strPath); CPluginDirectory newDir; bool success = newDir.StartScript(strPath, resume); if (success) { // update the play path and metadata, saving the old one as needed if (!resultItem.HasProperty("original_listitem_url")) resultItem.SetProperty("original_listitem_url", resultItem.GetPath()); resultItem.SetDynPath(newDir.m_fileResult->GetPath()); resultItem.SetMimeType(newDir.m_fileResult->GetMimeType()); resultItem.SetContentLookup(newDir.m_fileResult->ContentLookup()); if (resultItem.HasProperty("OverrideInfotag") && resultItem.GetProperty("OverrideInfotag").asBoolean()) resultItem.UpdateInfo(*newDir.m_fileResult); else resultItem.MergeInfo(*newDir.m_fileResult); if (newDir.m_fileResult->HasVideoInfoTag() && newDir.m_fileResult->GetVideoInfoTag()->GetResumePoint().IsSet()) resultItem.SetStartOffset( STARTOFFSET_RESUME); // resume point set in the resume item, so force resume } return success; } bool CPluginDirectory::AddItem(int handle, const CFileItem *item, int totalItems) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (!dir) return false; CFileItemPtr pItem(new CFileItem(*item)); dir->m_listItems->Add(pItem); dir->m_totalItems = totalItems; return !dir->m_cancelled; } bool CPluginDirectory::AddItems(int handle, const CFileItemList *items, int totalItems) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (!dir) return false; CFileItemList pItemList; pItemList.Copy(*items); dir->m_listItems->Append(pItemList); dir->m_totalItems = totalItems; return !dir->m_cancelled; } void CPluginDirectory::EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (!dir) return; // set cache to disc dir->m_listItems->SetCacheToDisc(cacheToDisc ? CFileItemList::CACHE_IF_SLOW : CFileItemList::CACHE_NEVER); dir->m_success = success; dir->m_listItems->SetReplaceListing(replaceListing); if (!dir->m_listItems->HasSortDetails()) dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS("%L", "%D")); // set the event to mark that we're done dir->SetDone(); } void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod, const std::string &labelMask, const std::string &label2Mask) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (!dir) return; //! @todo Add all sort methods and fix which labels go on the right or left switch(sortMethod) { case SORT_METHOD_LABEL: case SORT_METHOD_LABEL_IGNORE_THE: { dir->m_listItems->AddSortMethod(SortByLabel, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_TITLE: case SORT_METHOD_TITLE_IGNORE_THE: { dir->m_listItems->AddSortMethod(SortByTitle, 556, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_ARTIST: case SORT_METHOD_ARTIST_IGNORE_THE: { dir->m_listItems->AddSortMethod(SortByArtist, 557, LABEL_MASKS(labelMask, "%A", labelMask, "%A"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_ALBUM: case SORT_METHOD_ALBUM_IGNORE_THE: { dir->m_listItems->AddSortMethod(SortByAlbum, 558, LABEL_MASKS(labelMask, "%B", labelMask, "%B"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_DATE: { dir->m_listItems->AddSortMethod(SortByDate, 552, LABEL_MASKS(labelMask, "%J", labelMask, "%J")); break; } case SORT_METHOD_BITRATE: { dir->m_listItems->AddSortMethod(SortByBitrate, 623, LABEL_MASKS(labelMask, "%X", labelMask, "%X")); break; } case SORT_METHOD_SIZE: { dir->m_listItems->AddSortMethod(SortBySize, 553, LABEL_MASKS(labelMask, "%I", labelMask, "%I")); break; } case SORT_METHOD_FILE: { dir->m_listItems->AddSortMethod(SortByFile, 561, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } case SORT_METHOD_TRACKNUM: { dir->m_listItems->AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } case SORT_METHOD_DURATION: case SORT_METHOD_VIDEO_RUNTIME: { dir->m_listItems->AddSortMethod(SortByTime, 180, LABEL_MASKS(labelMask, "%D", labelMask, "%D")); break; } case SORT_METHOD_VIDEO_RATING: case SORT_METHOD_SONG_RATING: { dir->m_listItems->AddSortMethod(SortByRating, 563, LABEL_MASKS(labelMask, "%R", labelMask, "%R")); break; } case SORT_METHOD_YEAR: { dir->m_listItems->AddSortMethod(SortByYear, 562, LABEL_MASKS(labelMask, "%Y", labelMask, "%Y")); break; } case SORT_METHOD_GENRE: { dir->m_listItems->AddSortMethod(SortByGenre, 515, LABEL_MASKS(labelMask, "%G", labelMask, "%G")); break; } case SORT_METHOD_COUNTRY: { dir->m_listItems->AddSortMethod(SortByCountry, 574, LABEL_MASKS(labelMask, "%G", labelMask, "%G")); break; } case SORT_METHOD_VIDEO_TITLE: { dir->m_listItems->AddSortMethod(SortByTitle, 369, LABEL_MASKS(labelMask, "%M", labelMask, "%M")); break; } case SORT_METHOD_VIDEO_SORT_TITLE: case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE: { dir->m_listItems->AddSortMethod(SortBySortTitle, 556, LABEL_MASKS(labelMask, "%M", labelMask, "%M"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_VIDEO_ORIGINAL_TITLE: case SORT_METHOD_VIDEO_ORIGINAL_TITLE_IGNORE_THE: { dir->m_listItems->AddSortMethod( SortByOriginalTitle, 20376, LABEL_MASKS(labelMask, "%M", labelMask, "%M"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_MPAA_RATING: { dir->m_listItems->AddSortMethod(SortByMPAA, 20074, LABEL_MASKS(labelMask, "%O", labelMask, "%O")); break; } case SORT_METHOD_STUDIO: case SORT_METHOD_STUDIO_IGNORE_THE: { dir->m_listItems->AddSortMethod(SortByStudio, 572, LABEL_MASKS(labelMask, "%U", labelMask, "%U"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); break; } case SORT_METHOD_PROGRAM_COUNT: { dir->m_listItems->AddSortMethod(SortByProgramCount, 567, LABEL_MASKS(labelMask, "%C", labelMask, "%C")); break; } case SORT_METHOD_UNSORTED: { dir->m_listItems->AddSortMethod(SortByNone, 571, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } case SORT_METHOD_NONE: { dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } case SORT_METHOD_DRIVE_TYPE: { dir->m_listItems->AddSortMethod(SortByDriveType, 564, LABEL_MASKS()); // Preformatted break; } case SORT_METHOD_PLAYLIST_ORDER: { std::string strTrack=CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT); dir->m_listItems->AddSortMethod(SortByPlaylistOrder, 559, LABEL_MASKS(strTrack, "%D")); break; } case SORT_METHOD_EPISODE: { dir->m_listItems->AddSortMethod(SortByEpisodeNumber, 20359, LABEL_MASKS(labelMask, "%R", labelMask, "%R")); break; } case SORT_METHOD_PRODUCTIONCODE: { //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P")); dir->m_listItems->AddSortMethod(SortByProductionCode, 20368, LABEL_MASKS(labelMask, "%P", labelMask, "%P")); break; } case SORT_METHOD_LISTENERS: { dir->m_listItems->AddSortMethod(SortByListeners, 20455, LABEL_MASKS(labelMask, "%W")); break; } case SORT_METHOD_DATEADDED: { dir->m_listItems->AddSortMethod(SortByDateAdded, 570, LABEL_MASKS(labelMask, "%a")); break; } case SORT_METHOD_FULLPATH: { dir->m_listItems->AddSortMethod(SortByPath, 573, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } case SORT_METHOD_LABEL_IGNORE_FOLDERS: { dir->m_listItems->AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } case SORT_METHOD_LASTPLAYED: { dir->m_listItems->AddSortMethod(SortByLastPlayed, 568, LABEL_MASKS(labelMask, "%G")); break; } case SORT_METHOD_PLAYCOUNT: { dir->m_listItems->AddSortMethod(SortByPlaycount, 567, LABEL_MASKS(labelMask, "%V", labelMask, "%V")); break; } case SORT_METHOD_CHANNEL: { dir->m_listItems->AddSortMethod(SortByChannel, 19029, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask)); break; } default: break; } } bool CPluginDirectory::GetDirectory(const CURL& url, CFileItemList& items) { const std::string pathToUrl(url.Get()); bool success = StartScript(pathToUrl, false); // append the items to the list items.Assign(*m_listItems, true); // true to keep the current items m_listItems->Clear(); return success; } bool CPluginDirectory::RunScriptWithParams(const std::string& strPath, bool resume) { CURL url(strPath); if (url.GetHostName().empty()) // called with no script - should never happen return false; AddonPtr addon; if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::PLUGIN, OnlyEnabled::CHOICE_YES) && !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon, InstallModalPrompt::CHOICE_YES)) { CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName()); return false; } return ExecuteScript(addon, strPath, resume) >= 0; } void CPluginDirectory::SetResolvedUrl(int handle, bool success, const CFileItem *resultItem) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (!dir) return; dir->m_success = success; *dir->m_fileResult = *resultItem; // set the event to mark that we're done dir->SetDone(); } std::string CPluginDirectory::GetSetting(int handle, const std::string &strID) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (dir && dir->GetAddon()) return dir->GetAddon()->GetSetting(strID); else return ""; } void CPluginDirectory::SetSetting(int handle, const std::string &strID, const std::string &value) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (dir && dir->GetAddon()) dir->GetAddon()->UpdateSetting(strID, value); } void CPluginDirectory::SetContent(int handle, const std::string &strContent) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (dir) dir->m_listItems->SetContent(strContent); } void CPluginDirectory::SetProperty(int handle, const std::string &strProperty, const std::string &strValue) { std::unique_lock lock(GetScriptsLock()); CPluginDirectory* dir = GetScriptFromHandle(handle); if (!dir) return; if (strProperty == "fanart_image") dir->m_listItems->SetArt("fanart", strValue); else dir->m_listItems->SetProperty(strProperty, strValue); } void CPluginDirectory::CancelDirectory() { m_cancelled = true; } float CPluginDirectory::GetProgress() const { if (m_totalItems > 0) return (m_listItems->Size() * 100.0f) / m_totalItems; return 0.0f; } bool CPluginDirectory::IsMediaLibraryScanningAllowed(const std::string& content, const std::string& strPath) { if (content.empty()) return false; CURL url(strPath); if (url.GetHostName().empty()) return false; AddonPtr addon; if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::PLUGIN, OnlyEnabled::CHOICE_YES)) { CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName()); return false; } CPluginSource* plugin = dynamic_cast(addon.get()); if (!plugin) return false; auto& paths = plugin->MediaLibraryScanPaths(); if (paths.empty()) return false; auto it = paths.find(content); if (it == paths.end()) return false; const std::string& path = url.GetFileName(); for (const auto& p : it->second) if (p.empty() || p == "/" || URIUtils::PathHasParent(path, p)) return true; return false; } bool CPluginDirectory::CheckExists(const std::string& content, const std::string& strPath) { if (!IsMediaLibraryScanningAllowed(content, strPath)) return false; // call the plugin at specified path with option "kodi_action=check_exists" // url exists if the plugin returns any fileitem with setResolvedUrl CURL url(strPath); url.SetOption("kodi_action", "check_exists"); CFileItem item; return CPluginDirectory::GetPluginResult(url.Get(), item, false); }