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