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