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.
9 #include "DirectoryProvider.h"
11 #include "ContextMenuManager.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"
48 using namespace XFILE
;
50 using namespace KODI::MESSAGING
;
51 using namespace KODI::UTILS::GUILIB
;
54 class CDirectoryJob
: public CJob
57 CDirectoryJob(const std::string
& url
,
58 const std::string
& target
,
61 CDirectoryProvider::BrowseMode browse
,
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
)
84 bool DoWork() override
87 if (CDirectory::GetDirectory(m_url
, items
, "", DIR_FLAG_DEFAULTS
))
89 // sort the items if necessary
90 if (m_sort
.sortBy
!= SortByNone
)
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);
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
));
130 CLog::LogF(LOGWARNING
, "Cannot add 'More...' item to list. No target window given.");
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
178 for (const auto& i
: m_thumbloaders
)
179 itemTypes
.push_back(i
.first
);
184 std::string m_target
;
185 SortDescription m_sort
;
186 unsigned int m_limit
;
187 CDirectoryProvider::BrowseMode m_browse
{CDirectoryProvider::BrowseMode::AUTO
};
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
)
197 if (!element
->NoChildren())
199 const char *target
= element
->Attribute("target");
201 m_target
.SetLabel(target
, "", parentID
);
203 const char *sortMethod
= element
->Attribute("sortby");
205 m_sortMethod
.SetLabel(sortMethod
, "", parentID
);
207 const char *sortOrder
= element
->Attribute("sortorder");
209 m_sortOrder
.SetLabel(sortOrder
, "", parentID
);
211 const char *limit
= element
->Attribute("limit");
213 m_limit
.SetLabel(limit
, "", parentID
);
215 const char* browse
= element
->Attribute("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
),
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()
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
)
266 else if (m_updateState
== DONE
)
273 CLog::Log(LOGDEBUG
, "CDirectoryProvider[{}]: refreshing..", m_currentUrl
);
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
),
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())))
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
;
319 // if we're in a database transaction, don't bother doing anything just yet
320 if (data
.isMember("transaction") && data
["transaction"].asBoolean())
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
);
336 for (const auto& i
: m_items
)
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
);
396 CServiceBroker::GetJobManager()->CancelJob(m_jobID
);
399 m_currentTarget
.clear();
400 m_currentUrl
.clear();
402 m_currentSort
.sortBy
= SortByNone
;
403 m_currentSort
.sortOrder
= SortOrderAscending
;
405 m_currentBrowse
= BrowseMode::AUTO
;
409 std::unique_lock
<CCriticalSection
> subscriptionLock(m_subscriptionSection
);
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
);
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
;
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
);
448 target
= m_currentTarget
;
450 target
= m_target
.GetLabel(m_parentID
, false);
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
);
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())
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
);
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())
513 if (CPlayerUtils::IsItemPlayable(*targetItem
))
515 const CExecString exec
{*targetItem
, GetTarget(*targetItem
)};
516 if (exec
.GetFunction() == "playmedia")
519 return CGUIBuiltinsUtils::ExecuteAction(exec
, targetItem
);
523 // build a playmedia execute string for given target and exec this
524 return CGUIBuiltinsUtils::ExecutePlayMediaAskResume(targetItem
);
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
)
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
)};
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
)
564 m_currentUrl
= value
;
567 std::unique_lock
<CCriticalSection
> subscriptionLock(m_subscriptionSection
);
570 m_isSubscribed
= true;
571 CServiceBroker::GetAnnouncementManager()->AddAnnouncer(
572 this, ANNOUNCEMENT::VideoLibrary
| ANNOUNCEMENT::AudioLibrary
| ANNOUNCEMENT::Player
|
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
);
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
)
589 m_currentLimit
= value
;
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
)
609 m_currentBrowse
= value
;
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
)
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
);