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.
9 #include "AddonManager.h"
11 #include "CompileInfo.h"
13 #include "FileItemList.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"
45 using namespace XFILE
;
50 /**********************************************************
55 std::map
<AddonType
, IAddonMgrCallback
*> CAddonMgr::m_managers
;
57 static bool LoadManifest(std::set
<std::string
>& system
, std::set
<std::string
>& optional
)
60 if (!doc
.LoadFile("special://xbmc/system/addon-manifest.xml"))
62 CLog::Log(LOGERROR
, "ADDONS: manifest missing");
66 auto root
= doc
.RootElement();
67 if (!root
|| root
->ValueStr() != "addons")
69 CLog::Log(LOGERROR
, "ADDONS: malformed manifest");
73 auto elem
= root
->FirstChildElement("addon");
76 if (elem
->FirstChild())
78 if (XMLUtils::GetAttribute(elem
, "optional") == "true")
79 optional
.insert(elem
->FirstChild()->ValueStr());
81 system
.insert(elem
->FirstChild()->ValueStr());
83 elem
= elem
->NextSiblingElement("addon");
88 CAddonMgr::CAddonMgr()
89 : m_database(std::make_unique
<CAddonDatabase
>()),
90 m_updateRules(std::make_unique
<CAddonUpdateRules
>())
94 CAddonMgr::~CAddonMgr()
99 IAddonMgrCallback
* CAddonMgr::GetCallbackForType(AddonType type
)
101 if (m_managers
.find(type
) == m_managers
.end())
104 return m_managers
[type
];
107 bool CAddonMgr::RegisterAddonMgrCallback(AddonType type
, IAddonMgrCallback
* cb
)
112 m_managers
.erase(type
);
113 m_managers
[type
] = cb
;
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");
133 if (!m_database
->Open())
134 CLog::Log(LOGFATAL
, "ADDONS: Failed to open database");
138 //Ensure required add-ons are installed and enabled
139 for (const auto& id
: m_systemAddons
)
142 if (!GetAddon(id
, addon
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_YES
))
144 CLog::Log(LOGFATAL
, "addon '{}' not installed or not enabled.", id
);
152 void CAddonMgr::DeInit()
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()))
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
))
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
);
204 explicit AddonIdFinder(const std::string
& id
)
208 bool operator()(const AddonPtr
& addon
)
210 return m_id
== addon
->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
);
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",
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());
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());
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()))
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
)
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()); });
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())
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
)
422 // check if the addon matches the provided addon type
423 if (type
!= AddonType::UNKNOWN
&& addon
->Type() != type
&& !addon
->HasType(type
))
426 if (!this->CanAddonBeInstalled(addon
))
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())
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
))
453 // get the latest version from all repos if the
454 // addon is up-to-date or not installed yet
458 "addon {} is up-to-date or not installed. falling back to get latest version from all repos",
461 return addonRepos
.GetLatestAddonVersionFromAllRepos(addonId
, result
);
464 bool CAddonMgr::GetAddonsInternal(AddonType type
,
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
))
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
))))
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
)
488 AddonPtr addon
= CAddonBuilder::Generate(addonInfo
.second
, type
);
491 // if the addon has a running instance, grab that
492 AddonPtr runningAddon
= addon
->GetRunningInstance();
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
);
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
); }),
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...");
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());
548 if (!DisableAddon(addon
->ID(), AddonDisabledReason::INCOMPATIBLE
))
550 CLog::Log(LOGWARNING
, "ADDON: failed to disable {}", addon
->ID());
553 changed
.emplace_back(addon
);
559 void CAddonMgr::CheckAndInstallAddonUpdates(bool wait
) const
561 std::lock_guard
<std::mutex
> lock(m_installAddonsMutex
);
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();
572 std::remove_if(updates
.begin(), updates
.end(),
573 [this](const AddonPtr
& addon
) { return !IsAutoUpdateable(addon
->ID()); }),
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;
608 // add to the end of sorted list of addons
611 sorted
.emplace_back(addon
);
612 it
= updates
.erase(it
);
623 void CAddonMgr::InstallAddonUpdates(VECADDONS
& updates
,
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
,
635 OnlyEnabled onlyEnabled
) const
637 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
639 AddonInfoPtr addonInfo
= GetAddonInfo(str
, type
);
642 addon
= CAddonBuilder::Generate(addonInfo
, type
);
645 if (onlyEnabled
== OnlyEnabled::CHOICE_YES
&& IsAddonDisabled(addonInfo
->ID()))
648 // if the addon has a running instance, grab that
649 AddonPtr runningAddon
= addon
->GetRunningInstance();
651 addon
= runningAddon
;
653 return nullptr != addon
.get();
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
)
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
)
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
);
696 std::map
<std::string
, AddonDisabledReason
> tmpDisabled
;
697 m_database
->GetDisabled(tmpDisabled
);
698 m_disabled
= std::move(tmpDisabled
);
700 m_updateRules
->RefreshRulesMap(*m_database
);
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
);
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
);
732 std::map
<std::string
, AddonDisabledReason
> tmpDisabled
;
733 m_database
->GetDisabled(tmpDisabled
);
734 m_disabled
= std::move(tmpDisabled
);
736 m_updateRules
->RefreshRulesMap(*m_database
);
741 bool CAddonMgr::UnloadAddon(const std::string
& addonId
)
743 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
745 if (!IsAddonInstalled(addonId
))
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__
,
758 m_installedAddons
.erase(addonId
);
759 CLog::Log(LOGDEBUG
, "CAddonMgr::{}: {} unloaded", __func__
, addonId
);
762 AddonEvents::Unload
event(addonId
);
763 m_unloadEvents
.HandleEvent(event
);
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
);
775 if (GetAddon(addonId
, addon
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_NO
))
780 if (!FindAddon(addonId
, origin
, addonVersion
))
782 CLog::Log(LOGERROR
, "CAddonMgr: could not reload add-on {}. FindAddon failed.", addonId
);
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.",
795 AddonEvents::Load
event(addon
->ID());
796 m_unloadEvents
.HandleEvent(event
);
798 if (IsAddonDisabled(addon
->ID()))
800 EnableAddon(addon
->ID());
804 m_events
.Publish(AddonEvents::ReInstalled(addon
->ID()));
805 CLog::Log(LOGDEBUG
, "CAddonMgr: {} successfully loaded", addon
->ID());
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
);
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())
838 if (!CServiceBroker::GetAddonMgr().GetAddon(addonId
, addon
, AddonType::UNKNOWN
,
839 OnlyEnabled::CHOICE_NO
))
840 missing
.push_back(addonId
);
843 needed
.push_back(addonId
);
844 for (const auto& dep
: addon
->GetDependencies())
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
))
855 if (m_disabled
.find(id
) != m_disabled
.end())
856 return true; //already disabled
857 if (!m_database
->DisableAddon(id
, disabledReason
))
859 if (!m_disabled
.emplace(id
, disabledReason
).second
)
863 CLog::Log(LOGDEBUG
, "CAddonMgr: {} disabled", id
);
865 if (GetAddon(id
, addon
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_NO
) && addon
!= nullptr)
867 auto eventLog
= CServiceBroker::GetEventLog();
869 eventLog
->Add(EventPtr(new CAddonManagementEvent(addon
, 24141)));
872 m_events
.Publish(AddonEvents::Disabled(id
));
876 bool CAddonMgr::UpdateDisabledReason(const std::string
& id
, AddonDisabledReason newDisabledReason
)
878 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
879 if (!IsAddonDisabled(id
))
881 if (!m_database
->DisableAddon(id
, newDisabledReason
))
884 m_disabled
[id
] = newDisabledReason
;
887 CLog::Log(LOGDEBUG
, "CAddonMgr: DisabledReason for {} updated to {}", id
,
888 static_cast<int>(newDisabledReason
));
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
900 if (!GetAddon(id
, addon
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_NO
) || addon
== nullptr)
903 auto eventLog
= CServiceBroker::GetEventLog();
905 if (!IsCompatible(addon
))
907 CLog::Log(LOGERROR
, "Add-on '{}' is not compatible with Kodi", addon
->ID());
909 eventLog
->AddWithNotification(
910 EventPtr(new CNotificationEvent(addon
->Name(), 24152, EventLevel::Error
)));
911 UpdateDisabledReason(addon
->ID(), AddonDisabledReason::INCOMPATIBLE
);
915 if (!m_database
->EnableAddon(id
))
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);
924 eventLog
->Add(EventPtr(new CAddonManagementEvent(addon
, 24064)));
926 CLog::Log(LOGDEBUG
, "CAddonMgr: enabled {}", addon
->ID());
927 m_events
.Publish(AddonEvents::Enabled(id
));
931 bool CAddonMgr::EnableAddon(const std::string
& id
)
933 if (id
.empty() || !IsAddonInstalled(id
))
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 "
943 for (auto it
= needed
.rbegin(); it
!= needed
.rend(); ++it
)
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
)
968 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
969 // Required system add-ons can not be disabled
970 if (IsRequiredSystemAddon(ID
))
974 // can't disable an addon that isn't installed
975 if (!GetAddon(ID
, localAddon
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_NO
))
978 // can't disable an addon that is in use
979 if (localAddon
->IsInUse())
985 bool CAddonMgr::CanAddonBeEnabled(const std::string
& id
)
987 return !id
.empty() && IsAddonInstalled(id
);
990 bool CAddonMgr::IsAddonInstalled(const std::string
& ID
)
993 return GetAddon(ID
, tmp
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_NO
);
996 bool CAddonMgr::IsAddonInstalled(const std::string
& ID
, const std::string
& origin
) const
1000 if (GetAddon(ID
, tmp
, AddonType::UNKNOWN
, OnlyEnabled::CHOICE_NO
) && tmp
)
1002 if (tmp
->Origin() == ORIGIN_SYSTEM
)
1004 return CAddonRepos::IsOfficialRepo(origin
);
1008 return tmp
->Origin() == origin
;
1014 bool CAddonMgr::IsAddonInstalled(const std::string
& ID
,
1015 const std::string
& origin
,
1016 const CAddonVersion
& version
)
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
;
1028 return tmp
->Origin() == origin
&& tmp
->Version() == version
;
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
);
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
))
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
))
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
)))
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."))
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
)
1186 bool aopt
= added_it
->optional
;
1187 added
.erase(added_it
);
1188 added
.push_back(current_dep
);
1189 if (!current_dep
.optional
&& aopt
)
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
);
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())
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
;
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())
1237 if (info
.second
->MainType() == AddonType::UNKNOWN
)
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
);
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
,
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())
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
;
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
);
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());
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
;
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.
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
);
1350 m_database
->SetLastUpdated(addonId
, CDateTime::GetCurrentDateTime());
1352 // If available in manager update
1353 const AddonInfoPtr info
= GetAddonInfo(addonId
, AddonType::UNKNOWN
);
1355 m_database
->GetInstallData(info
);
1359 bool CAddonMgr::AddonsFromRepoXML(const RepositoryDirInfo
& repo
,
1360 const std::string
& xml
,
1361 std::vector
<AddonInfoPtr
>& addons
)
1364 if (!doc
.Parse(xml
))
1366 CLog::Log(LOGERROR
, "CAddonMgr::{}: Failed to parse addons.xml", __func__
);
1370 if (doc
.RootElement() == nullptr ||
1371 !StringUtils::EqualsNoCase(doc
.RootElement()->Value(), "addons"))
1373 CLog::Log(LOGERROR
, "CAddonMgr::{}: Failed to parse addons.xml. Malformed", __func__
);
1377 // each addon XML should have a UTF-8 declaration
1378 auto element
= doc
.RootElement()->FirstChildElement("addon");
1381 auto addonInfo
= CAddonInfoBuilder::Generate(element
, repo
);
1383 addons
.emplace_back(addonInfo
);
1385 element
= element
->NextSiblingElement("addon");
1393 } /* namespace ADDON */