diff options
Diffstat (limited to 'xbmc/addons/AddonRepos.cpp')
-rw-r--r-- | xbmc/addons/AddonRepos.cpp | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/xbmc/addons/AddonRepos.cpp b/xbmc/addons/AddonRepos.cpp new file mode 100644 index 0000000..77ae837 --- /dev/null +++ b/xbmc/addons/AddonRepos.cpp @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AddonRepos.h" + +#include "CompileInfo.h" +#include "ServiceBroker.h" +#include "addons/Addon.h" +#include "addons/AddonManager.h" +#include "addons/AddonRepoInfo.h" +#include "addons/AddonSystemSettings.h" +#include "addons/Repository.h" +#include "addons/RepositoryUpdater.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <vector> + +namespace +{ +constexpr auto ALL_ADDON_IDS = ""; +constexpr auto ALL_REPOSITORIES = nullptr; +} // anonymous namespace + +using namespace ADDON; + +static std::vector<RepoInfo> officialRepoInfos = CCompileInfo::LoadOfficialRepoInfos(); + +/********************************************************** + * CAddonRepos + * + */ + +CAddonRepos::CAddonRepos() : m_addonMgr(CServiceBroker::GetAddonMgr()) +{ + m_valid = m_addonDb.Open() && LoadAddonsFromDatabase(ALL_ADDON_IDS, ALL_REPOSITORIES); +} + +CAddonRepos::CAddonRepos(const std::string& addonId) : m_addonMgr(CServiceBroker::GetAddonMgr()) +{ + m_valid = m_addonDb.Open() && LoadAddonsFromDatabase(addonId, ALL_REPOSITORIES); +} + +CAddonRepos::CAddonRepos(const std::shared_ptr<IAddon>& repoAddon) + : m_addonMgr(CServiceBroker::GetAddonMgr()) +{ + m_valid = m_addonDb.Open() && LoadAddonsFromDatabase(ALL_ADDON_IDS, repoAddon); +} + +bool CAddonRepos::IsFromOfficialRepo(const std::shared_ptr<IAddon>& addon, + CheckAddonPath checkAddonPath) +{ + auto comparator = [&](const RepoInfo& officialRepo) { + if (checkAddonPath == CheckAddonPath::CHOICE_YES) + { + return (addon->Origin() == officialRepo.m_repoId && + StringUtils::StartsWithNoCase(addon->Path(), officialRepo.m_origin)); + } + + return addon->Origin() == officialRepo.m_repoId; + }; + + return addon->Origin() == ORIGIN_SYSTEM || + std::any_of(officialRepoInfos.begin(), officialRepoInfos.end(), comparator); +} + +bool CAddonRepos::IsOfficialRepo(const std::string& repoId) +{ + return repoId == ORIGIN_SYSTEM || std::any_of(officialRepoInfos.begin(), officialRepoInfos.end(), + [&repoId](const RepoInfo& officialRepo) { + return repoId == officialRepo.m_repoId; + }); +} + +bool CAddonRepos::LoadAddonsFromDatabase(const std::string& addonId, + const std::shared_ptr<IAddon>& repoAddon) +{ + if (repoAddon != ALL_REPOSITORIES) + { + if (!m_addonDb.GetRepositoryContent(repoAddon->ID(), m_allAddons)) + { + // Repo content is invalid. Ask for update and wait. + CServiceBroker::GetRepositoryUpdater().CheckForUpdates( + std::static_pointer_cast<CRepository>(repoAddon)); + CServiceBroker::GetRepositoryUpdater().Await(); + + if (!m_addonDb.GetRepositoryContent(repoAddon->ID(), m_allAddons)) + { + + // could not connect to repository + KODI::MESSAGING::HELPERS::ShowOKDialogText(CVariant{repoAddon->Name()}, CVariant{24991}); + return false; + } + } + } + else if (addonId == ALL_ADDON_IDS) + { + // load full repository content + m_addonDb.GetRepositoryContent(m_allAddons); + if (m_allAddons.empty()) + return true; + } + else + { + // load specific addonId only + m_addonDb.FindByAddonId(addonId, m_allAddons); + } + + if (m_allAddons.empty()) + return false; + + for (const auto& addon : m_allAddons) + { + if (m_addonMgr.IsCompatible(*addon)) + { + m_addonsByRepoMap[addon->Origin()].insert({addon->ID(), addon}); + } + } + + for (const auto& repo : m_addonsByRepoMap) + { + CLog::Log(LOGDEBUG, "ADDONS: {} - {} addon(s) loaded", repo.first, repo.second.size()); + + const auto& addonsPerRepo = repo.second; + + for (const auto& addonMapEntry : addonsPerRepo) + { + const auto& addonToAdd = addonMapEntry.second; + + if (IsFromOfficialRepo(addonToAdd, CheckAddonPath::CHOICE_YES)) + { + AddAddonIfLatest(addonToAdd, m_latestOfficialVersions); + } + else + { + AddAddonIfLatest(addonToAdd, m_latestPrivateVersions); + } + + // add to latestVersionsByRepo + AddAddonIfLatest(repo.first, addonToAdd, m_latestVersionsByRepo); + } + } + + return true; +} + +void CAddonRepos::AddAddonIfLatest(const std::shared_ptr<IAddon>& addonToAdd, + std::map<std::string, std::shared_ptr<IAddon>>& map) const +{ + const auto& latestKnown = map.find(addonToAdd->ID()); + if (latestKnown == map.end() || addonToAdd->Version() > latestKnown->second->Version()) + map[addonToAdd->ID()] = addonToAdd; +} + +void CAddonRepos::AddAddonIfLatest( + const std::string& repoId, + const std::shared_ptr<IAddon>& addonToAdd, + std::map<std::string, std::map<std::string, std::shared_ptr<IAddon>>>& map) const +{ + const auto& latestVersionByRepo = map.find(repoId); + + if (latestVersionByRepo == map.end()) // repo not found + { + map[repoId].insert({addonToAdd->ID(), addonToAdd}); + } + else + { + const auto& latestVersionEntryByRepo = latestVersionByRepo->second; + const auto& latestKnown = latestVersionEntryByRepo.find(addonToAdd->ID()); + + if (latestKnown == latestVersionEntryByRepo.end() || + addonToAdd->Version() > latestKnown->second->Version()) + map[repoId][addonToAdd->ID()] = addonToAdd; + } +} + +void CAddonRepos::BuildUpdateOrOutdatedList(const std::vector<std::shared_ptr<IAddon>>& installed, + std::vector<std::shared_ptr<IAddon>>& result, + AddonCheckType addonCheckType) const +{ + std::shared_ptr<IAddon> update; + + CLog::Log(LOGDEBUG, "CAddonRepos::{}: Building {} list from installed add-ons", __func__, + addonCheckType == AddonCheckType::OUTDATED_ADDONS ? "outdated" : "update"); + + for (const auto& addon : installed) + { + if (DoAddonUpdateCheck(addon, update)) + { + result.emplace_back(addonCheckType == AddonCheckType::OUTDATED_ADDONS ? addon : update); + } + } +} + +void CAddonRepos::BuildAddonsWithUpdateList( + const std::vector<std::shared_ptr<IAddon>>& installed, + std::map<std::string, AddonWithUpdate>& addonsWithUpdate) const +{ + std::shared_ptr<IAddon> update; + + CLog::Log(LOGDEBUG, + "CAddonRepos::{}: Building combined addons-with-update map from installed add-ons", + __func__); + + for (const auto& addon : installed) + { + if (DoAddonUpdateCheck(addon, update)) + { + addonsWithUpdate.insert({addon->ID(), {addon, update}}); + } + } +} + +bool CAddonRepos::DoAddonUpdateCheck(const std::shared_ptr<IAddon>& addon, + std::shared_ptr<IAddon>& update) const +{ + CLog::Log(LOGDEBUG, "ADDONS: update check: addonID = {} / Origin = {} / Version = {}", + addon->ID(), addon->Origin(), addon->Version().asString()); + + update.reset(); + + const AddonRepoUpdateMode updateMode = + CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode(); + + bool hasOfficialUpdate = FindAddonAndCheckForUpdate(addon, m_latestOfficialVersions, update); + + // addons with an empty origin have at least been checked against official repositories + if (!addon->Origin().empty()) + { + if (ORIGIN_SYSTEM != addon->Origin() && !hasOfficialUpdate) // not a system addon + { + + // we didn't find an official update. + // either version is current or that add-on isn't contained in official repos + if (IsFromOfficialRepo(addon, CheckAddonPath::CHOICE_NO)) + { + + // check further if it IS contained in official repos + if (updateMode == AddonRepoUpdateMode::ANY_REPOSITORY) + { + if (!FindAddonAndCheckForUpdate(addon, m_latestPrivateVersions, update)) + { + return false; + } + } + } + else + { + // ...we check for updates in the origin repo only + const auto& repoEntry = m_latestVersionsByRepo.find(addon->Origin()); + if (repoEntry != m_latestVersionsByRepo.end()) + { + if (!FindAddonAndCheckForUpdate(addon, repoEntry->second, update)) + { + return false; + } + } + } + } + } + + if (update != nullptr) + { + CLog::Log(LOGDEBUG, "ADDONS: -- found -->: addonID = {} / Origin = {} / Version = {}", + update->ID(), update->Origin(), update->Version().asString()); + return true; + } + + return false; +} + +bool CAddonRepos::FindAddonAndCheckForUpdate( + const std::shared_ptr<IAddon>& addonToCheck, + const std::map<std::string, std::shared_ptr<IAddon>>& map, + std::shared_ptr<IAddon>& update) const +{ + const auto& remote = map.find(addonToCheck->ID()); + if (remote != map.end()) // is addon in the desired map? + { + if ((remote->second->Version() > addonToCheck->Version()) || + m_addonMgr.IsAddonDisabledWithReason(addonToCheck->ID(), AddonDisabledReason::INCOMPATIBLE)) + { + // return addon update + update = remote->second; + return true; // update found + } + } + + // either addon wasn't found or it's up to date + return false; +} + +bool CAddonRepos::GetLatestVersionByMap(const std::string& addonId, + const std::map<std::string, std::shared_ptr<IAddon>>& map, + std::shared_ptr<IAddon>& addon) const +{ + const auto& remote = map.find(addonId); + if (remote != map.end()) // is addon in the desired map? + { + addon = remote->second; + return true; + } + + return false; +} + +bool CAddonRepos::GetLatestAddonVersionFromAllRepos(const std::string& addonId, + std::shared_ptr<IAddon>& addon) const +{ + const AddonRepoUpdateMode updateMode = + CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode(); + + bool hasOfficialVersion = GetLatestVersionByMap(addonId, m_latestOfficialVersions, addon); + + if (hasOfficialVersion) + { + if (updateMode == AddonRepoUpdateMode::ANY_REPOSITORY) + { + std::shared_ptr<IAddon> thirdPartyAddon; + + // only use this version if it's higher than the official one + if (GetLatestVersionByMap(addonId, m_latestPrivateVersions, thirdPartyAddon)) + { + if (thirdPartyAddon->Version() > addon->Version()) + addon = thirdPartyAddon; + } + } + } + else + { + if (!GetLatestVersionByMap(addonId, m_latestPrivateVersions, addon)) + return false; + } + + return true; +} + +void CAddonRepos::GetLatestAddonVersions(std::vector<std::shared_ptr<IAddon>>& addonList) const +{ + const AddonRepoUpdateMode updateMode = + CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode(); + + addonList.clear(); + + // first we insert all official addon versions into the resulting vector + + for (const auto& officialVersion : m_latestOfficialVersions) + addonList.emplace_back(officialVersion.second); + + // then we insert private addon versions if they don't exist in the official map + // or installation from ANY_REPOSITORY is allowed and the private version is higher + + for (const auto& privateVersion : m_latestPrivateVersions) + { + const auto& officialVersion = m_latestOfficialVersions.find(privateVersion.first); + if (officialVersion == m_latestOfficialVersions.end() || + (updateMode == AddonRepoUpdateMode::ANY_REPOSITORY && + privateVersion.second->Version() > officialVersion->second->Version())) + { + addonList.emplace_back(privateVersion.second); + } + } +} + +void CAddonRepos::GetLatestAddonVersionsFromAllRepos( + std::vector<std::shared_ptr<IAddon>>& addonList) const +{ + const AddonRepoUpdateMode updateMode = + CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode(); + + addonList.clear(); + + // first we insert all official addon versions into the resulting vector + + for (const auto& officialVersion : m_latestOfficialVersions) + addonList.emplace_back(officialVersion.second); + + // then we insert latest version per addon and repository if they don't exist in the official map + // or installation from ANY_REPOSITORY is allowed and the private version is higher + + for (const auto& repo : m_latestVersionsByRepo) + { + // content of official repos is stored in m_latestVersionsByRepo too + // so we need to filter them out + + if (std::none_of(officialRepoInfos.begin(), officialRepoInfos.end(), + [&](const ADDON::RepoInfo& officialRepo) { + return repo.first == officialRepo.m_repoId; + })) + { + for (const auto& latestAddon : repo.second) + { + const auto& officialVersion = m_latestOfficialVersions.find(latestAddon.first); + if (officialVersion == m_latestOfficialVersions.end() || + (updateMode == AddonRepoUpdateMode::ANY_REPOSITORY && + latestAddon.second->Version() > officialVersion->second->Version())) + { + addonList.emplace_back(latestAddon.second); + } + } + } + } +} + +bool CAddonRepos::FindDependency(const std::string& dependsId, + const std::string& parentRepoId, + std::shared_ptr<IAddon>& dependencyToInstall, + std::shared_ptr<CRepository>& repoForDep) const +{ + const AddonRepoUpdateMode updateMode = + CAddonSystemSettings::GetInstance().GetAddonRepoUpdateMode(); + + bool dependencyHasOfficialVersion = + GetLatestVersionByMap(dependsId, m_latestOfficialVersions, dependencyToInstall); + + if (dependencyHasOfficialVersion) + { + if (updateMode == AddonRepoUpdateMode::ANY_REPOSITORY) + { + std::shared_ptr<IAddon> thirdPartyDependency; + + // only use this version if it's higher than the official one + if (GetLatestVersionByMap(dependsId, m_latestPrivateVersions, thirdPartyDependency)) + { + if (thirdPartyDependency->Version() > dependencyToInstall->Version()) + dependencyToInstall = thirdPartyDependency; + } + } + } + else + { + // If we didn't find an official version of this dependency + // ...we check in the origin repo of the parent + if (!FindDependencyByParentRepo(dependsId, parentRepoId, dependencyToInstall)) + return false; + } + + // we got the dependency, so now get a repository-pointer to return + + std::shared_ptr<IAddon> tmp; + if (!m_addonMgr.GetAddon(dependencyToInstall->Origin(), tmp, AddonType::REPOSITORY, + OnlyEnabled::CHOICE_YES)) + return false; + + repoForDep = std::static_pointer_cast<CRepository>(tmp); + + CLog::Log(LOGDEBUG, "ADDONS: found dependency [{}] for install/update from repo [{}]", + dependencyToInstall->ID(), repoForDep->ID()); + + if (dependencyToInstall->HasType(AddonType::REPOSITORY)) + { + CLog::Log(LOGDEBUG, + "ADDONS: dependency with id [{}] has type ADDON_REPOSITORY and will not install!", + dependencyToInstall->ID()); + + return false; + } + + return true; +} + +bool CAddonRepos::FindDependencyByParentRepo(const std::string& dependsId, + const std::string& parentRepoId, + std::shared_ptr<IAddon>& dependencyToInstall) const +{ + const auto& repoEntry = m_latestVersionsByRepo.find(parentRepoId); + if (repoEntry != m_latestVersionsByRepo.end()) + { + if (GetLatestVersionByMap(dependsId, repoEntry->second, dependencyToInstall)) + return true; + } + + return false; +} + +void CAddonRepos::BuildCompatibleVersionsList( + std::vector<std::shared_ptr<IAddon>>& compatibleVersions) const +{ + std::vector<std::shared_ptr<IAddon>> officialVersions; + std::vector<std::shared_ptr<IAddon>> privateVersions; + + for (const auto& addon : m_allAddons) + { + if (m_addonMgr.IsCompatible(*addon)) + { + if (IsFromOfficialRepo(addon, CheckAddonPath::CHOICE_YES)) + { + officialVersions.emplace_back(addon); + } + else + { + privateVersions.emplace_back(addon); + } + } + } + + auto comparator = [](const std::shared_ptr<IAddon>& a, const std::shared_ptr<IAddon>& b) { + return (a->Version() > b->Version()); + }; + + std::sort(officialVersions.begin(), officialVersions.end(), comparator); + std::sort(privateVersions.begin(), privateVersions.end(), comparator); + + compatibleVersions = std::move(officialVersions); + std::move(privateVersions.begin(), privateVersions.end(), std::back_inserter(compatibleVersions)); +} |