Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / guilib / listproviders / DirectoryProvider.cpp
blob54cb922f5467d79f4f4b725e7b8025a503b70042
1 /*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "DirectoryProvider.h"
11 #include "ContextMenuManager.h"
12 #include "FileItem.h"
13 #include "ServiceBroker.h"
14 #include "addons/AddonManager.h"
15 #include "favourites/FavouritesService.h"
16 #include "filesystem/Directory.h"
17 #include "guilib/LocalizeStrings.h"
18 #include "interfaces/AnnouncementManager.h"
19 #include "music/MusicFileItemClassify.h"
20 #include "music/MusicThumbLoader.h"
21 #include "pictures/PictureThumbLoader.h"
22 #include "pvr/PVRManager.h"
23 #include "pvr/PVRThumbLoader.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "utils/ExecString.h"
27 #include "utils/JobManager.h"
28 #include "utils/PlayerUtils.h"
29 #include "utils/SortUtils.h"
30 #include "utils/StringUtils.h"
31 #include "utils/URIUtils.h"
32 #include "utils/Variant.h"
33 #include "utils/XMLUtils.h"
34 #include "utils/guilib/GUIBuiltinsUtils.h"
35 #include "utils/guilib/GUIContentUtils.h"
36 #include "utils/log.h"
37 #include "video/VideoFileItemClassify.h"
38 #include "video/VideoInfoTag.h"
39 #include "video/VideoThumbLoader.h"
40 #include "video/guilib/VideoGUIUtils.h"
41 #include "video/guilib/VideoPlayActionProcessor.h"
42 #include "video/guilib/VideoSelectActionProcessor.h"
44 #include <memory>
45 #include <mutex>
46 #include <utility>
48 using namespace XFILE;
49 using namespace KODI;
50 using namespace KODI::MESSAGING;
51 using namespace KODI::UTILS::GUILIB;
52 using namespace PVR;
54 class CDirectoryJob : public CJob
56 public:
57 CDirectoryJob(const std::string& url,
58 const std::string& target,
59 SortDescription sort,
60 int limit,
61 CDirectoryProvider::BrowseMode browse,
62 int parentID)
63 : m_url(url),
64 m_target(target),
65 m_sort(sort),
66 m_limit(limit),
67 m_browse(browse),
68 m_parentID(parentID)
69 { }
70 ~CDirectoryJob() override = default;
72 const char* GetType() const override { return "directory"; }
73 bool operator==(const CJob *job) const override
75 if (strcmp(job->GetType(),GetType()) == 0)
77 const CDirectoryJob* dirJob = dynamic_cast<const CDirectoryJob*>(job);
78 if (dirJob && dirJob->m_url == m_url)
79 return true;
81 return false;
84 bool DoWork() override
86 CFileItemList items;
87 if (CDirectory::GetDirectory(m_url, items, "", DIR_FLAG_DEFAULTS))
89 // sort the items if necessary
90 if (m_sort.sortBy != SortByNone)
91 items.Sort(m_sort);
93 // limit must not exceed the number of items
94 int limit = (m_limit == 0) ? items.Size() : std::min(static_cast<int>(m_limit), items.Size());
95 if (limit < items.Size())
96 m_items.reserve(limit + 1);
97 else
98 m_items.reserve(limit);
99 // convert to CGUIStaticItem's and set visibility and targets
100 for (int i = 0; i < limit; i++)
102 CGUIStaticItemPtr item(new CGUIStaticItem(*items[i]));
103 if (item->HasProperty("node.visible"))
104 item->SetVisibleCondition(item->GetProperty("node.visible").asString(), m_parentID);
106 getThumbLoader(item)->LoadItem(item.get());
108 m_items.push_back(item);
111 if (items.HasProperty("node.target"))
112 m_target = items.GetProperty("node.target").asString();
114 if ((m_browse == CDirectoryProvider::BrowseMode::ALWAYS && !items.IsEmpty()) ||
115 (m_browse == CDirectoryProvider::BrowseMode::AUTO && limit < items.Size()))
117 // Add a special item to the end of the list, which can be used to open the
118 // full listing containg all items in the given target window.
119 if (!m_target.empty())
121 CFileItem item(m_url, true);
122 item.SetLabel(g_localizeStrings.Get(22082)); // More...
123 item.SetArt("icon", "DefaultFolder.png");
124 item.SetProperty("node.target", m_target);
125 item.SetProperty("node.type", "target_folder"); // make item identifyable, e.g. by skins
127 m_items.emplace_back(std::make_shared<CGUIStaticItem>(item));
129 else
130 CLog::LogF(LOGWARNING, "Cannot add 'More...' item to list. No target window given.");
133 return true;
136 std::shared_ptr<CThumbLoader> getThumbLoader(const CGUIStaticItemPtr& item)
138 if (VIDEO::IsVideo(*item))
140 initThumbLoader<CVideoThumbLoader>(InfoTagType::VIDEO);
141 return m_thumbloaders[InfoTagType::VIDEO];
143 if (MUSIC::IsAudio(*item))
145 initThumbLoader<CMusicThumbLoader>(InfoTagType::AUDIO);
146 return m_thumbloaders[InfoTagType::AUDIO];
148 if (item->IsPicture())
150 initThumbLoader<CPictureThumbLoader>(InfoTagType::PICTURE);
151 return m_thumbloaders[InfoTagType::PICTURE];
153 if (item->IsPVRChannelGroup())
155 initThumbLoader<CPVRThumbLoader>(InfoTagType::PVR);
156 return m_thumbloaders[InfoTagType::PVR];
158 initThumbLoader<CProgramThumbLoader>(InfoTagType::PROGRAM);
159 return m_thumbloaders[InfoTagType::PROGRAM];
162 template<class CThumbLoaderClass>
163 void initThumbLoader(InfoTagType type)
165 if (!m_thumbloaders.count(type))
167 std::shared_ptr<CThumbLoader> thumbLoader = std::make_shared<CThumbLoaderClass>();
168 thumbLoader->OnLoaderStart();
169 m_thumbloaders.insert(make_pair(type, thumbLoader));
173 const std::vector<CGUIStaticItemPtr> &GetItems() const { return m_items; }
174 const std::string &GetTarget() const { return m_target; }
175 std::vector<InfoTagType> GetItemTypes(std::vector<InfoTagType> &itemTypes) const
177 itemTypes.clear();
178 for (const auto& i : m_thumbloaders)
179 itemTypes.push_back(i.first);
180 return itemTypes;
182 private:
183 std::string m_url;
184 std::string m_target;
185 SortDescription m_sort;
186 unsigned int m_limit;
187 CDirectoryProvider::BrowseMode m_browse{CDirectoryProvider::BrowseMode::AUTO};
188 int m_parentID;
189 std::vector<CGUIStaticItemPtr> m_items;
190 std::map<InfoTagType, std::shared_ptr<CThumbLoader> > m_thumbloaders;
193 CDirectoryProvider::CDirectoryProvider(const TiXmlElement* element, int parentID)
194 : IListProvider(parentID)
196 assert(element);
197 if (!element->NoChildren())
199 const char *target = element->Attribute("target");
200 if (target)
201 m_target.SetLabel(target, "", parentID);
203 const char *sortMethod = element->Attribute("sortby");
204 if (sortMethod)
205 m_sortMethod.SetLabel(sortMethod, "", parentID);
207 const char *sortOrder = element->Attribute("sortorder");
208 if (sortOrder)
209 m_sortOrder.SetLabel(sortOrder, "", parentID);
211 const char *limit = element->Attribute("limit");
212 if (limit)
213 m_limit.SetLabel(limit, "", parentID);
215 const char* browse = element->Attribute("browse");
216 if (browse)
217 m_browse.SetLabel(browse, "", parentID);
219 m_url.SetLabel(element->FirstChild()->ValueStr(), "", parentID);
223 CDirectoryProvider::CDirectoryProvider(const CDirectoryProvider& other)
224 : IListProvider(other.m_parentID),
225 m_updateState(INVALIDATED),
226 m_url(other.m_url),
227 m_target(other.m_target),
228 m_sortMethod(other.m_sortMethod),
229 m_sortOrder(other.m_sortOrder),
230 m_limit(other.m_limit),
231 m_browse(other.m_browse),
232 m_currentUrl(other.m_currentUrl),
233 m_currentTarget(other.m_currentTarget),
234 m_currentSort(other.m_currentSort),
235 m_currentLimit(other.m_currentLimit),
236 m_currentBrowse(other.m_currentBrowse)
240 CDirectoryProvider::~CDirectoryProvider()
242 Reset();
245 std::unique_ptr<IListProvider> CDirectoryProvider::Clone()
247 return std::make_unique<CDirectoryProvider>(*this);
250 bool CDirectoryProvider::Update(bool forceRefresh)
252 // we never need to force refresh here
253 bool changed = false;
254 bool fireJob = false;
256 // update the URL & limit and fire off a new job if needed
257 fireJob |= UpdateURL();
258 fireJob |= UpdateSort();
259 fireJob |= UpdateLimit();
260 fireJob |= UpdateBrowse();
261 fireJob &= !m_currentUrl.empty();
263 std::unique_lock<CCriticalSection> lock(m_section);
264 if (m_updateState == INVALIDATED)
265 fireJob = true;
266 else if (m_updateState == DONE)
267 changed = true;
269 m_updateState = OK;
271 if (fireJob)
273 CLog::Log(LOGDEBUG, "CDirectoryProvider[{}]: refreshing..", m_currentUrl);
274 if (m_jobID)
275 CServiceBroker::GetJobManager()->CancelJob(m_jobID);
276 m_jobID = CServiceBroker::GetJobManager()->AddJob(
277 new CDirectoryJob(m_currentUrl, m_target.GetLabel(m_parentID, false), m_currentSort,
278 m_currentLimit, m_currentBrowse, m_parentID),
279 this);
282 if (!changed)
284 for (auto& i : m_items)
285 changed |= i->UpdateVisibility(m_parentID);
287 return changed; //! @todo Also returned changed if properties are changed (if so, need to update scroll to letter).
290 void CDirectoryProvider::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
291 const std::string& sender,
292 const std::string& message,
293 const CVariant& data)
296 std::unique_lock<CCriticalSection> lock(m_section);
297 // we don't need to refresh anything if there are no fitting
298 // items in this list provider for the announcement flag
299 if (((flag & ANNOUNCEMENT::VideoLibrary) &&
300 (std::find(m_itemTypes.begin(), m_itemTypes.end(), InfoTagType::VIDEO) == m_itemTypes.end())) ||
301 ((flag & ANNOUNCEMENT::AudioLibrary) &&
302 (std::find(m_itemTypes.begin(), m_itemTypes.end(), InfoTagType::AUDIO) == m_itemTypes.end())))
303 return;
305 if (flag & ANNOUNCEMENT::Player)
307 if (message == "OnPlay" || message == "OnResume" || message == "OnStop")
309 if (m_currentSort.sortBy ==
310 SortByNone || // not nice, but many directories that need to be refreshed on start/stop have no special sort order (e.g. in progress movies)
311 m_currentSort.sortBy == SortByLastPlayed ||
312 m_currentSort.sortBy == SortByDateAdded || m_currentSort.sortBy == SortByPlaycount ||
313 m_currentSort.sortBy == SortByLastUsed)
314 m_updateState = INVALIDATED;
317 else
319 // if we're in a database transaction, don't bother doing anything just yet
320 if (data.isMember("transaction") && data["transaction"].asBoolean())
321 return;
323 // if there was a database update, we set the update state
324 // to PENDING to fire off a new job in the next update
325 if (message == "OnScanFinished" || message == "OnCleanFinished" || message == "OnUpdate" ||
326 message == "OnRemove" || message == "OnRefresh")
327 m_updateState = INVALIDATED;
332 void CDirectoryProvider::Fetch(std::vector<std::shared_ptr<CGUIListItem>>& items)
334 std::unique_lock<CCriticalSection> lock(m_section);
335 items.clear();
336 for (const auto& i : m_items)
338 if (i->IsVisible())
339 items.push_back(i);
343 void CDirectoryProvider::OnAddonEvent(const ADDON::AddonEvent& event)
345 std::unique_lock<CCriticalSection> lock(m_section);
346 if (URIUtils::IsProtocol(m_currentUrl, "addons"))
348 if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) ||
349 typeid(event) == typeid(ADDON::AddonEvents::Disabled) ||
350 typeid(event) == typeid(ADDON::AddonEvents::ReInstalled) ||
351 typeid(event) == typeid(ADDON::AddonEvents::UnInstalled) ||
352 typeid(event) == typeid(ADDON::AddonEvents::MetadataChanged) ||
353 typeid(event) == typeid(ADDON::AddonEvents::AutoUpdateStateChanged))
354 m_updateState = INVALIDATED;
358 void CDirectoryProvider::OnAddonRepositoryEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated& event)
360 std::unique_lock<CCriticalSection> lock(m_section);
361 if (URIUtils::IsProtocol(m_currentUrl, "addons"))
363 m_updateState = INVALIDATED;
367 void CDirectoryProvider::OnPVRManagerEvent(const PVR::PVREvent& event)
369 std::unique_lock<CCriticalSection> lock(m_section);
370 if (URIUtils::IsProtocol(m_currentUrl, "pvr"))
372 if (event == PVR::PVREvent::ManagerStarted || event == PVR::PVREvent::ManagerStopped ||
373 event == PVR::PVREvent::ManagerError || event == PVR::PVREvent::ManagerInterrupted ||
374 event == PVR::PVREvent::RecordingsInvalidated ||
375 event == PVR::PVREvent::TimersInvalidated ||
376 event == PVR::PVREvent::ChannelGroupsInvalidated ||
377 event == PVR::PVREvent::SavedSearchesInvalidated ||
378 event == PVR::PVREvent::ClientsInvalidated ||
379 event == PVR::PVREvent::ClientsPrioritiesInvalidated)
380 m_updateState = INVALIDATED;
384 void CDirectoryProvider::OnFavouritesEvent(const CFavouritesService::FavouritesUpdated& event)
386 std::unique_lock<CCriticalSection> lock(m_section);
387 if (URIUtils::IsProtocol(m_currentUrl, "favourites"))
388 m_updateState = INVALIDATED;
391 void CDirectoryProvider::Reset()
394 std::unique_lock<CCriticalSection> lock(m_section);
395 if (m_jobID)
396 CServiceBroker::GetJobManager()->CancelJob(m_jobID);
397 m_jobID = 0;
398 m_items.clear();
399 m_currentTarget.clear();
400 m_currentUrl.clear();
401 m_itemTypes.clear();
402 m_currentSort.sortBy = SortByNone;
403 m_currentSort.sortOrder = SortOrderAscending;
404 m_currentLimit = 0;
405 m_currentBrowse = BrowseMode::AUTO;
406 m_updateState = OK;
409 std::unique_lock<CCriticalSection> subscriptionLock(m_subscriptionSection);
410 if (m_isSubscribed)
412 m_isSubscribed = false;
413 CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
414 CServiceBroker::GetFavouritesService().Events().Unsubscribe(this);
415 CServiceBroker::GetRepositoryUpdater().Events().Unsubscribe(this);
416 CServiceBroker::GetAddonMgr().Events().Unsubscribe(this);
417 CServiceBroker::GetPVRManager().Events().Unsubscribe(this);
421 void CDirectoryProvider::FreeResources(bool immediately)
423 std::unique_lock<CCriticalSection> lock(m_section);
424 for (const auto& item : m_items)
425 item->FreeMemory(immediately);
428 void CDirectoryProvider::OnJobComplete(unsigned int jobID, bool success, CJob *job)
430 std::unique_lock<CCriticalSection> lock(m_section);
431 if (success)
433 m_items = static_cast<CDirectoryJob*>(job)->GetItems();
434 m_currentTarget = static_cast<CDirectoryJob*>(job)->GetTarget();
435 static_cast<CDirectoryJob*>(job)->GetItemTypes(m_itemTypes);
436 if (m_updateState == OK)
437 m_updateState = DONE;
439 m_jobID = 0;
442 std::string CDirectoryProvider::GetTarget(const CFileItem& item) const
444 std::string target = item.GetProperty("node.target").asString();
446 std::unique_lock<CCriticalSection> lock(m_section);
447 if (target.empty())
448 target = m_currentTarget;
449 if (target.empty())
450 target = m_target.GetLabel(m_parentID, false);
452 return target;
455 bool CDirectoryProvider::OnClick(const std::shared_ptr<CGUIListItem>& item)
457 std::shared_ptr<CFileItem> targetItem{std::static_pointer_cast<CFileItem>(item)};
459 if (targetItem->IsFavourite())
461 targetItem = CServiceBroker::GetFavouritesService().ResolveFavourite(*targetItem);
462 if (!targetItem)
463 return false;
466 const CExecString exec{*targetItem, GetTarget(*targetItem)};
467 const bool isPlayMedia{exec.GetFunction() == "playmedia"};
469 // video select action setting is for files only, except exec func is playmedia...
470 if (targetItem->HasVideoInfoTag() && (!targetItem->m_bIsFolder || isPlayMedia))
472 // play the given/default video version, even if multiple versions are available
473 targetItem->SetProperty("has_resolved_video_asset", true);
475 const std::string targetWindow{GetTarget(*targetItem)};
476 if (!targetWindow.empty())
477 targetItem->SetProperty("targetwindow", targetWindow);
479 KODI::VIDEO::GUILIB::CVideoSelectActionProcessor proc{targetItem};
480 if (proc.ProcessDefaultAction())
481 return true;
484 // exec the execute string for the original (!) item
485 CFileItem fileItem{*std::static_pointer_cast<CFileItem>(item)};
487 if (fileItem.HasProperty("node.target_url"))
488 fileItem.SetPath(fileItem.GetProperty("node.target_url").asString());
490 return CGUIBuiltinsUtils::ExecuteAction({fileItem, GetTarget(fileItem)}, targetItem);
493 bool CDirectoryProvider::OnPlay(const std::shared_ptr<CGUIListItem>& item)
495 std::shared_ptr<CFileItem> targetItem{std::static_pointer_cast<CFileItem>(item)};
497 if (targetItem->IsFavourite())
499 targetItem = CServiceBroker::GetFavouritesService().ResolveFavourite(*targetItem);
500 if (!targetItem)
501 return false;
504 // video play action setting is for files and folders...
505 if (targetItem->HasVideoInfoTag() ||
506 (targetItem->m_bIsFolder && VIDEO::UTILS::IsItemPlayable(*targetItem)))
508 KODI::VIDEO::GUILIB::CVideoPlayActionProcessor proc{targetItem};
509 if (proc.ProcessDefaultAction())
510 return true;
513 if (CPlayerUtils::IsItemPlayable(*targetItem))
515 const CExecString exec{*targetItem, GetTarget(*targetItem)};
516 if (exec.GetFunction() == "playmedia")
518 // exec as is
519 return CGUIBuiltinsUtils::ExecuteAction(exec, targetItem);
521 else
523 // build a playmedia execute string for given target and exec this
524 return CGUIBuiltinsUtils::ExecutePlayMediaAskResume(targetItem);
527 return true;
530 bool CDirectoryProvider::OnInfo(const std::shared_ptr<CGUIListItem>& item)
532 const auto fileItem{std::static_pointer_cast<CFileItem>(item)};
533 const auto targetItem{fileItem->IsFavourite()
534 ? CServiceBroker::GetFavouritesService().ResolveFavourite(*fileItem)
535 : fileItem};
537 return CGUIContentUtils::ShowInfoForItem(*targetItem);
540 bool CDirectoryProvider::OnContextMenu(const std::shared_ptr<CGUIListItem>& item)
542 const auto fileItem{std::static_pointer_cast<CFileItem>(item)};
543 const std::string target{GetTarget(*fileItem)};
544 if (!target.empty())
545 fileItem->SetProperty("targetwindow", target);
547 return CONTEXTMENU::ShowFor(fileItem, CContextMenuManager::MAIN);
550 bool CDirectoryProvider::IsUpdating() const
552 std::unique_lock<CCriticalSection> lock(m_section);
553 return m_jobID || m_updateState == DONE || m_updateState == INVALIDATED;
556 bool CDirectoryProvider::UpdateURL()
559 std::unique_lock<CCriticalSection> lock(m_section);
560 std::string value(m_url.GetLabel(m_parentID, false));
561 if (value == m_currentUrl)
562 return false;
564 m_currentUrl = value;
567 std::unique_lock<CCriticalSection> subscriptionLock(m_subscriptionSection);
568 if (!m_isSubscribed)
570 m_isSubscribed = true;
571 CServiceBroker::GetAnnouncementManager()->AddAnnouncer(
572 this, ANNOUNCEMENT::VideoLibrary | ANNOUNCEMENT::AudioLibrary | ANNOUNCEMENT::Player |
573 ANNOUNCEMENT::GUI);
574 CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CDirectoryProvider::OnAddonEvent);
575 CServiceBroker::GetRepositoryUpdater().Events().Subscribe(this, &CDirectoryProvider::OnAddonRepositoryEvent);
576 CServiceBroker::GetPVRManager().Events().Subscribe(this, &CDirectoryProvider::OnPVRManagerEvent);
577 CServiceBroker::GetFavouritesService().Events().Subscribe(this, &CDirectoryProvider::OnFavouritesEvent);
579 return true;
582 bool CDirectoryProvider::UpdateLimit()
584 std::unique_lock<CCriticalSection> lock(m_section);
585 unsigned int value = m_limit.GetIntValue(m_parentID);
586 if (value == m_currentLimit)
587 return false;
589 m_currentLimit = value;
591 return true;
594 bool CDirectoryProvider::UpdateBrowse()
596 std::unique_lock<CCriticalSection> lock(m_section);
597 const std::string stringValue{m_browse.GetLabel(m_parentID, false)};
598 BrowseMode value{m_currentBrowse};
599 if (StringUtils::EqualsNoCase(stringValue, "always"))
600 value = BrowseMode::ALWAYS;
601 else if (StringUtils::EqualsNoCase(stringValue, "auto"))
602 value = BrowseMode::AUTO;
603 else if (StringUtils::EqualsNoCase(stringValue, "never"))
604 value = BrowseMode::NEVER;
606 if (value == m_currentBrowse)
607 return false;
609 m_currentBrowse = value;
610 return true;
613 bool CDirectoryProvider::UpdateSort()
615 std::unique_lock<CCriticalSection> lock(m_section);
616 SortBy sortMethod(SortUtils::SortMethodFromString(m_sortMethod.GetLabel(m_parentID, false)));
617 SortOrder sortOrder(SortUtils::SortOrderFromString(m_sortOrder.GetLabel(m_parentID, false)));
618 if (sortOrder == SortOrderNone)
619 sortOrder = SortOrderAscending;
621 if (sortMethod == m_currentSort.sortBy && sortOrder == m_currentSort.sortOrder)
622 return false;
624 m_currentSort.sortBy = sortMethod;
625 m_currentSort.sortOrder = sortOrder;
626 m_currentSort.sortAttributes = SortAttributeIgnoreFolders;
628 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
629 m_currentSort.sortAttributes = static_cast<SortAttribute>(m_currentSort.sortAttributes | SortAttributeIgnoreArticle);
631 return true;