1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "AddonsDirectory.h"
10
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "addons/AddonDatabase.h"
15 #include "addons/AddonInstaller.h"
16 #include "addons/AddonRepos.h"
17 #include "addons/AddonSystemSettings.h"
18 #include "addons/PluginSource.h"
19 #include "addons/RepositoryUpdater.h"
20 #include "games/GameUtils.h"
21 #include "games/addons/GameClient.h"
22 #include "guilib/LocalizeStrings.h"
23 #include "guilib/TextureManager.h"
24 #include "interfaces/generic/ScriptInvocationManager.h"
25 #include "messaging/helpers/DialogOKHelper.h"
26 #include "settings/Settings.h"
27 #include "settings/SettingsComponent.h"
28 #include "utils/StringUtils.h"
29 #include "utils/URIUtils.h"
30
31 #include <algorithm>
32 #include <array>
33 #include <functional>
34 #include <set>
35
36 using namespace KODI;
37 using namespace ADDON;
38 using namespace KODI::MESSAGING;
39
40 namespace XFILE
41 {
42
43 CAddonsDirectory::CAddonsDirectory(void) = default;
44
45 CAddonsDirectory::~CAddonsDirectory(void) = default;
46
47 const auto CATEGORY_INFO_PROVIDERS = "category.infoproviders";
48 const auto CATEGORY_LOOK_AND_FEEL = "category.lookandfeel";
49 const auto CATEGORY_GAME_ADDONS = "category.gameaddons";
50 const auto CATEGORY_EMULATORS = "category.emulators";
51 const auto CATEGORY_STANDALONE_GAMES = "category.standalonegames";
52 const auto CATEGORY_GAME_PROVIDERS = "category.gameproviders";
53 const auto CATEGORY_GAME_RESOURCES = "category.gameresources";
54 const auto CATEGORY_GAME_SUPPORT_ADDONS = "category.gamesupport";
55
56 const std::set<TYPE> infoProviderTypes = {
57 ADDON_SCRAPER_ALBUMS,
58 ADDON_SCRAPER_ARTISTS,
59 ADDON_SCRAPER_MOVIES,
60 ADDON_SCRAPER_MUSICVIDEOS,
61 ADDON_SCRAPER_TVSHOWS,
62 };
63
64 const std::set<TYPE> lookAndFeelTypes = {
65 ADDON_SKIN,
66 ADDON_SCREENSAVER,
67 ADDON_RESOURCE_IMAGES,
68 ADDON_RESOURCE_LANGUAGE,
69 ADDON_RESOURCE_UISOUNDS,
70 ADDON_RESOURCE_FONT,
71 ADDON_VIZ,
72 };
73
74 const std::set<TYPE> gameTypes = {
75 ADDON_GAME_CONTROLLER,
76 ADDON_GAMEDLL,
77 ADDON_GAME,
78 ADDON_RESOURCE_GAMES,
79 };
80
IsInfoProviderType(TYPE type)81 static bool IsInfoProviderType(TYPE type)
82 {
83 return infoProviderTypes.find(type) != infoProviderTypes.end();
84 }
85
IsInfoProviderTypeAddon(const AddonPtr & addon)86 static bool IsInfoProviderTypeAddon(const AddonPtr& addon)
87 {
88 return IsInfoProviderType(addon->Type());
89 }
90
IsLookAndFeelType(TYPE type)91 static bool IsLookAndFeelType(TYPE type)
92 {
93 return lookAndFeelTypes.find(type) != lookAndFeelTypes.end();
94 }
95
IsLookAndFeelTypeAddon(const AddonPtr & addon)96 static bool IsLookAndFeelTypeAddon(const AddonPtr& addon)
97 {
98 return IsLookAndFeelType(addon->Type());
99 }
100
IsGameType(TYPE type)101 static bool IsGameType(TYPE type)
102 {
103 return gameTypes.find(type) != gameTypes.end();
104 }
105
IsStandaloneGame(const AddonPtr & addon)106 static bool IsStandaloneGame(const AddonPtr& addon)
107 {
108 return GAME::CGameUtils::IsStandaloneGame(addon);
109 }
110
IsEmulator(const AddonPtr & addon)111 static bool IsEmulator(const AddonPtr& addon)
112 {
113 return addon->Type() == ADDON_GAMEDLL && std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath();
114 }
115
IsGameProvider(const AddonPtr & addon)116 static bool IsGameProvider(const AddonPtr& addon)
117 {
118 return addon->Type() == ADDON_PLUGIN && addon->HasType(ADDON_GAME);
119 }
120
IsGameResource(const AddonPtr & addon)121 static bool IsGameResource(const AddonPtr& addon)
122 {
123 return addon->Type() == ADDON_RESOURCE_GAMES;
124 }
125
IsGameSupportAddon(const AddonPtr & addon)126 static bool IsGameSupportAddon(const AddonPtr& addon)
127 {
128 return addon->Type() == ADDON_GAMEDLL &&
129 !std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath() &&
130 !std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsStandalone();
131 }
132
IsGameAddon(const AddonPtr & addon)133 static bool IsGameAddon(const AddonPtr& addon)
134 {
135 return IsGameType(addon->Type()) ||
136 IsStandaloneGame(addon) ||
137 IsGameProvider(addon) ||
138 IsGameResource(addon) ||
139 IsGameSupportAddon(addon);
140 }
141
IsUserInstalled(const AddonPtr & addon)142 static bool IsUserInstalled(const AddonPtr& addon)
143 {
144 return !CAddonType::IsDependencyType(addon->MainType());
145 }
146
IsOrphaned(const AddonPtr & addon,const VECADDONS & all)147 static bool IsOrphaned(const AddonPtr& addon, const VECADDONS& all)
148 {
149 if (CServiceBroker::GetAddonMgr().IsSystemAddon(addon->ID()) || IsUserInstalled(addon))
150 return false;
151
152 for (const AddonPtr& other : all)
153 {
154 const auto& deps = other->GetDependencies();
155 auto it = std::find_if(deps.begin(), deps.end(), [&](const DependencyInfo& dep){ return dep.id == addon->ID(); });
156 if (it != deps.end())
157 return false;
158 }
159 return true;
160 }
161
162 // Creates categories from addon types, if we have any addons with that type.
GenerateTypeListing(const CURL & path,const std::set<TYPE> & types,const VECADDONS & addons,CFileItemList & items)163 static void GenerateTypeListing(const CURL& path, const std::set<TYPE>& types,
164 const VECADDONS& addons, CFileItemList& items)
165 {
166 for (const auto& type : types)
167 {
168 for (const auto& addon : addons)
169 {
170 if (addon->HasType(type))
171 {
172 CFileItemPtr item(new CFileItem(CAddonInfo::TranslateType(type, true)));
173 CURL itemPath = path;
174 itemPath.SetFileName(CAddonInfo::TranslateType(type, false));
175 item->SetPath(itemPath.Get());
176 item->m_bIsFolder = true;
177 std::string thumb = CAddonInfo::TranslateIconType(type);
178 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
179 item->SetArt("thumb", thumb);
180 items.Add(item);
181 break;
182 }
183 }
184 }
185 }
186
187 // Creates categories for game add-ons, if we have any game add-ons
GenerateGameListing(const CURL & path,const VECADDONS & addons,CFileItemList & items)188 static void GenerateGameListing(const CURL& path, const VECADDONS& addons, CFileItemList& items)
189 {
190 // Game controllers
191 for (const auto& addon : addons)
192 {
193 if (addon->Type() == ADDON_GAME_CONTROLLER)
194 {
195 CFileItemPtr item(new CFileItem(CAddonInfo::TranslateType(ADDON_GAME_CONTROLLER, true)));
196 CURL itemPath = path;
197 itemPath.SetFileName(CAddonInfo::TranslateType(ADDON_GAME_CONTROLLER, false));
198 item->SetPath(itemPath.Get());
199 item->m_bIsFolder = true;
200 std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAME_CONTROLLER);
201 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
202 item->SetArt("thumb", thumb);
203 items.Add(item);
204 break;
205 }
206 }
207 // Emulators
208 for (const auto& addon : addons)
209 {
210 if (IsEmulator(addon))
211 {
212 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35207))); // Emulators
213 CURL itemPath = path;
214 itemPath.SetFileName(CATEGORY_EMULATORS);
215 item->SetPath(itemPath.Get());
216 item->m_bIsFolder = true;
217 std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAMEDLL);
218 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
219 item->SetArt("thumb", thumb);
220 items.Add(item);
221 break;
222 }
223 }
224 // Standalone games
225 for (const auto& addon : addons)
226 {
227 if (IsStandaloneGame(addon))
228 {
229 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35208))); // Standalone games
230 CURL itemPath = path;
231 itemPath.SetFileName(CATEGORY_STANDALONE_GAMES);
232 item->SetPath(itemPath.Get());
233 item->m_bIsFolder = true;
234 std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAMEDLL);
235 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
236 item->SetArt("thumb", thumb);
237 items.Add(item);
238 break;
239 }
240 }
241 // Game providers
242 for (const auto& addon : addons)
243 {
244 if (IsGameProvider(addon))
245 {
246 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35220))); // Game providers
247 CURL itemPath = path;
248 itemPath.SetFileName(CATEGORY_GAME_PROVIDERS);
249 item->SetPath(itemPath.Get());
250 item->m_bIsFolder = true;
251 std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAMEDLL);
252 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
253 item->SetArt("thumb", thumb);
254 items.Add(item);
255 break;
256 }
257 }
258 // Game resources
259 for (const auto& addon : addons)
260 {
261 if (IsGameResource(addon))
262 {
263 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35209))); // Game resources
264 CURL itemPath = path;
265 itemPath.SetFileName(CATEGORY_GAME_RESOURCES);
266 item->SetPath(itemPath.Get());
267 item->m_bIsFolder = true;
268 std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAMEDLL);
269 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
270 item->SetArt("thumb", thumb);
271 items.Add(item);
272 break;
273 }
274 }
275 // Game support add-ons
276 for (const auto& addon : addons)
277 {
278 if (IsGameSupportAddon(addon))
279 {
280 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35216))); // Support add-ons
281 CURL itemPath = path;
282 itemPath.SetFileName(CATEGORY_GAME_SUPPORT_ADDONS);
283 item->SetPath(itemPath.Get());
284 item->m_bIsFolder = true;
285 std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAMEDLL);
286 if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
287 item->SetArt("thumb", thumb);
288 items.Add(item);
289 break;
290 }
291 }
292 }
293
294 //Creates the top-level category list
GenerateMainCategoryListing(const CURL & path,const VECADDONS & addons,CFileItemList & items)295 static void GenerateMainCategoryListing(const CURL& path, const VECADDONS& addons,
296 CFileItemList& items)
297 {
298 if (std::any_of(addons.begin(), addons.end(), IsInfoProviderTypeAddon))
299 {
300 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(24993)));
301 item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_INFO_PROVIDERS));
302 item->m_bIsFolder = true;
303 const std::string thumb = "DefaultAddonInfoProvider.png";
304 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
305 item->SetArt("thumb", thumb);
306 items.Add(item);
307 }
308 if (std::any_of(addons.begin(), addons.end(), IsLookAndFeelTypeAddon))
309 {
310 CFileItemPtr item(new CFileItem(g_localizeStrings.Get(24997)));
311 item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_LOOK_AND_FEEL));
312 item->m_bIsFolder = true;
313 const std::string thumb = "DefaultAddonLookAndFeel.png";
314 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
315 item->SetArt("thumb", thumb);
316 items.Add(item);
317 }
318 if (std::any_of(addons.begin(), addons.end(), IsGameAddon))
319 {
320 CFileItemPtr item(new CFileItem(CAddonInfo::TranslateType(ADDON_GAME, true)));
321 item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_GAME_ADDONS));
322 item->m_bIsFolder = true;
323 const std::string thumb = CAddonInfo::TranslateIconType(ADDON_GAME);
324 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
325 item->SetArt("thumb", thumb);
326 items.Add(item);
327 }
328
329 std::set<TYPE> uncategorized;
330 for (unsigned int i = ADDON_UNKNOWN + 1; i < ADDON_MAX - 1; ++i)
331 {
332 const TYPE type = (TYPE)i;
333 /*
334 * Check and prevent insert for this cases:
335 * - By a provider, look and feel, dependency and game becomes given to
336 * subdirectory to control the types
337 * - By ADDON_SCRIPT and ADDON_PLUGIN, them contains one of the possible
338 * subtypes (audio, video, app or/and game) and not needed to show
339 * together in a Script or Plugin list
340 */
341 if (!IsInfoProviderType(type) && !IsLookAndFeelType(type) &&
342 !CAddonType::IsDependencyType(type) && !IsGameType(type) && type != ADDON_SCRIPT &&
343 type != ADDON_PLUGIN)
344 uncategorized.insert(static_cast<TYPE>(i));
345 }
346 GenerateTypeListing(path, uncategorized, addons, items);
347 }
348
349 //Creates sub-categories or addon list for a category
GenerateCategoryListing(const CURL & path,VECADDONS & addons,CFileItemList & items)350 static void GenerateCategoryListing(const CURL& path, VECADDONS& addons,
351 CFileItemList& items)
352 {
353 const std::string& category = path.GetFileName();
354 if (category == CATEGORY_INFO_PROVIDERS)
355 {
356 items.SetProperty("addoncategory", g_localizeStrings.Get(24993));
357 items.SetLabel(g_localizeStrings.Get(24993));
358 GenerateTypeListing(path, infoProviderTypes, addons, items);
359 }
360 else if (category == CATEGORY_LOOK_AND_FEEL)
361 {
362 items.SetProperty("addoncategory", g_localizeStrings.Get(24997));
363 items.SetLabel(g_localizeStrings.Get(24997));
364 GenerateTypeListing(path, lookAndFeelTypes, addons, items);
365 }
366 else if (category == CATEGORY_GAME_ADDONS)
367 {
368 items.SetProperty("addoncategory", CAddonInfo::TranslateType(ADDON_GAME, true));
369 items.SetLabel(CAddonInfo::TranslateType(ADDON_GAME, true));
370 GenerateGameListing(path, addons, items);
371 }
372 else if (category == CATEGORY_EMULATORS)
373 {
374 items.SetProperty("addoncategory", g_localizeStrings.Get(35207)); // Emulators
375 addons.erase(std::remove_if(addons.begin(), addons.end(),
376 [](const AddonPtr& addon){ return !IsEmulator(addon); }), addons.end());
377 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35207)); // Emulators
378 }
379 else if (category == CATEGORY_STANDALONE_GAMES)
380 {
381 items.SetProperty("addoncategory", g_localizeStrings.Get(35208)); // Standalone games
382 addons.erase(std::remove_if(addons.begin(), addons.end(),
383 [](const AddonPtr& addon){ return !IsStandaloneGame(addon); }), addons.end());
384 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35208)); // Standalone games
385 }
386 else if (category == CATEGORY_GAME_PROVIDERS)
387 {
388 items.SetProperty("addoncategory", g_localizeStrings.Get(35220)); // Game providers
389 addons.erase(std::remove_if(addons.begin(), addons.end(),
390 [](const AddonPtr& addon){ return !IsGameProvider(addon); }), addons.end());
391 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35220)); // Game providers
392 }
393 else if (category == CATEGORY_GAME_RESOURCES)
394 {
395 items.SetProperty("addoncategory", g_localizeStrings.Get(35209)); // Game resources
396 addons.erase(std::remove_if(addons.begin(), addons.end(),
397 [](const AddonPtr& addon){ return !IsGameResource(addon); }), addons.end());
398 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35209)); // Game resources
399 }
400 else if (category == CATEGORY_GAME_SUPPORT_ADDONS)
401 {
402 items.SetProperty("addoncategory", g_localizeStrings.Get(35216)); // Support add-ons
403 addons.erase(std::remove_if(addons.begin(), addons.end(),
404 [](const AddonPtr& addon) { return !IsGameSupportAddon(addon); }), addons.end());
405 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35216)); // Support add-ons
406 }
407 else
408 { // fallback to addon type
409 TYPE type = CAddonInfo::TranslateType(category);
410 items.SetProperty("addoncategory", CAddonInfo::TranslateType(type, true));
411 addons.erase(std::remove_if(addons.begin(), addons.end(),
412 [type](const AddonPtr& addon) { return !addon->HasType(type); }),
413 addons.end());
414 CAddonsDirectory::GenerateAddonListing(path, addons, items, CAddonInfo::TranslateType(type, true));
415 }
416 }
417
GetSearchResults(const CURL & path,CFileItemList & items)418 bool CAddonsDirectory::GetSearchResults(const CURL& path, CFileItemList &items)
419 {
420 std::string search(path.GetFileName());
421 if (search.empty() && !GetKeyboardInput(16017, search))
422 return false;
423
424 CAddonDatabase database;
425 database.Open();
426
427 VECADDONS addons;
428 database.Search(search, addons);
429 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(283));
430 CURL searchPath(path);
431 searchPath.SetFileName(search);
432 items.SetPath(searchPath.Get());
433 return true;
434 }
435
UserInstalledAddons(const CURL & path,CFileItemList & items)436 static void UserInstalledAddons(const CURL& path, CFileItemList &items)
437 {
438 items.ClearItems();
439 items.SetLabel(g_localizeStrings.Get(24998));
440
441 VECADDONS addons;
442 CServiceBroker::GetAddonMgr().GetInstalledAddons(addons);
443 addons.erase(std::remove_if(addons.begin(), addons.end(),
444 [](const AddonPtr& addon) { return !IsUserInstalled(addon); }), addons.end());
445
446 if (addons.empty())
447 return;
448
449 const std::string& category = path.GetFileName();
450 if (category.empty())
451 {
452 GenerateMainCategoryListing(path, addons, items);
453
454 //"All" node
455 CFileItemPtr item(new CFileItem());
456 item->m_bIsFolder = true;
457 CURL itemPath = path;
458 itemPath.SetFileName("all");
459 item->SetPath(itemPath.Get());
460 item->SetLabel(g_localizeStrings.Get(593));
461 item->SetSpecialSort(SortSpecialOnTop);
462 items.Add(item);
463 }
464 else if (category == "all")
465 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24998));
466 else
467 GenerateCategoryListing(path, addons, items);
468 }
469
DependencyAddons(const CURL & path,CFileItemList & items)470 static void DependencyAddons(const CURL& path, CFileItemList &items)
471 {
472 VECADDONS all;
473 CServiceBroker::GetAddonMgr().GetInstalledAddons(all);
474
475 VECADDONS deps;
476 std::copy_if(all.begin(), all.end(), std::back_inserter(deps),
477 [&](const AddonPtr& _){ return !IsUserInstalled(_); });
478
479 CAddonsDirectory::GenerateAddonListing(path, deps, items, g_localizeStrings.Get(24996));
480
481 //Set orphaned status
482 std::set<std::string> orphaned;
483 for (const auto& addon : deps)
484 {
485 if (IsOrphaned(addon, all))
486 orphaned.insert(addon->ID());
487 }
488
489 for (int i = 0; i < items.Size(); ++i)
490 {
491 if (orphaned.find(items[i]->GetProperty("Addon.ID").asString()) != orphaned.end())
492 {
493 items[i]->SetProperty("Addon.Status", g_localizeStrings.Get(24995));
494 items[i]->SetProperty("Addon.Orphaned", true);
495 }
496 }
497 }
498
OutdatedAddons(const CURL & path,CFileItemList & items)499 static void OutdatedAddons(const CURL& path, CFileItemList &items)
500 {
501 VECADDONS addons = CServiceBroker::GetAddonMgr().GetAvailableUpdates();
502 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24043));
503
504 if (!items.IsEmpty())
505 {
506 if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON)
507 {
508 const CFileItemPtr itemUpdateAllowed(
509 std::make_shared<CFileItem>("addons://update_allowed/", false));
510 itemUpdateAllowed->SetLabel(g_localizeStrings.Get(24137));
511 itemUpdateAllowed->SetSpecialSort(SortSpecialOnTop);
512 items.Add(itemUpdateAllowed);
513 }
514
515 const CFileItemPtr itemUpdateAll(std::make_shared<CFileItem>("addons://update_all/", false));
516 itemUpdateAll->SetLabel(g_localizeStrings.Get(24122));
517 itemUpdateAll->SetSpecialSort(SortSpecialOnTop);
518 items.Add(itemUpdateAll);
519 }
520 }
521
RunningAddons(const CURL & path,CFileItemList & items)522 static void RunningAddons(const CURL& path, CFileItemList &items)
523 {
524 VECADDONS addons;
525 CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON_SERVICE);
526
527 addons.erase(std::remove_if(addons.begin(), addons.end(),
528 [](const AddonPtr& addon){ return !CScriptInvocationManager::GetInstance().IsRunning(addon->LibPath()); }), addons.end());
529 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24994));
530 }
531
Browse(const CURL & path,CFileItemList & items)532 static bool Browse(const CURL& path, CFileItemList &items)
533 {
534 const std::string& repoId = path.GetHostName();
535
536 VECADDONS addons;
537 items.SetPath(path.Get());
538 if (repoId == "all")
539 {
540 const auto& addonMgr = CServiceBroker::GetAddonMgr();
541 CAddonRepos addonRepos(addonMgr);
542 CAddonDatabase database;
543
544 if (!database.Open() || !addonRepos.LoadAddonsFromDatabase(database))
545 {
546 return false;
547 }
548 database.Close();
549
550 // get all latest addon versions by repo
551 addonRepos.GetLatestAddonVersionsFromAllRepos(addons);
552
553 items.SetProperty("reponame", g_localizeStrings.Get(24087));
554 items.SetLabel(g_localizeStrings.Get(24087));
555 }
556 else
557 {
558 AddonPtr repoAddon;
559 const auto& addonMgr = CServiceBroker::GetAddonMgr();
560
561 if (!addonMgr.GetAddon(repoId, repoAddon, ADDON_REPOSITORY, OnlyEnabled::YES))
562 return false;
563
564 CAddonRepos addonRepos(addonMgr);
565 CAddonDatabase database;
566
567 if (!database.Open() || !addonRepos.LoadAddonsFromDatabase(database, repoAddon))
568 {
569 return false;
570 }
571 database.Close();
572
573 // get all addons from the single repository
574 addonRepos.GetLatestAddonVersions(addons);
575
576 items.SetProperty("reponame", repoAddon->Name());
577 items.SetLabel(repoAddon->Name());
578 }
579
580 const std::string& category = path.GetFileName();
581 if (category.empty())
582 GenerateMainCategoryListing(path, addons, items);
583 else
584 GenerateCategoryListing(path, addons, items);
585 return true;
586 }
587
GetRecentlyUpdatedAddons(VECADDONS & addons)588 static bool GetRecentlyUpdatedAddons(VECADDONS& addons)
589 {
590 if (!CServiceBroker::GetAddonMgr().GetInstalledAddons(addons))
591 return false;
592
593 auto limit = CDateTime::GetCurrentDateTime() - CDateTimeSpan(14, 0, 0, 0);
594 auto isOld = [limit](const AddonPtr& addon){ return addon->LastUpdated() < limit; };
595 addons.erase(std::remove_if(addons.begin(), addons.end(), isOld), addons.end());
596 return true;
597 }
598
HasRecentlyUpdatedAddons()599 static bool HasRecentlyUpdatedAddons()
600 {
601 VECADDONS addons;
602 return GetRecentlyUpdatedAddons(addons) && !addons.empty();
603 }
604
Repos(const CURL & path,CFileItemList & items)605 static bool Repos(const CURL& path, CFileItemList &items)
606 {
607 items.SetLabel(g_localizeStrings.Get(24033));
608
609 VECADDONS addons;
610 CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON_REPOSITORY);
611 if (addons.empty())
612 return true;
613 else if (addons.size() == 1)
614 return Browse(CURL("addons://" + addons[0]->ID()), items);
615 CFileItemPtr item(new CFileItem("addons://all/", true));
616 item->SetLabel(g_localizeStrings.Get(24087));
617 item->SetSpecialSort(SortSpecialOnTop);
618 items.Add(item);
619 for (const auto& repo : addons)
620 {
621 CFileItemPtr item = CAddonsDirectory::FileItemFromAddon(repo, "addons://" + repo->ID(), true);
622 items.Add(item);
623 }
624 items.SetContent("addons");
625 return true;
626 }
627
RootDirectory(CFileItemList & items)628 static void RootDirectory(CFileItemList& items)
629 {
630 items.SetLabel(g_localizeStrings.Get(10040));
631 {
632 CFileItemPtr item(new CFileItem("addons://user/", true));
633 item->SetLabel(g_localizeStrings.Get(24998));
634 item->SetArt("icon", "DefaultAddonsInstalled.png");
635 items.Add(item);
636 }
637 if (CServiceBroker::GetAddonMgr().HasAvailableUpdates())
638 {
639 CFileItemPtr item(new CFileItem("addons://outdated/", true));
640 item->SetLabel(g_localizeStrings.Get(24043));
641 item->SetArt("icon", "DefaultAddonsUpdates.png");
642 items.Add(item);
643 }
644 if (CAddonInstaller::GetInstance().IsDownloading())
645 {
646 CFileItemPtr item(new CFileItem("addons://downloading/", true));
647 item->SetLabel(g_localizeStrings.Get(24067));
648 item->SetArt("icon", "DefaultNetwork.png");
649 items.Add(item);
650 }
651 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_ADDONS_AUTOUPDATES) == ADDON::AUTO_UPDATES_ON
652 && HasRecentlyUpdatedAddons())
653 {
654 CFileItemPtr item(new CFileItem("addons://recently_updated/", true));
655 item->SetLabel(g_localizeStrings.Get(24004));
656 item->SetArt("icon", "DefaultAddonsRecentlyUpdated.png");
657 items.Add(item);
658 }
659 if (CServiceBroker::GetAddonMgr().HasAddons(ADDON_REPOSITORY))
660 {
661 CFileItemPtr item(new CFileItem("addons://repos/", true));
662 item->SetLabel(g_localizeStrings.Get(24033));
663 item->SetArt("icon", "DefaultAddonsRepo.png");
664 items.Add(item);
665 }
666 {
667 CFileItemPtr item(new CFileItem("addons://install/", false));
668 item->SetLabel(g_localizeStrings.Get(24041));
669 item->SetArt("icon", "DefaultAddonsZip.png");
670 items.Add(item);
671 }
672 {
673 CFileItemPtr item(new CFileItem("addons://search/", true));
674 item->SetLabel(g_localizeStrings.Get(137));
675 item->SetArt("icon", "DefaultAddonsSearch.png");
676 items.Add(item);
677 }
678 }
679
GetDirectory(const CURL & url,CFileItemList & items)680 bool CAddonsDirectory::GetDirectory(const CURL& url, CFileItemList &items)
681 {
682 std::string tmp(url.Get());
683 URIUtils::RemoveSlashAtEnd(tmp);
684 CURL path(tmp);
685 const std::string& endpoint = path.GetHostName();
686 items.ClearItems();
687 items.ClearProperties();
688 items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
689 items.SetPath(path.Get());
690
691 if (endpoint.empty())
692 {
693 RootDirectory(items);
694 return true;
695 }
696 else if (endpoint == "user")
697 {
698 UserInstalledAddons(path, items);
699 return true;
700 }
701 else if (endpoint == "dependencies")
702 {
703 DependencyAddons(path, items);
704 return true;
705 }
706 // PVR hardcodes this view so keep for compatibility
707 else if (endpoint == "disabled")
708 {
709 VECADDONS addons;
710 ADDON::TYPE type;
711
712 if (path.GetFileName() == "kodi.pvrclient")
713 type = ADDON_PVRDLL;
714 else if (path.GetFileName() == "kodi.vfs")
715 type = ADDON_VFS;
716 else
717 type = ADDON_UNKNOWN;
718
719 if (type != ADDON_UNKNOWN && CServiceBroker::GetAddonMgr().GetInstalledAddons(addons, type))
720 {
721 CAddonsDirectory::GenerateAddonListing(path, addons, items, CAddonInfo::TranslateType(type, true));
722 return true;
723 }
724 return false;
725 }
726 else if (endpoint == "outdated")
727 {
728 OutdatedAddons(path, items);
729 return true;
730 }
731 else if (endpoint == "running")
732 {
733 RunningAddons(path, items);
734 return true;
735 }
736 else if (endpoint == "repos")
737 {
738 return Repos(path, items);
739 }
740 else if (endpoint == "sources")
741 {
742 return GetScriptsAndPlugins(path.GetFileName(), items);
743 }
744 else if (endpoint == "search")
745 {
746 return GetSearchResults(path, items);
747 }
748 else if (endpoint == "recently_updated")
749 {
750 VECADDONS addons;
751 if (!GetRecentlyUpdatedAddons(addons))
752 return false;
753
754 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24004));
755 return true;
756 }
757 else if (endpoint == "downloading")
758 {
759 VECADDONS addons;
760 CAddonInstaller::GetInstance().GetInstallList(addons);
761 CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24067));
762 return true;
763 }
764 else if (endpoint == "more")
765 {
766 const std::string& type = path.GetFileName();
767 if (type == "video" || type == "audio" || type == "image" || type == "executable")
768 return Browse(CURL("addons://all/xbmc.addon." + type), items);
769 else if (type == "game")
770 return Browse(CURL("addons://all/category.gameaddons"), items);
771 return false;
772 }
773 else
774 {
775 return Browse(path, items);
776 }
777 }
778
IsRepoDirectory(const CURL & url)779 bool CAddonsDirectory::IsRepoDirectory(const CURL& url)
780 {
781 if (url.GetHostName().empty() || !url.IsProtocol("addons"))
782 return false;
783
784 AddonPtr tmp;
785 return url.GetHostName() == "repos" || url.GetHostName() == "all" ||
786 url.GetHostName() == "search" ||
787 CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), tmp, ADDON_REPOSITORY,
788 OnlyEnabled::YES);
789 }
790
GenerateAddonListing(const CURL & path,const VECADDONS & addons,CFileItemList & items,const std::string & label)791 void CAddonsDirectory::GenerateAddonListing(const CURL& path,
792 const VECADDONS& addons,
793 CFileItemList& items,
794 const std::string& label)
795 {
796 std::map<std::string, CAddonWithUpdate> addonsWithUpdate;
797
798 CServiceBroker::GetAddonMgr().GetAddonsWithAvailableUpdate(addonsWithUpdate);
799
800 items.ClearItems();
801 items.SetContent("addons");
802 items.SetLabel(label);
803 for (const auto& addon : addons)
804 {
805 CURL itemPath = path;
806 itemPath.SetFileName(addon->ID());
807 CFileItemPtr pItem = FileItemFromAddon(addon, itemPath.Get(), false);
808
809 bool installed = CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID(), addon->Origin(),
810 addon->Version());
811 bool disabled = CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID());
812
813 std::function<bool(bool)> CheckOutdatedOrUpdate = [&](bool checkOutdated) -> bool {
814 auto mapEntry = addonsWithUpdate.find(addon->ID());
815 if (mapEntry != addonsWithUpdate.end())
816 {
817 const std::shared_ptr<IAddon>& checkedObject =
818 checkOutdated ? mapEntry->second.m_installed : mapEntry->second.m_update;
819
820 return (checkedObject->Origin() == addon->Origin() &&
821 checkedObject->Version() == addon->Version());
822 }
823 return false;
824 };
825
826 bool isUpdate = CheckOutdatedOrUpdate(false); // check if it's an available update
827 bool hasUpdate = CheckOutdatedOrUpdate(true); // check if it's an outdated addon
828
829 std::string validUpdateVersion;
830 std::string validUpdateOrigin;
831 if (hasUpdate)
832 {
833 auto mapEntry = addonsWithUpdate.find(addon->ID());
834 validUpdateVersion = mapEntry->second.m_update->Version().asString();
835 validUpdateOrigin = mapEntry->second.m_update->Origin();
836 }
837
838 bool fromOfficialRepo = CAddonRepos::IsFromOfficialRepo(addon, CheckAddonPath::NO);
839
840 pItem->SetProperty("Addon.IsInstalled", installed);
841 pItem->SetProperty("Addon.IsEnabled", installed && !disabled);
842 pItem->SetProperty("Addon.HasUpdate", hasUpdate);
843 pItem->SetProperty("Addon.IsUpdate", isUpdate);
844 pItem->SetProperty("Addon.ValidUpdateVersion", validUpdateVersion);
845 pItem->SetProperty("Addon.ValidUpdateOrigin", validUpdateOrigin);
846 pItem->SetProperty("Addon.IsFromOfficialRepo", fromOfficialRepo);
847 pItem->SetProperty("Addon.IsBinary", addon->IsBinary());
848
849 if (installed)
850 pItem->SetProperty("Addon.Status", g_localizeStrings.Get(305));
851 if (disabled)
852 pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24023));
853 if (hasUpdate)
854 pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24068));
855 else if (addon->LifecycleState() == AddonLifecycleState::BROKEN)
856 pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24098));
857 else if (addon->LifecycleState() == AddonLifecycleState::DEPRECATED)
858 pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24170));
859
860 items.Add(pItem);
861 }
862 }
863
FileItemFromAddon(const AddonPtr & addon,const std::string & path,bool folder)864 CFileItemPtr CAddonsDirectory::FileItemFromAddon(const AddonPtr &addon,
865 const std::string& path, bool folder)
866 {
867 if (!addon)
868 return CFileItemPtr();
869
870 CFileItemPtr item(new CFileItem(addon));
871 item->m_bIsFolder = folder;
872 item->SetPath(path);
873
874 std::string strLabel(addon->Name());
875 if (CURL(path).GetHostName() == "search")
876 strLabel = StringUtils::Format("%s - %s", CAddonInfo::TranslateType(addon->Type(), true).c_str(), addon->Name().c_str());
877 item->SetLabel(strLabel);
878 item->SetArt(addon->Art());
879 item->SetArt("thumb", addon->Icon());
880 item->SetArt("icon", "DefaultAddon.png");
881
882 //! @todo fix hacks that depends on these
883 item->SetProperty("Addon.ID", addon->ID());
884 item->SetProperty("Addon.Name", addon->Name());
885 const auto it = addon->ExtraInfo().find("language");
886 if (it != addon->ExtraInfo().end())
887 item->SetProperty("Addon.Language", it->second);
888
889 return item;
890 }
891
GetScriptsAndPlugins(const std::string & content,VECADDONS & addons)892 bool CAddonsDirectory::GetScriptsAndPlugins(const std::string &content, VECADDONS &addons)
893 {
894 CPluginSource::Content type = CPluginSource::Translate(content);
895 if (type == CPluginSource::UNKNOWN)
896 return false;
897
898 VECADDONS tempAddons;
899 CServiceBroker::GetAddonMgr().GetAddons(tempAddons, ADDON_PLUGIN);
900 for (unsigned i=0; i<tempAddons.size(); i++)
901 {
902 PluginPtr plugin = std::dynamic_pointer_cast<CPluginSource>(tempAddons[i]);
903 if (plugin && plugin->Provides(type))
904 addons.push_back(tempAddons[i]);
905 }
906 tempAddons.clear();
907 CServiceBroker::GetAddonMgr().GetAddons(tempAddons, ADDON_SCRIPT);
908 for (unsigned i=0; i<tempAddons.size(); i++)
909 {
910 PluginPtr plugin = std::dynamic_pointer_cast<CPluginSource>(tempAddons[i]);
911 if (plugin && plugin->Provides(type))
912 addons.push_back(tempAddons[i]);
913 }
914 tempAddons.clear();
915
916 if (type == CPluginSource::GAME)
917 {
918 CServiceBroker::GetAddonMgr().GetAddons(tempAddons, ADDON_GAMEDLL);
919 for (auto& addon : tempAddons)
920 {
921 if (IsStandaloneGame(addon))
922 addons.push_back(addon);
923 }
924 }
925
926 return true;
927 }
928
GetScriptsAndPlugins(const std::string & content,CFileItemList & items)929 bool CAddonsDirectory::GetScriptsAndPlugins(const std::string &content, CFileItemList &items)
930 {
931 VECADDONS addons;
932 if (!GetScriptsAndPlugins(content, addons))
933 return false;
934
935 for (AddonPtr& addon : addons)
936 {
937 const bool bIsFolder = (addon->Type() == ADDON_PLUGIN);
938
939 std::string path;
940 if (addon->HasType(ADDON_PLUGIN))
941 {
942 path = "plugin://" + addon->ID();
943 PluginPtr plugin = std::dynamic_pointer_cast<CPluginSource>(addon);
944 if (plugin && plugin->ProvidesSeveral())
945 {
946 CURL url(path);
947 std::string opt = StringUtils::Format("?content_type=%s", content.c_str());
948 url.SetOptions(opt);
949 path = url.Get();
950 }
951 }
952 else if (addon->HasType(ADDON_SCRIPT))
953 {
954 path = "script://" + addon->ID();
955 }
956 else if (addon->HasType(ADDON_GAMEDLL))
957 {
958 // Kodi fails to launch games with empty path from home screen
959 path = "game://" + addon->ID();
960 }
961
962 items.Add(FileItemFromAddon(addon, path, bIsFolder));
963 }
964
965 items.SetContent("addons");
966 items.SetLabel(g_localizeStrings.Get(24001)); // Add-ons
967
968 return true;
969 }
970
971 }
972
973