Merge pull request #26264 from the-black-eagle/mka_end_durations
[xbmc.git] / xbmc / addons / AddonRepos.cpp
blob362f051b97bd305f315de5986b7d346fecef8bac
1 /*
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.
7 */
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"
25 #include <algorithm>
26 #include <vector>
28 namespace
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 /**********************************************************
39 * CAddonRepos
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;
81 });
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});
101 return false;
105 else if (addonId == ALL_ADDON_IDS)
107 // load full repository content
108 m_addonDb.GetRepositoryContent(m_allAddons);
109 if (m_allAddons.empty())
110 return true;
112 else
114 // load specific addonId only
115 m_addonDb.FindByAddonId(addonId, m_allAddons);
118 if (m_allAddons.empty())
119 return false;
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);
139 else
141 AddAddonIfLatest(addonToAdd, m_latestPrivateVersions);
144 // add to latestVersionsByRepo
145 AddAddonIfLatest(repoId, addonToAdd, m_latestVersionsByRepo);
149 return true;
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
165 bool doInsert{true};
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())
176 doInsert = false;
180 if (doInsert)
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());
224 update.reset();
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))
247 return false;
251 else
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))
259 return false;
266 if (update != nullptr)
268 CLog::LogFC(LOGDEBUG, LOGADDONS, "-- found -->: addonID = {} / Origin = {} / Version = {}",
269 update->ID(), update->Origin(), update->Version().asString());
270 return true;
273 return false;
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
294 return false;
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;
305 return true;
308 return false;
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;
333 else
335 if (!GetLatestVersionByMap(addonId, m_latestPrivateVersions, addon))
336 return false;
339 return true;
342 void CAddonRepos::GetLatestAddonVersions(std::vector<std::shared_ptr<IAddon>>& addonList) const
344 const AddonRepoUpdateMode updateMode =
345 CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode();
347 addonList.clear();
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();
376 addonList.clear();
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;
435 else
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))
440 return false;
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))
448 return false;
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());
461 return false;
464 return true;
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))
475 return true;
478 return false;
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);
495 else
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));