2 * Copyright (C) 2005-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 "PluginDirectory.h"
12 #include "ServiceBroker.h"
14 #include "addons/AddonInstaller.h"
15 #include "addons/AddonManager.h"
16 #include "addons/IAddon.h"
17 #include "addons/PluginSource.h"
18 #include "interfaces/generic/RunningScriptObserver.h"
19 #include "messaging/ApplicationMessenger.h"
20 #include "settings/Settings.h"
21 #include "settings/SettingsComponent.h"
22 #include "utils/URIUtils.h"
23 #include "utils/log.h"
24 #include "video/VideoInfoTag.h"
28 using namespace XFILE
;
29 using namespace ADDON
;
30 using namespace KODI::MESSAGING
;
34 const unsigned int maxPluginResolutions
= 5;
37 \brief Get the plugin path from a CFileItem.
39 \param item CFileItem where to get the path.
40 \return The plugin path if found otherwise an empty string.
42 std::string
GetOriginalPluginPath(const CFileItem
& item
)
44 std::string currentPath
= item
.GetPath();
45 if (URIUtils::IsPlugin(currentPath
))
48 currentPath
= item
.GetDynPath();
49 if (URIUtils::IsPlugin(currentPath
))
54 } // unnamed namespace
56 CPluginDirectory::CPluginDirectory() : m_cancelled(false)
58 m_listItems
= new CFileItemList
;
59 m_fileResult
= new CFileItem
;
62 CPluginDirectory::~CPluginDirectory(void)
68 bool CPluginDirectory::StartScript(const std::string
& strPath
, bool resume
)
72 ADDON::AddonPtr addon
;
73 // try the plugin type first, and if not found, try an unknown type
74 if (!CServiceBroker::GetAddonMgr().GetAddon(url
.GetHostName(), addon
, ADDON_PLUGIN
,
75 OnlyEnabled::CHOICE_YES
) &&
76 !CServiceBroker::GetAddonMgr().GetAddon(url
.GetHostName(), addon
, ADDON_UNKNOWN
,
77 OnlyEnabled::CHOICE_YES
) &&
78 !CAddonInstaller::GetInstance().InstallModal(url
.GetHostName(), addon
,
79 InstallModalPrompt::CHOICE_YES
))
81 CLog::Log(LOGERROR
, "Unable to find plugin {}", url
.GetHostName());
85 // clear out our status variables
86 m_fileResult
->Reset();
88 m_listItems
->SetPath(strPath
);
89 m_listItems
->SetLabel(addon
->Name());
95 return RunScript(this, addon
, strPath
, resume
);
98 bool CPluginDirectory::GetResolvedPluginResult(CFileItem
& resultItem
)
100 std::string lastResolvedPath
;
101 if (resultItem
.HasProperty("ForceResolvePlugin") &&
102 resultItem
.GetProperty("ForceResolvePlugin").asBoolean())
104 // ensures that a plugin have the callback to resolve the paths in any case
105 // also when the same items in the playlist are played more times
106 lastResolvedPath
= GetOriginalPluginPath(resultItem
);
110 lastResolvedPath
= resultItem
.GetDynPath();
113 if (!lastResolvedPath
.empty())
115 // we try to resolve recursively up to n. (maxPluginResolutions) nested plugin paths
116 // to avoid deadlocks (plugin:// paths can resolve to plugin:// paths)
117 for (unsigned int i
= 0; URIUtils::IsPlugin(lastResolvedPath
) && i
< maxPluginResolutions
; ++i
)
119 bool resume
= resultItem
.GetStartOffset() == STARTOFFSET_RESUME
;
121 // we modify the item so that it becomes a real URL
122 if (!XFILE::CPluginDirectory::GetPluginResult(lastResolvedPath
, resultItem
, resume
) ||
123 resultItem
.GetDynPath() ==
124 resultItem
.GetPath()) // GetPluginResult resolved to an empty path
128 lastResolvedPath
= resultItem
.GetDynPath();
130 // if after the maximum allowed resolution attempts the item is still a plugin just return, it isn't playable
131 if (URIUtils::IsPlugin(resultItem
.GetDynPath()))
138 bool CPluginDirectory::GetPluginResult(const std::string
& strPath
, CFileItem
&resultItem
, bool resume
)
141 CPluginDirectory newDir
;
143 bool success
= newDir
.StartScript(strPath
, resume
);
146 { // update the play path and metadata, saving the old one as needed
147 if (!resultItem
.HasProperty("original_listitem_url"))
148 resultItem
.SetProperty("original_listitem_url", resultItem
.GetPath());
149 resultItem
.SetDynPath(newDir
.m_fileResult
->GetPath());
150 resultItem
.SetMimeType(newDir
.m_fileResult
->GetMimeType());
151 resultItem
.SetContentLookup(newDir
.m_fileResult
->ContentLookup());
153 if (resultItem
.HasProperty("OverrideInfotag") &&
154 resultItem
.GetProperty("OverrideInfotag").asBoolean())
155 resultItem
.UpdateInfo(*newDir
.m_fileResult
);
157 resultItem
.MergeInfo(*newDir
.m_fileResult
);
159 if (newDir
.m_fileResult
->HasVideoInfoTag() && newDir
.m_fileResult
->GetVideoInfoTag()->GetResumePoint().IsSet())
160 resultItem
.SetStartOffset(
161 STARTOFFSET_RESUME
); // resume point set in the resume item, so force resume
167 bool CPluginDirectory::AddItem(int handle
, const CFileItem
*item
, int totalItems
)
169 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
170 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
174 CFileItemPtr
pItem(new CFileItem(*item
));
175 dir
->m_listItems
->Add(pItem
);
176 dir
->m_totalItems
= totalItems
;
178 return !dir
->m_cancelled
;
181 bool CPluginDirectory::AddItems(int handle
, const CFileItemList
*items
, int totalItems
)
183 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
184 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
188 CFileItemList pItemList
;
189 pItemList
.Copy(*items
);
190 dir
->m_listItems
->Append(pItemList
);
191 dir
->m_totalItems
= totalItems
;
193 return !dir
->m_cancelled
;
196 void CPluginDirectory::EndOfDirectory(int handle
, bool success
, bool replaceListing
, bool cacheToDisc
)
198 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
199 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
204 dir
->m_listItems
->SetCacheToDisc(cacheToDisc
? CFileItemList::CACHE_IF_SLOW
: CFileItemList::CACHE_NEVER
);
206 dir
->m_success
= success
;
207 dir
->m_listItems
->SetReplaceListing(replaceListing
);
209 if (!dir
->m_listItems
->HasSortDetails())
210 dir
->m_listItems
->AddSortMethod(SortByNone
, 552, LABEL_MASKS("%L", "%D"));
212 // set the event to mark that we're done
216 void CPluginDirectory::AddSortMethod(int handle
, SORT_METHOD sortMethod
, const std::string
&labelMask
, const std::string
&label2Mask
)
218 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
219 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
223 //! @todo Add all sort methods and fix which labels go on the right or left
226 case SORT_METHOD_LABEL
:
227 case SORT_METHOD_LABEL_IGNORE_THE
:
229 dir
->m_listItems
->AddSortMethod(SortByLabel
, 551, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
232 case SORT_METHOD_TITLE
:
233 case SORT_METHOD_TITLE_IGNORE_THE
:
235 dir
->m_listItems
->AddSortMethod(SortByTitle
, 556, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
238 case SORT_METHOD_ARTIST
:
239 case SORT_METHOD_ARTIST_IGNORE_THE
:
241 dir
->m_listItems
->AddSortMethod(SortByArtist
, 557, LABEL_MASKS(labelMask
, "%A", labelMask
, "%A"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
244 case SORT_METHOD_ALBUM
:
245 case SORT_METHOD_ALBUM_IGNORE_THE
:
247 dir
->m_listItems
->AddSortMethod(SortByAlbum
, 558, LABEL_MASKS(labelMask
, "%B", labelMask
, "%B"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
250 case SORT_METHOD_DATE
:
252 dir
->m_listItems
->AddSortMethod(SortByDate
, 552, LABEL_MASKS(labelMask
, "%J", labelMask
, "%J"));
255 case SORT_METHOD_BITRATE
:
257 dir
->m_listItems
->AddSortMethod(SortByBitrate
, 623, LABEL_MASKS(labelMask
, "%X", labelMask
, "%X"));
260 case SORT_METHOD_SIZE
:
262 dir
->m_listItems
->AddSortMethod(SortBySize
, 553, LABEL_MASKS(labelMask
, "%I", labelMask
, "%I"));
265 case SORT_METHOD_FILE
:
267 dir
->m_listItems
->AddSortMethod(SortByFile
, 561, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
270 case SORT_METHOD_TRACKNUM
:
272 dir
->m_listItems
->AddSortMethod(SortByTrackNumber
, 554, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
275 case SORT_METHOD_DURATION
:
276 case SORT_METHOD_VIDEO_RUNTIME
:
278 dir
->m_listItems
->AddSortMethod(SortByTime
, 180, LABEL_MASKS(labelMask
, "%D", labelMask
, "%D"));
281 case SORT_METHOD_VIDEO_RATING
:
282 case SORT_METHOD_SONG_RATING
:
284 dir
->m_listItems
->AddSortMethod(SortByRating
, 563, LABEL_MASKS(labelMask
, "%R", labelMask
, "%R"));
287 case SORT_METHOD_YEAR
:
289 dir
->m_listItems
->AddSortMethod(SortByYear
, 562, LABEL_MASKS(labelMask
, "%Y", labelMask
, "%Y"));
292 case SORT_METHOD_GENRE
:
294 dir
->m_listItems
->AddSortMethod(SortByGenre
, 515, LABEL_MASKS(labelMask
, "%G", labelMask
, "%G"));
297 case SORT_METHOD_COUNTRY
:
299 dir
->m_listItems
->AddSortMethod(SortByCountry
, 574, LABEL_MASKS(labelMask
, "%G", labelMask
, "%G"));
302 case SORT_METHOD_VIDEO_TITLE
:
304 dir
->m_listItems
->AddSortMethod(SortByTitle
, 369, LABEL_MASKS(labelMask
, "%M", labelMask
, "%M"));
307 case SORT_METHOD_VIDEO_SORT_TITLE
:
308 case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE
:
310 dir
->m_listItems
->AddSortMethod(SortBySortTitle
, 556, LABEL_MASKS(labelMask
, "%M", labelMask
, "%M"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
313 case SORT_METHOD_VIDEO_ORIGINAL_TITLE
:
314 case SORT_METHOD_VIDEO_ORIGINAL_TITLE_IGNORE_THE
:
316 dir
->m_listItems
->AddSortMethod(
317 SortByOriginalTitle
, 20376, LABEL_MASKS(labelMask
, "%M", labelMask
, "%M"),
318 CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
319 CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
)
320 ? SortAttributeIgnoreArticle
321 : SortAttributeNone
);
324 case SORT_METHOD_MPAA_RATING
:
326 dir
->m_listItems
->AddSortMethod(SortByMPAA
, 20074, LABEL_MASKS(labelMask
, "%O", labelMask
, "%O"));
329 case SORT_METHOD_STUDIO
:
330 case SORT_METHOD_STUDIO_IGNORE_THE
:
332 dir
->m_listItems
->AddSortMethod(SortByStudio
, 572, LABEL_MASKS(labelMask
, "%U", labelMask
, "%U"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
335 case SORT_METHOD_PROGRAM_COUNT
:
337 dir
->m_listItems
->AddSortMethod(SortByProgramCount
, 567, LABEL_MASKS(labelMask
, "%C", labelMask
, "%C"));
340 case SORT_METHOD_UNSORTED
:
342 dir
->m_listItems
->AddSortMethod(SortByNone
, 571, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
345 case SORT_METHOD_NONE
:
347 dir
->m_listItems
->AddSortMethod(SortByNone
, 552, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
350 case SORT_METHOD_DRIVE_TYPE
:
352 dir
->m_listItems
->AddSortMethod(SortByDriveType
, 564, LABEL_MASKS()); // Preformatted
355 case SORT_METHOD_PLAYLIST_ORDER
:
357 std::string strTrack
=CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT
);
358 dir
->m_listItems
->AddSortMethod(SortByPlaylistOrder
, 559, LABEL_MASKS(strTrack
, "%D"));
361 case SORT_METHOD_EPISODE
:
363 dir
->m_listItems
->AddSortMethod(SortByEpisodeNumber
, 20359, LABEL_MASKS(labelMask
, "%R", labelMask
, "%R"));
366 case SORT_METHOD_PRODUCTIONCODE
:
368 //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P"));
369 dir
->m_listItems
->AddSortMethod(SortByProductionCode
, 20368, LABEL_MASKS(labelMask
, "%P", labelMask
, "%P"));
372 case SORT_METHOD_LISTENERS
:
374 dir
->m_listItems
->AddSortMethod(SortByListeners
, 20455, LABEL_MASKS(labelMask
, "%W"));
377 case SORT_METHOD_DATEADDED
:
379 dir
->m_listItems
->AddSortMethod(SortByDateAdded
, 570, LABEL_MASKS(labelMask
, "%a"));
382 case SORT_METHOD_FULLPATH
:
384 dir
->m_listItems
->AddSortMethod(SortByPath
, 573, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
387 case SORT_METHOD_LABEL_IGNORE_FOLDERS
:
389 dir
->m_listItems
->AddSortMethod(SortByLabel
, SortAttributeIgnoreFolders
, 551, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
392 case SORT_METHOD_LASTPLAYED
:
394 dir
->m_listItems
->AddSortMethod(SortByLastPlayed
, 568, LABEL_MASKS(labelMask
, "%G"));
397 case SORT_METHOD_PLAYCOUNT
:
399 dir
->m_listItems
->AddSortMethod(SortByPlaycount
, 567, LABEL_MASKS(labelMask
, "%V", labelMask
, "%V"));
402 case SORT_METHOD_CHANNEL
:
404 dir
->m_listItems
->AddSortMethod(SortByChannel
, 19029, LABEL_MASKS(labelMask
, label2Mask
, labelMask
, label2Mask
));
413 bool CPluginDirectory::GetDirectory(const CURL
& url
, CFileItemList
& items
)
415 const std::string
pathToUrl(url
.Get());
416 bool success
= StartScript(pathToUrl
, false);
418 // append the items to the list
419 items
.Assign(*m_listItems
, true); // true to keep the current items
420 m_listItems
->Clear();
424 bool CPluginDirectory::RunScriptWithParams(const std::string
& strPath
, bool resume
)
427 if (url
.GetHostName().empty()) // called with no script - should never happen
431 if (!CServiceBroker::GetAddonMgr().GetAddon(url
.GetHostName(), addon
, ADDON_PLUGIN
,
432 OnlyEnabled::CHOICE_YES
) &&
433 !CAddonInstaller::GetInstance().InstallModal(url
.GetHostName(), addon
,
434 InstallModalPrompt::CHOICE_YES
))
436 CLog::Log(LOGERROR
, "Unable to find plugin {}", url
.GetHostName());
440 return ExecuteScript(addon
, strPath
, resume
) >= 0;
443 void CPluginDirectory::SetResolvedUrl(int handle
, bool success
, const CFileItem
*resultItem
)
445 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
446 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
450 dir
->m_success
= success
;
451 *dir
->m_fileResult
= *resultItem
;
453 // set the event to mark that we're done
457 std::string
CPluginDirectory::GetSetting(int handle
, const std::string
&strID
)
459 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
460 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
461 if (dir
&& dir
->GetAddon())
462 return dir
->GetAddon()->GetSetting(strID
);
467 void CPluginDirectory::SetSetting(int handle
, const std::string
&strID
, const std::string
&value
)
469 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
470 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
471 if (dir
&& dir
->GetAddon())
472 dir
->GetAddon()->UpdateSetting(strID
, value
);
475 void CPluginDirectory::SetContent(int handle
, const std::string
&strContent
)
477 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
478 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
480 dir
->m_listItems
->SetContent(strContent
);
483 void CPluginDirectory::SetProperty(int handle
, const std::string
&strProperty
, const std::string
&strValue
)
485 std::unique_lock
<CCriticalSection
> lock(GetScriptsLock());
486 CPluginDirectory
* dir
= GetScriptFromHandle(handle
);
489 if (strProperty
== "fanart_image")
490 dir
->m_listItems
->SetArt("fanart", strValue
);
492 dir
->m_listItems
->SetProperty(strProperty
, strValue
);
495 void CPluginDirectory::CancelDirectory()
500 float CPluginDirectory::GetProgress() const
502 if (m_totalItems
> 0)
503 return (m_listItems
->Size() * 100.0f
) / m_totalItems
;
507 bool CPluginDirectory::IsMediaLibraryScanningAllowed(const std::string
& content
, const std::string
& strPath
)
513 if (url
.GetHostName().empty())
516 if (!CServiceBroker::GetAddonMgr().GetAddon(url
.GetHostName(), addon
, ADDON_PLUGIN
,
517 OnlyEnabled::CHOICE_YES
))
519 CLog::Log(LOGERROR
, "Unable to find plugin {}", url
.GetHostName());
522 CPluginSource
* plugin
= dynamic_cast<CPluginSource
*>(addon
.get());
526 auto& paths
= plugin
->MediaLibraryScanPaths();
529 auto it
= paths
.find(content
);
530 if (it
== paths
.end())
532 const std::string
& path
= url
.GetFileName();
533 for (const auto& p
: it
->second
)
534 if (p
.empty() || p
== "/" || URIUtils::PathHasParent(path
, p
))
539 bool CPluginDirectory::CheckExists(const std::string
& content
, const std::string
& strPath
)
541 if (!IsMediaLibraryScanningAllowed(content
, strPath
))
543 // call the plugin at specified path with option "kodi_action=check_exists"
544 // url exists if the plugin returns any fileitem with setResolvedUrl
546 url
.SetOption("kodi_action", "check_exists");
548 return CPluginDirectory::GetPluginResult(url
.Get(), item
, false);