diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/addons/addoninfo/AddonInfoBuilder.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/addons/addoninfo/AddonInfoBuilder.cpp')
-rw-r--r-- | xbmc/addons/addoninfo/AddonInfoBuilder.cpp | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp new file mode 100644 index 0000000..268efa6 --- /dev/null +++ b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp @@ -0,0 +1,874 @@ +/* + * Copyright (C) 2005-2018 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 "AddonInfoBuilder.h" + +#include "CompileInfo.h" +#include "LangInfo.h" +#include "addons/Repository.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/JSONVariantParser.h" +#include "utils/JSONVariantWriter.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> +#include <memory> +#include <regex> + +namespace +{ +// Note that all of these characters are url-safe +const std::string VALID_ADDON_IDENTIFIER_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_@!$"; +} + +namespace ADDON +{ + +CAddonInfoBuilderFromDB::CAddonInfoBuilderFromDB() : m_addonInfo(std::make_shared<CAddonInfo>()) +{ +} + +void CAddonInfoBuilderFromDB::SetId(std::string id) +{ + m_addonInfo->m_id = std::move(id); +} + +void CAddonInfoBuilderFromDB::SetName(std::string name) +{ + m_addonInfo->m_name = std::move(name); +} + +void CAddonInfoBuilderFromDB::SetLicense(std::string license) +{ + m_addonInfo->m_license = std::move(license); +} + +void CAddonInfoBuilderFromDB::SetSummary(std::string summary) +{ + m_addonInfo->m_summary.insert(std::pair<std::string, std::string>("unk", std::move(summary))); +} + +void CAddonInfoBuilderFromDB::SetDescription(std::string description) +{ + m_addonInfo->m_description.insert( + std::pair<std::string, std::string>("unk", std::move(description))); +} + +void CAddonInfoBuilderFromDB::SetDisclaimer(std::string disclaimer) +{ + m_addonInfo->m_disclaimer.insert( + std::pair<std::string, std::string>("unk", std::move(disclaimer))); +} + +void CAddonInfoBuilderFromDB::SetAuthor(std::string author) +{ + m_addonInfo->m_author = std::move(author); +} + +void CAddonInfoBuilderFromDB::SetSource(std::string source) +{ + m_addonInfo->m_source = std::move(source); +} + +void CAddonInfoBuilderFromDB::SetWebsite(std::string website) +{ + m_addonInfo->m_website = std::move(website); +} + +void CAddonInfoBuilderFromDB::SetForum(std::string forum) +{ + m_addonInfo->m_forum = std::move(forum); +} + +void CAddonInfoBuilderFromDB::SetEMail(std::string email) +{ + m_addonInfo->m_email = std::move(email); +} + +void CAddonInfoBuilderFromDB::SetIcon(std::string icon) +{ + m_addonInfo->m_icon = std::move(icon); +} + +void CAddonInfoBuilderFromDB::SetArt(const std::string& type, std::string value) +{ + m_addonInfo->m_art[type] = std::move(value); +} + +void CAddonInfoBuilderFromDB::SetArt(std::map<std::string, std::string> art) +{ + m_addonInfo->m_art = std::move(art); +} + +void CAddonInfoBuilderFromDB::SetScreenshots(std::vector<std::string> screenshots) +{ + m_addonInfo->m_screenshots = std::move(screenshots); +} + +void CAddonInfoBuilderFromDB::SetChangelog(std::string changelog) +{ + m_addonInfo->m_changelog.insert(std::pair<std::string, std::string>("unk", std::move(changelog))); +} + +void CAddonInfoBuilderFromDB::SetLifecycleState(AddonLifecycleState state, std::string description) +{ + m_addonInfo->m_lifecycleState = state; + m_addonInfo->m_lifecycleStateDescription.emplace("unk", std::move(description)); +} + +void CAddonInfoBuilderFromDB::SetPath(std::string path) +{ + m_addonInfo->m_path = std::move(path); +} + +void CAddonInfoBuilderFromDB::SetLibName(std::string libname) +{ + m_addonInfo->m_libname = std::move(libname); +} + +void CAddonInfoBuilderFromDB::SetVersion(CAddonVersion version) +{ + m_addonInfo->m_version = std::move(version); +} + +void CAddonInfoBuilderFromDB::SetDependencies(std::vector<DependencyInfo> dependencies) +{ + m_addonInfo->m_dependencies = std::move(dependencies); +} + +void CAddonInfoBuilderFromDB::SetExtrainfo(InfoMap extrainfo) +{ + m_addonInfo->m_extrainfo = std::move(extrainfo); +} + +void CAddonInfoBuilderFromDB::SetInstallDate(const CDateTime& installDate) +{ + m_addonInfo->m_installDate = installDate; +} + +void CAddonInfoBuilderFromDB::SetLastUpdated(const CDateTime& lastUpdated) +{ + m_addonInfo->m_lastUpdated = lastUpdated; +} + +void CAddonInfoBuilderFromDB::SetLastUsed(const CDateTime& lastUsed) +{ + m_addonInfo->m_lastUsed = lastUsed; +} + +void CAddonInfoBuilderFromDB::SetOrigin(std::string origin) +{ + m_addonInfo->m_origin = std::move(origin); +} + +void CAddonInfoBuilderFromDB::SetPackageSize(uint64_t size) +{ + m_addonInfo->m_packageSize = size; +} + +void CAddonInfoBuilderFromDB::SetExtensions(CAddonType addonType) +{ + if (!addonType.GetValue("provides").empty()) + addonType.SetProvides(addonType.GetValue("provides").asString()); + + m_addonInfo->m_types.push_back(std::move(addonType)); + m_addonInfo->m_mainType = addonType.m_type; +} + +AddonInfoPtr CAddonInfoBuilder::Generate(const std::string& id, AddonType type) +{ + // Check addon identifier for forbidden characters + // The identifier is used e.g. in URLs so we shouldn't allow just + // any character to go through. + if (id.empty() || id.find_first_not_of(VALID_ADDON_IDENTIFIER_CHARACTERS) != std::string::npos) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: identifier '{}' is invalid", __FUNCTION__, id); + return nullptr; + } + + AddonInfoPtr addon = std::make_shared<CAddonInfo>(); + addon->m_id = id; + addon->m_mainType = type; + return addon; +} + +AddonInfoPtr CAddonInfoBuilder::Generate(const std::string& addonPath, bool platformCheck /*= true*/) +{ + auto addonRealPath = CSpecialProtocol::TranslatePath(addonPath); + + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(addonRealPath, "addon.xml"))) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: Unable to load '{}', Line {}\n{}", + __FUNCTION__, + URIUtils::AddFileToFolder(addonRealPath, "addon.xml"), + xmlDoc.ErrorRow(), + xmlDoc.ErrorDesc()); + return nullptr; + } + + AddonInfoPtr addon = std::make_shared<CAddonInfo>(); + if (!ParseXML(addon, xmlDoc.RootElement(), addonRealPath)) + return nullptr; + + if (!platformCheck || PlatformSupportsAddon(addon)) + return addon; + + return nullptr; +} + +AddonInfoPtr CAddonInfoBuilder::Generate(const TiXmlElement* baseElement, + const RepositoryDirInfo& repo, + bool platformCheck /*= true*/) +{ + AddonInfoPtr addon = std::make_shared<CAddonInfo>(); + if (!ParseXML(addon, baseElement, repo.datadir, repo)) + return nullptr; + + if (!platformCheck || PlatformSupportsAddon(addon)) + return addon; + + return nullptr; +} + +void CAddonInfoBuilder::SetInstallData(const AddonInfoPtr& addon, const CDateTime& installDate, const CDateTime& lastUpdated, + const CDateTime& lastUsed, const std::string& origin) +{ + if (!addon) + return; + + addon->m_installDate = installDate; + addon->m_lastUpdated = lastUpdated; + addon->m_lastUsed = lastUsed; + addon->m_origin = origin; +} + +bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, + const TiXmlElement* element, + const std::string& addonPath) +{ + return ParseXML(addon, element, addonPath, {}); +} + +bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, + const TiXmlElement* element, + const std::string& addonPath, + const RepositoryDirInfo& repo) +{ + /* + * Following values currently not set from creator: + * - CDateTime installDate; + * - CDateTime lastUpdated; + * - CDateTime lastUsed; + * - std::string origin; + */ + + if (!StringUtils::EqualsNoCase(element->Value(), "addon")) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file from '{}' doesn't contain <addon>", __FUNCTION__, addonPath); + return false; + } + + /* + * The function variable "repo" is only used when reading data stored on the internet. + * A boolean value is then set here for easier identification. + */ + const bool isRepoXMLContent = !repo.datadir.empty(); + + /* + * Parse addon.xml: + * <addon id="???" + * name="???" + * version="???" + * provider-name="???"> + */ + addon->m_id = StringUtils::CreateFromCString(element->Attribute("id")); + addon->m_name = StringUtils::CreateFromCString(element->Attribute("name")); + addon->m_author = StringUtils::CreateFromCString(element->Attribute("provider-name")); + + const std::string version = StringUtils::CreateFromCString(element->Attribute("version")); + addon->m_version = CAddonVersion(version); + + if (addon->m_id.empty() || addon->m_version.empty()) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain required values on <addon ... > id='{}', version='{}'", + __FUNCTION__, + addonPath, + addon->m_id.empty() ? "missing" : addon->m_id, + addon->m_version.empty() ? "missing" : addon->m_version.asString()); + return false; + } + + // Check addon identifier for forbidden characters + // The identifier is used e.g. in URLs so we shouldn't allow just + // any character to go through. + if (addon->m_id.find_first_not_of(VALID_ADDON_IDENTIFIER_CHARACTERS) != std::string::npos) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: identifier {} is invalid", __FUNCTION__, addon->m_id); + return false; + } + + /* + * Parse addon.xml: + * <backwards-compatibility abi="???"/> + */ + const TiXmlElement* backwards = element->FirstChildElement("backwards-compatibility"); + if (backwards) + { + const std::string minVersion = StringUtils::CreateFromCString(backwards->Attribute("abi")); + addon->m_minversion = CAddonVersion(minVersion); + } + + /* + * Parse addon.xml: + * <requires> + * <import addon="???" minversion="???" version="???" optional="???"/> + * </requires> + */ + const TiXmlElement* requires = element->FirstChildElement("requires"); + if (requires) + { + for (const TiXmlElement* child = requires->FirstChildElement("import"); child != nullptr; child = child->NextSiblingElement("import")) + { + if (child->Attribute("addon")) + { + const std::string minVersion = + StringUtils::CreateFromCString(child->Attribute("minversion")); + const std::string version = StringUtils::CreateFromCString(child->Attribute("version")); + + bool optional = false; + child->QueryBoolAttribute("optional", &optional); + + addon->m_dependencies.emplace_back(child->Attribute("addon"), CAddonVersion(minVersion), + CAddonVersion(version), optional); + } + } + } + + std::string assetBasePath; + if (!isRepoXMLContent && !addonPath.empty()) + { + // Default for add-on information not loaded from repository + assetBasePath = addonPath; + addon->m_path = addonPath; + } + else + { + assetBasePath = URIUtils::AddFileToFolder(repo.artdir, addon->m_id); + addon->m_path = URIUtils::AddFileToFolder(repo.datadir, addon->m_id, StringUtils::Format("{}-{}.zip", addon->m_id, addon->m_version.asString())); + } + + addon->m_profilePath = StringUtils::Format("special://profile/addon_data/{}/", addon->m_id); + + /* + * Parse addon.xml: + * <extension> + * ... + * </extension> + */ + for (const TiXmlElement* child = element->FirstChildElement("extension"); child != nullptr; child = child->NextSiblingElement("extension")) + { + const std::string point = StringUtils::CreateFromCString(child->Attribute("point")); + + if (point == "kodi.addon.metadata" || point == "xbmc.addon.metadata") + { + /* + * Parse addon.xml "<path">...</path>" (special related to repository path), + * do first and if present override the default. Also set assetBasePath to + * find screenshots and icons. + */ + element = child->FirstChildElement("path"); + if (element && element->GetText() != nullptr && !repo.datadir.empty()) + { + addon->m_path = URIUtils::AddFileToFolder(repo.datadir, element->GetText()); + assetBasePath = URIUtils::GetDirectory(URIUtils::AddFileToFolder(repo.artdir, element->GetText())); + } + + /* + * Parse addon.xml "<summary lang="..">...</summary>" + */ + GetTextList(child, "summary", addon->m_summary); + + /* + * Parse addon.xml "<description lang="..">...</description>" + */ + GetTextList(child, "description", addon->m_description); + + /* + * Parse addon.xml "<disclaimer lang="..">...</disclaimer>" + */ + GetTextList(child, "disclaimer", addon->m_disclaimer); + + /* + * Parse addon.xml "<assets>...</assets>" + */ + const TiXmlElement* element = child->FirstChildElement("assets"); + if (element) + { + for (const TiXmlElement* elementsAssets = element->FirstChildElement(); elementsAssets != nullptr; elementsAssets = elementsAssets->NextSiblingElement()) + { + std::string value = elementsAssets->Value(); + if (value == "icon") + { + if (elementsAssets->GetText() != nullptr) + addon->m_icon = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); + } + else if (value == "screenshot") + { + if (elementsAssets->GetText() != nullptr) + addon->m_screenshots.emplace_back(URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText())); + } + else if (value == "fanart") + { + if (elementsAssets->GetText() != nullptr) + addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); + } + else if (value == "banner") + { + if (elementsAssets->GetText() != nullptr) + addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); + } + else if (value == "clearlogo") + { + if (elementsAssets->GetText() != nullptr) + addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); + } + else if (value == "thumb") + { + if (elementsAssets->GetText() != nullptr) + addon->m_art[value] = + URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); + } + } + } + + /* Parse addon.xml "<platform">...</platform>" */ + element = child->FirstChildElement("platform"); + if (element && element->GetText() != nullptr) + { + auto platforms = StringUtils::Split(element->GetText(), + {" ", "\t", "\n", "\r"}); + platforms.erase(std::remove_if(platforms.begin(), platforms.end(), + [](const std::string& platform) { return platform.empty(); }), + platforms.cend()); + addon->m_platforms = platforms; + } + + /* Parse addon.xml "<license">...</license>" */ + element = child->FirstChildElement("license"); + if (element && element->GetText() != nullptr) + addon->m_license = element->GetText(); + + /* Parse addon.xml "<source">...</source>" */ + element = child->FirstChildElement("source"); + if (element && element->GetText() != nullptr) + addon->m_source = element->GetText(); + + /* Parse addon.xml "<email">...</email>" */ + element = child->FirstChildElement("email"); + if (element && element->GetText() != nullptr) + addon->m_email = element->GetText(); + + /* Parse addon.xml "<website">...</website>" */ + element = child->FirstChildElement("website"); + if (element && element->GetText() != nullptr) + addon->m_website = element->GetText(); + + /* Parse addon.xml "<forum">...</forum>" */ + element = child->FirstChildElement("forum"); + if (element && element->GetText() != nullptr) + addon->m_forum = element->GetText(); + + /* Parse addon.xml "<broken">...</broken>" + * NOTE: Replaced with <lifecyclestate>, available for backward compatibility */ + element = child->FirstChildElement("broken"); + if (element && element->GetText() != nullptr) + { + addon->m_lifecycleState = AddonLifecycleState::BROKEN; + addon->m_lifecycleStateDescription.emplace(KODI_ADDON_DEFAULT_LANGUAGE_CODE, + element->GetText()); + } + + /* Parse addon.xml "<lifecyclestate">...</lifecyclestate>" */ + element = child->FirstChildElement("lifecyclestate"); + if (element && element->GetText() != nullptr) + { + const char* lang = element->Attribute("type"); + if (lang) + { + if (strcmp(lang, "broken") == 0) + addon->m_lifecycleState = AddonLifecycleState::BROKEN; + else if (strcmp(lang, "deprecated") == 0) + addon->m_lifecycleState = AddonLifecycleState::DEPRECATED; + else + addon->m_lifecycleState = AddonLifecycleState::NORMAL; + + GetTextList(child, "lifecyclestate", addon->m_lifecycleStateDescription); + } + } + + /* Parse addon.xml "<language">...</language>" */ + element = child->FirstChildElement("language"); + if (element && element->GetText() != nullptr) + addon->AddExtraInfo("language", element->GetText()); + + /* Parse addon.xml "<reuselanguageinvoker">...</reuselanguageinvoker>" */ + element = child->FirstChildElement("reuselanguageinvoker"); + if (element && element->GetText() != nullptr) + addon->AddExtraInfo("reuselanguageinvoker", element->GetText()); + + /* Parse addon.xml "<size">...</size>" */ + element = child->FirstChildElement("size"); + if (element && element->GetText() != nullptr) + addon->m_packageSize = StringUtils::ToUint64(element->GetText(), 0); + + /* Parse addon.xml "<news lang="..">...</news>" + * + * In the event that the changelog (news) in addon.xml is empty, check + * whether it is an installed addon and read a changelog.txt as a + * replacement, if available. */ + GetTextList(child, "news", addon->m_changelog); + if (addon->m_changelog.empty() && !isRepoXMLContent && !addonPath.empty()) + { + using XFILE::CFile; + + const std::string changelog = URIUtils::AddFileToFolder(addonPath, "changelog.txt"); + if (CFile::Exists(changelog)) + { + CFile file; + std::vector<uint8_t> buf; + if (file.LoadFile(changelog, buf) > 0) + addon->m_changelog[KODI_ADDON_DEFAULT_LANGUAGE_CODE].assign( + reinterpret_cast<char*>(buf.data()), buf.size()); + } + } + } + else + { + AddonType type = CAddonInfo::TranslateType(point); + if (type == AddonType::UNKNOWN || type >= AddonType::MAX_TYPES) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain a valid add-on type name ({})", __FUNCTION__, addon->m_path, point); + return false; + } + + CAddonType addonType(type); + if (ParseXMLTypes(addonType, addon, child)) + addon->m_types.emplace_back(std::move(addonType)); + } + } + + /* + * If nothing is defined in addon.xml set addon as unknown to have minimum one + * instance type present. + */ + if (addon->m_types.empty()) + { + CAddonType addonType(AddonType::UNKNOWN); + addon->m_types.emplace_back(std::move(addonType)); + } + + addon->m_mainType = addon->m_types[0].Type(); + addon->m_libname = addon->m_types[0].m_libname; + if (!addon->m_types[0].GetValue("provides").empty()) + addon->AddExtraInfo("provides", addon->m_types[0].GetValue("provides").asString()); + + // Ensure binary types have a valid library for the platform + if (addon->m_mainType == AddonType::VISUALIZATION || + addon->m_mainType == AddonType::SCREENSAVER || addon->m_mainType == AddonType::PVRDLL || + addon->m_mainType == AddonType::AUDIOENCODER || + addon->m_mainType == AddonType::AUDIODECODER || addon->m_mainType == AddonType::VFS || + addon->m_mainType == AddonType::IMAGEDECODER || addon->m_mainType == AddonType::INPUTSTREAM || + addon->m_mainType == AddonType::PERIPHERALDLL || addon->m_mainType == AddonType::GAMEDLL) + { + if (addon->m_libname.empty()) + { + // Prevent log file entry if data is from repository, there normal on + // addons for other OS's + if (!isRepoXMLContent) + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: addon.xml from '{}' for binary type '{}' doesn't contain library and addon becomes ignored", + __FUNCTION__, addon->ID(), CAddonInfo::TranslateType(addon->m_mainType)); + return false; + } + } + + if (!isRepoXMLContent) + { + using XFILE::CFile; + if (CFile::Exists(URIUtils::AddFileToFolder(addonPath, "resources", "settings.xml"))) + addon->m_supportsAddonSettings = true; + if (CFile::Exists(URIUtils::AddFileToFolder(addonPath, "resources", "instance-settings.xml"))) + addon->m_supportsInstanceSettings = true; + } + + addon->m_addonInstanceSupportType = CAddonInfo::InstanceSupportType(addon->m_mainType); + + return true; +} + +bool CAddonInfoBuilder::ParseXMLTypes(CAddonType& addonType, + const AddonInfoPtr& info, + const TiXmlElement* child) +{ + if (child) + { + addonType.m_path = info->Path(); + + // Get add-on library file name (if present) + const char* library = child->Attribute("library"); + if (library == nullptr) + library = GetPlatformLibraryName(child); + if (library != nullptr) + { + addonType.m_libname = library; + + try + { + // linux is different and has the version number after the suffix + static const std::regex libRegex("^.*" + + CCompileInfo::CCompileInfo::GetSharedLibrarySuffix() + + "\\.?[0-9]*\\.?[0-9]*\\.?[0-9]*$"); + if (std::regex_match(library, libRegex)) + { + info->SetBinary(true); + CLog::Log(LOGDEBUG, "CAddonInfoBuilder::{}: Binary addon found: {}", __func__, + info->ID()); + } + } + catch (const std::regex_error& e) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: Regex error caught: {}", __func__, + e.what()); + } + } + + if (!ParseXMLExtension(addonType, child)) + { + CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: addon.xml file doesn't contain a valid add-on extensions ({})", __FUNCTION__, info->ID()); + return false; + } + if (!addonType.GetValue("provides").empty()) + addonType.SetProvides(addonType.GetValue("provides").asString()); + return true; + } + return false; +} + +bool CAddonInfoBuilder::ParseXMLExtension(CAddonExtensions& addonExt, const TiXmlElement* element) +{ + addonExt.m_point = StringUtils::CreateFromCString(element->Attribute("point")); + + EXT_VALUE extension; + const TiXmlAttribute* attribute = element->FirstAttribute(); + while (attribute) + { + std::string name = attribute->Name(); + if (name != "point") + { + const std::string value = StringUtils::CreateFromCString(attribute->Value()); + if (!value.empty()) + { + name = "@" + name; + extension.emplace_back(std::make_pair(name, SExtValue(value))); + } + } + attribute = attribute->Next(); + } + if (!extension.empty()) + addonExt.m_values.emplace_back(std::pair<std::string, EXT_VALUE>("", std::move(extension))); + + const TiXmlElement* childElement = element->FirstChildElement(); + while (childElement) + { + const std::string id = StringUtils::CreateFromCString(childElement->Value()); + if (!id.empty()) + { + EXT_VALUE extension; + const TiXmlAttribute* attribute = childElement->FirstAttribute(); + while (attribute) + { + std::string name = attribute->Name(); + if (name != "point") + { + const std::string value = StringUtils::CreateFromCString(attribute->Value()); + if (!value.empty()) + { + name = id + "@" + name; + extension.emplace_back(std::make_pair(name, SExtValue(value))); + } + } + attribute = attribute->Next(); + } + + const std::string childElementText = StringUtils::CreateFromCString(childElement->GetText()); + + if (!childElementText.empty()) + { + extension.emplace_back(std::make_pair(id, SExtValue(childElementText))); + } + + if (!extension.empty()) + addonExt.m_values.emplace_back(std::make_pair(id, std::move(extension))); + + if (childElementText.empty()) + { + const TiXmlElement* childSubElement = childElement->FirstChildElement(); + if (childSubElement) + { + CAddonExtensions subElement; + if (ParseXMLExtension(subElement, childElement)) + addonExt.m_children.emplace_back(std::make_pair(id, std::move(subElement))); + } + } + } + childElement = childElement->NextSiblingElement(); + } + + return true; +} + +bool CAddonInfoBuilder::GetTextList(const TiXmlElement* element, const std::string& tag, std::unordered_map<std::string, std::string>& translatedValues) +{ + if (!element) + return false; + + translatedValues.clear(); + + for (const TiXmlElement* child = element->FirstChildElement(tag); child != nullptr; child = child->NextSiblingElement(tag)) + { + const char* lang = child->Attribute("lang"); + const char* text = child->GetText(); + if (lang != nullptr) + { + if (strcmp(lang, "no") == 0) + translatedValues.insert(std::make_pair("nb_NO", text != nullptr ? text : "")); + else + translatedValues.insert(std::make_pair(lang, text != nullptr ? text : "")); + } + else + translatedValues.insert( + std::make_pair(KODI_ADDON_DEFAULT_LANGUAGE_CODE, text != nullptr ? text : "")); + } + + return !translatedValues.empty(); +} + +const char* CAddonInfoBuilder::GetPlatformLibraryName(const TiXmlElement* element) +{ + const char* libraryName; +#if defined(TARGET_ANDROID) + libraryName = element->Attribute("library_android"); +#elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD) +#if defined(TARGET_FREEBSD) + libraryName = element->Attribute("library_freebsd"); + if (libraryName == nullptr) +#endif + libraryName = element->Attribute("library_linux"); +#elif defined(TARGET_WINDOWS_DESKTOP) + libraryName = element->Attribute("library_windx"); + if (libraryName == nullptr) + libraryName = element->Attribute("library_windows"); +#elif defined(TARGET_WINDOWS_STORE) + libraryName = element->Attribute("library_windowsstore"); +#elif defined(TARGET_DARWIN) +#if defined(TARGET_DARWIN_EMBEDDED) + libraryName = element->Attribute("library_darwin_embedded"); +#else + libraryName = element->Attribute("library_osx"); +#endif +#endif + + return libraryName; +} + +bool CAddonInfoBuilder::PlatformSupportsAddon(const AddonInfoPtr& addon) +{ + if (addon->m_platforms.empty()) + return true; + + std::vector<std::string> supportedPlatforms = { + "all", +#if defined(TARGET_ANDROID) + "android", +#if defined(__ARM_ARCH_7A__) + "android-armv7", +#elif defined(__aarch64__) + "android-aarch64", +#elif defined(__i686__) + "android-i686", +#elif defined(__x86_64__) + "android-x86_64", +#else + #warning no architecture dependant platform tag +#endif +#elif defined(TARGET_FREEBSD) + "freebsd", +#elif defined(TARGET_LINUX) + "linux", +#if defined(__ARM_ARCH_7A__) + "linux-armv7", +#elif defined(__aarch64__) + "linux-aarch64", +#elif defined(__i686__) + "linux-i686", +#elif defined(__x86_64__) + "linux-x86_64", +#else + #warning no architecture dependant platform tag +#endif +#elif defined(TARGET_WINDOWS_DESKTOP) + "windx", + "windows", +#if defined(_M_IX86) + "windows-i686", +#elif defined(_M_AMD64) + "windows-x86_64", +#else +#error no architecture dependant platform tag +#endif +#elif defined(TARGET_WINDOWS_STORE) + "windowsstore", +#elif defined(TARGET_DARWIN_EMBEDDED) + "darwin_embedded", +#if defined(TARGET_DARWIN_IOS) + "ios", +#if defined(__aarch64__) + "ios-aarch64", +#else +#warning no architecture dependant platform tag +#endif +#elif defined(TARGET_DARWIN_TVOS) + "tvos", + "tvos-aarch64", +#endif +#elif defined(TARGET_DARWIN_OSX) + "osx", +#if defined(__x86_64__) + "osx64", + "osx-x86_64", +#elif defined(__aarch64__) + "osxarm64", + "osx-arm64", +#else +#warning no architecture dependant platform tag +#endif +#endif + }; + + return std::find_first_of(addon->m_platforms.begin(), addon->m_platforms.end(), + supportedPlatforms.begin(), supportedPlatforms.end()) != addon->m_platforms.end(); +} + +} |