2 * Copyright (C) 2005-2020 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 "AddonRepos.h"
11 #include "CompileInfo.h"
12 #include "ServiceBroker.h"
13 #include "addons/Addon.h"
14 #include "addons/AddonManager.h"
15 #include "addons/AddonRepoInfo.h"
16 #include "addons/AddonSystemSettings.h"
17 #include "addons/Repository.h"
18 #include "addons/RepositoryUpdater.h"
19 #include "addons/addoninfo/AddonInfo.h"
20 #include "addons/addoninfo/AddonType.h"
21 #include "messaging/helpers/DialogOKHelper.h"
22 #include "utils/StringUtils.h"
23 #include "utils/log.h"
30 constexpr auto ALL_ADDON_IDS
= "";
31 constexpr auto ALL_REPOSITORIES
= nullptr;
32 } // anonymous namespace
34 using namespace ADDON
;
36 static std::vector
<RepoInfo
> officialRepoInfos
= CCompileInfo::LoadOfficialRepoInfos();
38 /**********************************************************
43 CAddonRepos::CAddonRepos() : m_addonMgr(CServiceBroker::GetAddonMgr())
45 m_valid
= m_addonDb
.Open() && LoadAddonsFromDatabase(ALL_ADDON_IDS
, ALL_REPOSITORIES
);
48 CAddonRepos::CAddonRepos(const std::string
& addonId
) : m_addonMgr(CServiceBroker::GetAddonMgr())
50 m_valid
= m_addonDb
.Open() && LoadAddonsFromDatabase(addonId
, ALL_REPOSITORIES
);
53 CAddonRepos::CAddonRepos(const std::shared_ptr
<IAddon
>& repoAddon
)
54 : m_addonMgr(CServiceBroker::GetAddonMgr())
56 m_valid
= m_addonDb
.Open() && LoadAddonsFromDatabase(ALL_ADDON_IDS
, repoAddon
);
59 bool CAddonRepos::IsFromOfficialRepo(const std::shared_ptr
<IAddon
>& addon
,
60 CheckAddonPath checkAddonPath
)
62 auto comparator
= [&](const RepoInfo
& officialRepo
) {
63 if (checkAddonPath
== CheckAddonPath::CHOICE_YES
)
65 return (addon
->Origin() == officialRepo
.m_repoId
&&
66 StringUtils::StartsWithNoCase(addon
->Path(), officialRepo
.m_origin
));
69 return addon
->Origin() == officialRepo
.m_repoId
;
72 return addon
->Origin() == ORIGIN_SYSTEM
||
73 std::any_of(officialRepoInfos
.begin(), officialRepoInfos
.end(), comparator
);
76 bool CAddonRepos::IsOfficialRepo(const std::string
& repoId
)
78 return repoId
== ORIGIN_SYSTEM
|| std::any_of(officialRepoInfos
.begin(), officialRepoInfos
.end(),
79 [&repoId
](const RepoInfo
& officialRepo
) {
80 return repoId
== officialRepo
.m_repoId
;
84 bool CAddonRepos::LoadAddonsFromDatabase(const std::string
& addonId
,
85 const std::shared_ptr
<IAddon
>& repoAddon
)
87 if (repoAddon
!= ALL_REPOSITORIES
)
89 if (!m_addonDb
.GetRepositoryContent(repoAddon
->ID(), m_allAddons
))
91 // Repo content is invalid. Ask for update and wait.
92 CServiceBroker::GetRepositoryUpdater().CheckForUpdates(
93 std::static_pointer_cast
<CRepository
>(repoAddon
));
94 CServiceBroker::GetRepositoryUpdater().Await();
96 if (!m_addonDb
.GetRepositoryContent(repoAddon
->ID(), m_allAddons
))
99 // could not connect to repository
100 KODI::MESSAGING::HELPERS::ShowOKDialogText(CVariant
{repoAddon
->Name()}, CVariant
{24991});
105 else if (addonId
== ALL_ADDON_IDS
)
107 // load full repository content
108 m_addonDb
.GetRepositoryContent(m_allAddons
);
109 if (m_allAddons
.empty())
114 // load specific addonId only
115 m_addonDb
.FindByAddonId(addonId
, m_allAddons
);
118 if (m_allAddons
.empty())
121 for (const auto& addon
: m_allAddons
)
123 if (m_addonMgr
.IsCompatible(addon
))
125 m_addonsByRepoMap
[addon
->Origin()].emplace(addon
->ID(), addon
);
129 for (const auto& [repoId
, addonsPerRepo
] : m_addonsByRepoMap
)
131 CLog::LogFC(LOGDEBUG
, LOGADDONS
, "{} - {} addon(s) loaded", repoId
, addonsPerRepo
.size());
133 for (const auto& [addonId
, addonToAdd
] : addonsPerRepo
)
135 if (IsFromOfficialRepo(addonToAdd
, CheckAddonPath::CHOICE_YES
))
137 AddAddonIfLatest(addonToAdd
, m_latestOfficialVersions
);
141 AddAddonIfLatest(addonToAdd
, m_latestPrivateVersions
);
144 // add to latestVersionsByRepo
145 AddAddonIfLatest(repoId
, addonToAdd
, m_latestVersionsByRepo
);
152 void CAddonRepos::AddAddonIfLatest(const std::shared_ptr
<IAddon
>& addonToAdd
,
153 std::map
<std::string
, std::shared_ptr
<IAddon
>>& map
) const
155 const auto latestKnownIt
= map
.find(addonToAdd
->ID());
156 if (latestKnownIt
== map
.end() || addonToAdd
->Version() > latestKnownIt
->second
->Version())
157 map
[addonToAdd
->ID()] = addonToAdd
;
160 void CAddonRepos::AddAddonIfLatest(
161 const std::string
& repoId
,
162 const std::shared_ptr
<IAddon
>& addonToAdd
,
163 std::map
<std::string
, std::map
<std::string
, std::shared_ptr
<IAddon
>>>& map
) const
167 const auto latestVersionByRepoIt
= map
.find(repoId
);
168 if (latestVersionByRepoIt
!= map
.end()) // we already have this repository in the outer map
170 const auto& latestVersionEntryByRepo
= latestVersionByRepoIt
->second
;
171 const auto latestKnownIt
= latestVersionEntryByRepo
.find(addonToAdd
->ID());
173 if (latestKnownIt
!= latestVersionEntryByRepo
.end() &&
174 addonToAdd
->Version() <= latestKnownIt
->second
->Version())
181 map
[repoId
][addonToAdd
->ID()] = addonToAdd
;
184 void CAddonRepos::BuildUpdateOrOutdatedList(const std::vector
<std::shared_ptr
<IAddon
>>& installed
,
185 std::vector
<std::shared_ptr
<IAddon
>>& result
,
186 AddonCheckType addonCheckType
) const
188 std::shared_ptr
<IAddon
> update
;
190 CLog::LogFC(LOGDEBUG
, LOGADDONS
, "Building {} list from installed add-ons",
191 addonCheckType
== AddonCheckType::OUTDATED_ADDONS
? "outdated" : "update");
192 for (const auto& addon
: installed
)
194 if (DoAddonUpdateCheck(addon
, update
))
196 result
.emplace_back(addonCheckType
== AddonCheckType::OUTDATED_ADDONS
? addon
: update
);
201 void CAddonRepos::BuildAddonsWithUpdateList(
202 const std::vector
<std::shared_ptr
<IAddon
>>& installed
,
203 std::map
<std::string
, AddonWithUpdate
>& addonsWithUpdate
) const
205 std::shared_ptr
<IAddon
> update
;
207 CLog::LogFC(LOGDEBUG
, LOGADDONS
,
208 "Building combined addons-with-update map from installed add-ons");
209 for (const auto& addon
: installed
)
211 if (DoAddonUpdateCheck(addon
, update
))
213 addonsWithUpdate
.try_emplace(addon
->ID(), addon
, update
);
218 bool CAddonRepos::DoAddonUpdateCheck(const std::shared_ptr
<IAddon
>& addon
,
219 std::shared_ptr
<IAddon
>& update
) const
221 CLog::LogFC(LOGDEBUG
, LOGADDONS
, "update check: addonID = {} / Origin = {} / Version = {}",
222 addon
->ID(), addon
->Origin(), addon
->Version().asString());
226 const AddonRepoUpdateMode updateMode
=
227 CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode();
229 bool hasOfficialUpdate
= FindAddonAndCheckForUpdate(addon
, m_latestOfficialVersions
, update
);
231 // addons with an empty origin have at least been checked against official repositories
232 if (!addon
->Origin().empty())
234 if (ORIGIN_SYSTEM
!= addon
->Origin() && !hasOfficialUpdate
) // not a system addon
237 // we didn't find an official update.
238 // either version is current or that add-on isn't contained in official repos
239 if (IsFromOfficialRepo(addon
, CheckAddonPath::CHOICE_NO
))
242 // check further if it IS contained in official repos
243 if (updateMode
== AddonRepoUpdateMode::ANY_REPOSITORY
)
245 if (!FindAddonAndCheckForUpdate(addon
, m_latestPrivateVersions
, update
))
253 // ...we check for updates in the origin repo only
254 const auto repoEntryIt
= m_latestVersionsByRepo
.find(addon
->Origin());
255 if (repoEntryIt
!= m_latestVersionsByRepo
.end())
257 if (!FindAddonAndCheckForUpdate(addon
, repoEntryIt
->second
, update
))
266 if (update
!= nullptr)
268 CLog::LogFC(LOGDEBUG
, LOGADDONS
, "-- found -->: addonID = {} / Origin = {} / Version = {}",
269 update
->ID(), update
->Origin(), update
->Version().asString());
276 bool CAddonRepos::FindAddonAndCheckForUpdate(
277 const std::shared_ptr
<IAddon
>& addonToCheck
,
278 const std::map
<std::string
, std::shared_ptr
<IAddon
>>& map
,
279 std::shared_ptr
<IAddon
>& update
) const
281 const auto remoteIt
= map
.find(addonToCheck
->ID());
282 if (remoteIt
!= map
.end()) // is addon in the desired map?
284 if ((remoteIt
->second
->Version() > addonToCheck
->Version()) ||
285 m_addonMgr
.IsAddonDisabledWithReason(addonToCheck
->ID(), AddonDisabledReason::INCOMPATIBLE
))
287 // return addon update
288 update
= remoteIt
->second
;
289 return true; // update found
293 // either addon wasn't found or it's up to date
297 bool CAddonRepos::GetLatestVersionByMap(const std::string
& addonId
,
298 const std::map
<std::string
, std::shared_ptr
<IAddon
>>& map
,
299 std::shared_ptr
<IAddon
>& addon
) const
301 const auto remoteIt
= map
.find(addonId
);
302 if (remoteIt
!= map
.end()) // is addon in the desired map?
304 addon
= remoteIt
->second
;
311 bool CAddonRepos::GetLatestAddonVersionFromAllRepos(const std::string
& addonId
,
312 std::shared_ptr
<IAddon
>& addon
) const
314 const AddonRepoUpdateMode updateMode
=
315 CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode();
317 bool hasOfficialVersion
= GetLatestVersionByMap(addonId
, m_latestOfficialVersions
, addon
);
319 if (hasOfficialVersion
)
321 if (updateMode
== AddonRepoUpdateMode::ANY_REPOSITORY
)
323 std::shared_ptr
<IAddon
> thirdPartyAddon
;
325 // only use this version if it's higher than the official one
326 if (GetLatestVersionByMap(addonId
, m_latestPrivateVersions
, thirdPartyAddon
))
328 if (thirdPartyAddon
->Version() > addon
->Version())
329 addon
= thirdPartyAddon
;
335 if (!GetLatestVersionByMap(addonId
, m_latestPrivateVersions
, addon
))
342 void CAddonRepos::GetLatestAddonVersions(std::vector
<std::shared_ptr
<IAddon
>>& addonList
) const
344 const AddonRepoUpdateMode updateMode
=
345 CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode();
349 // first we insert all official addon versions into the resulting vector
351 for (const auto& officialVersion
: m_latestOfficialVersions
)
352 addonList
.emplace_back(officialVersion
.second
);
354 // then we insert private addon versions if they don't exist in the official map
355 // or installation from ANY_REPOSITORY is allowed and the private version is higher
357 for (const auto& [privateVersionId
, privateVersion
] : m_latestPrivateVersions
)
359 const auto officialVersionIt
= m_latestOfficialVersions
.find(privateVersionId
);
361 if (officialVersionIt
== m_latestOfficialVersions
.end() ||
362 (updateMode
== AddonRepoUpdateMode::ANY_REPOSITORY
&&
363 privateVersion
->Version() > officialVersionIt
->second
->Version()))
365 addonList
.emplace_back(privateVersion
);
370 void CAddonRepos::GetLatestAddonVersionsFromAllRepos(
371 std::vector
<std::shared_ptr
<IAddon
>>& addonList
) const
373 const AddonRepoUpdateMode updateMode
=
374 CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode();
378 // first we insert all official addon versions into the resulting vector
380 for (const auto& officialVersion
: m_latestOfficialVersions
)
381 addonList
.emplace_back(officialVersion
.second
);
383 // then we insert latest version per addon and repository if they don't exist in the official map
384 // or installation from ANY_REPOSITORY is allowed and the private version is higher
386 for (const auto& repo
: m_latestVersionsByRepo
)
388 // content of official repos is stored in m_latestVersionsByRepo too
389 // so we need to filter them out
391 if (std::none_of(officialRepoInfos
.begin(), officialRepoInfos
.end(),
392 [&repo
](const ADDON::RepoInfo
& officialRepo
)
393 { return repo
.first
== officialRepo
.m_repoId
; }))
395 for (const auto& [latestAddonId
, latestAddon
] : repo
.second
)
397 const auto officialVersionIt
= m_latestOfficialVersions
.find(latestAddonId
);
399 if (officialVersionIt
== m_latestOfficialVersions
.end() ||
400 (updateMode
== AddonRepoUpdateMode::ANY_REPOSITORY
&&
401 latestAddon
->Version() > officialVersionIt
->second
->Version()))
403 addonList
.emplace_back(latestAddon
);
410 bool CAddonRepos::FindDependency(const std::string
& dependsId
,
411 const std::string
& parentRepoId
,
412 std::shared_ptr
<IAddon
>& dependencyToInstall
,
413 std::shared_ptr
<CRepository
>& repoForDep
) const
415 const AddonRepoUpdateMode updateMode
=
416 CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode();
418 bool dependencyHasOfficialVersion
=
419 GetLatestVersionByMap(dependsId
, m_latestOfficialVersions
, dependencyToInstall
);
421 if (dependencyHasOfficialVersion
)
423 if (updateMode
== AddonRepoUpdateMode::ANY_REPOSITORY
)
425 std::shared_ptr
<IAddon
> thirdPartyDependency
;
427 // only use this version if it's higher than the official one
428 if (GetLatestVersionByMap(dependsId
, m_latestPrivateVersions
, thirdPartyDependency
))
430 if (thirdPartyDependency
->Version() > dependencyToInstall
->Version())
431 dependencyToInstall
= thirdPartyDependency
;
437 // If we didn't find an official version of this dependency
438 // ...we check in the origin repo of the parent
439 if (!FindDependencyByParentRepo(dependsId
, parentRepoId
, dependencyToInstall
))
443 // we got the dependency, so now get a repository-pointer to return
445 std::shared_ptr
<IAddon
> tmp
;
446 if (!m_addonMgr
.GetAddon(dependencyToInstall
->Origin(), tmp
, AddonType::REPOSITORY
,
447 OnlyEnabled::CHOICE_YES
))
450 repoForDep
= std::static_pointer_cast
<CRepository
>(tmp
);
452 CLog::LogFC(LOGDEBUG
, LOGADDONS
, "found dependency [{}] for install/update from repo [{}]",
453 dependencyToInstall
->ID(), repoForDep
->ID());
455 if (dependencyToInstall
->HasType(AddonType::REPOSITORY
))
457 CLog::LogFC(LOGDEBUG
, LOGADDONS
,
458 "dependency with id [{}] has type ADDON_REPOSITORY and will not install!",
459 dependencyToInstall
->ID());
467 bool CAddonRepos::FindDependencyByParentRepo(const std::string
& dependsId
,
468 const std::string
& parentRepoId
,
469 std::shared_ptr
<IAddon
>& dependencyToInstall
) const
471 const auto repoEntryIt
= m_latestVersionsByRepo
.find(parentRepoId
);
472 if (repoEntryIt
!= m_latestVersionsByRepo
.end())
474 if (GetLatestVersionByMap(dependsId
, repoEntryIt
->second
, dependencyToInstall
))
481 void CAddonRepos::BuildCompatibleVersionsList(
482 std::vector
<std::shared_ptr
<IAddon
>>& compatibleVersions
) const
484 std::vector
<std::shared_ptr
<IAddon
>> officialVersions
;
485 std::vector
<std::shared_ptr
<IAddon
>> privateVersions
;
487 for (const auto& addon
: m_allAddons
)
489 if (m_addonMgr
.IsCompatible(addon
))
491 if (IsFromOfficialRepo(addon
, CheckAddonPath::CHOICE_YES
))
493 officialVersions
.emplace_back(addon
);
497 privateVersions
.emplace_back(addon
);
502 auto comparator
= [](const std::shared_ptr
<IAddon
>& a
, const std::shared_ptr
<IAddon
>& b
) {
503 return (a
->Version() > b
->Version());
506 std::sort(officialVersions
.begin(), officialVersions
.end(), comparator
);
507 std::sort(privateVersions
.begin(), privateVersions
.end(), comparator
);
509 compatibleVersions
= std::move(officialVersions
);
510 std::move(privateVersions
.begin(), privateVersions
.end(), std::back_inserter(compatibleVersions
));