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 "AddonsDirectory.h"
12 #include "ServiceBroker.h"
14 #include "addons/AddonDatabase.h"
15 #include "addons/AddonInstaller.h"
16 #include "addons/AddonManager.h"
17 #include "addons/AddonRepos.h"
18 #include "addons/AddonSystemSettings.h"
19 #include "addons/PluginSource.h"
20 #include "addons/RepositoryUpdater.h"
21 #include "addons/addoninfo/AddonInfo.h"
22 #include "addons/addoninfo/AddonType.h"
23 #include "games/GameUtils.h"
24 #include "games/addons/GameClient.h"
25 #include "guilib/LocalizeStrings.h"
26 #include "guilib/TextureManager.h"
27 #include "interfaces/generic/ScriptInvocationManager.h"
28 #include "messaging/helpers/DialogOKHelper.h"
29 #include "settings/Settings.h"
30 #include "settings/SettingsComponent.h"
31 #include "utils/StringUtils.h"
32 #include "utils/URIUtils.h"
40 using namespace ADDON
;
41 using namespace KODI::MESSAGING
;
46 CAddonsDirectory::CAddonsDirectory(void) = default;
48 CAddonsDirectory::~CAddonsDirectory(void) = default;
50 const auto CATEGORY_INFO_PROVIDERS
= "category.infoproviders";
51 const auto CATEGORY_LOOK_AND_FEEL
= "category.lookandfeel";
52 const auto CATEGORY_GAME_ADDONS
= "category.gameaddons";
53 const auto CATEGORY_EMULATORS
= "category.emulators";
54 const auto CATEGORY_STANDALONE_GAMES
= "category.standalonegames";
55 const auto CATEGORY_GAME_PROVIDERS
= "category.gameproviders";
56 const auto CATEGORY_GAME_RESOURCES
= "category.gameresources";
57 const auto CATEGORY_GAME_SUPPORT_ADDONS
= "category.gamesupport";
59 const std::set
<AddonType
> infoProviderTypes
= {
60 AddonType::SCRAPER_ALBUMS
, AddonType::SCRAPER_ARTISTS
, AddonType::SCRAPER_MOVIES
,
61 AddonType::SCRAPER_MUSICVIDEOS
, AddonType::SCRAPER_TVSHOWS
,
64 const std::set
<AddonType
> lookAndFeelTypes
= {
66 AddonType::SCREENSAVER
,
67 AddonType::RESOURCE_IMAGES
,
68 AddonType::RESOURCE_LANGUAGE
,
69 AddonType::RESOURCE_UISOUNDS
,
70 AddonType::RESOURCE_FONT
,
71 AddonType::VISUALIZATION
,
74 const std::set
<AddonType
> gameTypes
= {
75 AddonType::GAME_CONTROLLER
,
78 AddonType::RESOURCE_GAMES
,
81 static bool IsInfoProviderType(AddonType type
)
83 return infoProviderTypes
.find(type
) != infoProviderTypes
.end();
86 static bool IsInfoProviderTypeAddon(const AddonPtr
& addon
)
88 return IsInfoProviderType(addon
->Type());
91 static bool IsLookAndFeelType(AddonType type
)
93 return lookAndFeelTypes
.find(type
) != lookAndFeelTypes
.end();
96 static bool IsLookAndFeelTypeAddon(const AddonPtr
& addon
)
98 return IsLookAndFeelType(addon
->Type());
101 static bool IsGameType(AddonType type
)
103 return gameTypes
.find(type
) != gameTypes
.end();
106 static bool IsStandaloneGame(const AddonPtr
& addon
)
108 return GAME::CGameUtils::IsStandaloneGame(addon
);
111 static bool IsEmulator(const AddonPtr
& addon
)
113 return addon
->Type() == AddonType::GAMEDLL
&&
114 std::static_pointer_cast
<GAME::CGameClient
>(addon
)->SupportsPath();
117 static bool IsGameProvider(const AddonPtr
& addon
)
119 return addon
->Type() == AddonType::PLUGIN
&& addon
->HasType(AddonType::GAME
);
122 static bool IsGameResource(const AddonPtr
& addon
)
124 return addon
->Type() == AddonType::RESOURCE_GAMES
;
127 static bool IsGameSupportAddon(const AddonPtr
& addon
)
129 return addon
->Type() == AddonType::GAMEDLL
&&
130 !std::static_pointer_cast
<GAME::CGameClient
>(addon
)->SupportsPath() &&
131 !std::static_pointer_cast
<GAME::CGameClient
>(addon
)->SupportsStandalone();
134 static bool IsGameAddon(const AddonPtr
& addon
)
136 return IsGameType(addon
->Type()) ||
137 IsStandaloneGame(addon
) ||
138 IsGameProvider(addon
) ||
139 IsGameResource(addon
) ||
140 IsGameSupportAddon(addon
);
143 static bool IsUserInstalled(const AddonPtr
& addon
)
145 return !CAddonType::IsDependencyType(addon
->MainType());
148 // Creates categories from addon types, if we have any addons with that type.
149 static void GenerateTypeListing(const CURL
& path
,
150 const std::set
<AddonType
>& types
,
151 const VECADDONS
& addons
,
152 CFileItemList
& items
)
154 for (const auto& type
: types
)
156 for (const auto& addon
: addons
)
158 if (addon
->HasType(type
))
160 CFileItemPtr
item(new CFileItem(CAddonInfo::TranslateType(type
, true)));
161 CURL itemPath
= path
;
162 itemPath
.SetFileName(CAddonInfo::TranslateType(type
, false));
163 item
->SetPath(itemPath
.Get());
164 item
->m_bIsFolder
= true;
165 std::string thumb
= CAddonInfo::TranslateIconType(type
);
166 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
167 item
->SetArt("thumb", thumb
);
175 // Creates categories for game add-ons, if we have any game add-ons
176 static void GenerateGameListing(const CURL
& path
, const VECADDONS
& addons
, CFileItemList
& items
)
179 for (const auto& addon
: addons
)
181 if (addon
->Type() == AddonType::GAME_CONTROLLER
)
183 CFileItemPtr
item(new CFileItem(CAddonInfo::TranslateType(AddonType::GAME_CONTROLLER
, true)));
184 CURL itemPath
= path
;
185 itemPath
.SetFileName(CAddonInfo::TranslateType(AddonType::GAME_CONTROLLER
, false));
186 item
->SetPath(itemPath
.Get());
187 item
->m_bIsFolder
= true;
188 std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAME_CONTROLLER
);
189 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
190 item
->SetArt("thumb", thumb
);
196 for (const auto& addon
: addons
)
198 if (IsEmulator(addon
))
200 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(35207))); // Emulators
201 CURL itemPath
= path
;
202 itemPath
.SetFileName(CATEGORY_EMULATORS
);
203 item
->SetPath(itemPath
.Get());
204 item
->m_bIsFolder
= true;
205 std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAMEDLL
);
206 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
207 item
->SetArt("thumb", thumb
);
213 for (const auto& addon
: addons
)
215 if (IsStandaloneGame(addon
))
217 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(35208))); // Standalone games
218 CURL itemPath
= path
;
219 itemPath
.SetFileName(CATEGORY_STANDALONE_GAMES
);
220 item
->SetPath(itemPath
.Get());
221 item
->m_bIsFolder
= true;
222 std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAMEDLL
);
223 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
224 item
->SetArt("thumb", thumb
);
230 for (const auto& addon
: addons
)
232 if (IsGameProvider(addon
))
234 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(35220))); // Game providers
235 CURL itemPath
= path
;
236 itemPath
.SetFileName(CATEGORY_GAME_PROVIDERS
);
237 item
->SetPath(itemPath
.Get());
238 item
->m_bIsFolder
= true;
239 std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAMEDLL
);
240 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
241 item
->SetArt("thumb", thumb
);
247 for (const auto& addon
: addons
)
249 if (IsGameResource(addon
))
251 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(35209))); // Game resources
252 CURL itemPath
= path
;
253 itemPath
.SetFileName(CATEGORY_GAME_RESOURCES
);
254 item
->SetPath(itemPath
.Get());
255 item
->m_bIsFolder
= true;
256 std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAMEDLL
);
257 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
258 item
->SetArt("thumb", thumb
);
263 // Game support add-ons
264 for (const auto& addon
: addons
)
266 if (IsGameSupportAddon(addon
))
268 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(35216))); // Support add-ons
269 CURL itemPath
= path
;
270 itemPath
.SetFileName(CATEGORY_GAME_SUPPORT_ADDONS
);
271 item
->SetPath(itemPath
.Get());
272 item
->m_bIsFolder
= true;
273 std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAMEDLL
);
274 if (!thumb
.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
275 item
->SetArt("thumb", thumb
);
282 //Creates the top-level category list
283 static void GenerateMainCategoryListing(const CURL
& path
, const VECADDONS
& addons
,
284 CFileItemList
& items
)
286 if (std::any_of(addons
.begin(), addons
.end(), IsInfoProviderTypeAddon
))
288 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(24993)));
289 item
->SetPath(URIUtils::AddFileToFolder(path
.Get(), CATEGORY_INFO_PROVIDERS
));
290 item
->m_bIsFolder
= true;
291 const std::string thumb
= "DefaultAddonInfoProvider.png";
292 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
293 item
->SetArt("thumb", thumb
);
296 if (std::any_of(addons
.begin(), addons
.end(), IsLookAndFeelTypeAddon
))
298 CFileItemPtr
item(new CFileItem(g_localizeStrings
.Get(24997)));
299 item
->SetPath(URIUtils::AddFileToFolder(path
.Get(), CATEGORY_LOOK_AND_FEEL
));
300 item
->m_bIsFolder
= true;
301 const std::string thumb
= "DefaultAddonLookAndFeel.png";
302 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
303 item
->SetArt("thumb", thumb
);
306 if (std::any_of(addons
.begin(), addons
.end(), IsGameAddon
))
308 CFileItemPtr
item(new CFileItem(CAddonInfo::TranslateType(AddonType::GAME
, true)));
309 item
->SetPath(URIUtils::AddFileToFolder(path
.Get(), CATEGORY_GAME_ADDONS
));
310 item
->m_bIsFolder
= true;
311 const std::string thumb
= CAddonInfo::TranslateIconType(AddonType::GAME
);
312 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb
))
313 item
->SetArt("thumb", thumb
);
317 std::set
<AddonType
> uncategorized
;
318 for (unsigned int i
= static_cast<unsigned int>(AddonType::UNKNOWN
) + 1;
319 i
< static_cast<unsigned int>(AddonType::MAX_TYPES
) - 1; ++i
)
321 const AddonType type
= static_cast<AddonType
>(i
);
323 * Check and prevent insert for this cases:
324 * - By a provider, look and feel, dependency and game becomes given to
325 * subdirectory to control the types
326 * - By ADDON_SCRIPT and ADDON_PLUGIN, them contains one of the possible
327 * subtypes (audio, video, app or/and game) and not needed to show
328 * together in a Script or Plugin list
330 if (!IsInfoProviderType(type
) && !IsLookAndFeelType(type
) &&
331 !CAddonType::IsDependencyType(type
) && !IsGameType(type
) && type
!= AddonType::SCRIPT
&&
332 type
!= AddonType::PLUGIN
)
333 uncategorized
.insert(type
);
335 GenerateTypeListing(path
, uncategorized
, addons
, items
);
338 //Creates sub-categories or addon list for a category
339 static void GenerateCategoryListing(const CURL
& path
, VECADDONS
& addons
,
340 CFileItemList
& items
)
342 const std::string
& category
= path
.GetFileName();
343 if (category
== CATEGORY_INFO_PROVIDERS
)
345 items
.SetProperty("addoncategory", g_localizeStrings
.Get(24993));
346 items
.SetLabel(g_localizeStrings
.Get(24993));
347 GenerateTypeListing(path
, infoProviderTypes
, addons
, items
);
349 else if (category
== CATEGORY_LOOK_AND_FEEL
)
351 items
.SetProperty("addoncategory", g_localizeStrings
.Get(24997));
352 items
.SetLabel(g_localizeStrings
.Get(24997));
353 GenerateTypeListing(path
, lookAndFeelTypes
, addons
, items
);
355 else if (category
== CATEGORY_GAME_ADDONS
)
357 items
.SetProperty("addoncategory", CAddonInfo::TranslateType(AddonType::GAME
, true));
358 items
.SetLabel(CAddonInfo::TranslateType(AddonType::GAME
, true));
359 GenerateGameListing(path
, addons
, items
);
361 else if (category
== CATEGORY_EMULATORS
)
363 items
.SetProperty("addoncategory", g_localizeStrings
.Get(35207)); // Emulators
364 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
365 [](const AddonPtr
& addon
){ return !IsEmulator(addon
); }), addons
.end());
366 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(35207)); // Emulators
368 else if (category
== CATEGORY_STANDALONE_GAMES
)
370 items
.SetProperty("addoncategory", g_localizeStrings
.Get(35208)); // Standalone games
371 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
372 [](const AddonPtr
& addon
){ return !IsStandaloneGame(addon
); }), addons
.end());
373 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(35208)); // Standalone games
375 else if (category
== CATEGORY_GAME_PROVIDERS
)
377 items
.SetProperty("addoncategory", g_localizeStrings
.Get(35220)); // Game providers
378 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
379 [](const AddonPtr
& addon
){ return !IsGameProvider(addon
); }), addons
.end());
380 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(35220)); // Game providers
382 else if (category
== CATEGORY_GAME_RESOURCES
)
384 items
.SetProperty("addoncategory", g_localizeStrings
.Get(35209)); // Game resources
385 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
386 [](const AddonPtr
& addon
){ return !IsGameResource(addon
); }), addons
.end());
387 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(35209)); // Game resources
389 else if (category
== CATEGORY_GAME_SUPPORT_ADDONS
)
391 items
.SetProperty("addoncategory", g_localizeStrings
.Get(35216)); // Support add-ons
392 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
393 [](const AddonPtr
& addon
) { return !IsGameSupportAddon(addon
); }), addons
.end());
394 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(35216)); // Support add-ons
397 { // fallback to addon type
398 AddonType type
= CAddonInfo::TranslateType(category
);
399 items
.SetProperty("addoncategory", CAddonInfo::TranslateType(type
, true));
400 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
401 [type
](const AddonPtr
& addon
) { return !addon
->HasType(type
); }),
403 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, CAddonInfo::TranslateType(type
, true));
407 bool CAddonsDirectory::GetSearchResults(const CURL
& path
, CFileItemList
&items
)
409 std::string
search(path
.GetFileName());
410 if (search
.empty() && !GetKeyboardInput(16017, search
))
413 CAddonDatabase database
;
417 database
.Search(search
, addons
);
418 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(283));
419 CURL
searchPath(path
);
420 searchPath
.SetFileName(search
);
421 items
.SetPath(searchPath
.Get());
425 static void UserInstalledAddons(const CURL
& path
, CFileItemList
&items
)
428 items
.SetLabel(g_localizeStrings
.Get(24998));
431 CServiceBroker::GetAddonMgr().GetInstalledAddons(addons
);
432 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
433 [](const AddonPtr
& addon
) { return !IsUserInstalled(addon
); }), addons
.end());
438 const std::string
& category
= path
.GetFileName();
439 if (category
.empty())
441 GenerateMainCategoryListing(path
, addons
, items
);
444 CFileItemPtr
item(new CFileItem());
445 item
->m_bIsFolder
= true;
446 CURL itemPath
= path
;
447 itemPath
.SetFileName("all");
448 item
->SetPath(itemPath
.Get());
449 item
->SetLabel(g_localizeStrings
.Get(593));
450 item
->SetSpecialSort(SortSpecialOnTop
);
453 else if (category
== "all")
454 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(24998));
456 GenerateCategoryListing(path
, addons
, items
);
459 static void DependencyAddons(const CURL
& path
, CFileItemList
&items
)
462 CServiceBroker::GetAddonMgr().GetInstalledAddons(all
);
465 std::copy_if(all
.begin(), all
.end(), std::back_inserter(deps
),
466 [&](const AddonPtr
& _
){ return !IsUserInstalled(_
); });
468 CAddonsDirectory::GenerateAddonListing(path
, deps
, items
, g_localizeStrings
.Get(24996));
470 //Set orphaned status
471 std::set
<std::string
> orphaned
;
472 for (const auto& addon
: deps
)
474 if (CServiceBroker::GetAddonMgr().IsOrphaned(addon
, all
))
475 orphaned
.insert(addon
->ID());
478 for (int i
= 0; i
< items
.Size(); ++i
)
480 if (orphaned
.find(items
[i
]->GetProperty("Addon.ID").asString()) != orphaned
.end())
482 items
[i
]->SetProperty("Addon.Status", g_localizeStrings
.Get(24995));
483 items
[i
]->SetProperty("Addon.Orphaned", true);
488 static void OutdatedAddons(const CURL
& path
, CFileItemList
&items
)
490 VECADDONS addons
= CServiceBroker::GetAddonMgr().GetAvailableUpdates();
491 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(24043));
493 if (!items
.IsEmpty())
495 if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON
)
497 const CFileItemPtr
itemUpdateAllowed(
498 std::make_shared
<CFileItem
>("addons://update_allowed/", false));
499 itemUpdateAllowed
->SetLabel(g_localizeStrings
.Get(24137));
500 itemUpdateAllowed
->SetSpecialSort(SortSpecialOnTop
);
501 items
.Add(itemUpdateAllowed
);
504 const CFileItemPtr
itemUpdateAll(std::make_shared
<CFileItem
>("addons://update_all/", false));
505 itemUpdateAll
->SetLabel(g_localizeStrings
.Get(24122));
506 itemUpdateAll
->SetSpecialSort(SortSpecialOnTop
);
507 items
.Add(itemUpdateAll
);
511 static void RunningAddons(const CURL
& path
, CFileItemList
&items
)
514 CServiceBroker::GetAddonMgr().GetAddons(addons
, AddonType::SERVICE
);
516 addons
.erase(std::remove_if(addons
.begin(), addons
.end(),
517 [](const AddonPtr
& addon
){ return !CScriptInvocationManager::GetInstance().IsRunning(addon
->LibPath()); }), addons
.end());
518 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(24994));
521 static bool Browse(const CURL
& path
, CFileItemList
&items
)
523 const std::string
& repoId
= path
.GetHostName();
526 items
.SetPath(path
.Get());
529 CAddonRepos addonRepos
;
530 if (!addonRepos
.IsValid())
533 // get all latest addon versions by repo
534 addonRepos
.GetLatestAddonVersionsFromAllRepos(addons
);
536 items
.SetProperty("reponame", g_localizeStrings
.Get(24087));
537 items
.SetLabel(g_localizeStrings
.Get(24087));
542 if (!CServiceBroker::GetAddonMgr().GetAddon(repoId
, repoAddon
, AddonType::REPOSITORY
,
543 OnlyEnabled::CHOICE_YES
))
548 CAddonRepos
addonRepos(repoAddon
);
549 if (!addonRepos
.IsValid())
552 // get all addons from the single repository
553 addonRepos
.GetLatestAddonVersions(addons
);
555 items
.SetProperty("reponame", repoAddon
->Name());
556 items
.SetLabel(repoAddon
->Name());
559 const std::string
& category
= path
.GetFileName();
560 if (category
.empty())
561 GenerateMainCategoryListing(path
, addons
, items
);
563 GenerateCategoryListing(path
, addons
, items
);
567 static bool GetRecentlyUpdatedAddons(VECADDONS
& addons
)
569 if (!CServiceBroker::GetAddonMgr().GetInstalledAddons(addons
))
572 auto limit
= CDateTime::GetCurrentDateTime() - CDateTimeSpan(14, 0, 0, 0);
573 auto isOld
= [limit
](const AddonPtr
& addon
){ return addon
->LastUpdated() < limit
; };
574 addons
.erase(std::remove_if(addons
.begin(), addons
.end(), isOld
), addons
.end());
578 static bool HasRecentlyUpdatedAddons()
581 return GetRecentlyUpdatedAddons(addons
) && !addons
.empty();
584 static bool Repos(const CURL
& path
, CFileItemList
&items
)
586 items
.SetLabel(g_localizeStrings
.Get(24033));
589 CServiceBroker::GetAddonMgr().GetAddons(addons
, AddonType::REPOSITORY
);
592 else if (addons
.size() == 1)
593 return Browse(CURL("addons://" + addons
[0]->ID()), items
);
594 CFileItemPtr
item(new CFileItem("addons://all/", true));
595 item
->SetLabel(g_localizeStrings
.Get(24087));
596 item
->SetSpecialSort(SortSpecialOnTop
);
598 for (const auto& repo
: addons
)
600 CFileItemPtr item
= CAddonsDirectory::FileItemFromAddon(repo
, "addons://" + repo
->ID(), true);
603 items
.SetContent("addons");
607 static void RootDirectory(CFileItemList
& items
)
609 items
.SetLabel(g_localizeStrings
.Get(10040));
611 CFileItemPtr
item(new CFileItem("addons://user/", true));
612 item
->SetLabel(g_localizeStrings
.Get(24998));
613 item
->SetArt("icon", "DefaultAddonsInstalled.png");
616 if (CServiceBroker::GetAddonMgr().HasAvailableUpdates())
618 CFileItemPtr
item(new CFileItem("addons://outdated/", true));
619 item
->SetLabel(g_localizeStrings
.Get(24043));
620 item
->SetArt("icon", "DefaultAddonsUpdates.png");
623 if (CAddonInstaller::GetInstance().IsDownloading())
625 CFileItemPtr
item(new CFileItem("addons://downloading/", true));
626 item
->SetLabel(g_localizeStrings
.Get(24067));
627 item
->SetArt("icon", "DefaultNetwork.png");
630 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_ADDONS_AUTOUPDATES
) == ADDON::AUTO_UPDATES_ON
631 && HasRecentlyUpdatedAddons())
633 CFileItemPtr
item(new CFileItem("addons://recently_updated/", true));
634 item
->SetLabel(g_localizeStrings
.Get(24004));
635 item
->SetArt("icon", "DefaultAddonsRecentlyUpdated.png");
638 if (CServiceBroker::GetAddonMgr().HasAddons(AddonType::REPOSITORY
))
640 CFileItemPtr
item(new CFileItem("addons://repos/", true));
641 item
->SetLabel(g_localizeStrings
.Get(24033));
642 item
->SetArt("icon", "DefaultAddonsRepo.png");
646 CFileItemPtr
item(new CFileItem("addons://install/", false));
647 item
->SetLabel(g_localizeStrings
.Get(24041));
648 item
->SetArt("icon", "DefaultAddonsZip.png");
652 CFileItemPtr
item(new CFileItem("addons://search/", true));
653 item
->SetLabel(g_localizeStrings
.Get(137));
654 item
->SetArt("icon", "DefaultAddonsSearch.png");
659 bool CAddonsDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
661 std::string
tmp(url
.Get());
662 URIUtils::RemoveSlashAtEnd(tmp
);
664 const std::string
& endpoint
= path
.GetHostName();
666 items
.ClearProperties();
667 items
.SetCacheToDisc(CFileItemList::CACHE_NEVER
);
668 items
.SetPath(path
.Get());
670 if (endpoint
.empty())
672 RootDirectory(items
);
675 else if (endpoint
== "user")
677 UserInstalledAddons(path
, items
);
680 else if (endpoint
== "dependencies")
682 DependencyAddons(path
, items
);
685 // PVR hardcodes this view so keep for compatibility
686 else if (endpoint
== "disabled")
691 if (path
.GetFileName() == "kodi.pvrclient")
692 type
= AddonType::PVRDLL
;
693 else if (path
.GetFileName() == "kodi.vfs")
694 type
= AddonType::VFS
;
696 type
= AddonType::UNKNOWN
;
698 if (type
!= AddonType::UNKNOWN
&&
699 CServiceBroker::GetAddonMgr().GetInstalledAddons(addons
, type
))
701 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, CAddonInfo::TranslateType(type
, true));
706 else if (endpoint
== "outdated")
708 OutdatedAddons(path
, items
);
711 else if (endpoint
== "running")
713 RunningAddons(path
, items
);
716 else if (endpoint
== "repos")
718 return Repos(path
, items
);
720 else if (endpoint
== "sources")
722 return GetScriptsAndPlugins(path
.GetFileName(), items
);
724 else if (endpoint
== "search")
726 return GetSearchResults(path
, items
);
728 else if (endpoint
== "recently_updated")
731 if (!GetRecentlyUpdatedAddons(addons
))
734 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(24004));
737 else if (endpoint
== "downloading")
740 CAddonInstaller::GetInstance().GetInstallList(addons
);
741 CAddonsDirectory::GenerateAddonListing(path
, addons
, items
, g_localizeStrings
.Get(24067));
744 else if (endpoint
== "more")
746 const std::string
& type
= path
.GetFileName();
747 if (type
== "video" || type
== "audio" || type
== "image" || type
== "executable")
748 return Browse(CURL("addons://all/xbmc.addon." + type
), items
);
749 else if (type
== "game")
750 return Browse(CURL("addons://all/category.gameaddons"), items
);
755 return Browse(path
, items
);
759 bool CAddonsDirectory::IsRepoDirectory(const CURL
& url
)
761 if (url
.GetHostName().empty() || !url
.IsProtocol("addons"))
765 return url
.GetHostName() == "repos" || url
.GetHostName() == "all" ||
766 url
.GetHostName() == "search" ||
767 CServiceBroker::GetAddonMgr().GetAddon(url
.GetHostName(), tmp
, AddonType::REPOSITORY
,
768 OnlyEnabled::CHOICE_YES
);
771 void CAddonsDirectory::GenerateAddonListing(const CURL
& path
,
772 const VECADDONS
& addons
,
773 CFileItemList
& items
,
774 const std::string
& label
)
776 std::map
<std::string
, AddonWithUpdate
> addonsWithUpdate
=
777 CServiceBroker::GetAddonMgr().GetAddonsWithAvailableUpdate();
780 items
.SetContent("addons");
781 items
.SetLabel(label
);
782 for (const auto& addon
: addons
)
784 CURL itemPath
= path
;
785 itemPath
.SetFileName(addon
->ID());
786 CFileItemPtr pItem
= FileItemFromAddon(addon
, itemPath
.Get(), false);
788 bool installed
= CServiceBroker::GetAddonMgr().IsAddonInstalled(addon
->ID(), addon
->Origin(),
790 bool disabled
= CServiceBroker::GetAddonMgr().IsAddonDisabled(addon
->ID());
792 std::function
<bool(bool)> CheckOutdatedOrUpdate
= [&](bool checkOutdated
) -> bool {
793 auto mapEntry
= addonsWithUpdate
.find(addon
->ID());
794 if (mapEntry
!= addonsWithUpdate
.end())
796 const std::shared_ptr
<IAddon
>& checkedObject
=
797 checkOutdated
? mapEntry
->second
.m_installed
: mapEntry
->second
.m_update
;
799 return (checkedObject
->Origin() == addon
->Origin() &&
800 checkedObject
->Version() == addon
->Version());
805 bool isUpdate
= CheckOutdatedOrUpdate(false); // check if it's an available update
806 bool hasUpdate
= CheckOutdatedOrUpdate(true); // check if it's an outdated addon
808 std::string validUpdateVersion
;
809 std::string validUpdateOrigin
;
812 auto mapEntry
= addonsWithUpdate
.find(addon
->ID());
813 validUpdateVersion
= mapEntry
->second
.m_update
->Version().asString();
814 validUpdateOrigin
= mapEntry
->second
.m_update
->Origin();
817 bool fromOfficialRepo
= CAddonRepos::IsFromOfficialRepo(addon
, CheckAddonPath::CHOICE_NO
);
819 pItem
->SetProperty("Addon.IsInstalled", installed
);
820 pItem
->SetProperty("Addon.IsEnabled", installed
&& !disabled
);
821 pItem
->SetProperty("Addon.HasUpdate", hasUpdate
);
822 pItem
->SetProperty("Addon.IsUpdate", isUpdate
);
823 pItem
->SetProperty("Addon.ValidUpdateVersion", validUpdateVersion
);
824 pItem
->SetProperty("Addon.ValidUpdateOrigin", validUpdateOrigin
);
825 pItem
->SetProperty("Addon.IsFromOfficialRepo", fromOfficialRepo
);
826 pItem
->SetProperty("Addon.IsBinary", addon
->IsBinary());
829 pItem
->SetProperty("Addon.Status", g_localizeStrings
.Get(305));
831 pItem
->SetProperty("Addon.Status", g_localizeStrings
.Get(24023));
833 pItem
->SetProperty("Addon.Status", g_localizeStrings
.Get(24068));
834 else if (addon
->LifecycleState() == AddonLifecycleState::BROKEN
)
835 pItem
->SetProperty("Addon.Status", g_localizeStrings
.Get(24098));
836 else if (addon
->LifecycleState() == AddonLifecycleState::DEPRECATED
)
837 pItem
->SetProperty("Addon.Status", g_localizeStrings
.Get(24170));
843 CFileItemPtr
CAddonsDirectory::FileItemFromAddon(const AddonPtr
&addon
,
844 const std::string
& path
, bool folder
)
847 return CFileItemPtr();
849 CFileItemPtr
item(new CFileItem(addon
));
850 item
->m_bIsFolder
= folder
;
853 std::string
strLabel(addon
->Name());
854 if (CURL(path
).GetHostName() == "search")
855 strLabel
= StringUtils::Format("{} - {}", CAddonInfo::TranslateType(addon
->Type(), true),
857 item
->SetLabel(strLabel
);
858 item
->SetArt(addon
->Art());
859 item
->SetArt("thumb", addon
->Icon());
860 item
->SetArt("icon", "DefaultAddon.png");
862 //! @todo fix hacks that depends on these
863 item
->SetProperty("Addon.ID", addon
->ID());
864 item
->SetProperty("Addon.Name", addon
->Name());
865 item
->SetCanQueue(false);
866 const auto it
= addon
->ExtraInfo().find("language");
867 if (it
!= addon
->ExtraInfo().end())
868 item
->SetProperty("Addon.Language", it
->second
);
873 bool CAddonsDirectory::GetScriptsAndPlugins(const std::string
&content
, VECADDONS
&addons
)
875 CPluginSource::Content type
= CPluginSource::Translate(content
);
876 if (type
== CPluginSource::UNKNOWN
)
879 VECADDONS tempAddons
;
880 CServiceBroker::GetAddonMgr().GetAddons(tempAddons
, AddonType::PLUGIN
);
881 for (unsigned i
=0; i
<tempAddons
.size(); i
++)
883 const auto plugin
= std::dynamic_pointer_cast
<CPluginSource
>(tempAddons
[i
]);
884 if (plugin
&& plugin
->Provides(type
))
885 addons
.push_back(tempAddons
[i
]);
888 CServiceBroker::GetAddonMgr().GetAddons(tempAddons
, AddonType::SCRIPT
);
889 for (unsigned i
=0; i
<tempAddons
.size(); i
++)
891 const auto plugin
= std::dynamic_pointer_cast
<CPluginSource
>(tempAddons
[i
]);
892 if (plugin
&& plugin
->Provides(type
))
893 addons
.push_back(tempAddons
[i
]);
897 if (type
== CPluginSource::GAME
)
899 CServiceBroker::GetAddonMgr().GetAddons(tempAddons
, AddonType::GAMEDLL
);
900 for (auto& addon
: tempAddons
)
902 if (IsStandaloneGame(addon
))
903 addons
.push_back(addon
);
910 bool CAddonsDirectory::GetScriptsAndPlugins(const std::string
&content
, CFileItemList
&items
)
913 if (!GetScriptsAndPlugins(content
, addons
))
916 for (AddonPtr
& addon
: addons
)
918 const bool bIsFolder
= (addon
->Type() == AddonType::PLUGIN
);
921 if (addon
->HasType(AddonType::PLUGIN
))
923 path
= "plugin://" + addon
->ID();
924 const auto plugin
= std::dynamic_pointer_cast
<CPluginSource
>(addon
);
925 if (plugin
&& plugin
->ProvidesSeveral())
928 std::string opt
= StringUtils::Format("?content_type={}", content
);
933 else if (addon
->HasType(AddonType::SCRIPT
))
935 path
= "script://" + addon
->ID();
937 else if (addon
->HasType(AddonType::GAMEDLL
))
939 // Kodi fails to launch games with empty path from home screen
940 path
= "game://" + addon
->ID();
943 items
.Add(FileItemFromAddon(addon
, path
, bIsFolder
));
946 items
.SetContent("addons");
947 items
.SetLabel(g_localizeStrings
.Get(24001)); // Add-ons