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 "AddonManager.h"
10 
11 #include "CompileInfo.h"
12 #include "LangInfo.h"
13 #include "ServiceBroker.h"
14 #include "addons/Addon.h"
15 #include "addons/AddonInstaller.h"
16 #include "addons/AddonRepos.h"
17 #include "addons/AddonSystemSettings.h"
18 #include "addons/addoninfo/AddonInfoBuilder.h"
19 #include "events/AddonManagementEvent.h"
20 #include "events/EventLog.h"
21 #include "events/NotificationEvent.h"
22 #include "filesystem/Directory.h"
23 #include "filesystem/File.h"
24 #include "filesystem/SpecialProtocol.h"
25 #include "utils/StringUtils.h"
26 #include "utils/URIUtils.h"
27 #include "utils/XMLUtils.h"
28 #include "utils/log.h"
29 
30 #include <algorithm>
31 #include <array>
32 #include <set>
33 #include <utility>
34 
35 using namespace XFILE;
36 
37 namespace ADDON
38 {
39 
40 /**********************************************************
41  * CAddonMgr
42  *
43  */
44 
45 std::map<TYPE, IAddonMgrCallback*> CAddonMgr::m_managers;
46 
LoadManifest(std::set<std::string> & system,std::set<std::string> & optional)47 static bool LoadManifest(std::set<std::string>& system, std::set<std::string>& optional)
48 {
49   CXBMCTinyXML doc;
50   if (!doc.LoadFile("special://xbmc/system/addon-manifest.xml"))
51   {
52     CLog::Log(LOGERROR, "ADDONS: manifest missing");
53     return false;
54   }
55 
56   auto root = doc.RootElement();
57   if (!root || root->ValueStr() != "addons")
58   {
59     CLog::Log(LOGERROR, "ADDONS: malformed manifest");
60     return false;
61   }
62 
63   auto elem = root->FirstChildElement("addon");
64   while (elem)
65   {
66     if (elem->FirstChild())
67     {
68       if (XMLUtils::GetAttribute(elem, "optional") == "true")
69         optional.insert(elem->FirstChild()->ValueStr());
70       else
71         system.insert(elem->FirstChild()->ValueStr());
72     }
73     elem = elem->NextSiblingElement("addon");
74   }
75   return true;
76 }
77 
~CAddonMgr()78 CAddonMgr::~CAddonMgr()
79 {
80   DeInit();
81 }
82 
GetCallbackForType(TYPE type)83 IAddonMgrCallback* CAddonMgr::GetCallbackForType(TYPE type)
84 {
85   if (m_managers.find(type) == m_managers.end())
86     return NULL;
87   else
88     return m_managers[type];
89 }
90 
RegisterAddonMgrCallback(const TYPE type,IAddonMgrCallback * cb)91 bool CAddonMgr::RegisterAddonMgrCallback(const TYPE type, IAddonMgrCallback* cb)
92 {
93   if (cb == NULL)
94     return false;
95 
96   m_managers.erase(type);
97   m_managers[type] = cb;
98 
99   return true;
100 }
101 
UnregisterAddonMgrCallback(TYPE type)102 void CAddonMgr::UnregisterAddonMgrCallback(TYPE type)
103 {
104   m_managers.erase(type);
105 }
106 
Init()107 bool CAddonMgr::Init()
108 {
109   CSingleLock lock(m_critSection);
110 
111   if (!LoadManifest(m_systemAddons, m_optionalSystemAddons))
112   {
113     CLog::Log(LOGERROR, "ADDONS: Failed to read manifest");
114     return false;
115   }
116 
117   if (!m_database.Open())
118     CLog::Log(LOGFATAL, "ADDONS: Failed to open database");
119 
120   FindAddons();
121 
122   //Ensure required add-ons are installed and enabled
123   for (const auto& id : m_systemAddons)
124   {
125     AddonPtr addon;
126     if (!GetAddon(id, addon, ADDON_UNKNOWN, OnlyEnabled::YES))
127     {
128       CLog::Log(LOGFATAL, "addon '%s' not installed or not enabled.", id.c_str());
129       return false;
130     }
131   }
132 
133   return true;
134 }
135 
DeInit()136 void CAddonMgr::DeInit()
137 {
138   m_database.Close();
139 
140   /* If temporary directory was used from add-on, delete it */
141   if (XFILE::CDirectory::Exists(m_tempAddonBasePath))
142     XFILE::CDirectory::RemoveRecursive(CSpecialProtocol::TranslatePath(m_tempAddonBasePath));
143 }
144 
HasAddons(const TYPE & type)145 bool CAddonMgr::HasAddons(const TYPE &type)
146 {
147   CSingleLock lock(m_critSection);
148 
149   for (const auto& addonInfo : m_installedAddons)
150   {
151     if (addonInfo.second->HasType(type) && !IsAddonDisabled(addonInfo.second->ID()))
152       return true;
153   }
154   return false;
155 }
156 
HasInstalledAddons(const TYPE & type)157 bool CAddonMgr::HasInstalledAddons(const TYPE &type)
158 {
159   CSingleLock lock(m_critSection);
160 
161   for (const auto& addonInfo : m_installedAddons)
162   {
163     if (addonInfo.second->HasType(type))
164       return true;
165   }
166   return false;
167 }
168 
AddToUpdateableAddons(AddonPtr & pAddon)169 void CAddonMgr::AddToUpdateableAddons(AddonPtr &pAddon)
170 {
171   CSingleLock lock(m_critSection);
172   m_updateableAddons.push_back(pAddon);
173 }
174 
RemoveFromUpdateableAddons(AddonPtr & pAddon)175 void CAddonMgr::RemoveFromUpdateableAddons(AddonPtr &pAddon)
176 {
177   CSingleLock lock(m_critSection);
178   VECADDONS::iterator it = std::find(m_updateableAddons.begin(), m_updateableAddons.end(), pAddon);
179 
180   if(it != m_updateableAddons.end())
181   {
182     m_updateableAddons.erase(it);
183   }
184 }
185 
186 struct AddonIdFinder
187 {
AddonIdFinderADDON::AddonIdFinder188     explicit AddonIdFinder(const std::string& id)
189       : m_id(id)
190     {}
191 
operator ()ADDON::AddonIdFinder192     bool operator()(const AddonPtr& addon)
193     {
194       return m_id == addon->ID();
195     }
196     private:
197     std::string m_id;
198 };
199 
ReloadSettings(const std::string & id)200 bool CAddonMgr::ReloadSettings(const std::string &id)
201 {
202   CSingleLock lock(m_critSection);
203   VECADDONS::iterator it = std::find_if(m_updateableAddons.begin(), m_updateableAddons.end(), AddonIdFinder(id));
204 
205   if( it != m_updateableAddons.end())
206   {
207     return (*it)->ReloadSettings();
208   }
209   return false;
210 }
211 
GetAvailableUpdates() const212 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetAvailableUpdates() const
213 {
214   std::vector<std::shared_ptr<IAddon>> availableUpdates =
215       GetAvailableUpdatesOrOutdatedAddons(AddonCheckType::AVAILABLE_UPDATES);
216 
217   std::lock_guard<std::mutex> lock(m_lastAvailableUpdatesCountMutex);
218   m_lastAvailableUpdatesCountAsString = std::to_string(availableUpdates.size());
219 
220   return availableUpdates;
221 }
222 
GetLastAvailableUpdatesCountAsString() const223 const std::string& CAddonMgr::GetLastAvailableUpdatesCountAsString() const
224 {
225   std::lock_guard<std::mutex> lock(m_lastAvailableUpdatesCountMutex);
226   return m_lastAvailableUpdatesCountAsString;
227 };
228 
GetOutdatedAddons() const229 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetOutdatedAddons() const
230 {
231   return GetAvailableUpdatesOrOutdatedAddons(AddonCheckType::OUTDATED_ADDONS);
232 }
233 
GetAvailableUpdatesOrOutdatedAddons(AddonCheckType addonCheckType) const234 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetAvailableUpdatesOrOutdatedAddons(
235     AddonCheckType addonCheckType) const
236 {
237   CSingleLock lock(m_critSection);
238   auto start = XbmcThreads::SystemClockMillis();
239 
240   std::vector<std::shared_ptr<IAddon>> result;
241   std::vector<std::shared_ptr<IAddon>> installed;
242   CAddonRepos addonRepos(*this);
243 
244   addonRepos.LoadAddonsFromDatabase(m_database);
245 
246   GetAddonsForUpdate(installed);
247 
248   addonRepos.BuildUpdateOrOutdatedList(installed, result, addonCheckType);
249 
250   CLog::Log(LOGDEBUG, "CAddonMgr::GetAvailableUpdatesOrOutdatedAddons took %i ms",
251             XbmcThreads::SystemClockMillis() - start);
252   return result;
253 }
254 
GetAddonsWithAvailableUpdate(std::map<std::string,CAddonWithUpdate> & addonsWithUpdate) const255 bool CAddonMgr::GetAddonsWithAvailableUpdate(
256     std::map<std::string, CAddonWithUpdate>& addonsWithUpdate) const
257 {
258   CSingleLock lock(m_critSection);
259   auto start = XbmcThreads::SystemClockMillis();
260 
261   std::vector<std::shared_ptr<IAddon>> installed;
262   CAddonRepos addonRepos(*this);
263 
264   addonRepos.LoadAddonsFromDatabase(m_database);
265   GetAddonsForUpdate(installed);
266   addonRepos.BuildAddonsWithUpdateList(installed, addonsWithUpdate);
267 
268   CLog::Log(LOGDEBUG, "CAddonMgr::{} took {} ms", __func__,
269             XbmcThreads::SystemClockMillis() - start);
270 
271   return true;
272 }
273 
GetCompatibleVersions(const std::string & addonId,std::vector<std::shared_ptr<IAddon>> & compatibleVersions) const274 bool CAddonMgr::GetCompatibleVersions(
275     const std::string& addonId, std::vector<std::shared_ptr<IAddon>>& compatibleVersions) const
276 {
277   CSingleLock lock(m_critSection);
278   auto start = XbmcThreads::SystemClockMillis();
279 
280   CAddonRepos addonRepos(*this);
281   addonRepos.LoadAddonsFromDatabase(m_database, addonId);
282   addonRepos.BuildCompatibleVersionsList(compatibleVersions);
283 
284   CLog::Log(LOGDEBUG, "CAddonMgr::{} took {} ms", __func__,
285             XbmcThreads::SystemClockMillis() - start);
286 
287   return true;
288 }
289 
HasAvailableUpdates()290 bool CAddonMgr::HasAvailableUpdates()
291 {
292   return !GetAvailableUpdates().empty();
293 }
294 
GetAddonsForUpdate(VECADDONS & addons) const295 bool CAddonMgr::GetAddonsForUpdate(VECADDONS& addons) const
296 {
297   return GetAddonsInternal(ADDON_UNKNOWN, addons, true, true);
298 }
299 
GetAddons(VECADDONS & addons) const300 bool CAddonMgr::GetAddons(VECADDONS& addons) const
301 {
302   return GetAddonsInternal(ADDON_UNKNOWN, addons, true);
303 }
304 
GetAddons(VECADDONS & addons,const TYPE & type)305 bool CAddonMgr::GetAddons(VECADDONS& addons, const TYPE& type)
306 {
307   return GetAddonsInternal(type, addons, true);
308 }
309 
GetInstalledAddons(VECADDONS & addons)310 bool CAddonMgr::GetInstalledAddons(VECADDONS& addons)
311 {
312   return GetAddonsInternal(ADDON_UNKNOWN, addons, false);
313 }
314 
GetInstalledAddons(VECADDONS & addons,const TYPE & type)315 bool CAddonMgr::GetInstalledAddons(VECADDONS& addons, const TYPE& type)
316 {
317   return GetAddonsInternal(type, addons, false);
318 }
319 
GetDisabledAddons(VECADDONS & addons)320 bool CAddonMgr::GetDisabledAddons(VECADDONS& addons)
321 {
322   return CAddonMgr::GetDisabledAddons(addons, ADDON_UNKNOWN);
323 }
324 
GetDisabledAddons(VECADDONS & addons,const TYPE & type)325 bool CAddonMgr::GetDisabledAddons(VECADDONS& addons, const TYPE& type)
326 {
327   VECADDONS all;
328   if (GetInstalledAddons(all, type))
329   {
330     std::copy_if(all.begin(), all.end(), std::back_inserter(addons),
331         [this](const AddonPtr& addon){ return IsAddonDisabled(addon->ID()); });
332     return true;
333   }
334   return false;
335 }
336 
GetInstallableAddons(VECADDONS & addons)337 bool CAddonMgr::GetInstallableAddons(VECADDONS& addons)
338 {
339   return GetInstallableAddons(addons, ADDON_UNKNOWN);
340 }
341 
GetInstallableAddons(VECADDONS & addons,const TYPE & type)342 bool CAddonMgr::GetInstallableAddons(VECADDONS& addons, const TYPE &type)
343 {
344   CSingleLock lock(m_critSection);
345   CAddonRepos addonRepos(*this);
346 
347   if (!addonRepos.LoadAddonsFromDatabase(m_database))
348     return false;
349 
350   // get all addons
351   addonRepos.GetLatestAddonVersions(addons);
352 
353   // go through all addons and remove all that are already installed
354 
355   addons.erase(std::remove_if(addons.begin(), addons.end(),
356     [this, type](const AddonPtr& addon)
357     {
358       bool bErase = false;
359 
360       // check if the addon matches the provided addon type
361       if (type != ADDON::ADDON_UNKNOWN && addon->Type() != type && !addon->HasType(type))
362         bErase = true;
363 
364       if (!this->CanAddonBeInstalled(addon))
365         bErase = true;
366 
367       return bErase;
368     }), addons.end());
369 
370   return true;
371 }
372 
FindInstallableById(const std::string & addonId,AddonPtr & result)373 bool CAddonMgr::FindInstallableById(const std::string& addonId, AddonPtr& result)
374 {
375   CSingleLock lock(m_critSection);
376 
377   CAddonRepos addonRepos(*this);
378   addonRepos.LoadAddonsFromDatabase(m_database, addonId);
379 
380   AddonPtr addonToUpdate;
381 
382   // check for an update if addon is installed already
383 
384   if (GetAddon(addonId, addonToUpdate, ADDON_UNKNOWN, OnlyEnabled::NO))
385   {
386     if (addonRepos.DoAddonUpdateCheck(addonToUpdate, result))
387       return true;
388   }
389 
390   // get the latest version from all repos if the
391   // addon is up-to-date or not installed yet
392 
393   CLog::Log(LOGDEBUG,
394             "CAddonMgr::{}: addon {} is up-to-date or not installed. falling back to get latest "
395             "version from all repos",
396             __FUNCTION__, addonId);
397 
398   return addonRepos.GetLatestAddonVersionFromAllRepos(addonId, result);
399 }
400 
GetAddonsInternal(const TYPE & type,VECADDONS & addons,bool onlyEnabled,bool checkIncompatible) const401 bool CAddonMgr::GetAddonsInternal(const TYPE& type,
402                                   VECADDONS& addons,
403                                   bool onlyEnabled,
404                                   bool checkIncompatible) const
405 {
406   CSingleLock lock(m_critSection);
407 
408   for (const auto& addonInfo : m_installedAddons)
409   {
410     if (type != ADDON_UNKNOWN && !addonInfo.second->HasType(type))
411       continue;
412 
413     if (onlyEnabled &&
414         ((!checkIncompatible && IsAddonDisabled(addonInfo.second->ID())) ||
415          (checkIncompatible &&
416           IsAddonDisabledExcept(addonInfo.second->ID(), AddonDisabledReason::INCOMPATIBLE))))
417       continue;
418 
419     //FIXME: hack for skipping special dependency addons (xbmc.python etc.).
420     //Will break if any extension point is added to them
421     if (addonInfo.second->MainType() == ADDON_UNKNOWN)
422       continue;
423 
424     AddonPtr addon = CAddonBuilder::Generate(addonInfo.second, type);
425     if (addon)
426     {
427       // if the addon has a running instance, grab that
428       AddonPtr runningAddon = addon->GetRunningInstance();
429       if (runningAddon)
430         addon = runningAddon;
431       addons.emplace_back(std::move(addon));
432     }
433   }
434   return addons.size() > 0;
435 }
436 
GetIncompatibleEnabledAddonInfos(std::vector<AddonInfoPtr> & incompatible) const437 bool CAddonMgr::GetIncompatibleEnabledAddonInfos(std::vector<AddonInfoPtr>& incompatible) const
438 {
439   return GetIncompatibleAddonInfos(incompatible, false);
440 }
441 
GetIncompatibleAddonInfos(std::vector<AddonInfoPtr> & incompatible,bool includeDisabled) const442 bool CAddonMgr::GetIncompatibleAddonInfos(std::vector<AddonInfoPtr>& incompatible,
443                                           bool includeDisabled) const
444 {
445   GetAddonInfos(incompatible, true, ADDON_UNKNOWN);
446   if (includeDisabled)
447     GetDisabledAddonInfos(incompatible, ADDON_UNKNOWN, AddonDisabledReason::INCOMPATIBLE);
448   incompatible.erase(std::remove_if(incompatible.begin(), incompatible.end(),
449                                     [this](const AddonInfoPtr& a) { return IsCompatible(a); }),
450                      incompatible.end());
451   return !incompatible.empty();
452 }
453 
MigrateAddons()454 std::vector<AddonInfoPtr> CAddonMgr::MigrateAddons()
455 {
456   // install all addon updates
457   std::lock_guard<std::mutex> lock(m_installAddonsMutex);
458   CLog::Log(LOGINFO, "ADDON: waiting for add-ons to update...");
459   VECADDONS updates;
460   GetAddonUpdateCandidates(updates);
461   InstallAddonUpdates(updates, true, AllowCheckForUpdates::NO);
462 
463   // get addons that became incompatible and disable them
464   std::vector<AddonInfoPtr> incompatible;
465   GetIncompatibleAddonInfos(incompatible, true);
466 
467   return DisableIncompatibleAddons(incompatible);
468 }
469 
DisableIncompatibleAddons(const std::vector<AddonInfoPtr> & incompatible)470 std::vector<AddonInfoPtr> CAddonMgr::DisableIncompatibleAddons(
471     const std::vector<AddonInfoPtr>& incompatible)
472 {
473   std::vector<AddonInfoPtr> changed;
474   for (const auto& addon : incompatible)
475   {
476     CLog::Log(LOGINFO, "ADDON: {} version {} is incompatible", addon->ID(),
477               addon->Version().asString());
478 
479     if (!CAddonSystemSettings::GetInstance().UnsetActive(addon))
480     {
481       CLog::Log(LOGWARNING, "ADDON: failed to unset {}", addon->ID());
482       continue;
483     }
484     if (!DisableAddon(addon->ID(), AddonDisabledReason::INCOMPATIBLE))
485     {
486       CLog::Log(LOGWARNING, "ADDON: failed to disable {}", addon->ID());
487     }
488 
489     changed.emplace_back(addon);
490   }
491 
492   return changed;
493 }
494 
CheckAndInstallAddonUpdates(bool wait) const495 void CAddonMgr::CheckAndInstallAddonUpdates(bool wait) const
496 {
497   std::lock_guard<std::mutex> lock(m_installAddonsMutex);
498   VECADDONS updates;
499   GetAddonUpdateCandidates(updates);
500   InstallAddonUpdates(updates, wait, AllowCheckForUpdates::YES);
501 }
502 
GetAddonUpdateCandidates(VECADDONS & updates) const503 bool CAddonMgr::GetAddonUpdateCandidates(VECADDONS& updates) const
504 {
505   // Get Addons in need of an update and remove all the blacklisted ones
506   updates = GetAvailableUpdates();
507   updates.erase(
508       std::remove_if(updates.begin(), updates.end(),
509                      [this](const AddonPtr& addon) { return !IsAutoUpdateable(addon->ID()); }),
510       updates.end());
511   return updates.empty();
512 }
513 
SortByDependencies(VECADDONS & updates) const514 void CAddonMgr::SortByDependencies(VECADDONS& updates) const
515 {
516   std::vector<std::shared_ptr<ADDON::IAddon>> sorted;
517   while (!updates.empty())
518   {
519     for (auto it = updates.begin(); it != updates.end();)
520     {
521       const auto& addon = *it;
522 
523       const auto& dependencies = addon->GetDependencies();
524       bool addToSortedList = true;
525       // if the addon has dependencies we need to check for each dependency if it also has
526       // an update to be installed (and in that case, if it is already in the sorted vector).
527       // if all dependency match the said conditions, the addon doesn't depend on other addons
528       // waiting to be updated. Hence, the addon being processed can be installed (i.e. added to
529       // the end of the sorted vector of addon updates)
530       for (const auto& dep : dependencies)
531       {
532         auto comparator = [&dep](const std::shared_ptr<ADDON::IAddon>& addon) {
533           return addon->ID() == dep.id;
534         };
535 
536         if ((std::any_of(updates.begin(), updates.end(), comparator)) &&
537             (!std::any_of(sorted.begin(), sorted.end(), comparator)))
538         {
539           addToSortedList = false;
540           break;
541         }
542       }
543 
544       // add to the end of sorted list of addons
545       if (addToSortedList)
546       {
547         sorted.emplace_back(addon);
548         it = updates.erase(it);
549       }
550       else
551       {
552         ++it;
553       }
554     }
555   }
556   updates = sorted;
557 }
558 
InstallAddonUpdates(VECADDONS & updates,bool wait,AllowCheckForUpdates allowCheckForUpdates) const559 void CAddonMgr::InstallAddonUpdates(VECADDONS& updates,
560                                     bool wait,
561                                     AllowCheckForUpdates allowCheckForUpdates) const
562 {
563   // sort addons by dependencies (ensure install order) and install all
564   SortByDependencies(updates);
565   CAddonInstaller::GetInstance().InstallAddons(updates, wait, allowCheckForUpdates);
566 }
567 
GetAddon(const std::string & str,AddonPtr & addon,const TYPE & type,OnlyEnabled onlyEnabled) const568 bool CAddonMgr::GetAddon(const std::string& str,
569                          AddonPtr& addon,
570                          const TYPE& type,
571                          OnlyEnabled onlyEnabled) const
572 {
573   CSingleLock lock(m_critSection);
574 
575   AddonInfoPtr addonInfo = GetAddonInfo(str, type);
576   if (addonInfo)
577   {
578     addon = CAddonBuilder::Generate(addonInfo, type);
579     if (addon)
580     {
581       if (onlyEnabled == OnlyEnabled::YES && IsAddonDisabled(addonInfo->ID()))
582         return false;
583 
584       // if the addon has a running instance, grab that
585       AddonPtr runningAddon = addon->GetRunningInstance();
586       if (runningAddon)
587         addon = runningAddon;
588     }
589     return NULL != addon.get();
590   }
591 
592   return false;
593 }
594 
HasType(const std::string & id,const TYPE & type)595 bool CAddonMgr::HasType(const std::string &id, const TYPE &type)
596 {
597   AddonPtr addon;
598   return GetAddon(id, addon, type, OnlyEnabled::NO);
599 }
600 
FindAddon(const std::string & addonId,const std::string & origin,const AddonVersion & addonVersion)601 bool CAddonMgr::FindAddon(const std::string& addonId,
602                           const std::string& origin,
603                           const AddonVersion& addonVersion)
604 {
605   std::map<std::string, std::shared_ptr<CAddonInfo>> installedAddons;
606 
607   FindAddons(installedAddons, "special://xbmcbin/addons");
608   FindAddons(installedAddons, "special://xbmc/addons");
609   FindAddons(installedAddons, "special://home/addons");
610 
611   const auto it = installedAddons.find(addonId);
612   if (it == installedAddons.cend() || it->second->Version() != addonVersion)
613     return false;
614 
615   CSingleLock lock(m_critSection);
616 
617   m_database.GetInstallData(it->second);
618   CLog::Log(LOGINFO, "CAddonMgr::{}: {} v{} installed", __FUNCTION__, addonId,
619             addonVersion.asString());
620 
621   m_installedAddons[addonId] = it->second; // insert/replace entry
622   m_database.AddInstalledAddon(it->second, origin);
623 
624   // Reload caches
625   std::map<std::string, AddonDisabledReason> tmpDisabled;
626   m_database.GetDisabled(tmpDisabled);
627   m_disabled = std::move(tmpDisabled);
628 
629   m_updateRules.RefreshRulesMap(m_database);
630   return true;
631 }
632 
FindAddons()633 bool CAddonMgr::FindAddons()
634 {
635   ADDON_INFO_LIST installedAddons;
636 
637   FindAddons(installedAddons, "special://xbmcbin/addons");
638   FindAddons(installedAddons, "special://xbmc/addons");
639   FindAddons(installedAddons, "special://home/addons");
640 
641   std::set<std::string> installed;
642   for (const auto& addon : installedAddons)
643     installed.insert(addon.second->ID());
644 
645   CSingleLock lock(m_critSection);
646 
647   // Sync with db
648   m_database.SyncInstalled(installed, m_systemAddons, m_optionalSystemAddons);
649   for (const auto& addon : installedAddons)
650   {
651     m_database.GetInstallData(addon.second);
652     CLog::Log(LOGINFO, "CAddonMgr::{}: {} v{} installed", __FUNCTION__, addon.second->ID(),
653               addon.second->Version().asString());
654   }
655 
656   m_installedAddons = std::move(installedAddons);
657 
658   // Reload caches
659   std::map<std::string, AddonDisabledReason> tmpDisabled;
660   m_database.GetDisabled(tmpDisabled);
661   m_disabled = std::move(tmpDisabled);
662 
663   m_updateRules.RefreshRulesMap(m_database);
664 
665   return true;
666 }
667 
UnloadAddon(const std::string & addonId)668 bool CAddonMgr::UnloadAddon(const std::string& addonId)
669 {
670   CSingleLock lock(m_critSection);
671 
672   if (!IsAddonInstalled(addonId))
673     return true;
674 
675   AddonPtr localAddon;
676   // can't unload an binary addon that is in use
677   if (GetAddon(addonId, localAddon, ADDON_UNKNOWN, OnlyEnabled::NO) && localAddon->IsBinary() &&
678       localAddon->IsInUse())
679   {
680     CLog::Log(LOGERROR, "CAddonMgr::{}: could not unload binary add-on {}, as is in use", __func__,
681               addonId);
682     return false;
683   }
684 
685   m_installedAddons.erase(addonId);
686   CLog::Log(LOGDEBUG, "CAddonMgr::{}: {} unloaded", __func__, addonId);
687 
688   lock.Leave();
689   AddonEvents::Unload event(addonId);
690   m_unloadEvents.HandleEvent(event);
691 
692   return true;
693 }
694 
LoadAddon(const std::string & addonId,const std::string & origin,const AddonVersion & addonVersion)695 bool CAddonMgr::LoadAddon(const std::string& addonId,
696                           const std::string& origin,
697                           const AddonVersion& addonVersion)
698 {
699   CSingleLock lock(m_critSection);
700 
701   AddonPtr addon;
702   if (GetAddon(addonId, addon, ADDON_UNKNOWN, OnlyEnabled::NO))
703   {
704     return true;
705   }
706 
707   if (!FindAddon(addonId, origin, addonVersion))
708   {
709     CLog::Log(LOGERROR, "CAddonMgr: could not reload add-on %s. FindAddon failed.", addonId.c_str());
710     return false;
711   }
712 
713   if (!GetAddon(addonId, addon, ADDON_UNKNOWN, OnlyEnabled::NO))
714   {
715     CLog::Log(LOGERROR, "CAddonMgr: could not load add-on %s. No add-on with that ID is installed.", addonId.c_str());
716     return false;
717   }
718 
719   lock.Leave();
720 
721   AddonEvents::Load event(addon->ID());
722   m_unloadEvents.HandleEvent(event);
723 
724   if (IsAddonDisabled(addon->ID()))
725   {
726     EnableAddon(addon->ID());
727     return true;
728   }
729 
730   m_events.Publish(AddonEvents::ReInstalled(addon->ID()));
731   CLog::Log(LOGDEBUG, "CAddonMgr: %s successfully loaded", addon->ID().c_str());
732   return true;
733 }
734 
OnPostUnInstall(const std::string & id)735 void CAddonMgr::OnPostUnInstall(const std::string& id)
736 {
737   CSingleLock lock(m_critSection);
738   m_disabled.erase(id);
739   RemoveAllUpdateRulesFromList(id);
740   m_events.Publish(AddonEvents::UnInstalled(id));
741 }
742 
UpdateLastUsed(const std::string & id)743 void CAddonMgr::UpdateLastUsed(const std::string& id)
744 {
745   auto time = CDateTime::GetCurrentDateTime();
746   CJobManager::GetInstance().Submit([this, id, time](){
747     {
748       CSingleLock lock(m_critSection);
749       m_database.SetLastUsed(id, time);
750       auto addonInfo = GetAddonInfo(id);
751       if (addonInfo)
752         addonInfo->SetLastUsed(time);
753     }
754     m_events.Publish(AddonEvents::MetadataChanged(id));
755   });
756 }
757 
ResolveDependencies(const std::string & addonId,std::vector<std::string> & needed,std::vector<std::string> & missing)758 static void ResolveDependencies(const std::string& addonId, std::vector<std::string>& needed, std::vector<std::string>& missing)
759 {
760   if (std::find(needed.begin(), needed.end(), addonId) != needed.end())
761     return;
762 
763   AddonPtr addon;
764   if (!CServiceBroker::GetAddonMgr().GetAddon(addonId, addon, ADDON_UNKNOWN, OnlyEnabled::NO))
765     missing.push_back(addonId);
766   else
767   {
768     needed.push_back(addonId);
769     for (const auto& dep : addon->GetDependencies())
770       if (!dep.optional)
771         ResolveDependencies(dep.id, needed, missing);
772   }
773 }
774 
DisableAddon(const std::string & id,AddonDisabledReason disabledReason)775 bool CAddonMgr::DisableAddon(const std::string& id, AddonDisabledReason disabledReason)
776 {
777   CSingleLock lock(m_critSection);
778   if (!CanAddonBeDisabled(id))
779     return false;
780   if (m_disabled.find(id) != m_disabled.end())
781     return true; //already disabled
782   if (!m_database.DisableAddon(id, disabledReason))
783     return false;
784   if (!m_disabled.emplace(id, disabledReason).second)
785     return false;
786 
787   //success
788   CLog::Log(LOGDEBUG, "CAddonMgr: %s disabled", id.c_str());
789   AddonPtr addon;
790   if (GetAddon(id, addon, ADDON_UNKNOWN, OnlyEnabled::NO) && addon != NULL)
791   {
792     CServiceBroker::GetEventLog().Add(EventPtr(new CAddonManagementEvent(addon, 24141)));
793   }
794 
795   m_events.Publish(AddonEvents::Disabled(id));
796   return true;
797 }
798 
UpdateDisabledReason(const std::string & id,AddonDisabledReason newDisabledReason)799 bool CAddonMgr::UpdateDisabledReason(const std::string& id, AddonDisabledReason newDisabledReason)
800 {
801   CSingleLock lock(m_critSection);
802   if (!IsAddonDisabled(id))
803     return false;
804   if (!m_database.DisableAddon(id, newDisabledReason))
805     return false;
806 
807   m_disabled[id] = newDisabledReason;
808 
809   // success
810   CLog::Log(LOGDEBUG, "CAddonMgr: DisabledReason for {} updated to {}", id,
811             static_cast<int>(newDisabledReason));
812   return true;
813 }
814 
EnableSingle(const std::string & id)815 bool CAddonMgr::EnableSingle(const std::string& id)
816 {
817   CSingleLock lock(m_critSection);
818 
819   if (m_disabled.find(id) == m_disabled.end())
820     return true; //already enabled
821 
822   AddonPtr addon;
823   if (!GetAddon(id, addon, ADDON_UNKNOWN, OnlyEnabled::NO) || addon == nullptr)
824     return false;
825 
826   if (!IsCompatible(*addon))
827   {
828     CLog::Log(LOGERROR, "Add-on '%s' is not compatible with Kodi", addon->ID().c_str());
829     CServiceBroker::GetEventLog().AddWithNotification(EventPtr(new CNotificationEvent(addon->Name(), 24152, EventLevel::Error)));
830     UpdateDisabledReason(addon->ID(), AddonDisabledReason::INCOMPATIBLE);
831     return false;
832   }
833 
834   if (!m_database.EnableAddon(id))
835     return false;
836   m_disabled.erase(id);
837 
838   // If enabling a repo add-on without an origin, set its origin to its own id
839   if (addon->HasType(ADDON_REPOSITORY) && addon->Origin().empty())
840     SetAddonOrigin(id, id, false);
841 
842   CServiceBroker::GetEventLog().Add(EventPtr(new CAddonManagementEvent(addon, 24064)));
843 
844   CLog::Log(LOGDEBUG, "CAddonMgr: enabled %s", addon->ID().c_str());
845   m_events.Publish(AddonEvents::Enabled(id));
846   return true;
847 }
848 
EnableAddon(const std::string & id)849 bool CAddonMgr::EnableAddon(const std::string& id)
850 {
851   if (id.empty() || !IsAddonInstalled(id))
852     return false;
853   std::vector<std::string> needed;
854   std::vector<std::string> missing;
855   ResolveDependencies(id, needed, missing);
856   for (const auto& dep : missing)
857     CLog::Log(LOGWARNING, "CAddonMgr: '%s' required by '%s' is missing. Add-on may not function "
858         "correctly", dep.c_str(), id.c_str());
859   for (auto it = needed.rbegin(); it != needed.rend(); ++it)
860     EnableSingle(*it);
861 
862   return true;
863 }
864 
IsAddonDisabled(const std::string & ID) const865 bool CAddonMgr::IsAddonDisabled(const std::string& ID) const
866 {
867   CSingleLock lock(m_critSection);
868   return m_disabled.find(ID) != m_disabled.end();
869 }
870 
IsAddonDisabledExcept(const std::string & ID,AddonDisabledReason disabledReason) const871 bool CAddonMgr::IsAddonDisabledExcept(const std::string& ID,
872                                       AddonDisabledReason disabledReason) const
873 {
874   CSingleLock lock(m_critSection);
875   const auto disabledAddon = m_disabled.find(ID);
876   return disabledAddon != m_disabled.end() && disabledAddon->second != disabledReason;
877 }
878 
CanAddonBeDisabled(const std::string & ID)879 bool CAddonMgr::CanAddonBeDisabled(const std::string& ID)
880 {
881   if (ID.empty())
882     return false;
883 
884   CSingleLock lock(m_critSection);
885   // Non-optional system add-ons can not be disabled
886   if (IsSystemAddon(ID) && !IsOptionalSystemAddon(ID))
887     return false;
888 
889   AddonPtr localAddon;
890   // can't disable an addon that isn't installed
891   if (!GetAddon(ID, localAddon, ADDON_UNKNOWN, OnlyEnabled::NO))
892     return false;
893 
894   // can't disable an addon that is in use
895   if (localAddon->IsInUse())
896     return false;
897 
898   return true;
899 }
900 
CanAddonBeEnabled(const std::string & id)901 bool CAddonMgr::CanAddonBeEnabled(const std::string& id)
902 {
903   return !id.empty() && IsAddonInstalled(id);
904 }
905 
IsAddonInstalled(const std::string & ID)906 bool CAddonMgr::IsAddonInstalled(const std::string& ID)
907 {
908   AddonPtr tmp;
909   return GetAddon(ID, tmp, ADDON_UNKNOWN, OnlyEnabled::NO);
910 }
911 
IsAddonInstalled(const std::string & ID,const std::string & origin) const912 bool CAddonMgr::IsAddonInstalled(const std::string& ID, const std::string& origin) const
913 {
914   AddonPtr tmp;
915 
916   if (GetAddon(ID, tmp, ADDON_UNKNOWN, OnlyEnabled::NO) && tmp)
917   {
918     if (tmp->Origin() == ORIGIN_SYSTEM)
919     {
920       return CAddonRepos::IsOfficialRepo(origin);
921     }
922     else
923     {
924       return tmp->Origin() == origin;
925     }
926   }
927   return false;
928 }
929 
IsAddonInstalled(const std::string & ID,const std::string & origin,const AddonVersion & version)930 bool CAddonMgr::IsAddonInstalled(const std::string& ID,
931                                  const std::string& origin,
932                                  const AddonVersion& version)
933 {
934   AddonPtr tmp;
935 
936   if (GetAddon(ID, tmp, ADDON_UNKNOWN, OnlyEnabled::NO) && tmp)
937   {
938     if (tmp->Origin() == ORIGIN_SYSTEM)
939     {
940       return CAddonRepos::IsOfficialRepo(origin) && tmp->Version() == version;
941     }
942     else
943     {
944       return tmp->Origin() == origin && tmp->Version() == version;
945     }
946   }
947   return false;
948 }
949 
CanAddonBeInstalled(const AddonPtr & addon)950 bool CAddonMgr::CanAddonBeInstalled(const AddonPtr& addon)
951 {
952   return addon != nullptr && addon->LifecycleState() != AddonLifecycleState::BROKEN &&
953          !IsAddonInstalled(addon->ID());
954 }
955 
CanUninstall(const AddonPtr & addon)956 bool CAddonMgr::CanUninstall(const AddonPtr& addon)
957 {
958   return addon && !IsSystemAddon(addon->ID()) && CanAddonBeDisabled(addon->ID()) &&
959          !StringUtils::StartsWith(addon->Path(),
960                                   CSpecialProtocol::TranslatePath("special://xbmc/addons"));
961 }
962 
IsSystemAddon(const std::string & id)963 bool CAddonMgr::IsSystemAddon(const std::string& id)
964 {
965   CSingleLock lock(m_critSection);
966   return IsOptionalSystemAddon(id) ||
967          std::find(m_systemAddons.begin(), m_systemAddons.end(), id) != m_systemAddons.end();
968 }
969 
IsOptionalSystemAddon(const std::string & id)970 bool CAddonMgr::IsOptionalSystemAddon(const std::string& id)
971 {
972   CSingleLock lock(m_critSection);
973   return std::find(m_optionalSystemAddons.begin(), m_optionalSystemAddons.end(), id) !=
974          m_optionalSystemAddons.end();
975 }
976 
LoadAddonDescription(const std::string & directory,AddonPtr & addon)977 bool CAddonMgr::LoadAddonDescription(const std::string &directory, AddonPtr &addon)
978 {
979   auto addonInfo = CAddonInfoBuilder::Generate(directory);
980   if (addonInfo)
981     addon = CAddonBuilder::Generate(addonInfo, ADDON_UNKNOWN);
982 
983   return addon != nullptr;
984 }
985 
AddUpdateRuleToList(const std::string & id,AddonUpdateRule updateRule)986 bool CAddonMgr::AddUpdateRuleToList(const std::string& id, AddonUpdateRule updateRule)
987 {
988   return m_updateRules.AddUpdateRuleToList(m_database, id, updateRule);
989 }
990 
RemoveAllUpdateRulesFromList(const std::string & id)991 bool CAddonMgr::RemoveAllUpdateRulesFromList(const std::string& id)
992 {
993   return m_updateRules.RemoveAllUpdateRulesFromList(m_database, id);
994 }
995 
RemoveUpdateRuleFromList(const std::string & id,AddonUpdateRule updateRule)996 bool CAddonMgr::RemoveUpdateRuleFromList(const std::string& id, AddonUpdateRule updateRule)
997 {
998   return m_updateRules.RemoveUpdateRuleFromList(m_database, id, updateRule);
999 }
1000 
IsAutoUpdateable(const std::string & id) const1001 bool CAddonMgr::IsAutoUpdateable(const std::string& id) const
1002 {
1003   return m_updateRules.IsAutoUpdateable(id);
1004 }
1005 
PublishEventAutoUpdateStateChanged(const std::string & id)1006 void CAddonMgr::PublishEventAutoUpdateStateChanged(const std::string& id)
1007 {
1008   m_events.Publish(AddonEvents::AutoUpdateStateChanged(id));
1009 }
1010 
IsCompatible(const IAddon & addon) const1011 bool CAddonMgr::IsCompatible(const IAddon& addon) const
1012 {
1013   for (const auto& dependency : addon.GetDependencies())
1014   {
1015     if (!dependency.optional)
1016     {
1017       // Intentionally only check the xbmc.* and kodi.* magic dependencies. Everything else will
1018       // not be missing anyway, unless addon was installed in an unsupported way.
1019       if (StringUtils::StartsWith(dependency.id, "xbmc.") ||
1020           StringUtils::StartsWith(dependency.id, "kodi."))
1021       {
1022         AddonPtr addon;
1023         bool haveAddon = GetAddon(dependency.id, addon, ADDON_UNKNOWN, OnlyEnabled::YES);
1024         if (!haveAddon || !addon->MeetsVersion(dependency.versionMin, dependency.version))
1025           return false;
1026       }
1027     }
1028   }
1029   return true;
1030 }
1031 
IsCompatible(const AddonInfoPtr & addonInfo) const1032 bool CAddonMgr::IsCompatible(const AddonInfoPtr& addonInfo) const
1033 {
1034   for (const auto& dependency : addonInfo->GetDependencies())
1035   {
1036     if (!dependency.optional)
1037     {
1038       // Intentionally only check the xbmc.* and kodi.* magic dependencies. Everything else will
1039       // not be missing anyway, unless addon was installed in an unsupported way.
1040       if (StringUtils::StartsWith(dependency.id, "xbmc.") ||
1041           StringUtils::StartsWith(dependency.id, "kodi."))
1042       {
1043         AddonInfoPtr addonInfo = GetAddonInfo(dependency.id);
1044         if (!addonInfo || !addonInfo->MeetsVersion(dependency.versionMin, dependency.version))
1045           return false;
1046       }
1047     }
1048   }
1049   return true;
1050 }
1051 
GetDepsRecursive(const std::string & id,OnlyEnabledRootAddon onlyEnabledRootAddon)1052 std::vector<DependencyInfo> CAddonMgr::GetDepsRecursive(const std::string& id,
1053                                                         OnlyEnabledRootAddon onlyEnabledRootAddon)
1054 {
1055   std::vector<DependencyInfo> added;
1056   AddonPtr root_addon;
1057   if (!FindInstallableById(id, root_addon) &&
1058       !GetAddon(id, root_addon, ADDON_UNKNOWN, static_cast<OnlyEnabled>(onlyEnabledRootAddon)))
1059   {
1060     return added;
1061   }
1062 
1063   std::vector<DependencyInfo> toProcess;
1064   for (const auto& dep : root_addon->GetDependencies())
1065     toProcess.push_back(dep);
1066 
1067   while (!toProcess.empty())
1068   {
1069     auto current_dep = *toProcess.begin();
1070     toProcess.erase(toProcess.begin());
1071     if (StringUtils::StartsWith(current_dep.id, "xbmc.") ||
1072         StringUtils::StartsWith(current_dep.id, "kodi."))
1073       continue;
1074 
1075     auto added_it = std::find_if(added.begin(), added.end(), [&](const DependencyInfo& d){ return d.id == current_dep.id;});
1076     if (added_it != added.end())
1077     {
1078       if (current_dep.version < added_it->version)
1079         continue;
1080 
1081       bool aopt = added_it->optional;
1082       added.erase(added_it);
1083       added.push_back(current_dep);
1084       if (!current_dep.optional && aopt)
1085         continue;
1086     }
1087     else
1088       added.push_back(current_dep);
1089 
1090     AddonPtr current_addon;
1091     if (FindInstallableById(current_dep.id, current_addon))
1092     {
1093       for (const auto& item : current_addon->GetDependencies())
1094         toProcess.push_back(item);
1095     }
1096   }
1097 
1098   return added;
1099 }
1100 
GetAddonInfos(AddonInfos & addonInfos,bool onlyEnabled,TYPE type) const1101 bool CAddonMgr::GetAddonInfos(AddonInfos& addonInfos, bool onlyEnabled, TYPE type) const
1102 {
1103   CSingleLock lock(m_critSection);
1104 
1105   bool forUnknown = type == ADDON_UNKNOWN;
1106   for (auto& info : m_installedAddons)
1107   {
1108     if (onlyEnabled && m_disabled.find(info.first) != m_disabled.end())
1109       continue;
1110 
1111     if (info.second->MainType() != ADDON_UNKNOWN && (forUnknown || info.second->HasType(type)))
1112       addonInfos.push_back(info.second);
1113   }
1114 
1115   return !addonInfos.empty();
1116 }
1117 
GetDisabledAddonInfos(std::vector<AddonInfoPtr> & addonInfos,TYPE type) const1118 bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos, TYPE type) const
1119 {
1120   return GetDisabledAddonInfos(addonInfos, type, AddonDisabledReason::NONE);
1121 }
1122 
GetDisabledAddonInfos(std::vector<AddonInfoPtr> & addonInfos,TYPE type,AddonDisabledReason disabledReason) const1123 bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos,
1124                                       TYPE type,
1125                                       AddonDisabledReason disabledReason) const
1126 {
1127   CSingleLock lock(m_critSection);
1128 
1129   bool forUnknown = type == ADDON_UNKNOWN;
1130   for (const auto& info : m_installedAddons)
1131   {
1132     const auto disabledAddon = m_disabled.find(info.first);
1133     if (disabledAddon == m_disabled.end())
1134       continue;
1135 
1136     if (info.second->MainType() != ADDON_UNKNOWN && (forUnknown || info.second->HasType(type)) &&
1137         (disabledReason == AddonDisabledReason::NONE || disabledReason == disabledAddon->second))
1138       addonInfos.emplace_back(info.second);
1139   }
1140 
1141   return !addonInfos.empty();
1142 }
1143 
GetAddonInfo(const std::string & id,TYPE type) const1144 const AddonInfoPtr CAddonMgr::GetAddonInfo(const std::string& id,
1145                                            TYPE type /*= ADDON_UNKNOWN*/) const
1146 {
1147   CSingleLock lock(m_critSection);
1148 
1149   auto addon = m_installedAddons.find(id);
1150   if (addon != m_installedAddons.end())
1151     if ((type == ADDON_UNKNOWN || addon->second->HasType(type)))
1152       return addon->second;
1153 
1154   return nullptr;
1155 }
1156 
FindAddons(ADDON_INFO_LIST & addonmap,const std::string & path)1157 void CAddonMgr::FindAddons(ADDON_INFO_LIST& addonmap, const std::string& path)
1158 {
1159   CFileItemList items;
1160   if (XFILE::CDirectory::GetDirectory(path, items, "", XFILE::DIR_FLAG_NO_FILE_DIRS))
1161   {
1162     for (int i = 0; i < items.Size(); ++i)
1163     {
1164       std::string path = items[i]->GetPath();
1165       if (XFILE::CFile::Exists(path + "addon.xml"))
1166       {
1167         AddonInfoPtr addonInfo = CAddonInfoBuilder::Generate(path);
1168         if (addonInfo)
1169         {
1170           const auto& it = addonmap.find(addonInfo->ID());
1171           if (it != addonmap.end())
1172           {
1173             if (it->second->Version() > addonInfo->Version())
1174             {
1175               CLog::Log(LOGWARNING, "CAddonMgr::{}: Addon '{}' already present with higher version {} at '{}' - other version {} at '{}' will be ignored",
1176                            __FUNCTION__, addonInfo->ID(), it->second->Version().asString(), it->second->Path(), addonInfo->Version().asString(), addonInfo->Path());
1177               continue;
1178             }
1179             CLog::Log(LOGDEBUG, "CAddonMgr::{}: Addon '{}' already present with version {} at '{}' replaced with version {} at '{}'",
1180                          __FUNCTION__, addonInfo->ID(), it->second->Version().asString(), it->second->Path(), addonInfo->Version().asString(), addonInfo->Path());
1181           }
1182 
1183           addonmap[addonInfo->ID()] = addonInfo;
1184         }
1185       }
1186     }
1187   }
1188 }
1189 
GetAddonOriginType(const AddonPtr & addon) const1190 AddonOriginType CAddonMgr::GetAddonOriginType(const AddonPtr& addon) const
1191 {
1192   if (addon->Origin() == ORIGIN_SYSTEM)
1193     return AddonOriginType::SYSTEM;
1194   else if (!addon->Origin().empty())
1195     return AddonOriginType::REPOSITORY;
1196   else
1197     return AddonOriginType::MANUAL;
1198 }
1199 
IsAddonDisabledWithReason(const std::string & ID,AddonDisabledReason disabledReason) const1200 bool CAddonMgr::IsAddonDisabledWithReason(const std::string& ID,
1201                                           AddonDisabledReason disabledReason) const
1202 {
1203   CSingleLock lock(m_critSection);
1204   const auto& disabledAddon = m_disabled.find(ID);
1205   return disabledAddon != m_disabled.end() && disabledAddon->second == disabledReason;
1206 }
1207 
1208 /*!
1209  * @brief Addon update and install management.
1210  */
1211 /*@{{{*/
1212 
SetAddonOrigin(const std::string & addonId,const std::string & repoAddonId,bool isUpdate)1213 bool CAddonMgr::SetAddonOrigin(const std::string& addonId, const std::string& repoAddonId, bool isUpdate)
1214 {
1215   CSingleLock lock(m_critSection);
1216 
1217   m_database.SetOrigin(addonId, repoAddonId);
1218   if (isUpdate)
1219     m_database.SetLastUpdated(addonId, CDateTime::GetCurrentDateTime());
1220 
1221   // If available in manager update
1222   const AddonInfoPtr info = GetAddonInfo(addonId);
1223   if (info)
1224     m_database.GetInstallData(info);
1225   return true;
1226 }
1227 
AddonsFromRepoXML(const CRepository::DirInfo & repo,const std::string & xml,std::vector<AddonInfoPtr> & addons)1228 bool CAddonMgr::AddonsFromRepoXML(const CRepository::DirInfo& repo,
1229                                   const std::string& xml,
1230                                   std::vector<AddonInfoPtr>& addons)
1231 {
1232   CXBMCTinyXML doc;
1233   if (!doc.Parse(xml))
1234   {
1235     CLog::Log(LOGERROR, "CAddonMgr::{}: Failed to parse addons.xml", __func__);
1236     return false;
1237   }
1238 
1239   if (doc.RootElement() == nullptr || doc.RootElement()->ValueStr() != "addons")
1240   {
1241     CLog::Log(LOGERROR, "CAddonMgr::{}: Failed to parse addons.xml. Malformed", __func__);
1242     return false;
1243   }
1244 
1245   // each addon XML should have a UTF-8 declaration
1246   auto element = doc.RootElement()->FirstChildElement("addon");
1247   while (element)
1248   {
1249     auto addonInfo = CAddonInfoBuilder::Generate(element, repo);
1250     if (addonInfo)
1251       addons.emplace_back(addonInfo);
1252 
1253     element = element->NextSiblingElement("addon");
1254   }
1255 
1256   return true;
1257 }
1258 
1259 /*@}}}*/
1260 
1261 } /* namespace ADDON */
1262