diff options
Diffstat (limited to 'xbmc/addons/addoninfo')
-rw-r--r-- | xbmc/addons/addoninfo/AddonExtensions.cpp | 68 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonExtensions.h | 76 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonInfo.cpp | 312 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonInfo.h | 309 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonInfoBuilder.cpp | 874 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonInfoBuilder.h | 109 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonType.cpp | 59 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/AddonType.h | 125 | ||||
-rw-r--r-- | xbmc/addons/addoninfo/CMakeLists.txt | 11 |
9 files changed, 1943 insertions, 0 deletions
diff --git a/xbmc/addons/addoninfo/AddonExtensions.cpp b/xbmc/addons/addoninfo/AddonExtensions.cpp new file mode 100644 index 0000000..b0bc3c5 --- /dev/null +++ b/xbmc/addons/addoninfo/AddonExtensions.cpp @@ -0,0 +1,68 @@ +/* + * 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 "AddonExtensions.h" + +#include "utils/StringUtils.h" + +using namespace ADDON; + +bool SExtValue::asBoolean() const +{ + return StringUtils::EqualsNoCase(str, "true"); +} + +const SExtValue CAddonExtensions::GetValue(const std::string& id) const +{ + for (const auto& values : m_values) + { + for (const auto& value : values.second) + { + if (value.first == id) + return value.second; + } + } + return SExtValue(""); +} + +const EXT_VALUES& CAddonExtensions::GetValues() const +{ + return m_values; +} + +const CAddonExtensions* CAddonExtensions::GetElement(const std::string& id) const +{ + for (const auto& child : m_children) + { + if (child.first == id) + return &child.second; + } + + return nullptr; +} + +const EXT_ELEMENTS CAddonExtensions::GetElements(const std::string& id) const +{ + if (id.empty()) + return m_children; + + EXT_ELEMENTS children; + for (const auto& child : m_children) + { + if (child.first == id) + children.push_back(std::make_pair(child.first, child.second)); + } + return children; +} + +void CAddonExtensions::Insert(const std::string& id, const std::string& value) +{ + EXT_VALUE extension; + extension.push_back(std::make_pair(id, SExtValue(value))); + m_values.push_back(std::make_pair(id, extension)); +} diff --git a/xbmc/addons/addoninfo/AddonExtensions.h b/xbmc/addons/addoninfo/AddonExtensions.h new file mode 100644 index 0000000..2ea2011 --- /dev/null +++ b/xbmc/addons/addoninfo/AddonExtensions.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#pragma once + +#include <stdlib.h> +#include <string> +#include <vector> + +namespace ADDON +{ + +class CAddonInfoBuilder; +class CAddonDatabaseSerializer; + +struct SExtValue +{ + explicit SExtValue(const std::string& strValue) : str(strValue) { } + const std::string& asString() const { return str; } + bool asBoolean() const; + int asInteger() const { return std::atoi(str.c_str()); } + float asFloat() const { return static_cast<float>(std::atof(str.c_str())); } + bool empty() const { return str.empty(); } + const std::string str; +}; + +class CExtValues; +class CAddonExtensions; +typedef std::vector<std::pair<std::string, CAddonExtensions>> EXT_ELEMENTS; +typedef std::vector<std::pair<std::string, SExtValue>> EXT_VALUE; +typedef std::vector<std::pair<std::string, CExtValues>> EXT_VALUES; + +class CExtValues : public EXT_VALUE +{ +public: + CExtValues(const EXT_VALUE& values) : EXT_VALUE(values) { } + + const SExtValue GetValue(const std::string& id) const + { + for (const auto& value : *this) + { + if (value.first == id) + return value.second; + } + return SExtValue(""); + } +}; + +class CAddonExtensions +{ +public: + CAddonExtensions() = default; + ~CAddonExtensions() = default; + + const SExtValue GetValue(const std::string& id) const; + const EXT_VALUES& GetValues() const; + const CAddonExtensions* GetElement(const std::string& id) const; + const EXT_ELEMENTS GetElements(const std::string& id = "") const; + + void Insert(const std::string& id, const std::string& value); + +private: + friend class CAddonInfoBuilder; + friend class CAddonDatabaseSerializer; + + std::string m_point; + EXT_VALUES m_values; + EXT_ELEMENTS m_children; +}; + +} /* namespace ADDON */ diff --git a/xbmc/addons/addoninfo/AddonInfo.cpp b/xbmc/addons/addoninfo/AddonInfo.cpp new file mode 100644 index 0000000..61bc087 --- /dev/null +++ b/xbmc/addons/addoninfo/AddonInfo.cpp @@ -0,0 +1,312 @@ +/* + * 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 "AddonInfo.h" + +#include "FileItem.h" +#include "LangInfo.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonType.h" +#include "filesystem/Directory.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#include <algorithm> +#include <array> +#include <string_view> + +namespace ADDON +{ + +typedef struct +{ + std::string_view name; + std::string_view old_name; + AddonType type; + int pretty; + AddonInstanceSupport instance_support; + std::string_view icon; +} TypeMapping; + +// clang-format off +static constexpr const std::array<TypeMapping, 40> types = + {{ + {"unknown", "", AddonType::UNKNOWN, 0, AddonInstanceSupport::SUPPORT_NONE, "" }, + {"xbmc.metadata.scraper.albums", "", AddonType::SCRAPER_ALBUMS, 24016, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonAlbumInfo.png" }, + {"xbmc.metadata.scraper.artists", "", AddonType::SCRAPER_ARTISTS, 24017, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonArtistInfo.png" }, + {"xbmc.metadata.scraper.movies", "", AddonType::SCRAPER_MOVIES, 24007, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonMovieInfo.png" }, + {"xbmc.metadata.scraper.musicvideos", "", AddonType::SCRAPER_MUSICVIDEOS, 24015, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonMusicVideoInfo.png" }, + {"xbmc.metadata.scraper.tvshows", "", AddonType::SCRAPER_TVSHOWS, 24014, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonTvInfo.png" }, + {"xbmc.metadata.scraper.library", "", AddonType::SCRAPER_LIBRARY, 24083, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonInfoLibrary.png" }, + {"xbmc.ui.screensaver", "", AddonType::SCREENSAVER, 24008, AddonInstanceSupport::SUPPORT_OPTIONAL, "DefaultAddonScreensaver.png" }, + {"xbmc.player.musicviz", "", AddonType::VISUALIZATION, 24010, AddonInstanceSupport::SUPPORT_OPTIONAL, "DefaultAddonVisualization.png" }, + {"xbmc.python.pluginsource", "", AddonType::PLUGIN, 24005, AddonInstanceSupport::SUPPORT_NONE, "" }, + {"xbmc.python.script", "", AddonType::SCRIPT, 24009, AddonInstanceSupport::SUPPORT_NONE, "" }, + {"xbmc.python.weather", "", AddonType::SCRIPT_WEATHER, 24027, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonWeather.png" }, + {"xbmc.python.lyrics", "", AddonType::SCRIPT_LYRICS, 24013, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonLyrics.png" }, + {"xbmc.python.library", "", AddonType::SCRIPT_LIBRARY, 24081, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonHelper.png" }, + {"xbmc.python.module", "", AddonType::SCRIPT_MODULE, 24082, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonLibrary.png" }, + {"xbmc.subtitle.module", "", AddonType::SUBTITLE_MODULE, 24012, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonSubtitles.png" }, + {"kodi.context.item", "", AddonType::CONTEXTMENU_ITEM, 24025, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonContextItem.png" }, + {"kodi.game.controller", "", AddonType::GAME_CONTROLLER, 35050, AddonInstanceSupport::SUPPORT_OPTIONAL, "DefaultAddonGame.png" }, + {"xbmc.gui.skin", "", AddonType::SKIN, 166, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonSkin.png" }, + {"xbmc.webinterface", "", AddonType::WEB_INTERFACE, 199, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonWebSkin.png" }, + {"xbmc.addon.repository", "", AddonType::REPOSITORY, 24011, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonRepository.png" }, + {"kodi.pvrclient", "xbmc.pvrclient", AddonType::PVRDLL, 24019, AddonInstanceSupport::SUPPORT_SETTINGS, "DefaultAddonPVRClient.png" }, + {"kodi.gameclient", "", AddonType::GAMEDLL, 35049, AddonInstanceSupport::SUPPORT_OPTIONAL, "DefaultAddonGame.png" }, + {"kodi.peripheral", "", AddonType::PERIPHERALDLL, 35010, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonPeripheral.png" }, + {"xbmc.addon.video", "", AddonType::VIDEO, 1037, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonVideo.png" }, + {"xbmc.addon.audio", "", AddonType::AUDIO, 1038, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonMusic.png" }, + {"xbmc.addon.image", "", AddonType::IMAGE, 1039, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonPicture.png" }, + {"xbmc.addon.executable", "", AddonType::EXECUTABLE, 1043, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonProgram.png" }, + {"kodi.addon.game", "", AddonType::GAME, 35049, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonGame.png" }, + {"kodi.audioencoder", "", AddonType::AUDIOENCODER, 200, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonAudioEncoder.png" }, + {"kodi.audiodecoder", "", AddonType::AUDIODECODER, 201, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonAudioDecoder.png" }, + {"xbmc.service", "", AddonType::SERVICE, 24018, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonService.png" }, + {"kodi.resource.images", "", AddonType::RESOURCE_IMAGES, 24035, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonImages.png" }, + {"kodi.resource.language", "", AddonType::RESOURCE_LANGUAGE, 24026, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonLanguage.png" }, + {"kodi.resource.uisounds", "", AddonType::RESOURCE_UISOUNDS, 24006, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonUISounds.png" }, + {"kodi.resource.games", "", AddonType::RESOURCE_GAMES, 35209, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonGame.png" }, + {"kodi.resource.font", "", AddonType::RESOURCE_FONT, 13303, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonFont.png" }, + {"kodi.inputstream", "", AddonType::INPUTSTREAM, 24048, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonInputstream.png" }, + {"kodi.vfs", "", AddonType::VFS, 39013, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonVfs.png" }, + {"kodi.imagedecoder", "", AddonType::IMAGEDECODER, 39015, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonImageDecoder.png" }, + }}; +// clang-format on + +const std::string& CAddonInfo::OriginName() const +{ + if (!m_originName) + { + ADDON::AddonPtr origin; + if (CServiceBroker::GetAddonMgr().GetAddon(m_origin, origin, ADDON::OnlyEnabled::CHOICE_NO)) + m_originName = std::make_unique<std::string>(origin->Name()); + else + m_originName = std::make_unique<std::string>(); // remember we tried to fetch the name + } + return *m_originName; +} + +/** + * static public helper functions + * + */ + +std::string CAddonInfo::TranslateType(AddonType type, bool pretty /*= false*/) +{ + for (const TypeMapping& map : types) + { + if (type == map.type) + { + if (pretty && map.pretty) + return g_localizeStrings.Get(map.pretty); + else + return std::string(map.name.data(), map.name.size()); + } + } + return ""; +} + +AddonType CAddonInfo::TranslateType(const std::string& string) +{ + for (const TypeMapping& map : types) + { + if (string == map.name || (!map.old_name.empty() && string == map.old_name)) + return map.type; + } + + return AddonType::UNKNOWN; +} + +std::string CAddonInfo::TranslateIconType(AddonType type) +{ + for (const TypeMapping& map : types) + { + if (type == map.type) + return std::string(map.icon.data(), map.icon.size()); + } + return ""; +} + +AddonType CAddonInfo::TranslateSubContent(const std::string& content) +{ + if (content == "audio") + return AddonType::AUDIO; + else if (content == "image") + return AddonType::IMAGE; + else if (content == "executable") + return AddonType::EXECUTABLE; + else if (content == "video") + return AddonType::VIDEO; + else if (content == "game") + return AddonType::GAME; + else + return AddonType::UNKNOWN; +} + +AddonInstanceSupport CAddonInfo::InstanceSupportType(AddonType type) +{ + const auto it = std::find_if(types.begin(), types.end(), + [type](const TypeMapping& entry) { return entry.type == type; }); + if (it != types.end()) + return it->instance_support; + + return AddonInstanceSupport::SUPPORT_NONE; +} + +CAddonInfo::CAddonInfo(std::string id, AddonType type) : m_id(std::move(id)), m_mainType(type) +{ + +} + +const CAddonType* CAddonInfo::Type(AddonType type) const +{ + static CAddonType dummy; + + if (!m_types.empty()) + { + if (type == AddonType::UNKNOWN) + return &m_types[0]; + + for (auto& addonType : m_types) + { + if (addonType.Type() == type) + return &addonType; + } + } + + return &dummy; +} + +bool CAddonInfo::HasType(AddonType type, bool mainOnly /*= false*/) const +{ + return (m_mainType == type || + ProvidesSubContent(type, mainOnly ? m_mainType : AddonType::UNKNOWN)); +} + +bool CAddonInfo::ProvidesSubContent(AddonType content, AddonType mainType) const +{ + if (content == AddonType::UNKNOWN) + return false; + + for (const auto& addonType : m_types) + { + if ((mainType == AddonType::UNKNOWN || addonType.Type() == mainType) && + addonType.ProvidesSubContent(content)) + return true; + } + + return false; +} + +bool CAddonInfo::ProvidesSeveralSubContents() const +{ + int contents = 0; + for (const auto& addonType : m_types) + contents += addonType.ProvidedSubContents(); + return contents > 0 ? true : false; +} + +bool CAddonInfo::MeetsVersion(const CAddonVersion& versionMin, const CAddonVersion& version) const +{ + return !(versionMin > m_version || version < m_minversion); +} + +const CAddonVersion& CAddonInfo::DependencyMinVersion(const std::string& dependencyID) const +{ + auto it = std::find_if(m_dependencies.begin(), m_dependencies.end(), + [&](const DependencyInfo& other) { return other.id == dependencyID; }); + + if (it != m_dependencies.end()) + return it->versionMin; + + static CAddonVersion emptyVersion; + return emptyVersion; +} + +const CAddonVersion& CAddonInfo::DependencyVersion(const std::string& dependencyID) const +{ + auto it = std::find_if(m_dependencies.begin(), m_dependencies.end(), [&](const DependencyInfo& other) { return other.id == dependencyID; }); + + if (it != m_dependencies.end()) + return it->version; + + static CAddonVersion emptyVersion; + return emptyVersion; +} + +const std::string& CAddonInfo::GetTranslatedText(const std::unordered_map<std::string, std::string>& locales) const +{ + if (locales.size() == 1) + return locales.begin()->second; + else if (locales.empty()) + return StringUtils::Empty; + + // find the language from the list that matches the current locale best + std::string matchingLanguage = g_langInfo.GetLocale().FindBestMatch(locales); + if (matchingLanguage.empty()) + matchingLanguage = KODI_ADDON_DEFAULT_LANGUAGE_CODE; + + auto const& translatedValue = locales.find(matchingLanguage); + if (translatedValue != locales.end()) + return translatedValue->second; + return StringUtils::Empty; +} + +bool CAddonInfo::SupportsMultipleInstances() const +{ + switch (m_addonInstanceSupportType) + { + case AddonInstanceSupport::SUPPORT_MANDATORY: + case AddonInstanceSupport::SUPPORT_OPTIONAL: + return true; + case AddonInstanceSupport::SUPPORT_SETTINGS: + return m_supportsInstanceSettings; + case AddonInstanceSupport::SUPPORT_NONE: + default: + return false; + } +} + +std::vector<AddonInstanceId> CAddonInfo::GetKnownInstanceIds() const +{ + static const std::vector<AddonInstanceId> singletonInstance = {ADDON_SINGLETON_INSTANCE_ID}; + + if (!m_supportsInstanceSettings) + return singletonInstance; + + const std::string searchPath = StringUtils::Format("special://profile/addon_data/{}/", m_id); + CFileItemList items; + XFILE::CDirectory::GetDirectory(searchPath, items, ".xml", XFILE::DIR_FLAG_NO_FILE_DIRS); + + std::vector<AddonInstanceId> ret; + + for (const auto& item : items) + { + const std::string startName = "instance-settings-"; + std::string filename = URIUtils::GetFileName(item->GetPath()); + if (StringUtils::StartsWithNoCase(URIUtils::GetFileName(item->GetPath()), startName)) + { + URIUtils::RemoveExtension(filename); + const std::string uid = filename.substr(startName.length()); + if (!uid.empty() && StringUtils::IsInteger(uid)) + ret.emplace_back(std::atoi(uid.c_str())); + } + } + + // If no instances are used, create first as default. + if (ret.empty()) + ret.emplace_back(ADDON_FIRST_INSTANCE_ID); + + return ret; +} + +} /* namespace ADDON */ diff --git a/xbmc/addons/addoninfo/AddonInfo.h b/xbmc/addons/addoninfo/AddonInfo.h new file mode 100644 index 0000000..5998e2d --- /dev/null +++ b/xbmc/addons/addoninfo/AddonInfo.h @@ -0,0 +1,309 @@ +/* + * 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. + */ + +#pragma once + +#include "XBDateTime.h" +#include "addons/AddonVersion.h" + +#include <map> +#include <memory> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +namespace ADDON +{ + +enum class AddonType; + +class CAddonBuilder; +class CAddonInfo; +class CAddonType; +typedef std::shared_ptr<CAddonInfo> AddonInfoPtr; +typedef std::vector<AddonInfoPtr> AddonInfos; + +using AddonInstanceId = uint32_t; + +/*! + * Defines the default language code used as fallback in case the requested language is not + * available. Used, for instance, to handle content from addon.xml. + */ +constexpr const char* KODI_ADDON_DEFAULT_LANGUAGE_CODE = "en_GB"; + +enum class AddonDisabledReason +{ + /// @brief Special reason for returning all disabled addons. + /// + /// Only used as an actual value when an addon is enabled. + NONE = 0, + USER = 1, + INCOMPATIBLE = 2, + PERMANENT_FAILURE = 3 +}; + +enum class AddonOriginType +{ + /// @brief The type of the origin of an addon. + /// + /// Represents where an addon was installed from. + SYSTEM = 0, /// The addon is a system addon + REPOSITORY = 1, /// The addon origin is a repository + MANUAL = 2 /// The addon origin is a zip file, package or development build +}; + +//! @brief Reasons why an addon is not updateable +enum class AddonUpdateRule +{ + ANY = 0, //!< used internally, not to be explicitly set + USER_DISABLED_AUTO_UPDATE = 1, //!< automatic updates disabled via AddonInfo dialog + PIN_OLD_VERSION = 2, //!< user downgraded to an older version + PIN_ZIP_INSTALL = 3, //!< user installed manually from zip +}; + +/*! + * @brief Independent add-on instance support. + * + * Used to be able to find out its instance path for the respective add-on types. + */ +enum class AddonInstanceSupport +{ + //! If add-on type does not support instances. + SUPPORT_NONE = 0, + + //! If add-on type needs support for several instances individually. + SUPPORT_MANDATORY = 1, + + //! If add-on type can support several instances individually. + SUPPORT_OPTIONAL = 2, + + //! If add-on type supports multiple instances using independent settings. + SUPPORT_SETTINGS = 3, +}; + +/*! + * @brief Add-on state defined within addon.xml to report about the current addon + * lifecycle state. + * + * E.g. the add-on is broken and can no longer be used. + * + * XML examples: + * ~~~~~~~~~~~~~{.xml} + * <lifecyclestate type="broken" lang="en_GB">SOME TEXT</lifecyclestate> + * ~~~~~~~~~~~~~ + */ +enum class AddonLifecycleState +{ + NORMAL = 0, //!< Used if an add-on has no special lifecycle state which is the default state + DEPRECATED = 1, //!< the add-on should be marked as deprecated but is still usable + BROKEN = 2, //!< the add-on should marked as broken in the repository +}; + +struct DependencyInfo +{ + std::string id; + CAddonVersion versionMin, version; + bool optional; + DependencyInfo(std::string id, + const CAddonVersion& versionMin, + const CAddonVersion& version, + bool optional) + : id(std::move(id)), + versionMin(versionMin.empty() ? version : versionMin), + version(version), + optional(optional) + { + } + + bool operator==(const DependencyInfo& rhs) const + { + return id == rhs.id && versionMin == rhs.versionMin && version == rhs.version && + optional == rhs.optional; + } + + bool operator!=(const DependencyInfo& rhs) const + { + return !(rhs == *this); + } +}; + +typedef std::map<std::string, std::string> InfoMap; +typedef std::map<std::string, std::string> ArtMap; + +class CAddonInfoBuilder; + +class CAddonInfo +{ +public: + CAddonInfo() = default; + CAddonInfo(std::string id, AddonType type); + + void SetMainType(AddonType type) { m_mainType = type; } + void SetBinary(bool isBinary) { m_isBinary = isBinary; } + void SetLibName(const std::string& libname) { m_libname = libname; } + void SetPath(const std::string& path) { m_path = path; } + void AddExtraInfo(const std::string& idName, const std::string& value) { m_extrainfo[idName] = value; } + void SetLastUsed(const CDateTime& dateTime) { m_lastUsed = dateTime; } + + const std::string& ID() const { return m_id; } + + /** + * @brief To get the main type of this addon + * + * This is the first type defined in addon.xml. + * + * @return The used main type of addon + */ + AddonType MainType() const { return m_mainType; } + + /** + * @brief To check addon contains a type + * + * @param[in] type The to checked type identifier + * @param[in] mainOnly to check only in first defined main addon inside addon.xml + * @return true in case the wanted type is supported, false if not + */ + bool HasType(AddonType type, bool mainOnly = false) const; + + /** + * @brief To get all available types inside the addon + * + * To have all `<extension point="..." />` defined in addon.xml inside a list. + * + * @return List of all supported types + */ + const std::vector<CAddonType>& Types() const { return m_types; } + + /** + * @brief The get for given addon type information and extension data + * + * @param[in] type The wanted type data + * @return addon type class with @ref CAddonExtensions as information + * + * @note This function return never a "nullptr", in case the wanted type is + * not supported, becomes a dummy of @ref CAddonType given. + * + * ------------------------------------------------------------------------ + * + * **Example:** + * ~~~~~~~~~~~~~{.cpp} + * // To get <extension ... name="blablabla" /> from addon.xml + * std::string name = Type(ADDON_...)->GetValue("@name").asString(); + * ~~~~~~~~~~~~~ + * + */ + const CAddonType* Type(AddonType type) const; + + bool ProvidesSubContent(AddonType content, AddonType mainType) const; + bool ProvidesSeveralSubContents() const; + + const CAddonVersion& Version() const { return m_version; } + const CAddonVersion& MinVersion() const { return m_minversion; } + bool IsBinary() const { return m_isBinary; } + const CAddonVersion& DependencyMinVersion(const std::string& dependencyID) const; + const CAddonVersion& DependencyVersion(const std::string& dependencyID) const; + const std::string& Name() const { return m_name; } + const std::string& License() const { return m_license; } + const std::string& Summary() const { return GetTranslatedText(m_summary); } + const std::string& Description() const { return GetTranslatedText(m_description); } + const std::string& LibName() const { return m_libname; } + const std::string& Author() const { return m_author; } + const std::string& Source() const { return m_source; } + const std::string& Website() const { return m_website; } + const std::string& Forum() const { return m_forum; } + const std::string& EMail() const { return m_email; } + const std::string& Path() const { return m_path; } + const std::string& ProfilePath() const { return m_profilePath; } + const std::string& ChangeLog() const { return GetTranslatedText(m_changelog); } + const std::string& Icon() const { return m_icon; } + const ArtMap& Art() const { return m_art; } + const std::vector<std::string>& Screenshots() const { return m_screenshots; } + const std::string& Disclaimer() const { return GetTranslatedText(m_disclaimer); } + const std::vector<DependencyInfo>& GetDependencies() const { return m_dependencies; } + AddonLifecycleState LifecycleState() const { return m_lifecycleState; } + const std::string& LifecycleStateDescription() const + { + return GetTranslatedText(m_lifecycleStateDescription); + } + const std::string& Origin() const { return m_origin; } + const std::string& OriginName() const; + + const InfoMap& ExtraInfo() const { return m_extrainfo; } + + bool MeetsVersion(const CAddonVersion& versionMin, const CAddonVersion& version) const; + uint64_t PackageSize() const { return m_packageSize; } + CDateTime InstallDate() const { return m_installDate; } + CDateTime LastUpdated() const { return m_lastUpdated; } + CDateTime LastUsed() const { return m_lastUsed; } + + bool SupportsMultipleInstances() const; + AddonInstanceSupport InstanceUseType() const { return m_addonInstanceSupportType; } + + bool SupportsAddonSettings() const { return m_supportsAddonSettings; } + bool SupportsInstanceSettings() const { return m_supportsInstanceSettings; } + std::vector<AddonInstanceId> GetKnownInstanceIds() const; + + /*! + * @brief Utilities to translate add-on parts to his requested part. + */ + //@{ + static std::string TranslateType(AddonType type, bool pretty = false); + static std::string TranslateIconType(AddonType type); + static AddonType TranslateType(const std::string& string); + static AddonType TranslateSubContent(const std::string& content); + static AddonInstanceSupport InstanceSupportType(AddonType type); + //@} + +private: + friend class CAddonInfoBuilder; + friend class CAddonInfoBuilderFromDB; + + std::string m_id; + AddonType m_mainType{}; + std::vector<CAddonType> m_types; + + CAddonVersion m_version; + CAddonVersion m_minversion; + bool m_isBinary = false; + std::string m_name; + std::string m_license; + std::unordered_map<std::string, std::string> m_summary; + std::unordered_map<std::string, std::string> m_description; + std::string m_author; + std::string m_source; + std::string m_website; + std::string m_forum; + std::string m_email; + std::string m_path; + std::string m_profilePath; + std::unordered_map<std::string, std::string> m_changelog; + std::string m_icon; + ArtMap m_art; + std::vector<std::string> m_screenshots; + std::unordered_map<std::string, std::string> m_disclaimer; + std::vector<DependencyInfo> m_dependencies; + AddonLifecycleState m_lifecycleState = AddonLifecycleState::NORMAL; + std::unordered_map<std::string, std::string> m_lifecycleStateDescription; + CDateTime m_installDate; + CDateTime m_lastUpdated; + CDateTime m_lastUsed; + std::string m_origin; + mutable std::unique_ptr<std::string> m_originName; // @todo use std::optional once we use c++17 + uint64_t m_packageSize = 0; + std::string m_libname; + InfoMap m_extrainfo; + std::vector<std::string> m_platforms; + AddonInstanceSupport m_addonInstanceSupportType{AddonInstanceSupport::SUPPORT_NONE}; + bool m_supportsAddonSettings{false}; + bool m_supportsInstanceSettings{false}; + + const std::string& GetTranslatedText(const std::unordered_map<std::string, std::string>& locales) const; +}; + +} /* namespace ADDON */ 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(); +} + +} diff --git a/xbmc/addons/addoninfo/AddonInfoBuilder.h b/xbmc/addons/addoninfo/AddonInfoBuilder.h new file mode 100644 index 0000000..adbbc84 --- /dev/null +++ b/xbmc/addons/addoninfo/AddonInfoBuilder.h @@ -0,0 +1,109 @@ +/* + * 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. + */ + +#pragma once + +#include "addons/IAddon.h" + +#include <map> +#include <memory> +#include <string> +#include <unordered_map> +#include <vector> + +class CDateTime; +class TiXmlElement; + +namespace ADDON +{ +enum class AddonLifecycleState; +enum class AddonType; + +class CAddonExtensions; +class CAddonType; + +struct DependencyInfo; +struct RepositoryDirInfo; + +class CAddonInfo; +using AddonInfoPtr = std::shared_ptr<CAddonInfo>; + +class CAddonInfoBuilder +{ +public: + static AddonInfoPtr Generate(const std::string& id, AddonType type); + static AddonInfoPtr Generate(const std::string& addonPath, bool platformCheck = true); + static AddonInfoPtr Generate(const TiXmlElement* baseElement, + const RepositoryDirInfo& repo, + bool platformCheck = true); + + /*! + * @brief Parts used from CAddonDatabase + */ + //@{ + static void SetInstallData(const AddonInfoPtr& addon, const CDateTime& installDate, + const CDateTime& lastUpdated, const CDateTime& lastUsed, const std::string& origin); + //@} + +private: + static bool ParseXML(const AddonInfoPtr& addon, + const TiXmlElement* element, + const std::string& addonPath); + static bool ParseXML(const AddonInfoPtr& addon, + const TiXmlElement* element, + const std::string& addonPath, + const RepositoryDirInfo& repo); + static bool ParseXMLTypes(CAddonType& addonType, + const AddonInfoPtr& info, + const TiXmlElement* child); + static bool ParseXMLExtension(CAddonExtensions& addonExt, const TiXmlElement* element); + static bool GetTextList(const TiXmlElement* element, const std::string& tag, std::unordered_map<std::string, std::string>& translatedValues); + static const char* GetPlatformLibraryName(const TiXmlElement* element); + static bool PlatformSupportsAddon(const AddonInfoPtr& addon); +}; + +class CAddonInfoBuilderFromDB +{ +public: + CAddonInfoBuilderFromDB(); + + void SetId(std::string id); + void SetName(std::string name); + void SetLicense(std::string license); + void SetSummary(std::string summary); + void SetDescription(std::string description); + void SetDisclaimer(std::string disclaimer); + void SetAuthor(std::string author); + void SetSource(std::string source); + void SetWebsite(std::string website); + void SetForum(std::string forum); + void SetEMail(std::string email); + void SetIcon(std::string icon); + void SetArt(const std::string& type, std::string value); + void SetArt(std::map<std::string, std::string> art); + void SetScreenshots(std::vector<std::string> screenshots); + void SetChangelog(std::string changelog); + void SetLifecycleState(AddonLifecycleState state, std::string description); + void SetPath(std::string path); + void SetLibName(std::string libname); + void SetVersion(CAddonVersion version); + void SetDependencies(std::vector<DependencyInfo> dependencies); + void SetExtrainfo(InfoMap extrainfo); + void SetInstallDate(const CDateTime& installDate); + void SetLastUpdated(const CDateTime& lastUpdated); + void SetLastUsed(const CDateTime& lastUsed); + void SetOrigin(std::string origin); + void SetPackageSize(uint64_t size); + void SetExtensions(CAddonType addonType); + + const AddonInfoPtr& get() { return m_addonInfo; } + +private: + AddonInfoPtr m_addonInfo; +}; +} diff --git a/xbmc/addons/addoninfo/AddonType.cpp b/xbmc/addons/addoninfo/AddonType.cpp new file mode 100644 index 0000000..875e66f --- /dev/null +++ b/xbmc/addons/addoninfo/AddonType.cpp @@ -0,0 +1,59 @@ +/* + * 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 "AddonType.h" + +#include "addons/addoninfo/AddonInfo.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +namespace ADDON +{ +static const std::set<AddonType> dependencyTypes = { + AddonType::SCRAPER_LIBRARY, + AddonType::SCRIPT_LIBRARY, + AddonType::SCRIPT_MODULE, +}; +} /* namespace ADDON */ + +using namespace ADDON; + +std::string CAddonType::LibPath() const +{ + if (m_libname.empty()) + return ""; + return URIUtils::AddFileToFolder(m_path, m_libname); +} + +void CAddonType::SetProvides(const std::string& content) +{ + if (!content.empty()) + { + /* + * Normally the "provides" becomes added from xml scan, but for add-ons + * stored in the database (e.g. repository contents) it might not be + * available. Since this information is available in add-on metadata for the + * main type (see extrainfo) we take the function contents and insert it if + * empty. + */ + if (GetValue("provides").empty()) + Insert("provides", content); + + for (const auto& provide : StringUtils::Split(content, ' ')) + { + AddonType content = CAddonInfo::TranslateSubContent(provide); + if (content != AddonType::UNKNOWN) + m_providedSubContent.insert(content); + } + } +} + +bool CAddonType::IsDependencyType(AddonType type) +{ + return dependencyTypes.find(type) != dependencyTypes.end(); +} diff --git a/xbmc/addons/addoninfo/AddonType.h b/xbmc/addons/addoninfo/AddonType.h new file mode 100644 index 0000000..9a0ee26 --- /dev/null +++ b/xbmc/addons/addoninfo/AddonType.h @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#pragma once + +#include "addons/addoninfo/AddonExtensions.h" + +#include <set> +#include <string> + +class TiXmlElement; + +namespace ADDON +{ + +enum class AddonType +{ + UNKNOWN = 0, + VISUALIZATION, + SKIN, + PVRDLL, + INPUTSTREAM, + GAMEDLL, + PERIPHERALDLL, + SCRIPT, + SCRIPT_WEATHER, + SUBTITLE_MODULE, + SCRIPT_LYRICS, + SCRAPER_ALBUMS, + SCRAPER_ARTISTS, + SCRAPER_MOVIES, + SCRAPER_MUSICVIDEOS, + SCRAPER_TVSHOWS, + SCREENSAVER, + PLUGIN, + REPOSITORY, + WEB_INTERFACE, + SERVICE, + AUDIOENCODER, + CONTEXTMENU_ITEM, + AUDIODECODER, + RESOURCE_IMAGES, + RESOURCE_LANGUAGE, + RESOURCE_UISOUNDS, + RESOURCE_GAMES, + RESOURCE_FONT, + VFS, + IMAGEDECODER, + SCRAPER_LIBRARY, + SCRIPT_LIBRARY, + SCRIPT_MODULE, + GAME_CONTROLLER, + VIDEOCODEC, + + /** + * @brief virtual addon types + */ + //@{ + VIDEO, + AUDIO, + IMAGE, + EXECUTABLE, + GAME, + //@} + + MAX_TYPES +}; + +class CAddonInfoBuilder; +class CAddonDatabaseSerializer; + +class CAddonType : public CAddonExtensions +{ +public: + CAddonType(AddonType type = AddonType::UNKNOWN) : m_type(type) {} + + AddonType Type() const { return m_type; } + std::string LibPath() const; + const std::string& LibName() const { return m_libname; } + + bool ProvidesSubContent(const AddonType& content) const + { + return content == AddonType::UNKNOWN + ? false + : m_type == content || m_providedSubContent.count(content) > 0; + } + + bool ProvidesSeveralSubContents() const + { + return m_providedSubContent.size() > 1; + } + + size_t ProvidedSubContents() const + { + return m_providedSubContent.size(); + } + + /*! + * @brief Indicates whether a given type is a dependency type (e.g. addons which the main type is + * a script.module) + * + * @param[in] type the provided type + * @return true if type is one of the dependency types + */ + static bool IsDependencyType(AddonType type); + +private: + friend class CAddonInfoBuilder; + friend class CAddonInfoBuilderFromDB; + friend class CAddonDatabaseSerializer; + + void SetProvides(const std::string& content); + + AddonType m_type; + std::string m_path; + std::string m_libname; + std::set<AddonType> m_providedSubContent; +}; + +} /* namespace ADDON */ diff --git a/xbmc/addons/addoninfo/CMakeLists.txt b/xbmc/addons/addoninfo/CMakeLists.txt new file mode 100644 index 0000000..eb66dd3 --- /dev/null +++ b/xbmc/addons/addoninfo/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES AddonInfoBuilder.cpp + AddonExtensions.cpp + AddonInfo.cpp + AddonType.cpp) + +set(HEADERS AddonInfoBuilder.h + AddonExtensions.h + AddonInfo.h + AddonType.h) + +core_add_library(addons_addoninfo) |