summaryrefslogtreecommitdiffstats
path: root/xbmc/listproviders
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/listproviders')
-rw-r--r--xbmc/listproviders/CMakeLists.txt11
-rw-r--r--xbmc/listproviders/DirectoryProvider.cpp586
-rw-r--r--xbmc/listproviders/DirectoryProvider.h102
-rw-r--r--xbmc/listproviders/IListProvider.cpp40
-rw-r--r--xbmc/listproviders/IListProvider.h116
-rw-r--r--xbmc/listproviders/MultiProvider.cpp123
-rw-r--r--xbmc/listproviders/MultiProvider.h45
-rw-r--r--xbmc/listproviders/StaticProvider.cpp137
-rw-r--r--xbmc/listproviders/StaticProvider.h39
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;
+};