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/listproviders | |
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/listproviders')
-rw-r--r-- | xbmc/listproviders/CMakeLists.txt | 11 | ||||
-rw-r--r-- | xbmc/listproviders/DirectoryProvider.cpp | 586 | ||||
-rw-r--r-- | xbmc/listproviders/DirectoryProvider.h | 102 | ||||
-rw-r--r-- | xbmc/listproviders/IListProvider.cpp | 40 | ||||
-rw-r--r-- | xbmc/listproviders/IListProvider.h | 116 | ||||
-rw-r--r-- | xbmc/listproviders/MultiProvider.cpp | 123 | ||||
-rw-r--r-- | xbmc/listproviders/MultiProvider.h | 45 | ||||
-rw-r--r-- | xbmc/listproviders/StaticProvider.cpp | 137 | ||||
-rw-r--r-- | xbmc/listproviders/StaticProvider.h | 39 |
9 files changed, 1199 insertions, 0 deletions
diff --git a/xbmc/listproviders/CMakeLists.txt b/xbmc/listproviders/CMakeLists.txt new file mode 100644 index 0000000..e9c0ef1 --- /dev/null +++ b/xbmc/listproviders/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES DirectoryProvider.cpp + IListProvider.cpp + MultiProvider.cpp + StaticProvider.cpp) + +set(HEADERS DirectoryProvider.h + IListProvider.h + MultiProvider.h + StaticProvider.h) + +core_add_library(listproviders) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp new file mode 100644 index 0000000..6a9395f --- /dev/null +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2013-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 "DirectoryProvider.h" + +#include "ContextMenuManager.h" +#include "FileItem.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/gui/GUIDialogAddonInfo.h" +#include "favourites/FavouritesService.h" +#include "favourites/FavouritesURL.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" +#include "music/MusicThumbLoader.h" +#include "music/dialogs/GUIDialogMusicInfo.h" +#include "pictures/PictureThumbLoader.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRThumbLoader.h" +#include "pvr/guilib/PVRGUIActionsUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/ExecString.h" +#include "utils/JobManager.h" +#include "utils/PlayerUtils.h" +#include "utils/SortUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" +#include "video/VideoThumbLoader.h" +#include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/windows/GUIWindowVideoBase.h" + +#include <memory> +#include <mutex> +#include <utility> + +using namespace XFILE; +using namespace KODI::MESSAGING; +using namespace PVR; + +class CDirectoryJob : public CJob +{ +public: + CDirectoryJob(const std::string &url, SortDescription sort, int limit, int parentID) + : m_url(url), + m_sort(sort), + m_limit(limit), + m_parentID(parentID) + { } + ~CDirectoryJob() override = default; + + const char* GetType() const override { return "directory"; } + bool operator==(const CJob *job) const override + { + if (strcmp(job->GetType(),GetType()) == 0) + { + const CDirectoryJob* dirJob = dynamic_cast<const CDirectoryJob*>(job); + if (dirJob && dirJob->m_url == m_url) + return true; + } + return false; + } + + bool DoWork() override + { + CFileItemList items; + if (CDirectory::GetDirectory(m_url, items, "", DIR_FLAG_DEFAULTS)) + { + // sort the items if necessary + if (m_sort.sortBy != SortByNone) + items.Sort(m_sort); + + // limit must not exceed the number of items + int limit = (m_limit == 0) ? items.Size() : std::min((int) m_limit, items.Size()); + // convert to CGUIStaticItem's and set visibility and targets + m_items.reserve(limit); + for (int i = 0; i < limit; i++) + { + CGUIStaticItemPtr item(new CGUIStaticItem(*items[i])); + if (item->HasProperty("node.visible")) + item->SetVisibleCondition(item->GetProperty("node.visible").asString(), m_parentID); + + getThumbLoader(item)->LoadItem(item.get()); + + m_items.push_back(item); + } + m_target = items.GetProperty("node.target").asString(); + } + return true; + } + + std::shared_ptr<CThumbLoader> getThumbLoader(const CGUIStaticItemPtr& item) + { + if (item->IsVideo()) + { + initThumbLoader<CVideoThumbLoader>(InfoTagType::VIDEO); + return m_thumbloaders[InfoTagType::VIDEO]; + } + if (item->IsAudio()) + { + initThumbLoader<CMusicThumbLoader>(InfoTagType::AUDIO); + return m_thumbloaders[InfoTagType::AUDIO]; + } + if (item->IsPicture()) + { + initThumbLoader<CPictureThumbLoader>(InfoTagType::PICTURE); + return m_thumbloaders[InfoTagType::PICTURE]; + } + if (item->IsPVRChannelGroup()) + { + initThumbLoader<CPVRThumbLoader>(InfoTagType::PVR); + return m_thumbloaders[InfoTagType::PVR]; + } + initThumbLoader<CProgramThumbLoader>(InfoTagType::PROGRAM); + return m_thumbloaders[InfoTagType::PROGRAM]; + } + + template<class CThumbLoaderClass> + void initThumbLoader(InfoTagType type) + { + if (!m_thumbloaders.count(type)) + { + std::shared_ptr<CThumbLoader> thumbLoader = std::make_shared<CThumbLoaderClass>(); + thumbLoader->OnLoaderStart(); + m_thumbloaders.insert(make_pair(type, thumbLoader)); + } + } + + const std::vector<CGUIStaticItemPtr> &GetItems() const { return m_items; } + const std::string &GetTarget() const { return m_target; } + std::vector<InfoTagType> GetItemTypes(std::vector<InfoTagType> &itemTypes) const + { + itemTypes.clear(); + for (const auto& i : m_thumbloaders) + itemTypes.push_back(i.first); + return itemTypes; + } +private: + std::string m_url; + std::string m_target; + SortDescription m_sort; + unsigned int m_limit; + int m_parentID; + std::vector<CGUIStaticItemPtr> m_items; + std::map<InfoTagType, std::shared_ptr<CThumbLoader> > m_thumbloaders; +}; + +CDirectoryProvider::CDirectoryProvider(const TiXmlElement *element, int parentID) + : IListProvider(parentID), + m_updateState(OK), + m_jobID(0), + m_currentLimit(0) +{ + assert(element); + if (!element->NoChildren()) + { + const char *target = element->Attribute("target"); + if (target) + m_target.SetLabel(target, "", parentID); + + const char *sortMethod = element->Attribute("sortby"); + if (sortMethod) + m_sortMethod.SetLabel(sortMethod, "", parentID); + + const char *sortOrder = element->Attribute("sortorder"); + if (sortOrder) + m_sortOrder.SetLabel(sortOrder, "", parentID); + + const char *limit = element->Attribute("limit"); + if (limit) + m_limit.SetLabel(limit, "", parentID); + + m_url.SetLabel(element->FirstChild()->ValueStr(), "", parentID); + } +} + +CDirectoryProvider::CDirectoryProvider(const CDirectoryProvider& other) + : IListProvider(other.m_parentID), + m_updateState(INVALIDATED), + m_jobID(0), + m_url(other.m_url), + m_target(other.m_target), + m_sortMethod(other.m_sortMethod), + m_sortOrder(other.m_sortOrder), + m_limit(other.m_limit), + m_currentUrl(other.m_currentUrl), + m_currentTarget(other.m_currentTarget), + m_currentSort(other.m_currentSort), + m_currentLimit(other.m_currentLimit) +{ +} + +CDirectoryProvider::~CDirectoryProvider() +{ + Reset(); +} + +std::unique_ptr<IListProvider> CDirectoryProvider::Clone() +{ + return std::make_unique<CDirectoryProvider>(*this); +} + +bool CDirectoryProvider::Update(bool forceRefresh) +{ + // we never need to force refresh here + bool changed = false; + bool fireJob = false; + + // update the URL & limit and fire off a new job if needed + fireJob |= UpdateURL(); + fireJob |= UpdateSort(); + fireJob |= UpdateLimit(); + fireJob &= !m_currentUrl.empty(); + + std::unique_lock<CCriticalSection> lock(m_section); + if (m_updateState == INVALIDATED) + fireJob = true; + else if (m_updateState == DONE) + changed = true; + + m_updateState = OK; + + if (fireJob) + { + CLog::Log(LOGDEBUG, "CDirectoryProvider[{}]: refreshing..", m_currentUrl); + if (m_jobID) + CServiceBroker::GetJobManager()->CancelJob(m_jobID); + m_jobID = CServiceBroker::GetJobManager()->AddJob( + new CDirectoryJob(m_currentUrl, m_currentSort, m_currentLimit, m_parentID), this); + } + + if (!changed) + { + for (auto& i : m_items) + changed |= i->UpdateVisibility(m_parentID); + } + return changed; //! @todo Also returned changed if properties are changed (if so, need to update scroll to letter). +} + +void CDirectoryProvider::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + // we are only interested in library, player and GUI changes + if ((flag & (ANNOUNCEMENT::VideoLibrary | ANNOUNCEMENT::AudioLibrary | ANNOUNCEMENT::Player | ANNOUNCEMENT::GUI)) == 0) + return; + + { + std::unique_lock<CCriticalSection> lock(m_section); + // we don't need to refresh anything if there are no fitting + // items in this list provider for the announcement flag + if (((flag & ANNOUNCEMENT::VideoLibrary) && + (std::find(m_itemTypes.begin(), m_itemTypes.end(), InfoTagType::VIDEO) == m_itemTypes.end())) || + ((flag & ANNOUNCEMENT::AudioLibrary) && + (std::find(m_itemTypes.begin(), m_itemTypes.end(), InfoTagType::AUDIO) == m_itemTypes.end()))) + return; + + if (flag & ANNOUNCEMENT::Player) + { + if (message == "OnPlay" || message == "OnResume" || message == "OnStop") + { + if (m_currentSort.sortBy == SortByNone || // not nice, but many directories that need to be refreshed on start/stop have no special sort order (e.g. in progress movies) + m_currentSort.sortBy == SortByLastPlayed || + m_currentSort.sortBy == SortByPlaycount || + m_currentSort.sortBy == SortByLastUsed) + m_updateState = INVALIDATED; + } + } + else + { + // if we're in a database transaction, don't bother doing anything just yet + if (data.isMember("transaction") && data["transaction"].asBoolean()) + return; + + // if there was a database update, we set the update state + // to PENDING to fire off a new job in the next update + if (message == "OnScanFinished" || message == "OnCleanFinished" || message == "OnUpdate" || + message == "OnRemove" || message == "OnRefresh") + m_updateState = INVALIDATED; + } + } +} + +void CDirectoryProvider::Fetch(std::vector<CGUIListItemPtr> &items) +{ + std::unique_lock<CCriticalSection> lock(m_section); + items.clear(); + for (const auto& i : m_items) + { + if (i->IsVisible()) + items.push_back(i); + } +} + +void CDirectoryProvider::OnAddonEvent(const ADDON::AddonEvent& event) +{ + std::unique_lock<CCriticalSection> lock(m_section); + if (URIUtils::IsProtocol(m_currentUrl, "addons")) + { + if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) || + typeid(event) == typeid(ADDON::AddonEvents::Disabled) || + typeid(event) == typeid(ADDON::AddonEvents::ReInstalled) || + typeid(event) == typeid(ADDON::AddonEvents::UnInstalled) || + typeid(event) == typeid(ADDON::AddonEvents::MetadataChanged) || + typeid(event) == typeid(ADDON::AddonEvents::AutoUpdateStateChanged)) + m_updateState = INVALIDATED; + } +} + +void CDirectoryProvider::OnAddonRepositoryEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated& event) +{ + std::unique_lock<CCriticalSection> lock(m_section); + if (URIUtils::IsProtocol(m_currentUrl, "addons")) + { + m_updateState = INVALIDATED; + } +} + +void CDirectoryProvider::OnPVRManagerEvent(const PVR::PVREvent& event) +{ + std::unique_lock<CCriticalSection> lock(m_section); + if (URIUtils::IsProtocol(m_currentUrl, "pvr")) + { + if (event == PVR::PVREvent::ManagerStarted || event == PVR::PVREvent::ManagerStopped || + event == PVR::PVREvent::ManagerError || event == PVR::PVREvent::ManagerInterrupted || + event == PVR::PVREvent::RecordingsInvalidated || + event == PVR::PVREvent::TimersInvalidated || + event == PVR::PVREvent::ChannelGroupsInvalidated || + event == PVR::PVREvent::SavedSearchesInvalidated) + m_updateState = INVALIDATED; + } +} + +void CDirectoryProvider::OnFavouritesEvent(const CFavouritesService::FavouritesUpdated& event) +{ + std::unique_lock<CCriticalSection> lock(m_section); + if (URIUtils::IsProtocol(m_currentUrl, "favourites")) + m_updateState = INVALIDATED; +} + +void CDirectoryProvider::Reset() +{ + { + std::unique_lock<CCriticalSection> lock(m_section); + if (m_jobID) + CServiceBroker::GetJobManager()->CancelJob(m_jobID); + m_jobID = 0; + m_items.clear(); + m_currentTarget.clear(); + m_currentUrl.clear(); + m_itemTypes.clear(); + m_currentSort.sortBy = SortByNone; + m_currentSort.sortOrder = SortOrderAscending; + m_currentLimit = 0; + m_updateState = OK; + } + + std::unique_lock<CCriticalSection> subscriptionLock(m_subscriptionSection); + if (m_isSubscribed) + { + m_isSubscribed = false; + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + CServiceBroker::GetFavouritesService().Events().Unsubscribe(this); + CServiceBroker::GetRepositoryUpdater().Events().Unsubscribe(this); + CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); + CServiceBroker::GetPVRManager().Events().Unsubscribe(this); + } +} + +void CDirectoryProvider::FreeResources(bool immediately) +{ + std::unique_lock<CCriticalSection> lock(m_section); + for (const auto& item : m_items) + item->FreeMemory(immediately); +} + +void CDirectoryProvider::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + std::unique_lock<CCriticalSection> lock(m_section); + if (success) + { + m_items = static_cast<CDirectoryJob*>(job)->GetItems(); + m_currentTarget = static_cast<CDirectoryJob*>(job)->GetTarget(); + static_cast<CDirectoryJob*>(job)->GetItemTypes(m_itemTypes); + if (m_updateState == OK) + m_updateState = DONE; + } + m_jobID = 0; +} + +std::string CDirectoryProvider::GetTarget(const CFileItem& item) const +{ + std::string target = item.GetProperty("node.target").asString(); + + std::unique_lock<CCriticalSection> lock(m_section); + if (target.empty()) + target = m_currentTarget; + if (target.empty()) + target = m_target.GetLabel(m_parentID, false); + + return target; +} + +namespace +{ +bool ExecuteAction(const std::string& execute) +{ + if (!execute.empty()) + { + CGUIMessage message(GUI_MSG_EXECUTE, 0, 0); + message.SetStringParam(execute); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + return true; + } + return false; +} +} // namespace + +bool CDirectoryProvider::OnClick(const CGUIListItemPtr &item) +{ + CFileItem fileItem(*std::static_pointer_cast<CFileItem>(item)); + + if (fileItem.HasVideoInfoTag() + && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION) == SELECT_ACTION_INFO + && OnInfo(item)) + return true; + + if (fileItem.HasProperty("node.target_url")) + fileItem.SetPath(fileItem.GetProperty("node.target_url").asString()); + + // grab and execute the execute string + return ExecuteAction(CExecString(fileItem, GetTarget(fileItem)).GetExecString()); +} + +bool CDirectoryProvider::OnPlay(const CGUIListItemPtr& item) +{ + CFileItem fileItem(*std::static_pointer_cast<CFileItem>(item)); + + if (fileItem.IsFavourite()) + { + // Resolve the favourite + const CFavouritesURL url(fileItem.GetPath()); + if (url.IsValid()) + { + // If action is playmedia, just play it + if (url.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) + return ExecuteAction(url.GetExecString()); + + CFileItem targetItem(url.GetTarget(), url.IsDir()); + fileItem = targetItem; + } + } + + if (CPlayerUtils::IsItemPlayable(fileItem)) + { + CExecString exec(fileItem, {}); + if (exec.GetFunction() == "playmedia") + { + return ExecuteAction(exec.GetExecString()); + } + else + { + // build and execute a playmedia execute string + exec = CExecString("PlayMedia", {StringUtils::Paramify(fileItem.GetPath())}); + return ExecuteAction(exec.GetExecString()); + } + } + return true; +} + +bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) +{ + auto fileItem = std::static_pointer_cast<CFileItem>(item); + + if (fileItem->HasAddonInfo()) + { + return CGUIDialogAddonInfo::ShowForItem(fileItem); + } + else if (fileItem->IsPVR()) + { + return CServiceBroker::GetPVRManager().Get<PVR::GUI::Utils>().OnInfo(*fileItem); + } + else if (fileItem->HasVideoInfoTag()) + { + auto mediaType = fileItem->GetVideoInfoTag()->m_type; + if (mediaType == MediaTypeMovie || + mediaType == MediaTypeTvShow || + mediaType == MediaTypeEpisode || + mediaType == MediaTypeVideo || + mediaType == MediaTypeMusicVideo) + { + CGUIDialogVideoInfo::ShowFor(*fileItem); + return true; + } + } + else if (fileItem->HasMusicInfoTag()) + { + CGUIDialogMusicInfo::ShowFor(fileItem.get()); + return true; + } + return false; +} + +bool CDirectoryProvider::OnContextMenu(const CGUIListItemPtr& item) +{ + auto fileItem = std::static_pointer_cast<CFileItem>(item); + + const std::string target = GetTarget(*fileItem); + if (!target.empty()) + fileItem->SetProperty("targetwindow", target); + + return CONTEXTMENU::ShowFor(fileItem); +} + +bool CDirectoryProvider::IsUpdating() const +{ + std::unique_lock<CCriticalSection> lock(m_section); + return m_jobID || m_updateState == DONE || m_updateState == INVALIDATED; +} + +bool CDirectoryProvider::UpdateURL() +{ + { + std::unique_lock<CCriticalSection> lock(m_section); + std::string value(m_url.GetLabel(m_parentID, false)); + if (value == m_currentUrl) + return false; + + m_currentUrl = value; + } + + std::unique_lock<CCriticalSection> subscriptionLock(m_subscriptionSection); + if (!m_isSubscribed) + { + m_isSubscribed = true; + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CDirectoryProvider::OnAddonEvent); + CServiceBroker::GetRepositoryUpdater().Events().Subscribe(this, &CDirectoryProvider::OnAddonRepositoryEvent); + CServiceBroker::GetPVRManager().Events().Subscribe(this, &CDirectoryProvider::OnPVRManagerEvent); + CServiceBroker::GetFavouritesService().Events().Subscribe(this, &CDirectoryProvider::OnFavouritesEvent); + } + return true; +} + +bool CDirectoryProvider::UpdateLimit() +{ + std::unique_lock<CCriticalSection> lock(m_section); + unsigned int value = m_limit.GetIntValue(m_parentID); + if (value == m_currentLimit) + return false; + + m_currentLimit = value; + + return true; +} + +bool CDirectoryProvider::UpdateSort() +{ + std::unique_lock<CCriticalSection> lock(m_section); + SortBy sortMethod(SortUtils::SortMethodFromString(m_sortMethod.GetLabel(m_parentID, false))); + SortOrder sortOrder(SortUtils::SortOrderFromString(m_sortOrder.GetLabel(m_parentID, false))); + if (sortOrder == SortOrderNone) + sortOrder = SortOrderAscending; + + if (sortMethod == m_currentSort.sortBy && sortOrder == m_currentSort.sortOrder) + return false; + + m_currentSort.sortBy = sortMethod; + m_currentSort.sortOrder = sortOrder; + m_currentSort.sortAttributes = SortAttributeIgnoreFolders; + + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)) + m_currentSort.sortAttributes = static_cast<SortAttribute>(m_currentSort.sortAttributes | SortAttributeIgnoreArticle); + + return true; +} diff --git a/xbmc/listproviders/DirectoryProvider.h b/xbmc/listproviders/DirectoryProvider.h new file mode 100644 index 0000000..8e39dc7 --- /dev/null +++ b/xbmc/listproviders/DirectoryProvider.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "IListProvider.h" +#include "addons/AddonEvents.h" +#include "addons/RepositoryUpdater.h" +#include "favourites/FavouritesService.h" +#include "guilib/GUIStaticItem.h" +#include "interfaces/IAnnouncer.h" +#include "threads/CriticalSection.h" +#include "utils/Job.h" + +#include <string> +#include <vector> + +class TiXmlElement; +class CVariant; + +namespace PVR +{ + enum class PVREvent; +} + +enum class InfoTagType +{ + VIDEO, + AUDIO, + PICTURE, + PROGRAM, + PVR, +}; + +class CDirectoryProvider : + public IListProvider, + public IJobCallback, + public ANNOUNCEMENT::IAnnouncer +{ +public: + typedef enum + { + OK, + INVALIDATED, + DONE + } UpdateState; + + CDirectoryProvider(const TiXmlElement *element, int parentID); + explicit CDirectoryProvider(const CDirectoryProvider& other); + ~CDirectoryProvider() override; + + // Implementation of IListProvider + std::unique_ptr<IListProvider> Clone() override; + bool Update(bool forceRefresh) override; + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + void Fetch(std::vector<CGUIListItemPtr> &items) override; + void Reset() override; + bool OnClick(const CGUIListItemPtr &item) override; + bool OnPlay(const CGUIListItemPtr& item) override; + bool OnInfo(const CGUIListItemPtr &item) override; + bool OnContextMenu(const CGUIListItemPtr &item) override; + bool IsUpdating() const override; + void FreeResources(bool immediately) override; + + // callback from directory job + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; +private: + UpdateState m_updateState; + unsigned int m_jobID; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_url; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_target; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_sortMethod; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_sortOrder; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_limit; + std::string m_currentUrl; + std::string m_currentTarget; ///< \brief node.target property on the list as a whole + SortDescription m_currentSort; + unsigned int m_currentLimit; + std::vector<CGUIStaticItemPtr> m_items; + std::vector<InfoTagType> m_itemTypes; + mutable CCriticalSection m_section; + + bool UpdateURL(); + bool UpdateLimit(); + bool UpdateSort(); + void OnAddonEvent(const ADDON::AddonEvent& event); + void OnAddonRepositoryEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated& event); + void OnPVRManagerEvent(const PVR::PVREvent& event); + void OnFavouritesEvent(const CFavouritesService::FavouritesUpdated& event); + std::string GetTarget(const CFileItem& item) const; + + CCriticalSection m_subscriptionSection; + bool m_isSubscribed{false}; +}; diff --git a/xbmc/listproviders/IListProvider.cpp b/xbmc/listproviders/IListProvider.cpp new file mode 100644 index 0000000..3979319 --- /dev/null +++ b/xbmc/listproviders/IListProvider.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013-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 "IListProvider.h" + +#include "DirectoryProvider.h" +#include "MultiProvider.h" +#include "StaticProvider.h" +#include "utils/XBMCTinyXML.h" + +std::unique_ptr<IListProvider> IListProvider::Create(const TiXmlNode* node, int parentID) +{ + const TiXmlNode *root = node->FirstChild("content"); + if (root) + { + const TiXmlNode *next = root->NextSibling("content"); + if (next) + return std::make_unique<CMultiProvider>(root, parentID); + + return CreateSingle(root, parentID); + } + return std::unique_ptr<IListProvider>{}; +} + +std::unique_ptr<IListProvider> IListProvider::CreateSingle(const TiXmlNode* content, int parentID) +{ + const TiXmlElement *item = content->FirstChildElement("item"); + if (item) + return std::make_unique<CStaticListProvider>(content->ToElement(), parentID); + + if (!content->NoChildren()) + return std::make_unique<CDirectoryProvider>(content->ToElement(), parentID); + + return std::unique_ptr<IListProvider>{}; +} diff --git a/xbmc/listproviders/IListProvider.h b/xbmc/listproviders/IListProvider.h new file mode 100644 index 0000000..fea2860 --- /dev/null +++ b/xbmc/listproviders/IListProvider.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include <memory> +#include <vector> + +class TiXmlNode; +class CGUIListItem; +typedef std::shared_ptr<CGUIListItem> CGUIListItemPtr; + +/*! + \ingroup listproviders + \brief An interface for providing lists to UI containers. + */ +class IListProvider +{ +public: + explicit IListProvider(int parentID) : m_parentID(parentID) {} + explicit IListProvider(const IListProvider& other) = default; + virtual ~IListProvider() = default; + + /*! \brief Factory to create list providers. + \param parent a parent TiXmlNode for the container. + \param parentID id of parent window for context. + \return the list provider, empty pointer if none. + */ + static std::unique_ptr<IListProvider> Create(const TiXmlNode* parent, int parentID); + + /*! \brief Factory to create list providers. Cannot create a multi-provider. + \param content the TiXmlNode for the content to create. + \param parentID id of parent window for context. + \return the list provider, empty pointer if none. + */ + static std::unique_ptr<IListProvider> CreateSingle(const TiXmlNode* content, int parentID); + + /*! \brief Create an instance of the derived class. Allows for polymorphic copies. + */ + virtual std::unique_ptr<IListProvider> Clone() = 0; + + /*! \brief Update the list content + \return true if the content has changed, false otherwise. + */ + virtual bool Update(bool forceRefresh)=0; + + /*! \brief Fetch the current list of items. + \param items [out] the list to be filled. + */ + virtual void Fetch(std::vector<CGUIListItemPtr> &items)=0; + + /*! \brief Check whether the list provider is updating content. + \return true if in the processing of updating, false otherwise. + */ + virtual bool IsUpdating() const { return false; } + + /*! \brief Reset the current list of items. + Derived classes may choose to ignore this. + */ + virtual void Reset() {} + + /*! \brief Free all GUI resources allocated by the items. + \param immediately true to free resources immediately, free resources async later otherwise. + */ + virtual void FreeResources(bool immediately) {} + + /*! \brief Click event on an item. + \param item the item that was clicked. + \return true if the click was handled, false otherwise. + */ + virtual bool OnClick(const CGUIListItemPtr &item)=0; + + /*! \brief Play event on an item. + \param item the item to play. + \return true if the event was handled, false otherwise. + */ + virtual bool OnPlay(const CGUIListItemPtr& item) { return false; } + + /*! \brief Open the info dialog for an item provided by this IListProvider. + \param item the item that was clicked. + \return true if the dialog was shown, false otherwise. + */ + virtual bool OnInfo(const CGUIListItemPtr &item)=0; + + /*! \brief Open the context menu for an item provided by this IListProvider. + \param item the item that was clicked. + \return true if the click was handled, false otherwise. + */ + virtual bool OnContextMenu(const CGUIListItemPtr &item)=0; + + /*! \brief Set the default item to focus. For backwards compatibility. + \param item the item to focus. + \param always whether this item should always be used on first focus. + \sa GetDefaultItem, AlwaysFocusDefaultItem + */ + virtual void SetDefaultItem(int item, bool always) {} + + /*! \brief The default item to focus. + \return the item to focus by default. -1 for none. + \sa SetDefaultItem, AlwaysFocusDefaultItem + */ + virtual int GetDefaultItem() const { return -1; } + + /*! \brief Whether to always focus the default item. + \return true if the default item should always be the one to receive focus. + \sa GetDefaultItem, SetDefaultItem + */ + virtual bool AlwaysFocusDefaultItem() const { return false; } +protected: + int m_parentID; +}; diff --git a/xbmc/listproviders/MultiProvider.cpp b/xbmc/listproviders/MultiProvider.cpp new file mode 100644 index 0000000..e05a037 --- /dev/null +++ b/xbmc/listproviders/MultiProvider.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013-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 "MultiProvider.h" + +#include "utils/XBMCTinyXML.h" + +#include <mutex> + +CMultiProvider::CMultiProvider(const TiXmlNode *first, int parentID) + : IListProvider(parentID) +{ + for (const TiXmlNode *content = first; content; content = content->NextSiblingElement("content")) + { + IListProviderPtr sub(IListProvider::CreateSingle(content, parentID)); + if (sub) + m_providers.push_back(std::move(sub)); + } +} + +CMultiProvider::CMultiProvider(const CMultiProvider& other) : IListProvider(other.m_parentID) +{ + for (const auto& provider : other.m_providers) + { + std::unique_ptr<IListProvider> newProvider = provider->Clone(); + if (newProvider) + m_providers.emplace_back(std::move(newProvider)); + } +} + +std::unique_ptr<IListProvider> CMultiProvider::Clone() +{ + return std::make_unique<CMultiProvider>(*this); +} + +bool CMultiProvider::Update(bool forceRefresh) +{ + bool result = false; + for (auto& provider : m_providers) + result |= provider->Update(forceRefresh); + return result; +} + +void CMultiProvider::Fetch(std::vector<CGUIListItemPtr> &items) +{ + std::unique_lock<CCriticalSection> lock(m_section); + std::vector<CGUIListItemPtr> subItems; + items.clear(); + m_itemMap.clear(); + for (auto const& provider : m_providers) + { + provider->Fetch(subItems); + for (auto& item : subItems) + { + auto key = GetItemKey(item); + m_itemMap[key] = provider.get(); + items.push_back(item); + } + subItems.clear(); + } +} + +bool CMultiProvider::IsUpdating() const +{ + bool result = false; + for (auto const& provider : m_providers) + result |= provider->IsUpdating(); + return result; +} + +void CMultiProvider::Reset() +{ + { + std::unique_lock<CCriticalSection> lock(m_section); + m_itemMap.clear(); + } + + for (auto const& provider : m_providers) + provider->Reset(); +} + +bool CMultiProvider::OnClick(const CGUIListItemPtr &item) +{ + std::unique_lock<CCriticalSection> lock(m_section); + auto key = GetItemKey(item); + auto it = m_itemMap.find(key); + if (it != m_itemMap.end()) + return it->second->OnClick(item); + else + return false; +} + +bool CMultiProvider::OnInfo(const CGUIListItemPtr &item) +{ + std::unique_lock<CCriticalSection> lock(m_section); + auto key = GetItemKey(item); + auto it = m_itemMap.find(key); + if (it != m_itemMap.end()) + return it->second->OnInfo(item); + else + return false; +} + +bool CMultiProvider::OnContextMenu(const CGUIListItemPtr &item) +{ + std::unique_lock<CCriticalSection> lock(m_section); + auto key = GetItemKey(item); + auto it = m_itemMap.find(key); + if (it != m_itemMap.end()) + return it->second->OnContextMenu(item); + else + return false; +} + +CMultiProvider::item_key_type CMultiProvider::GetItemKey(CGUIListItemPtr const &item) +{ + return reinterpret_cast<item_key_type>(item.get()); +} diff --git a/xbmc/listproviders/MultiProvider.h b/xbmc/listproviders/MultiProvider.h new file mode 100644 index 0000000..9eeddc0 --- /dev/null +++ b/xbmc/listproviders/MultiProvider.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "IListProvider.h" +#include "threads/CriticalSection.h" + +#include <map> +#include <vector> + +typedef std::unique_ptr<IListProvider> IListProviderPtr; + +/*! + \ingroup listproviders + \brief A listprovider that handles multiple individual providers. + */ +class CMultiProvider : public IListProvider +{ +public: + CMultiProvider(const TiXmlNode *first, int parentID); + explicit CMultiProvider(const CMultiProvider& other); + + // Implementation of IListProvider + std::unique_ptr<IListProvider> Clone() override; + bool Update(bool forceRefresh) override; + void Fetch(std::vector<CGUIListItemPtr> &items) override; + bool IsUpdating() const override; + void Reset() override; + bool OnClick(const CGUIListItemPtr &item) override; + bool OnInfo(const CGUIListItemPtr &item) override; + bool OnContextMenu(const CGUIListItemPtr &item) override; + +protected: + typedef size_t item_key_type; + static item_key_type GetItemKey(CGUIListItemPtr const &item); + std::vector<IListProviderPtr> m_providers; + std::map<item_key_type, IListProvider*> m_itemMap; + CCriticalSection m_section; // protects m_itemMap +}; diff --git a/xbmc/listproviders/StaticProvider.cpp b/xbmc/listproviders/StaticProvider.cpp new file mode 100644 index 0000000..a5750e6 --- /dev/null +++ b/xbmc/listproviders/StaticProvider.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2013-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 "StaticProvider.h" + +#include "utils/StringUtils.h" +#include "utils/TimeUtils.h" +#include "utils/XMLUtils.h" + +CStaticListProvider::CStaticListProvider(const TiXmlElement *element, int parentID) +: IListProvider(parentID), + m_defaultItem(-1), + m_defaultAlways(false), + m_updateTime(0) +{ + assert(element); + + const TiXmlElement *item = element->FirstChildElement("item"); + while (item) + { + if (item->FirstChild()) + { + CGUIStaticItemPtr newItem(new CGUIStaticItem(item, parentID)); + m_items.push_back(newItem); + } + item = item->NextSiblingElement("item"); + } + + if (XMLUtils::GetInt(element, "default", m_defaultItem)) + { + const char *always = element->FirstChildElement("default")->Attribute("always"); + if (always && StringUtils::CompareNoCase(always, "true", 4) == 0) + m_defaultAlways = true; + } +} + +CStaticListProvider::CStaticListProvider(const std::vector<CGUIStaticItemPtr> &items) +: IListProvider(0), + m_defaultItem(-1), + m_defaultAlways(false), + m_updateTime(0), + m_items(items) +{ +} + +CStaticListProvider::CStaticListProvider(const CStaticListProvider& other) + : IListProvider(other.m_parentID), + m_defaultItem(other.m_defaultItem), + m_defaultAlways(other.m_defaultAlways), + m_updateTime(other.m_updateTime) +{ + for (const auto& item : other.m_items) + { + std::shared_ptr<CGUIListItem> control(item->Clone()); + if (!control) + continue; + + std::shared_ptr<CGUIStaticItem> newItem = std::dynamic_pointer_cast<CGUIStaticItem>(control); + if (!newItem) + continue; + + m_items.emplace_back(std::move(newItem)); + } +} + +CStaticListProvider::~CStaticListProvider() = default; + +std::unique_ptr<IListProvider> CStaticListProvider::Clone() +{ + return std::make_unique<CStaticListProvider>(*this); +} + +bool CStaticListProvider::Update(bool forceRefresh) +{ + bool changed = forceRefresh; + if (!m_updateTime) + m_updateTime = CTimeUtils::GetFrameTime(); + else if (CTimeUtils::GetFrameTime() - m_updateTime > 1000) + { + m_updateTime = CTimeUtils::GetFrameTime(); + for (auto& i : m_items) + i->UpdateProperties(m_parentID); + } + for (auto& i : m_items) + changed |= i->UpdateVisibility(m_parentID); + return changed; //! @todo Also returned changed if properties are changed (if so, need to update scroll to letter). +} + +void CStaticListProvider::Fetch(std::vector<CGUIListItemPtr> &items) +{ + items.clear(); + for (const auto& i : m_items) + { + if (i->IsVisible()) + items.push_back(i); + } +} + +void CStaticListProvider::SetDefaultItem(int item, bool always) +{ + m_defaultItem = item; + m_defaultAlways = always; +} + +int CStaticListProvider::GetDefaultItem() const +{ + if (m_defaultItem >= 0) + { + unsigned int offset = 0; + for (const auto& i : m_items) + { + if (i->IsVisible()) + { + if (i->m_iprogramCount == m_defaultItem) + return offset; + offset++; + } + } + } + return -1; +} + +bool CStaticListProvider::AlwaysFocusDefaultItem() const +{ + return m_defaultAlways; +} + +bool CStaticListProvider::OnClick(const CGUIListItemPtr &item) +{ + CGUIStaticItem *staticItem = static_cast<CGUIStaticItem*>(item.get()); + return staticItem->GetClickActions().ExecuteActions(0, m_parentID); +} diff --git a/xbmc/listproviders/StaticProvider.h b/xbmc/listproviders/StaticProvider.h new file mode 100644 index 0000000..6aea6b1 --- /dev/null +++ b/xbmc/listproviders/StaticProvider.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "IListProvider.h" +#include "guilib/GUIStaticItem.h" + +#include <vector> + +class CStaticListProvider : public IListProvider +{ +public: + CStaticListProvider(const TiXmlElement *element, int parentID); + explicit CStaticListProvider(const std::vector<CGUIStaticItemPtr> &items); // for python + explicit CStaticListProvider(const CStaticListProvider& other); + ~CStaticListProvider() override; + + // Implementation of IListProvider + std::unique_ptr<IListProvider> Clone() override; + bool Update(bool forceRefresh) override; + void Fetch(std::vector<CGUIListItemPtr> &items) override; + bool OnClick(const CGUIListItemPtr &item) override; + bool OnInfo(const CGUIListItemPtr &item) override { return false; } + bool OnContextMenu(const CGUIListItemPtr &item) override { return false; } + void SetDefaultItem(int item, bool always) override; + int GetDefaultItem() const override; + bool AlwaysFocusDefaultItem() const override; +private: + int m_defaultItem; + bool m_defaultAlways; + unsigned int m_updateTime; + std::vector<CGUIStaticItemPtr> m_items; +}; |