Merge pull request #26264 from the-black-eagle/mka_end_durations
[xbmc.git] / xbmc / addons / AddonManager.cpp
blob46d265f12b2a98b42bd349c266de1b572a6d20e5
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "AddonManager.h"
11 #include "CompileInfo.h"
12 #include "FileItem.h"
13 #include "FileItemList.h"
14 #include "LangInfo.h"
15 #include "ServiceBroker.h"
16 #include "addons/AddonBuilder.h"
17 #include "addons/AddonDatabase.h"
18 #include "addons/AddonEvents.h"
19 #include "addons/AddonInstaller.h"
20 #include "addons/AddonRepos.h"
21 #include "addons/AddonSystemSettings.h"
22 #include "addons/AddonUpdateRules.h"
23 #include "addons/IAddon.h"
24 #include "addons/addoninfo/AddonInfo.h"
25 #include "addons/addoninfo/AddonInfoBuilder.h"
26 #include "addons/addoninfo/AddonType.h"
27 #include "events/AddonManagementEvent.h"
28 #include "events/EventLog.h"
29 #include "events/NotificationEvent.h"
30 #include "filesystem/Directory.h"
31 #include "filesystem/SpecialProtocol.h"
32 #include "utils/FileUtils.h"
33 #include "utils/StringUtils.h"
34 #include "utils/URIUtils.h"
35 #include "utils/XBMCTinyXML2.h"
36 #include "utils/XMLUtils.h"
37 #include "utils/log.h"
39 #include <algorithm>
40 #include <array>
41 #include <mutex>
42 #include <set>
43 #include <utility>
45 using namespace XFILE;
47 namespace ADDON
50 /**********************************************************
51 * CAddonMgr
55 std::map<AddonType, IAddonMgrCallback*> CAddonMgr::m_managers;
57 static bool LoadManifest(std::set<std::string>& system, std::set<std::string>& optional)
59 CXBMCTinyXML doc;
60 if (!doc.LoadFile("special://xbmc/system/addon-manifest.xml"))
62 CLog::Log(LOGERROR, "ADDONS: manifest missing");
63 return false;
66 auto root = doc.RootElement();
67 if (!root || root->ValueStr() != "addons")
69 CLog::Log(LOGERROR, "ADDONS: malformed manifest");
70 return false;
73 auto elem = root->FirstChildElement("addon");
74 while (elem)
76 if (elem->FirstChild())
78 if (XMLUtils::GetAttribute(elem, "optional") == "true")
79 optional.insert(elem->FirstChild()->ValueStr());
80 else
81 system.insert(elem->FirstChild()->ValueStr());
83 elem = elem->NextSiblingElement("addon");
85 return true;
88 CAddonMgr::CAddonMgr()
89 : m_database(std::make_unique<CAddonDatabase>()),
90 m_updateRules(std::make_unique<CAddonUpdateRules>())
94 CAddonMgr::~CAddonMgr()
96 DeInit();
99 IAddonMgrCallback* CAddonMgr::GetCallbackForType(AddonType type)
101 if (m_managers.find(type) == m_managers.end())
102 return nullptr;
103 else
104 return m_managers[type];
107 bool CAddonMgr::RegisterAddonMgrCallback(AddonType type, IAddonMgrCallback* cb)
109 if (cb == nullptr)
110 return false;
112 m_managers.erase(type);
113 m_managers[type] = cb;
115 return true;
118 void CAddonMgr::UnregisterAddonMgrCallback(AddonType type)
120 m_managers.erase(type);
123 bool CAddonMgr::Init()
125 std::unique_lock<CCriticalSection> lock(m_critSection);
127 if (!LoadManifest(m_systemAddons, m_optionalSystemAddons))
129 CLog::Log(LOGERROR, "ADDONS: Failed to read manifest");
130 return false;
133 if (!m_database->Open())
134 CLog::Log(LOGFATAL, "ADDONS: Failed to open database");
136 FindAddons();
138 //Ensure required add-ons are installed and enabled
139 for (const auto& id : m_systemAddons)
141 AddonPtr addon;
142 if (!GetAddon(id, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_YES))
144 CLog::Log(LOGFATAL, "addon '{}' not installed or not enabled.", id);
145 return false;
149 return true;
152 void CAddonMgr::DeInit()
154 m_database->Close();
156 /* If temporary directory was used from add-on, delete it */
157 if (XFILE::CDirectory::Exists(m_tempAddonBasePath))
158 XFILE::CDirectory::RemoveRecursive(CSpecialProtocol::TranslatePath(m_tempAddonBasePath));
161 bool CAddonMgr::HasAddons(AddonType type)
163 std::unique_lock<CCriticalSection> lock(m_critSection);
165 for (const auto& addonInfo : m_installedAddons)
167 if (addonInfo.second->HasType(type) && !IsAddonDisabled(addonInfo.second->ID()))
168 return true;
170 return false;
173 bool CAddonMgr::HasInstalledAddons(AddonType type)
175 std::unique_lock<CCriticalSection> lock(m_critSection);
177 for (const auto& addonInfo : m_installedAddons)
179 if (addonInfo.second->HasType(type))
180 return true;
182 return false;
185 void CAddonMgr::AddToUpdateableAddons(AddonPtr &pAddon)
187 std::unique_lock<CCriticalSection> lock(m_critSection);
188 m_updateableAddons.push_back(pAddon);
191 void CAddonMgr::RemoveFromUpdateableAddons(AddonPtr &pAddon)
193 std::unique_lock<CCriticalSection> lock(m_critSection);
194 VECADDONS::iterator it = std::find(m_updateableAddons.begin(), m_updateableAddons.end(), pAddon);
196 if(it != m_updateableAddons.end())
198 m_updateableAddons.erase(it);
202 struct AddonIdFinder
204 explicit AddonIdFinder(const std::string& id)
205 : m_id(id)
208 bool operator()(const AddonPtr& addon)
210 return m_id == addon->ID();
212 private:
213 std::string m_id;
216 bool CAddonMgr::ReloadSettings(const std::string& addonId, AddonInstanceId instanceId)
218 std::unique_lock<CCriticalSection> lock(m_critSection);
219 VECADDONS::iterator it =
220 std::find_if(m_updateableAddons.begin(), m_updateableAddons.end(), AddonIdFinder(addonId));
222 if( it != m_updateableAddons.end())
224 return (*it)->ReloadSettings(instanceId);
226 return false;
229 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetAvailableUpdates() const
231 std::vector<std::shared_ptr<IAddon>> availableUpdates =
232 GetAvailableUpdatesOrOutdatedAddons(AddonCheckType::AVAILABLE_UPDATES);
234 std::lock_guard<std::mutex> lock(m_lastAvailableUpdatesCountMutex);
235 m_lastAvailableUpdatesCountAsString = std::to_string(availableUpdates.size());
237 return availableUpdates;
240 const std::string& CAddonMgr::GetLastAvailableUpdatesCountAsString() const
242 std::lock_guard<std::mutex> lock(m_lastAvailableUpdatesCountMutex);
243 return m_lastAvailableUpdatesCountAsString;
246 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetOutdatedAddons() const
248 return GetAvailableUpdatesOrOutdatedAddons(AddonCheckType::OUTDATED_ADDONS);
251 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetAvailableUpdatesOrOutdatedAddons(
252 AddonCheckType addonCheckType) const
254 auto start = std::chrono::steady_clock::now();
256 std::vector<std::shared_ptr<IAddon>> result;
257 std::vector<std::shared_ptr<IAddon>> installed;
258 CAddonRepos addonRepos;
260 if (addonRepos.IsValid())
262 GetAddonsForUpdate(installed);
263 addonRepos.BuildUpdateOrOutdatedList(installed, result, addonCheckType);
266 auto end = std::chrono::steady_clock::now();
267 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
268 CLog::Log(LOGDEBUG, "CAddonMgr::GetAvailableUpdatesOrOutdatedAddons took {} ms",
269 duration.count());
271 return result;
274 std::map<std::string, AddonWithUpdate> CAddonMgr::GetAddonsWithAvailableUpdate() const
276 std::unique_lock<CCriticalSection> lock(m_critSection);
277 auto start = std::chrono::steady_clock::now();
279 std::vector<std::shared_ptr<IAddon>> installed;
280 std::map<std::string, AddonWithUpdate> result;
281 CAddonRepos addonRepos;
283 if (addonRepos.IsValid())
285 GetAddonsForUpdate(installed);
286 addonRepos.BuildAddonsWithUpdateList(installed, result);
289 auto end = std::chrono::steady_clock::now();
290 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
291 CLog::Log(LOGDEBUG, "CAddonMgr::{} took {} ms", __func__, duration.count());
293 return result;
296 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetCompatibleVersions(
297 const std::string& addonId) const
299 std::unique_lock<CCriticalSection> lock(m_critSection);
300 auto start = std::chrono::steady_clock::now();
302 CAddonRepos addonRepos(addonId);
303 std::vector<std::shared_ptr<IAddon>> result;
305 if (addonRepos.IsValid())
306 addonRepos.BuildCompatibleVersionsList(result);
308 auto end = std::chrono::steady_clock::now();
309 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
310 CLog::Log(LOGDEBUG, "CAddonMgr::{} took {} ms", __func__, duration.count());
312 return result;
315 bool CAddonMgr::HasAvailableUpdates()
317 return !GetAvailableUpdates().empty();
320 std::vector<std::shared_ptr<IAddon>> CAddonMgr::GetOrphanedDependencies() const
322 std::vector<std::shared_ptr<IAddon>> allAddons;
323 GetAddonsInternal(AddonType::UNKNOWN, allAddons, OnlyEnabled::CHOICE_NO,
324 CheckIncompatible::CHOICE_YES);
326 std::vector<std::shared_ptr<IAddon>> orphanedDependencies;
327 for (const auto& addon : allAddons)
329 if (IsOrphaned(addon, allAddons))
331 orphanedDependencies.emplace_back(addon);
335 return orphanedDependencies;
338 bool CAddonMgr::IsOrphaned(const std::shared_ptr<IAddon>& addon,
339 const std::vector<std::shared_ptr<IAddon>>& allAddons) const
341 if (CServiceBroker::GetAddonMgr().IsSystemAddon(addon->ID()) ||
342 !CAddonType::IsDependencyType(addon->MainType()))
343 return false;
345 auto dependsOnCapturedAddon = [&addon](const std::shared_ptr<IAddon>& _) {
346 const auto& deps = _->GetDependencies();
347 return std::any_of(deps.begin(), deps.end(),
348 [&addon](const DependencyInfo& dep) { return dep.id == addon->ID(); });
351 return std::none_of(allAddons.begin(), allAddons.end(), dependsOnCapturedAddon);
354 bool CAddonMgr::GetAddonsForUpdate(VECADDONS& addons) const
356 return GetAddonsInternal(AddonType::UNKNOWN, addons, OnlyEnabled::CHOICE_YES,
357 CheckIncompatible::CHOICE_YES);
360 bool CAddonMgr::GetAddons(VECADDONS& addons) const
362 return GetAddonsInternal(AddonType::UNKNOWN, addons, OnlyEnabled::CHOICE_YES,
363 CheckIncompatible::CHOICE_NO);
366 bool CAddonMgr::GetAddons(VECADDONS& addons, AddonType type)
368 return GetAddonsInternal(type, addons, OnlyEnabled::CHOICE_YES, CheckIncompatible::CHOICE_NO);
371 bool CAddonMgr::GetInstalledAddons(VECADDONS& addons)
373 return GetAddonsInternal(AddonType::UNKNOWN, addons, OnlyEnabled::CHOICE_NO,
374 CheckIncompatible::CHOICE_NO);
377 bool CAddonMgr::GetInstalledAddons(VECADDONS& addons, AddonType type)
379 return GetAddonsInternal(type, addons, OnlyEnabled::CHOICE_NO, CheckIncompatible::CHOICE_NO);
382 bool CAddonMgr::GetDisabledAddons(VECADDONS& addons)
384 return CAddonMgr::GetDisabledAddons(addons, AddonType::UNKNOWN);
387 bool CAddonMgr::GetDisabledAddons(VECADDONS& addons, AddonType type)
389 VECADDONS all;
390 if (GetInstalledAddons(all, type))
392 std::copy_if(all.begin(), all.end(), std::back_inserter(addons),
393 [this](const AddonPtr& addon){ return IsAddonDisabled(addon->ID()); });
394 return true;
396 return false;
399 bool CAddonMgr::GetInstallableAddons(VECADDONS& addons)
401 return GetInstallableAddons(addons, AddonType::UNKNOWN);
404 bool CAddonMgr::GetInstallableAddons(VECADDONS& addons, AddonType type)
406 std::unique_lock<CCriticalSection> lock(m_critSection);
407 CAddonRepos addonRepos;
409 if (!addonRepos.IsValid())
410 return false;
412 // get all addons
413 addonRepos.GetLatestAddonVersions(addons);
415 // go through all addons and remove all that are already installed
417 addons.erase(std::remove_if(addons.begin(), addons.end(),
418 [this, type](const AddonPtr& addon)
420 bool bErase = false;
422 // check if the addon matches the provided addon type
423 if (type != AddonType::UNKNOWN && addon->Type() != type && !addon->HasType(type))
424 bErase = true;
426 if (!this->CanAddonBeInstalled(addon))
427 bErase = true;
429 return bErase;
430 }), addons.end());
432 return true;
435 bool CAddonMgr::FindInstallableById(const std::string& addonId, AddonPtr& result)
437 std::unique_lock<CCriticalSection> lock(m_critSection);
439 CAddonRepos addonRepos(addonId);
440 if (!addonRepos.IsValid())
441 return false;
443 AddonPtr addonToUpdate;
445 // check for an update if addon is installed already
447 if (GetAddon(addonId, addonToUpdate, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO))
449 if (addonRepos.DoAddonUpdateCheck(addonToUpdate, result))
450 return true;
453 // get the latest version from all repos if the
454 // addon is up-to-date or not installed yet
456 CLog::LogFC(
457 LOGDEBUG, LOGADDONS,
458 "addon {} is up-to-date or not installed. falling back to get latest version from all repos",
459 addonId);
461 return addonRepos.GetLatestAddonVersionFromAllRepos(addonId, result);
464 bool CAddonMgr::GetAddonsInternal(AddonType type,
465 VECADDONS& addons,
466 OnlyEnabled onlyEnabled,
467 CheckIncompatible checkIncompatible) const
469 std::unique_lock<CCriticalSection> lock(m_critSection);
471 for (const auto& addonInfo : m_installedAddons)
473 if (type != AddonType::UNKNOWN && !addonInfo.second->HasType(type))
474 continue;
476 if (onlyEnabled == OnlyEnabled::CHOICE_YES &&
477 ((checkIncompatible == CheckIncompatible::CHOICE_NO &&
478 IsAddonDisabled(addonInfo.second->ID())) ||
479 (checkIncompatible == CheckIncompatible::CHOICE_YES &&
480 IsAddonDisabledExcept(addonInfo.second->ID(), AddonDisabledReason::INCOMPATIBLE))))
481 continue;
483 //FIXME: hack for skipping special dependency addons (xbmc.python etc.).
484 //Will break if any extension point is added to them
485 if (addonInfo.second->MainType() == AddonType::UNKNOWN)
486 continue;
488 AddonPtr addon = CAddonBuilder::Generate(addonInfo.second, type);
489 if (addon)
491 // if the addon has a running instance, grab that
492 AddonPtr runningAddon = addon->GetRunningInstance();
493 if (runningAddon)
494 addon = runningAddon;
495 addons.emplace_back(std::move(addon));
498 return addons.size() > 0;
501 bool CAddonMgr::GetIncompatibleEnabledAddonInfos(std::vector<AddonInfoPtr>& incompatible) const
503 return GetIncompatibleAddonInfos(incompatible, false);
506 bool CAddonMgr::GetIncompatibleAddonInfos(std::vector<AddonInfoPtr>& incompatible,
507 bool includeDisabled) const
509 GetAddonInfos(incompatible, true, AddonType::UNKNOWN);
510 if (includeDisabled)
511 GetDisabledAddonInfos(incompatible, AddonType::UNKNOWN, AddonDisabledReason::INCOMPATIBLE);
512 incompatible.erase(std::remove_if(incompatible.begin(), incompatible.end(),
513 [this](const AddonInfoPtr& a) { return IsCompatible(a); }),
514 incompatible.end());
515 return !incompatible.empty();
518 std::vector<AddonInfoPtr> CAddonMgr::MigrateAddons()
520 // install all addon updates
521 std::lock_guard<std::mutex> lock(m_installAddonsMutex);
522 CLog::Log(LOGINFO, "ADDON: waiting for add-ons to update...");
523 VECADDONS updates;
524 GetAddonUpdateCandidates(updates);
525 InstallAddonUpdates(updates, true, AllowCheckForUpdates::CHOICE_NO);
527 // get addons that became incompatible and disable them
528 std::vector<AddonInfoPtr> incompatible;
529 GetIncompatibleAddonInfos(incompatible, true);
531 return DisableIncompatibleAddons(incompatible);
534 std::vector<AddonInfoPtr> CAddonMgr::DisableIncompatibleAddons(
535 const std::vector<AddonInfoPtr>& incompatible)
537 std::vector<AddonInfoPtr> changed;
538 for (const auto& addon : incompatible)
540 CLog::Log(LOGINFO, "ADDON: {} version {} is incompatible", addon->ID(),
541 addon->Version().asString());
543 if (!CAddonSystemSettings::GetInstance().UnsetActive(addon))
545 CLog::Log(LOGWARNING, "ADDON: failed to unset {}", addon->ID());
546 continue;
548 if (!DisableAddon(addon->ID(), AddonDisabledReason::INCOMPATIBLE))
550 CLog::Log(LOGWARNING, "ADDON: failed to disable {}", addon->ID());
553 changed.emplace_back(addon);
556 return changed;
559 void CAddonMgr::CheckAndInstallAddonUpdates(bool wait) const
561 std::lock_guard<std::mutex> lock(m_installAddonsMutex);
562 VECADDONS updates;
563 GetAddonUpdateCandidates(updates);
564 InstallAddonUpdates(updates, wait, AllowCheckForUpdates::CHOICE_YES);
567 bool CAddonMgr::GetAddonUpdateCandidates(VECADDONS& updates) const
569 // Get Addons in need of an update and remove all the blacklisted ones
570 updates = GetAvailableUpdates();
571 updates.erase(
572 std::remove_if(updates.begin(), updates.end(),
573 [this](const AddonPtr& addon) { return !IsAutoUpdateable(addon->ID()); }),
574 updates.end());
575 return updates.empty();
578 void CAddonMgr::SortByDependencies(VECADDONS& updates) const
580 std::vector<std::shared_ptr<ADDON::IAddon>> sorted;
581 while (!updates.empty())
583 for (auto it = updates.begin(); it != updates.end();)
585 const auto& addon = *it;
587 const auto& dependencies = addon->GetDependencies();
588 bool addToSortedList = true;
589 // if the addon has dependencies we need to check for each dependency if it also has
590 // an update to be installed (and in that case, if it is already in the sorted vector).
591 // if all dependency match the said conditions, the addon doesn't depend on other addons
592 // waiting to be updated. Hence, the addon being processed can be installed (i.e. added to
593 // the end of the sorted vector of addon updates)
594 for (const auto& dep : dependencies)
596 auto comparator = [&dep](const std::shared_ptr<ADDON::IAddon>& addon) {
597 return addon->ID() == dep.id;
600 if ((std::any_of(updates.begin(), updates.end(), comparator)) &&
601 (!std::any_of(sorted.begin(), sorted.end(), comparator)))
603 addToSortedList = false;
604 break;
608 // add to the end of sorted list of addons
609 if (addToSortedList)
611 sorted.emplace_back(addon);
612 it = updates.erase(it);
614 else
616 ++it;
620 updates = sorted;
623 void CAddonMgr::InstallAddonUpdates(VECADDONS& updates,
624 bool wait,
625 AllowCheckForUpdates allowCheckForUpdates) const
627 // sort addons by dependencies (ensure install order) and install all
628 SortByDependencies(updates);
629 CAddonInstaller::GetInstance().InstallAddons(updates, wait, allowCheckForUpdates);
632 bool CAddonMgr::GetAddon(const std::string& str,
633 AddonPtr& addon,
634 AddonType type,
635 OnlyEnabled onlyEnabled) const
637 std::unique_lock<CCriticalSection> lock(m_critSection);
639 AddonInfoPtr addonInfo = GetAddonInfo(str, type);
640 if (addonInfo)
642 addon = CAddonBuilder::Generate(addonInfo, type);
643 if (addon)
645 if (onlyEnabled == OnlyEnabled::CHOICE_YES && IsAddonDisabled(addonInfo->ID()))
646 return false;
648 // if the addon has a running instance, grab that
649 AddonPtr runningAddon = addon->GetRunningInstance();
650 if (runningAddon)
651 addon = runningAddon;
653 return nullptr != addon.get();
656 return false;
659 bool CAddonMgr::GetAddon(const std::string& str, AddonPtr& addon, OnlyEnabled onlyEnabled) const
661 return GetAddon(str, addon, AddonType::UNKNOWN, onlyEnabled);
664 bool CAddonMgr::HasType(const std::string& id, AddonType type)
666 AddonPtr addon;
667 return GetAddon(id, addon, type, OnlyEnabled::CHOICE_NO);
670 bool CAddonMgr::FindAddon(const std::string& addonId,
671 const std::string& origin,
672 const CAddonVersion& addonVersion)
674 std::map<std::string, std::shared_ptr<CAddonInfo>> installedAddons;
676 FindAddons(installedAddons, "special://xbmcbin/addons");
677 // Confirm special://xbmcbin/addons and special://xbmc/addons are not the same
678 if (!CSpecialProtocol::ComparePath("special://xbmcbin/addons", "special://xbmc/addons"))
679 FindAddons(installedAddons, "special://xbmc/addons");
680 FindAddons(installedAddons, "special://home/addons");
682 const auto it = installedAddons.find(addonId);
683 if (it == installedAddons.cend() || it->second->Version() != addonVersion)
684 return false;
686 std::unique_lock<CCriticalSection> lock(m_critSection);
688 m_database->GetInstallData(it->second);
689 CLog::Log(LOGINFO, "CAddonMgr::{}: {} v{} installed", __FUNCTION__, addonId,
690 addonVersion.asString());
692 m_installedAddons[addonId] = it->second; // insert/replace entry
693 m_database->AddInstalledAddon(it->second, origin);
695 // Reload caches
696 std::map<std::string, AddonDisabledReason> tmpDisabled;
697 m_database->GetDisabled(tmpDisabled);
698 m_disabled = std::move(tmpDisabled);
700 m_updateRules->RefreshRulesMap(*m_database);
701 return true;
704 bool CAddonMgr::FindAddons()
706 ADDON_INFO_LIST installedAddons;
708 FindAddons(installedAddons, "special://xbmcbin/addons");
709 // Confirm special://xbmcbin/addons and special://xbmc/addons are not the same
710 if (!CSpecialProtocol::ComparePath("special://xbmcbin/addons", "special://xbmc/addons"))
711 FindAddons(installedAddons, "special://xbmc/addons");
712 FindAddons(installedAddons, "special://home/addons");
714 std::set<std::string> installed;
715 for (const auto& addon : installedAddons)
716 installed.insert(addon.second->ID());
718 std::unique_lock<CCriticalSection> lock(m_critSection);
720 // Sync with db
721 m_database->SyncInstalled(installed, m_systemAddons, m_optionalSystemAddons);
722 for (const auto& addon : installedAddons)
724 m_database->GetInstallData(addon.second);
725 CLog::Log(LOGINFO, "CAddonMgr::{}: {} v{} installed", __FUNCTION__, addon.second->ID(),
726 addon.second->Version().asString());
729 m_installedAddons = std::move(installedAddons);
731 // Reload caches
732 std::map<std::string, AddonDisabledReason> tmpDisabled;
733 m_database->GetDisabled(tmpDisabled);
734 m_disabled = std::move(tmpDisabled);
736 m_updateRules->RefreshRulesMap(*m_database);
738 return true;
741 bool CAddonMgr::UnloadAddon(const std::string& addonId)
743 std::unique_lock<CCriticalSection> lock(m_critSection);
745 if (!IsAddonInstalled(addonId))
746 return true;
748 AddonPtr localAddon;
749 // can't unload an binary addon that is in use
750 if (GetAddon(addonId, localAddon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO) &&
751 localAddon->IsBinary() && localAddon->IsInUse())
753 CLog::Log(LOGERROR, "CAddonMgr::{}: could not unload binary add-on {}, as is in use", __func__,
754 addonId);
755 return false;
758 m_installedAddons.erase(addonId);
759 CLog::Log(LOGDEBUG, "CAddonMgr::{}: {} unloaded", __func__, addonId);
761 lock.unlock();
762 AddonEvents::Unload event(addonId);
763 m_unloadEvents.HandleEvent(event);
765 return true;
768 bool CAddonMgr::LoadAddon(const std::string& addonId,
769 const std::string& origin,
770 const CAddonVersion& addonVersion)
772 std::unique_lock<CCriticalSection> lock(m_critSection);
774 AddonPtr addon;
775 if (GetAddon(addonId, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO))
777 return true;
780 if (!FindAddon(addonId, origin, addonVersion))
782 CLog::Log(LOGERROR, "CAddonMgr: could not reload add-on {}. FindAddon failed.", addonId);
783 return false;
786 if (!GetAddon(addonId, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO))
788 CLog::Log(LOGERROR, "CAddonMgr: could not load add-on {}. No add-on with that ID is installed.",
789 addonId);
790 return false;
793 lock.unlock();
795 AddonEvents::Load event(addon->ID());
796 m_unloadEvents.HandleEvent(event);
798 if (IsAddonDisabled(addon->ID()))
800 EnableAddon(addon->ID());
801 return true;
804 m_events.Publish(AddonEvents::ReInstalled(addon->ID()));
805 CLog::Log(LOGDEBUG, "CAddonMgr: {} successfully loaded", addon->ID());
806 return true;
809 void CAddonMgr::OnPostUnInstall(const std::string& id)
811 std::unique_lock<CCriticalSection> lock(m_critSection);
812 m_disabled.erase(id);
813 RemoveAllUpdateRulesFromList(id);
814 m_events.Publish(AddonEvents::UnInstalled(id));
817 void CAddonMgr::UpdateLastUsed(const std::string& id)
819 auto time = CDateTime::GetCurrentDateTime();
820 CServiceBroker::GetJobManager()->Submit([this, id, time]() {
822 std::unique_lock<CCriticalSection> lock(m_critSection);
823 m_database->SetLastUsed(id, time);
824 auto addonInfo = GetAddonInfo(id, AddonType::UNKNOWN);
825 if (addonInfo)
826 addonInfo->SetLastUsed(time);
828 m_events.Publish(AddonEvents::MetadataChanged(id));
832 static void ResolveDependencies(const std::string& addonId, std::vector<std::string>& needed, std::vector<std::string>& missing)
834 if (std::find(needed.begin(), needed.end(), addonId) != needed.end())
835 return;
837 AddonPtr addon;
838 if (!CServiceBroker::GetAddonMgr().GetAddon(addonId, addon, AddonType::UNKNOWN,
839 OnlyEnabled::CHOICE_NO))
840 missing.push_back(addonId);
841 else
843 needed.push_back(addonId);
844 for (const auto& dep : addon->GetDependencies())
845 if (!dep.optional)
846 ResolveDependencies(dep.id, needed, missing);
850 bool CAddonMgr::DisableAddon(const std::string& id, AddonDisabledReason disabledReason)
852 std::unique_lock<CCriticalSection> lock(m_critSection);
853 if (!CanAddonBeDisabled(id))
854 return false;
855 if (m_disabled.find(id) != m_disabled.end())
856 return true; //already disabled
857 if (!m_database->DisableAddon(id, disabledReason))
858 return false;
859 if (!m_disabled.emplace(id, disabledReason).second)
860 return false;
862 //success
863 CLog::Log(LOGDEBUG, "CAddonMgr: {} disabled", id);
864 AddonPtr addon;
865 if (GetAddon(id, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO) && addon != nullptr)
867 auto eventLog = CServiceBroker::GetEventLog();
868 if (eventLog)
869 eventLog->Add(EventPtr(new CAddonManagementEvent(addon, 24141)));
872 m_events.Publish(AddonEvents::Disabled(id));
873 return true;
876 bool CAddonMgr::UpdateDisabledReason(const std::string& id, AddonDisabledReason newDisabledReason)
878 std::unique_lock<CCriticalSection> lock(m_critSection);
879 if (!IsAddonDisabled(id))
880 return false;
881 if (!m_database->DisableAddon(id, newDisabledReason))
882 return false;
884 m_disabled[id] = newDisabledReason;
886 // success
887 CLog::Log(LOGDEBUG, "CAddonMgr: DisabledReason for {} updated to {}", id,
888 static_cast<int>(newDisabledReason));
889 return true;
892 bool CAddonMgr::EnableSingle(const std::string& id)
894 std::unique_lock<CCriticalSection> lock(m_critSection);
896 if (m_disabled.find(id) == m_disabled.end())
897 return true; //already enabled
899 AddonPtr addon;
900 if (!GetAddon(id, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO) || addon == nullptr)
901 return false;
903 auto eventLog = CServiceBroker::GetEventLog();
905 if (!IsCompatible(addon))
907 CLog::Log(LOGERROR, "Add-on '{}' is not compatible with Kodi", addon->ID());
908 if (eventLog)
909 eventLog->AddWithNotification(
910 EventPtr(new CNotificationEvent(addon->Name(), 24152, EventLevel::Error)));
911 UpdateDisabledReason(addon->ID(), AddonDisabledReason::INCOMPATIBLE);
912 return false;
915 if (!m_database->EnableAddon(id))
916 return false;
917 m_disabled.erase(id);
919 // If enabling a repo add-on without an origin, set its origin to its own id
920 if (addon->HasType(AddonType::REPOSITORY) && addon->Origin().empty())
921 SetAddonOrigin(id, id, false);
923 if (eventLog)
924 eventLog->Add(EventPtr(new CAddonManagementEvent(addon, 24064)));
926 CLog::Log(LOGDEBUG, "CAddonMgr: enabled {}", addon->ID());
927 m_events.Publish(AddonEvents::Enabled(id));
928 return true;
931 bool CAddonMgr::EnableAddon(const std::string& id)
933 if (id.empty() || !IsAddonInstalled(id))
934 return false;
935 std::vector<std::string> needed;
936 std::vector<std::string> missing;
937 ResolveDependencies(id, needed, missing);
938 for (const auto& dep : missing)
939 CLog::Log(LOGWARNING,
940 "CAddonMgr: '{}' required by '{}' is missing. Add-on may not function "
941 "correctly",
942 dep, id);
943 for (auto it = needed.rbegin(); it != needed.rend(); ++it)
944 EnableSingle(*it);
946 return true;
949 bool CAddonMgr::IsAddonDisabled(const std::string& ID) const
951 std::unique_lock<CCriticalSection> lock(m_critSection);
952 return m_disabled.find(ID) != m_disabled.end();
955 bool CAddonMgr::IsAddonDisabledExcept(const std::string& ID,
956 AddonDisabledReason disabledReason) const
958 std::unique_lock<CCriticalSection> lock(m_critSection);
959 const auto disabledAddon = m_disabled.find(ID);
960 return disabledAddon != m_disabled.end() && disabledAddon->second != disabledReason;
963 bool CAddonMgr::CanAddonBeDisabled(const std::string& ID)
965 if (ID.empty())
966 return false;
968 std::unique_lock<CCriticalSection> lock(m_critSection);
969 // Required system add-ons can not be disabled
970 if (IsRequiredSystemAddon(ID))
971 return false;
973 AddonPtr localAddon;
974 // can't disable an addon that isn't installed
975 if (!GetAddon(ID, localAddon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO))
976 return false;
978 // can't disable an addon that is in use
979 if (localAddon->IsInUse())
980 return false;
982 return true;
985 bool CAddonMgr::CanAddonBeEnabled(const std::string& id)
987 return !id.empty() && IsAddonInstalled(id);
990 bool CAddonMgr::IsAddonInstalled(const std::string& ID)
992 AddonPtr tmp;
993 return GetAddon(ID, tmp, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO);
996 bool CAddonMgr::IsAddonInstalled(const std::string& ID, const std::string& origin) const
998 AddonPtr tmp;
1000 if (GetAddon(ID, tmp, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO) && tmp)
1002 if (tmp->Origin() == ORIGIN_SYSTEM)
1004 return CAddonRepos::IsOfficialRepo(origin);
1006 else
1008 return tmp->Origin() == origin;
1011 return false;
1014 bool CAddonMgr::IsAddonInstalled(const std::string& ID,
1015 const std::string& origin,
1016 const CAddonVersion& version)
1018 AddonPtr tmp;
1020 if (GetAddon(ID, tmp, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO) && tmp)
1022 if (tmp->Origin() == ORIGIN_SYSTEM)
1024 return CAddonRepos::IsOfficialRepo(origin) && tmp->Version() == version;
1026 else
1028 return tmp->Origin() == origin && tmp->Version() == version;
1031 return false;
1034 bool CAddonMgr::CanAddonBeInstalled(const AddonPtr& addon)
1036 return addon != nullptr && addon->LifecycleState() != AddonLifecycleState::BROKEN &&
1037 !IsAddonInstalled(addon->ID());
1040 bool CAddonMgr::CanUninstall(const AddonPtr& addon)
1042 return addon && CanAddonBeDisabled(addon->ID()) && !IsBundledAddon(addon->ID());
1045 bool CAddonMgr::IsBundledAddon(const std::string& id)
1047 return XFILE::CDirectory::Exists(
1048 CSpecialProtocol::TranslatePath("special://xbmc/addons/" + id + "/")) ||
1049 XFILE::CDirectory::Exists(
1050 CSpecialProtocol::TranslatePath("special://xbmcbin/addons/" + id + "/"));
1053 bool CAddonMgr::IsSystemAddon(const std::string& id)
1055 return IsOptionalSystemAddon(id) || IsRequiredSystemAddon(id);
1058 bool CAddonMgr::IsRequiredSystemAddon(const std::string& id)
1060 std::unique_lock<CCriticalSection> lock(m_critSection);
1061 return std::find(m_systemAddons.begin(), m_systemAddons.end(), id) != m_systemAddons.end();
1064 bool CAddonMgr::IsOptionalSystemAddon(const std::string& id)
1066 std::unique_lock<CCriticalSection> lock(m_critSection);
1067 return std::find(m_optionalSystemAddons.begin(), m_optionalSystemAddons.end(), id) !=
1068 m_optionalSystemAddons.end();
1071 bool CAddonMgr::LoadAddonDescription(const std::string &directory, AddonPtr &addon)
1073 auto addonInfo = CAddonInfoBuilder::Generate(directory);
1074 if (addonInfo)
1075 addon = CAddonBuilder::Generate(addonInfo, AddonType::UNKNOWN);
1077 return addon != nullptr;
1080 bool CAddonMgr::AddUpdateRuleToList(const std::string& id, AddonUpdateRule updateRule)
1082 return m_updateRules->AddUpdateRuleToList(*m_database, id, updateRule);
1085 bool CAddonMgr::RemoveAllUpdateRulesFromList(const std::string& id)
1087 return m_updateRules->RemoveAllUpdateRulesFromList(*m_database, id);
1090 bool CAddonMgr::RemoveUpdateRuleFromList(const std::string& id, AddonUpdateRule updateRule)
1092 return m_updateRules->RemoveUpdateRuleFromList(*m_database, id, updateRule);
1095 bool CAddonMgr::IsAutoUpdateable(const std::string& id) const
1097 return m_updateRules->IsAutoUpdateable(id);
1100 void CAddonMgr::PublishEventAutoUpdateStateChanged(const std::string& id)
1102 m_events.Publish(AddonEvents::AutoUpdateStateChanged(id));
1105 void CAddonMgr::PublishInstanceAdded(const std::string& addonId, AddonInstanceId instanceId)
1107 m_events.Publish(AddonEvents::InstanceAdded(addonId, instanceId));
1110 void CAddonMgr::PublishInstanceRemoved(const std::string& addonId, AddonInstanceId instanceId)
1112 m_events.Publish(AddonEvents::InstanceRemoved(addonId, instanceId));
1115 bool CAddonMgr::IsCompatible(const std::shared_ptr<const IAddon>& addon) const
1117 for (const auto& dependency : addon->GetDependencies())
1119 if (!dependency.optional)
1121 // Intentionally only check the xbmc.* and kodi.* magic dependencies. Everything else will
1122 // not be missing anyway, unless addon was installed in an unsupported way.
1123 if (StringUtils::StartsWith(dependency.id, "xbmc.") ||
1124 StringUtils::StartsWith(dependency.id, "kodi."))
1126 std::shared_ptr<IAddon> dep;
1127 const bool haveDependency =
1128 GetAddon(dependency.id, dep, AddonType::UNKNOWN, OnlyEnabled::CHOICE_YES);
1129 if (!haveDependency || !dep->MeetsVersion(dependency.versionMin, dependency.version))
1130 return false;
1134 return true;
1137 bool CAddonMgr::IsCompatible(const AddonInfoPtr& addonInfo) const
1139 for (const auto& dependency : addonInfo->GetDependencies())
1141 if (!dependency.optional)
1143 // Intentionally only check the xbmc.* and kodi.* magic dependencies. Everything else will
1144 // not be missing anyway, unless addon was installed in an unsupported way.
1145 if (StringUtils::StartsWith(dependency.id, "xbmc.") ||
1146 StringUtils::StartsWith(dependency.id, "kodi."))
1148 AddonInfoPtr addonInfo = GetAddonInfo(dependency.id, AddonType::UNKNOWN);
1149 if (!addonInfo || !addonInfo->MeetsVersion(dependency.versionMin, dependency.version))
1150 return false;
1154 return true;
1157 std::vector<DependencyInfo> CAddonMgr::GetDepsRecursive(const std::string& id,
1158 OnlyEnabledRootAddon onlyEnabledRootAddon)
1160 std::vector<DependencyInfo> added;
1161 AddonPtr root_addon;
1162 if (!FindInstallableById(id, root_addon) &&
1163 !GetAddon(id, root_addon, AddonType::UNKNOWN, static_cast<OnlyEnabled>(onlyEnabledRootAddon)))
1165 return added;
1168 std::vector<DependencyInfo> toProcess;
1169 for (const auto& dep : root_addon->GetDependencies())
1170 toProcess.push_back(dep);
1172 while (!toProcess.empty())
1174 auto current_dep = *toProcess.begin();
1175 toProcess.erase(toProcess.begin());
1176 if (StringUtils::StartsWith(current_dep.id, "xbmc.") ||
1177 StringUtils::StartsWith(current_dep.id, "kodi."))
1178 continue;
1180 auto added_it = std::find_if(added.begin(), added.end(), [&](const DependencyInfo& d){ return d.id == current_dep.id;});
1181 if (added_it != added.end())
1183 if (current_dep.version < added_it->version)
1184 continue;
1186 bool aopt = added_it->optional;
1187 added.erase(added_it);
1188 added.push_back(current_dep);
1189 if (!current_dep.optional && aopt)
1190 continue;
1192 else
1193 added.push_back(current_dep);
1195 AddonPtr current_addon;
1196 if (FindInstallableById(current_dep.id, current_addon))
1198 for (const auto& item : current_addon->GetDependencies())
1199 toProcess.push_back(item);
1203 return added;
1206 bool CAddonMgr::GetAddonInfos(AddonInfos& addonInfos, bool onlyEnabled, AddonType type) const
1208 std::unique_lock<CCriticalSection> lock(m_critSection);
1210 bool forUnknown = type == AddonType::UNKNOWN;
1211 for (auto& info : m_installedAddons)
1213 if (onlyEnabled && m_disabled.find(info.first) != m_disabled.end())
1214 continue;
1216 if (info.second->MainType() != AddonType::UNKNOWN && (forUnknown || info.second->HasType(type)))
1217 addonInfos.push_back(info.second);
1220 return !addonInfos.empty();
1223 std::vector<AddonInfoPtr> CAddonMgr::GetAddonInfos(bool onlyEnabled,
1224 const std::vector<AddonType>& types) const
1226 std::vector<AddonInfoPtr> infos;
1227 if (types.empty())
1228 return infos;
1230 std::unique_lock<CCriticalSection> lock(m_critSection);
1232 for (auto& info : m_installedAddons)
1234 if (onlyEnabled && m_disabled.find(info.first) != m_disabled.end())
1235 continue;
1237 if (info.second->MainType() == AddonType::UNKNOWN)
1238 continue;
1240 const auto it = std::find_if(types.begin(), types.end(),
1241 [info](AddonType t) { return info.second->HasType(t); });
1242 if (it != types.end())
1243 infos.emplace_back(info.second);
1246 return infos;
1249 bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos, AddonType type) const
1251 return GetDisabledAddonInfos(addonInfos, type, AddonDisabledReason::NONE);
1254 bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos,
1255 AddonType type,
1256 AddonDisabledReason disabledReason) const
1258 std::unique_lock<CCriticalSection> lock(m_critSection);
1260 bool forUnknown = type == AddonType::UNKNOWN;
1261 for (const auto& info : m_installedAddons)
1263 const auto disabledAddon = m_disabled.find(info.first);
1264 if (disabledAddon == m_disabled.end())
1265 continue;
1267 if (info.second->MainType() != AddonType::UNKNOWN &&
1268 (forUnknown || info.second->HasType(type)) &&
1269 (disabledReason == AddonDisabledReason::NONE || disabledReason == disabledAddon->second))
1270 addonInfos.emplace_back(info.second);
1273 return !addonInfos.empty();
1276 const AddonInfoPtr CAddonMgr::GetAddonInfo(const std::string& id, AddonType type) const
1278 std::unique_lock<CCriticalSection> lock(m_critSection);
1280 auto addon = m_installedAddons.find(id);
1281 if (addon != m_installedAddons.end())
1282 if ((type == AddonType::UNKNOWN || addon->second->HasType(type)))
1283 return addon->second;
1285 return nullptr;
1288 void CAddonMgr::FindAddons(ADDON_INFO_LIST& addonmap, const std::string& path)
1290 CFileItemList items;
1291 if (XFILE::CDirectory::GetDirectory(path, items, "", XFILE::DIR_FLAG_NO_FILE_DIRS))
1293 for (int i = 0; i < items.Size(); ++i)
1295 std::string path = items[i]->GetPath();
1296 if (CFileUtils::Exists(path + "addon.xml"))
1298 AddonInfoPtr addonInfo = CAddonInfoBuilder::Generate(path);
1299 if (addonInfo)
1301 const auto& it = addonmap.find(addonInfo->ID());
1302 if (it != addonmap.end())
1304 if (it->second->Version() > addonInfo->Version())
1306 CLog::Log(LOGWARNING, "CAddonMgr::{}: Addon '{}' already present with higher version {} at '{}' - other version {} at '{}' will be ignored",
1307 __FUNCTION__, addonInfo->ID(), it->second->Version().asString(), it->second->Path(), addonInfo->Version().asString(), addonInfo->Path());
1308 continue;
1310 CLog::Log(LOGDEBUG, "CAddonMgr::{}: Addon '{}' already present with version {} at '{}' replaced with version {} at '{}'",
1311 __FUNCTION__, addonInfo->ID(), it->second->Version().asString(), it->second->Path(), addonInfo->Version().asString(), addonInfo->Path());
1314 addonmap[addonInfo->ID()] = addonInfo;
1321 AddonOriginType CAddonMgr::GetAddonOriginType(const AddonPtr& addon) const
1323 if (addon->Origin() == ORIGIN_SYSTEM)
1324 return AddonOriginType::SYSTEM;
1325 else if (!addon->Origin().empty())
1326 return AddonOriginType::REPOSITORY;
1327 else
1328 return AddonOriginType::MANUAL;
1331 bool CAddonMgr::IsAddonDisabledWithReason(const std::string& ID,
1332 AddonDisabledReason disabledReason) const
1334 std::unique_lock<CCriticalSection> lock(m_critSection);
1335 const auto& disabledAddon = m_disabled.find(ID);
1336 return disabledAddon != m_disabled.end() && disabledAddon->second == disabledReason;
1340 * @brief Addon update and install management.
1342 /*@{{{*/
1344 bool CAddonMgr::SetAddonOrigin(const std::string& addonId, const std::string& repoAddonId, bool isUpdate)
1346 std::unique_lock<CCriticalSection> lock(m_critSection);
1348 m_database->SetOrigin(addonId, repoAddonId);
1349 if (isUpdate)
1350 m_database->SetLastUpdated(addonId, CDateTime::GetCurrentDateTime());
1352 // If available in manager update
1353 const AddonInfoPtr info = GetAddonInfo(addonId, AddonType::UNKNOWN);
1354 if (info)
1355 m_database->GetInstallData(info);
1356 return true;
1359 bool CAddonMgr::AddonsFromRepoXML(const RepositoryDirInfo& repo,
1360 const std::string& xml,
1361 std::vector<AddonInfoPtr>& addons)
1363 CXBMCTinyXML2 doc;
1364 if (!doc.Parse(xml))
1366 CLog::Log(LOGERROR, "CAddonMgr::{}: Failed to parse addons.xml", __func__);
1367 return false;
1370 if (doc.RootElement() == nullptr ||
1371 !StringUtils::EqualsNoCase(doc.RootElement()->Value(), "addons"))
1373 CLog::Log(LOGERROR, "CAddonMgr::{}: Failed to parse addons.xml. Malformed", __func__);
1374 return false;
1377 // each addon XML should have a UTF-8 declaration
1378 auto element = doc.RootElement()->FirstChildElement("addon");
1379 while (element)
1381 auto addonInfo = CAddonInfoBuilder::Generate(element, repo);
1382 if (addonInfo)
1383 addons.emplace_back(addonInfo);
1385 element = element->NextSiblingElement("addon");
1388 return true;
1391 /*@}}}*/
1393 } /* namespace ADDON */