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/settings | |
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 '')
-rw-r--r-- | xbmc/addons/settings/AddonSettings.cpp | 1726 | ||||
-rw-r--r-- | xbmc/addons/settings/AddonSettings.h | 203 | ||||
-rw-r--r-- | xbmc/addons/settings/CMakeLists.txt | 7 | ||||
-rw-r--r-- | xbmc/addons/settings/SettingUrlEncodedString.cpp | 44 | ||||
-rw-r--r-- | xbmc/addons/settings/SettingUrlEncodedString.h | 33 |
5 files changed, 2013 insertions, 0 deletions
diff --git a/xbmc/addons/settings/AddonSettings.cpp b/xbmc/addons/settings/AddonSettings.cpp new file mode 100644 index 0000000..83bbdd9 --- /dev/null +++ b/xbmc/addons/settings/AddonSettings.cpp @@ -0,0 +1,1726 @@ +/* + * Copyright (C) 2017-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 "AddonSettings.h" + +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "LangInfo.h" +#include "ServiceBroker.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/binary-addons/AddonInstanceHandler.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "addons/settings/SettingUrlEncodedString.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/SettingAddon.h" +#include "settings/SettingConditions.h" +#include "settings/SettingControl.h" +#include "settings/SettingDateTime.h" +#include "settings/SettingPath.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingSection.h" +#include "settings/lib/SettingsManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <mutex> +#include <vector> + +namespace +{ + +constexpr auto OldSettingValuesSeparator = "|"; + +constexpr int UnknownSettingLabelIdStart = 100000; + +bool InfoBool(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetGUI()->GetInfoManager().EvaluateBool(value, INFO::DEFAULT_CONTEXT); +} + +template<class TSetting> +SettingPtr InitializeFromOldSettingWithoutDefinition(ADDON::CAddonSettings& settings, + const std::string& settingId, + const typename TSetting::Value& defaultValue) +{ + std::shared_ptr<TSetting> setting = + std::make_shared<TSetting>(settingId, settings.GetSettingsManager()); + setting->SetLevel(SettingLevel::Internal); + setting->SetVisible(false); + setting->SetDefault(defaultValue); + + return setting; +} + +template<> +SettingPtr InitializeFromOldSettingWithoutDefinition<CSettingString>( + ADDON::CAddonSettings& settings, + const std::string& settingId, + const typename CSettingString::Value& defaultValue) +{ + std::shared_ptr<CSettingString> setting = + std::make_shared<CSettingString>(settingId, settings.GetSettingsManager()); + setting->SetLevel(SettingLevel::Internal); + setting->SetVisible(false); + setting->SetDefault(defaultValue); + setting->SetAllowEmpty(true); + + return setting; +} + +template<class TSetting> +SettingPtr AddSettingWithoutDefinition(ADDON::CAddonSettings& settings, + const std::string& settingId, + typename TSetting::Value defaultValue, + const Logger& logger) +{ + if (settingId.empty()) + return nullptr; + + // if necessary try to initialize the settings manager on-the-fly without any definitions + if (!settings.IsInitialized() && !settings.Initialize(CXBMCTinyXML(), true)) + { + logger->warn("failed to initialize settings on-the-fly"); + return nullptr; + } + + // check if we need to add a section on-the-fly + auto sections = settings.GetSettingsManager()->GetSections(); + SettingSectionPtr section; + if (sections.empty()) + section = + std::make_shared<CSettingSection>(settings.GetAddonId(), settings.GetSettingsManager()); + else + section = sections.back(); + + // check if we need to add a category on-the-fly + auto categories = section->GetCategories(); + SettingCategoryPtr category; + if (categories.empty()) + category = std::make_shared<CSettingCategory>("category0", settings.GetSettingsManager()); + else + category = categories.back(); + + // check if we need to add a group on-the-fly + auto groups = category->GetGroups(); + SettingGroupPtr group; + if (groups.empty()) + group = std::make_shared<CSettingGroup>("0", settings.GetSettingsManager()); + else + group = groups.back(); + + // create a new setting on-the-fly + auto setting = + InitializeFromOldSettingWithoutDefinition<TSetting>(settings, settingId, defaultValue); + if (setting == nullptr) + { + logger->warn("failed to create setting \"{}\" on-the-fly", settingId); + return nullptr; + } + + // add the setting (and if necessary the section, category and/or group) + if (!settings.GetSettingsManager()->AddSetting(setting, section, category, group)) + { + logger->warn("failed to add setting \"{}\" on-the-fly", settingId); + return nullptr; + } + + return setting; +} + +} // namespace + +namespace ADDON +{ + +CAddonSettings::CAddonSettings(const std::shared_ptr<const IAddon>& addon, + AddonInstanceId instanceId) + : CSettingsBase(), + m_addonId(addon->ID()), + m_addonPath(addon->Path()), + m_addonProfile(addon->Profile()), + m_instanceId(instanceId), + m_unidentifiedSettingId(0), + m_unknownSettingLabelId(UnknownSettingLabelIdStart), + m_logger(CServiceBroker::GetLogging().GetLogger( + StringUtils::Format("CAddonSettings[{}@{}]", m_instanceId, m_addonId))) +{ +} + +std::shared_ptr<CSetting> CAddonSettings::CreateSetting( + const std::string& settingType, + const std::string& settingId, + CSettingsManager* settingsManager /* = nullptr */) const +{ + if (StringUtils::EqualsNoCase(settingType, "urlencodedstring")) + return std::make_shared<CSettingUrlEncodedString>(settingId, settingsManager); + + return CSettingCreator::CreateSetting(settingType, settingId, settingsManager); +} + +void CAddonSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + std::string actionData; + bool closeDialog = false; + + // check if it's an action setting + if (setting->GetType() == SettingType::Action) + { + auto settingAction = std::dynamic_pointer_cast<const CSettingAction>(setting); + if (settingAction != nullptr && settingAction->HasData()) + { + actionData = settingAction->GetData(); + // replace $CWD with the url of the add-on + StringUtils::Replace(actionData, "$CWD", m_addonPath); + // replace $ID with the id of the add-on + StringUtils::Replace(actionData, "$ID", m_addonId); + } + } + + // check if the setting control's is a button and its format is action + if (setting->GetControl()->GetType() == "button" && + setting->GetControl()->GetFormat() == "action") + { + auto controlButton = + std::dynamic_pointer_cast<const CSettingControlButton>(setting->GetControl()); + if (controlButton != nullptr) + { + if (actionData.empty() && controlButton->HasActionData()) + actionData = controlButton->GetActionData(); + + closeDialog = controlButton->CloseDialog(); + } + } + + if (actionData.empty()) + return; + + if (closeDialog) + CGUIDialogAddonSettings::SaveAndClose(); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, actionData); +} + +bool CAddonSettings::AddInstanceSettings() +{ + if (GetSetting(ADDON_SETTING_INSTANCE_NAME_VALUE) || + GetSetting(ADDON_SETTING_INSTANCE_ENABLED_VALUE)) + { + CLog::Log( + LOGDEBUG, + "CAddonSettings::{} - Add-on {} using instance setting values byself, Kodi's add ignored", + __func__, m_addonId); + return true; + } + + auto mgr = GetSettingsManager(); + if (!mgr) + return false; + + auto sections = mgr->GetSections(); + if (sections.empty()) + return false; + + SettingSectionPtr section = *sections.begin(); + + auto categories = section->GetCategories(); + if (categories.empty()) + return false; + + SettingCategoryPtr category = *categories.begin(); + + auto groups = category->GetGroups(); + auto itr = std::find_if(groups.begin(), groups.end(), + [](const SettingGroupPtr& group) + { return group->GetId() == ADDON_SETTING_INSTANCE_GROUP; }); + + SettingGroupPtr group; + if (itr != groups.end()) + { + group = *itr; + } + else + { + group = std::make_shared<CSettingGroup>(ADDON_SETTING_INSTANCE_GROUP, mgr); + group->SetLabel(10017); // Add-on configuration + category->AddGroupToFront(group); + } + + const std::shared_ptr<CSettingString> name = + std::make_shared<CSettingString>(ADDON_SETTING_INSTANCE_NAME_VALUE, 551, "", mgr); // Name + name->SetAllowEmpty(false); + name->SetControl(std::make_shared<CSettingControlEdit>()); + if (!mgr->AddSetting(name, section, category, group)) + return false; + + const std::shared_ptr<CSettingBool> enabled = std::make_shared<CSettingBool>( + ADDON_SETTING_INSTANCE_ENABLED_VALUE, 305, true, mgr); // Enabled + enabled->SetControl(std::make_shared<CSettingControlCheckmark>()); + if (!mgr->AddSetting(enabled, section, category, group)) + return false; + + return true; +} + +bool CAddonSettings::Initialize(const CXBMCTinyXML& doc, bool allowEmpty /* = false */) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (m_initialized) + return false; + + // register custom setting types + InitializeSettingTypes(); + // register custom setting controls + InitializeControls(); + + // conditions need to be initialized before the setting definitions + InitializeConditions(); + + // load the settings definitions + if (!InitializeDefinitions(doc) && !allowEmpty) + return false; + + // Add internal settings to set values about instance set + if (m_instanceId > 0 && !AddInstanceSettings()) + return false; + + GetSettingsManager()->SetInitialized(); + + m_initialized = true; + + return true; +} + +bool CAddonSettings::Load(const CXBMCTinyXML& doc) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (!m_initialized) + return false; + + // figure out the version of the setting definitions + uint32_t version = 0; + if (!ParseSettingVersion(doc, version)) + { + m_logger->error("failed to determine setting values version"); + return false; + } + + std::map<std::string, std::string> settingValues; + + // for new/"normal" setting values use the standard process + if (version != 0) + { + bool updated; + if (!LoadValuesFromXml(doc, updated)) + return false; + + // helper lambda for parsing a setting's ID and value from XML + auto parseSettingValue = [&settingValues](const TiXmlNode* setting, + const std::string& categoryId = "") { + // put together the setting ID + auto settingId = categoryId; + if (!settingId.empty()) + settingId += "."; + auto id = setting->ToElement()->Attribute("id"); + if (id) + settingId += id; + + // parse the setting value + std::string settingValue; + if (setting->FirstChild()) + settingValue = setting->FirstChild()->ValueStr(); + + // add the setting to the map + settingValues.emplace(std::make_pair(settingId, settingValue)); + }; + + // check if there were any setting values without a definition + auto category = doc.RootElement()->FirstChild(); + while (category != nullptr) + { + // check if this really is a category with setting elements + if (category->FirstChild() && category->FirstChild()->Type() == CXBMCTinyXML::TINYXML_ELEMENT) + { + const auto& categoryId = category->ValueStr(); + auto setting = category->FirstChild(); + while (setting != nullptr) + { + parseSettingValue(setting, categoryId); + + setting = setting->NextSibling(); + } + } + else + parseSettingValue(category); + + category = category->NextSibling(); + } + } + // for old setting values do it manually + else if (!LoadOldSettingValues(doc, settingValues)) + { + m_logger->error("failed to determine setting values from old format"); + return false; + } + + // process all settings + for (const auto& setting : settingValues) + { + // ignore setting values without a setting identifier + if (setting.first.empty()) + continue; + + // try to find a matching setting + SettingPtr newSetting = GetSetting(setting.first); + if (newSetting == nullptr) + { + // create a hidden/internal string setting on-the-fly + newSetting = AddSettingWithoutDefinition<CSettingString>(*this, setting.first, setting.second, + m_logger); + } + + // try to load the old setting value + if (!newSetting) + { + m_logger->error("had null newSetting for value \"{}\" for setting {}", setting.second, + setting.first); + } + else if (!newSetting->FromString(setting.second)) + { + m_logger->warn("failed to load value \"{}\" for setting {}", setting.second, setting.first); + } + } + + SetLoaded(); + + return true; +} + +bool CAddonSettings::Save(CXBMCTinyXML& doc) const +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (!m_initialized) + return false; + + if (!SaveValuesToXml(doc)) + { + m_logger->error("failed to save settings"); + return false; + } + + return true; +} + +bool CAddonSettings::HasSettings() const +{ + return IsInitialized() && GetSettingsManager()->HasSettings(); +} + +std::string CAddonSettings::GetSettingLabel(int label) const +{ + if (label < UnknownSettingLabelIdStart || label >= m_unknownSettingLabelId) + return ""; + + const auto labelIt = m_unknownSettingLabels.find(label); + if (labelIt == m_unknownSettingLabels.end()) + return ""; + + return labelIt->second; +} + +std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, bool value) +{ + return AddSettingWithoutDefinition<CSettingBool>(*this, settingId, value, m_logger); +} + +std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, int value) +{ + return AddSettingWithoutDefinition<CSettingInt>(*this, settingId, value, m_logger); +} + +std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, double value) +{ + return AddSettingWithoutDefinition<CSettingNumber>(*this, settingId, value, m_logger); +} + +std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, + const std::string& value) +{ + return AddSettingWithoutDefinition<CSettingString>(*this, settingId, value, m_logger); +} + +void CAddonSettings::InitializeSettingTypes() +{ + GetSettingsManager()->RegisterSettingType("addon", this); + GetSettingsManager()->RegisterSettingType("date", this); + GetSettingsManager()->RegisterSettingType("path", this); + GetSettingsManager()->RegisterSettingType("time", this); + GetSettingsManager()->RegisterSettingType("urlencodedstring", this); +} + +void CAddonSettings::InitializeControls() +{ + GetSettingsManager()->RegisterSettingControl("toggle", this); + GetSettingsManager()->RegisterSettingControl("spinner", this); + GetSettingsManager()->RegisterSettingControl("edit", this); + GetSettingsManager()->RegisterSettingControl("button", this); + GetSettingsManager()->RegisterSettingControl("list", this); + GetSettingsManager()->RegisterSettingControl("slider", this); + GetSettingsManager()->RegisterSettingControl("range", this); + GetSettingsManager()->RegisterSettingControl("title", this); + GetSettingsManager()->RegisterSettingControl("colorbutton", this); +} + +void CAddonSettings::InitializeConditions() +{ + CSettingConditions::Initialize(); + + // add basic conditions + const std::set<std::string>& simpleConditions = CSettingConditions::GetSimpleConditions(); + for (const auto& condition : simpleConditions) + GetSettingsManager()->AddCondition(condition); + + GetSettingsManager()->AddDynamicCondition("InfoBool", InfoBool); +} + +bool CAddonSettings::InitializeDefinitions(const CXBMCTinyXML& doc) +{ + // figure out the version of the setting definitions + uint32_t version = 0; + if (!ParseSettingVersion(doc, version)) + { + m_logger->error("failed to determine setting definitions version"); + return false; + } + + // for new/"normal" setting definitions use the standard process + if (version != 0) + return InitializeDefinitionsFromXml(doc); + + // for old setting definitions do it manually + return InitializeFromOldSettingDefinitions(doc); +} + +bool CAddonSettings::ParseSettingVersion(const CXBMCTinyXML& doc, uint32_t& version) const +{ + const TiXmlElement* root = doc.RootElement(); + if (root == nullptr) + return false; + + if (!StringUtils::EqualsNoCase(root->ValueStr(), SETTING_XML_ROOT)) + { + m_logger->error("error reading setting definitions: no <settings> tag"); + return false; + } + + version = GetSettingsManager()->ParseVersion(root); + return true; +} + +std::shared_ptr<CSettingGroup> CAddonSettings::ParseOldSettingElement( + const TiXmlElement* categoryElement, + const std::shared_ptr<CSettingCategory>& category, + std::set<std::string>& settingIds) +{ + // build a vector of settings from the same category + std::vector<std::shared_ptr<const CSetting>> categorySettings; + + // prepare for settings with enable/visible conditions + struct SettingWithConditions + { + SettingPtr setting; + std::string enableCondition; + std::string visibleCondition; + SettingDependencies deps; + }; + std::vector<SettingWithConditions> settingsWithConditions; + + auto group = std::make_shared<CSettingGroup>("0", GetSettingsManager()); + uint32_t groupId = 1; + + // go through all settings in the category + const TiXmlElement* settingElement = categoryElement->FirstChildElement("setting"); + while (settingElement != nullptr) + { + // read the possible attributes + const auto settingType = XMLUtils::GetAttribute(settingElement, "type"); + const auto settingId = XMLUtils::GetAttribute(settingElement, "id"); + const auto defaultValue = XMLUtils::GetAttribute(settingElement, "default"); + const auto settingValues = XMLUtils::GetAttribute(settingElement, "values"); + const auto settingLValues = StringUtils::Split( + XMLUtils::GetAttribute(settingElement, "lvalues"), OldSettingValuesSeparator); + int settingLabel = -1; + bool settingLabelParsed = ParseOldLabel(settingElement, settingId, settingLabel); + + SettingPtr setting; + if (settingType == "sep" || settingType == "lsep") + { + // check if we need to create a new group + if (!group->GetSettings().empty()) + { + // add the current group to the category + category->AddGroup(group); + + // and create a new one + group.reset(new CSettingGroup(std::to_string(groupId), GetSettingsManager())); + groupId += 1; + } + + if (settingType == "lsep" && settingLabelParsed) + group->SetLabel(settingLabel); + } + else if (settingId.empty() || settingType == "action") + { + if (settingType == "action") + setting = InitializeFromOldSettingAction(settingId, settingElement, defaultValue); + else + setting = InitializeFromOldSettingLabel(); + } + else if (settingType == "bool") + setting = InitializeFromOldSettingBool(settingId, settingElement, defaultValue); + else if (settingType == "text" || settingType == "ipaddress") + setting = InitializeFromOldSettingTextIpAddress(settingId, settingType, settingElement, + defaultValue, settingLabel); + else if (settingType == "number") + setting = + InitializeFromOldSettingNumber(settingId, settingElement, defaultValue, settingLabel); + else if (settingType == "video" || settingType == "audio" || settingType == "image" || + settingType == "executable" || settingType == "file" || settingType == "folder") + setting = InitializeFromOldSettingPath(settingId, settingType, settingElement, defaultValue, + settingLabel); + else if (settingType == "date") + setting = InitializeFromOldSettingDate(settingId, settingElement, defaultValue, settingLabel); + else if (settingType == "time") + setting = InitializeFromOldSettingTime(settingId, settingElement, defaultValue, settingLabel); + else if (settingType == "select") + setting = InitializeFromOldSettingSelect(settingId, settingElement, defaultValue, + settingLabel, settingValues, settingLValues); + else if (settingType == "addon") + setting = + InitializeFromOldSettingAddon(settingId, settingElement, defaultValue, settingLabel); + else if (settingType == "enum" || settingType == "labelenum") + setting = InitializeFromOldSettingEnums(settingId, settingType, settingElement, defaultValue, + settingValues, settingLValues); + else if (settingType == "fileenum") + setting = + InitializeFromOldSettingFileEnum(settingId, settingElement, defaultValue, settingValues); + else if (settingType == "rangeofnum") + setting = InitializeFromOldSettingRangeOfNum(settingId, settingElement, defaultValue); + else if (settingType == "slider") + setting = InitializeFromOldSettingSlider(settingId, settingElement, defaultValue); + else if (settingType.empty()) + { + // setting definitions without a type are considered as "text" / strings but are hidden + setting = InitializeFromOldSettingTextIpAddress(settingId, "text", settingElement, + defaultValue, settingLabel); + setting->SetLevel(SettingLevel::Internal); + } + else + { + m_logger->warn("failed to parse old setting definition for \"{}\" of type \"{}\"", settingId, + settingType); + } + + // process general properties + if (setting != nullptr) + { + // set the default level to be Basic + if (setting->GetLevel() != SettingLevel::Internal) + { + setting->SetLevel(SettingLevel::Basic); + } + + // use the setting's ID if there's no label + if (settingLabel < 0) + { + settingLabel = m_unknownSettingLabelId; + m_unknownSettingLabelId += 1; + + m_unknownSettingLabels.emplace(settingLabel, settingId); + } + + // set the setting's label + setting->SetLabel(settingLabel); + + // handle subsettings + bool isSubsetting = false; + if (settingElement->QueryBoolAttribute("subsetting", &isSubsetting) == TIXML_SUCCESS && + isSubsetting) + { + // find the last non-subsetting in the current group and use that as the parent setting + const auto groupSettings = group->GetSettings(); + const auto parentSetting = std::find_if( + groupSettings.crbegin(), groupSettings.crend(), + [](const SettingConstPtr& setting) { return setting->GetParent().empty(); }); + + if (parentSetting != groupSettings.crend()) + { + if ((*parentSetting)->IsReference()) + setting->SetParent((*parentSetting)->GetReferencedId()); + else + setting->SetParent((*parentSetting)->GetId()); + } + } + + SettingWithConditions settingWithConditions; + + // parse enable status + const auto conditionEnable = XMLUtils::GetAttribute(settingElement, "enable"); + if (StringUtils::EqualsNoCase(conditionEnable, "true")) + setting->SetEnabled(true); + else if (StringUtils::EqualsNoCase(conditionEnable, "false")) + setting->SetEnabled(false); + else if (!conditionEnable.empty()) + settingWithConditions.enableCondition = conditionEnable; + + // parse visible status + const auto conditionVisible = XMLUtils::GetAttribute(settingElement, "visible"); + if (StringUtils::EqualsNoCase(conditionVisible, "true")) + setting->SetVisible(true); + else if (StringUtils::EqualsNoCase(conditionVisible, "false")) + setting->SetVisible(false); + else if (!conditionVisible.empty()) + settingWithConditions.visibleCondition = conditionVisible; + + // check if there already is a setting with the setting identifier + if (settingIds.find(settingId) != settingIds.end()) + { + // turn the setting into a reference setting + setting->MakeReference(); + } + else + { + // add the setting's identifier to the list of all identifiers + settingIds.insert(setting->GetId()); + } + + if (!settingWithConditions.enableCondition.empty() || + !settingWithConditions.visibleCondition.empty()) + { + settingWithConditions.setting = setting; + settingsWithConditions.push_back(settingWithConditions); + } + + // add the setting to the list of settings from the same category + categorySettings.push_back(setting); + + // add the setting to the current group + group->AddSetting(setting); + } + else + { + // add a dummy setting for the group / separator to the list of settings from the same category + categorySettings.push_back(nullptr); + } + + // look for the next setting + settingElement = settingElement->NextSiblingElement("setting"); + } + + // process settings with enable/visible conditions + for (auto setting : settingsWithConditions) + { + if (!setting.enableCondition.empty()) + { + CSettingDependency dependencyEnable(SettingDependencyType::Enable, GetSettingsManager()); + if (ParseOldCondition(setting.setting, categorySettings, setting.enableCondition, + dependencyEnable)) + setting.deps.push_back(dependencyEnable); + else + { + m_logger->warn( + "failed to parse enable condition \"{}\" of old setting definition for \"{}\"", + setting.enableCondition, setting.setting->GetId()); + } + } + + if (!setting.visibleCondition.empty()) + { + CSettingDependency dependencyVisible(SettingDependencyType::Visible, GetSettingsManager()); + if (ParseOldCondition(setting.setting, categorySettings, setting.visibleCondition, + dependencyVisible)) + setting.deps.push_back(dependencyVisible); + else + { + m_logger->warn( + "failed to parse visible condition \"{}\" of old setting definition for \"{}\"", + setting.visibleCondition, setting.setting->GetId()); + } + } + + // set dependencies + setting.setting->SetDependencies(setting.deps); + } + + return group; +} + +std::shared_ptr<CSettingCategory> CAddonSettings::ParseOldCategoryElement( + uint32_t& categoryId, const TiXmlElement* categoryElement, std::set<std::string>& settingIds) +{ + // create the category + auto category = std::make_shared<CSettingCategory>(StringUtils::Format("category{}", categoryId), + GetSettingsManager()); + categoryId += 1; + + // try to get the category's label and fall back to "General" + int categoryLabel = 128; + ParseOldLabel(categoryElement, g_localizeStrings.Get(categoryLabel), categoryLabel); + category->SetLabel(categoryLabel); + + // prepare a setting group + auto group = ParseOldSettingElement(categoryElement, category, settingIds); + + // add the group to the category + category->AddGroup(group); + + return category; +} + +bool CAddonSettings::InitializeFromOldSettingDefinitions(const CXBMCTinyXML& doc) +{ + m_logger->debug("trying to load setting definitions from old format..."); + + const TiXmlElement* root = doc.RootElement(); + if (root == nullptr) + return false; + + std::shared_ptr<CSettingSection> section = + std::make_shared<CSettingSection>(m_addonId, GetSettingsManager()); + + std::shared_ptr<CSettingCategory> category; + uint32_t categoryId = 0; + + // Settings id set + std::set<std::string> settingIds; + + // Special case for no category settings + section->AddCategory(ParseOldCategoryElement(categoryId, root, settingIds)); + + const TiXmlElement* categoryElement = root->FirstChildElement("category"); + while (categoryElement != nullptr) + { + section->AddCategory(ParseOldCategoryElement(categoryId, categoryElement, settingIds)); + + // look for the next category + categoryElement = categoryElement->NextSiblingElement("category"); + } + + // add the section to the settingsmanager + GetSettingsManager()->AddSection(section); + + return true; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingAction(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue) +{ + // parse the action attribute + std::string action = XMLUtils::GetAttribute(settingElement, "action"); + // replace $CWD with the url of the add-on + StringUtils::Replace(action, "$CWD", m_addonPath); + // replace $ID with the id of the add-on + StringUtils::Replace(action, "$ID", m_addonId); + + // prepare the setting's control + auto control = std::make_shared<CSettingControlButton>(); + control->SetFormat("action"); + + SettingPtr setting = nullptr; + // action settings don't require a setting id + if (settingId.empty()) + { + auto actionSettingId = StringUtils::Format("action{}", m_unidentifiedSettingId); + m_unidentifiedSettingId += 1; + + auto settingAction = std::make_shared<CSettingAction>(actionSettingId, GetSettingsManager()); + settingAction->SetData(action); + + setting = settingAction; + } + else + { + // assume that the setting might store a value as a string + auto settingString = std::make_shared<CSettingString>(settingId, GetSettingsManager()); + settingString->SetDefault(defaultValue); + settingString->SetAllowEmpty(true); + + control->SetActionData(action); + + setting = settingString; + } + + // get any options + std::string option = XMLUtils::GetAttribute(settingElement, "option"); + // handle the "close" option + if (StringUtils::EqualsNoCase(option, "close")) + control->SetCloseDialog(true); + + setting->SetControl(control); + + return setting; +} + +std::shared_ptr<CSetting> CAddonSettings::InitializeFromOldSettingLabel() +{ + // label settings don't require a setting id + auto labelSettingId = StringUtils::Format("label{}", m_unidentifiedSettingId); + m_unidentifiedSettingId += 1; + + auto settingLabel = std::make_shared<CSettingString>(labelSettingId, GetSettingsManager()); + + // create the setting's control + settingLabel->SetControl(std::make_shared<CSettingControlLabel>()); + + return settingLabel; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingBool(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue) +{ + auto setting = std::make_shared<CSettingBool>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + setting->SetControl(std::make_shared<CSettingControlCheckmark>()); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingTextIpAddress(const std::string& settingId, + const std::string& settingType, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel) +{ + std::shared_ptr<CSettingString> setting; + auto control = std::make_shared<CSettingControlEdit>(); + control->SetHeading(settingLabel); + + // get any options + std::string option = XMLUtils::GetAttribute(settingElement, "option"); + + if (settingType == "ipaddress") + { + setting = std::make_shared<CSettingString>(settingId, GetSettingsManager()); + control->SetFormat("ip"); + } + else if (settingType == "text") + { + + if (StringUtils::EqualsNoCase(option, "urlencoded")) + { + setting = std::make_shared<CSettingUrlEncodedString>(settingId, GetSettingsManager()); + control->SetFormat("urlencoded"); + } + else + { + setting = std::make_shared<CSettingString>(settingId, GetSettingsManager()); + control->SetFormat("string"); + control->SetHidden(StringUtils::EqualsNoCase(option, "hidden")); + } + } + + setting->SetDefault(defaultValue); + setting->SetAllowEmpty(true); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingNumber(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel) +{ + auto setting = std::make_shared<CSettingInt>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + auto control = std::make_shared<CSettingControlEdit>(); + control->SetHeading(settingLabel); + control->SetFormat("integer"); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingPath(const std::string& settingId, + const std::string& settingType, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel) +{ + auto setting = std::make_shared<CSettingPath>(settingId, GetSettingsManager()); + setting->SetDefault(defaultValue); + + // parse sources/shares + const auto source = XMLUtils::GetAttribute(settingElement, "source"); + if (!source.empty()) + setting->SetSources({source}); + + // setup masking + const auto audioMask = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + const auto videoMask = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + const auto imageMask = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + auto execMask = ""; +#if defined(TARGET_WINDOWS) + execMask = ".exe|.bat|.cmd|.py"; +#endif // defined(TARGET_WINDOWS) + + std::string mask = XMLUtils::GetAttribute(settingElement, "mask"); + if (!mask.empty()) + { + // convert mask qualifiers + StringUtils::Replace(mask, "$AUDIO", audioMask); + StringUtils::Replace(mask, "$VIDEO", videoMask); + StringUtils::Replace(mask, "$IMAGE", imageMask); + StringUtils::Replace(mask, "$EXECUTABLE", execMask); + } + else + { + if (settingType == "video") + mask = videoMask; + else if (settingType == "audio") + mask = audioMask; + else if (settingType == "image") + mask = imageMask; + else if (settingType == "executable") + mask = execMask; + } + setting->SetMasking(mask); + + // parse options + const auto option = XMLUtils::GetAttribute(settingElement, "option"); + setting->SetWritable(StringUtils::EqualsNoCase(option, "writeable")); + + auto control = std::make_shared<CSettingControlButton>(); + if (settingType == "folder") + control->SetFormat("path"); + else if (settingType == "image") + control->SetFormat("image"); + else + { + control->SetFormat("file"); + + // parse the options + const auto options = StringUtils::Split(option, OldSettingValuesSeparator); + control->SetUseImageThumbs(std::find(options.cbegin(), options.cend(), "usethumbs") != + options.cend()); + control->SetUseFileDirectories(std::find(options.cbegin(), options.cend(), "treatasfolder") != + options.cend()); + } + control->SetHeading(settingLabel); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingDate(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel) +{ + auto setting = std::make_shared<CSettingDate>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + auto control = std::make_shared<CSettingControlButton>(); + control->SetFormat("date"); + control->SetHeading(settingLabel); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingTime(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel) +{ + auto setting = std::make_shared<CSettingTime>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + auto control = std::make_shared<CSettingControlButton>(); + control->SetFormat("time"); + control->SetHeading(settingLabel); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingSelect( + const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel, + const std::string& settingValues, + const std::vector<std::string>& settingLValues) +{ + // process values and lvalues + std::vector<std::string> values; + if (!settingLValues.empty()) + values = settingLValues; + else + values = StringUtils::Split(settingValues, OldSettingValuesSeparator); + + SettingPtr setting = nullptr; + if (!values.empty()) + { + if (settingLValues.empty()) + { + auto settingString = std::make_shared<CSettingString>(settingId, GetSettingsManager()); + settingString->SetDefault(defaultValue); + + StringSettingOptions options; + for (const auto& value : values) + options.push_back(StringSettingOption(value, value)); + settingString->SetOptions(options); + + setting = settingString; + } + else + { + auto settingInt = std::make_shared<CSettingInt>(settingId, GetSettingsManager()); + if (settingInt->FromString(defaultValue)) + settingInt->SetDefault(settingInt->GetValue()); + + TranslatableIntegerSettingOptions options; + for (uint32_t i = 0; i < values.size(); ++i) + options.push_back(TranslatableIntegerSettingOption( + static_cast<int>(strtol(values[i].c_str(), nullptr, 0)), i)); + settingInt->SetTranslatableOptions(options); + + setting = settingInt; + } + } + else + { + // parse sources/shares + const auto source = XMLUtils::GetAttribute(settingElement, "source"); + if (!source.empty()) + setting = InitializeFromOldSettingFileWithSource(settingId, settingElement, defaultValue, + settingValues); + else + m_logger->warn("failed to parse old setting definition for \"{}\" of type \"select\"", + settingId); + } + + if (setting != nullptr) + { + auto control = std::make_shared<CSettingControlList>(); + control->SetHeading(settingLabel); + control->SetFormat("string"); + setting->SetControl(control); + } + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingAddon(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel) +{ + // get addon types + std::string addonTypeStr = XMLUtils::GetAttribute(settingElement, "addontype"); + const auto addonTypesStr = StringUtils::Split(addonTypeStr, ","); + std::set<AddonType> addonTypes; + for (auto addonType : addonTypesStr) + { + auto type = ADDON::CAddonInfo::TranslateType(StringUtils::Trim(addonType)); + if (type != ADDON::AddonType::UNKNOWN) + addonTypes.insert(type); + } + + if (addonTypes.empty()) + { + m_logger->error("missing addon type for addon setting \"{}\"", settingId); + return nullptr; + } + + // TODO: support multiple addon types + if (addonTypes.size() > 1) + { + m_logger->error("multiple addon types are not supported (addon setting \"{}\")", settingId); + return nullptr; + } + + // parse addon ids + auto addonIds = StringUtils::Split(defaultValue, ","); + + // parse multiselect option + bool multiselect = false; + settingElement->QueryBoolAttribute("multiselect", &multiselect); + + // sanity check + if (addonIds.size() > 1 && !multiselect) + { + m_logger->warn("multiple default addon ids on non-multiselect addon setting \"{}\"", settingId); + addonIds.erase(++addonIds.begin(), addonIds.end()); + } + + auto settingAddon = std::make_shared<CSettingAddon>(settingId, GetSettingsManager()); + settingAddon->SetAddonType(*addonTypes.begin()); + + SettingPtr setting = settingAddon; + if (multiselect) + { + auto settingList = + std::make_shared<CSettingList>(settingId, settingAddon, GetSettingsManager()); + settingList->SetDelimiter(","); + if (settingList->FromString(addonIds)) + settingList->SetDefault(settingList->GetValue()); + + setting = settingList; + } + else if (!addonIds.empty()) + settingAddon->SetDefault(addonIds.front()); + + auto control = std::make_shared<CSettingControlButton>(); + control->SetFormat("addon"); + control->SetHeading(settingLabel); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingEnums( + const std::string& settingId, + const std::string& settingType, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const std::string& settingValues, + const std::vector<std::string>& settingLValues) +{ + // process values and lvalues + std::vector<std::string> values; + if (!settingLValues.empty()) + values = settingLValues; + else if (settingValues == "$HOURS") + { + for (uint32_t hour = 0; hour < 24; hour++) + values.push_back( + CDateTime(2000, 1, 1, hour, 0, 0).GetAsLocalizedTime(g_langInfo.GetTimeFormat(), false)); + } + else + values = StringUtils::Split(settingValues, OldSettingValuesSeparator); + + // process entries + const auto settingEntries = StringUtils::Split(XMLUtils::GetAttribute(settingElement, "entries"), + OldSettingValuesSeparator); + + // process sort + bool sortAscending = false; + std::string sort = XMLUtils::GetAttribute(settingElement, "sort"); + if (sort == "true" || sort == "yes") + sortAscending = true; + + SettingPtr setting = nullptr; + if (settingType == "enum") + { + auto settingInt = std::make_shared<CSettingInt>(settingId, GetSettingsManager()); + + if (settingLValues.empty()) + { + IntegerSettingOptions options; + for (uint32_t i = 0; i < values.size(); ++i) + { + std::string label = values[i]; + int value = i; + if (settingEntries.size() > i) + value = static_cast<int>(strtol(settingEntries[i].c_str(), nullptr, 0)); + + options.push_back(IntegerSettingOption(label, value)); + } + + settingInt->SetOptions(options); + } + else + { + TranslatableIntegerSettingOptions options; + for (uint32_t i = 0; i < values.size(); ++i) + { + int label = static_cast<int>(strtol(values[i].c_str(), nullptr, 0)); + int value = i; + if (settingEntries.size() > i) + value = static_cast<int>(strtol(settingEntries[i].c_str(), nullptr, 0)); + + options.push_back(TranslatableIntegerSettingOption(label, value)); + } + + settingInt->SetTranslatableOptions(options); + } + + if (sortAscending) + settingInt->SetOptionsSort(SettingOptionsSort::Ascending); + + // set the default value + if (settingInt->FromString(defaultValue)) + settingInt->SetDefault(settingInt->GetValue()); + + setting = settingInt; + } + else + { + auto settingString = std::make_shared<CSettingString>(settingId, GetSettingsManager()); + + if (settingLValues.empty()) + { + StringSettingOptions options; + for (uint32_t i = 0; i < values.size(); ++i) + { + std::string value = values[i]; + if (settingEntries.size() > i) + value = settingEntries[i]; + + options.push_back(StringSettingOption(value, value)); + } + + settingString->SetOptions(options); + } + else + { + TranslatableStringSettingOptions options; + for (uint32_t i = 0; i < values.size(); ++i) + { + int label = static_cast<int>(strtol(values[i].c_str(), nullptr, 0)); + std::string value = g_localizeStrings.GetAddonString(m_addonId, label); + if (settingEntries.size() > i) + value = settingEntries[i]; + + options.push_back(std::make_pair(label, value)); + } + + settingString->SetTranslatableOptions(options); + } + + if (sortAscending) + settingString->SetOptionsSort(SettingOptionsSort::Ascending); + + // set the default value + settingString->SetDefault(defaultValue); + + setting = settingString; + } + + auto control = std::make_shared<CSettingControlSpinner>(); + control->SetFormat("string"); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingFileEnum(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const std::string& settingValues) +{ + auto setting = InitializeFromOldSettingFileWithSource(settingId, settingElement, defaultValue, + settingValues); + + auto control = std::make_shared<CSettingControlSpinner>(); + control->SetFormat("string"); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingRangeOfNum(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue) +{ + auto setting = std::make_shared<CSettingNumber>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + // parse rangestart and rangeend + double rangeStart = 0.0, rangeEnd = 1.0; + settingElement->QueryDoubleAttribute("rangestart", &rangeStart); + settingElement->QueryDoubleAttribute("rangeend", &rangeEnd); + setting->SetMinimum(rangeStart); + setting->SetMaximum(rangeEnd); + + // parse elements + uint32_t elements = 2; + settingElement->QueryUnsignedAttribute("elements", &elements); + if (elements > 1) + setting->SetStep((rangeEnd - rangeStart) / (elements - 1)); + + // parse valueformat + int valueFormat = -1; + settingElement->QueryIntAttribute("valueformat", &valueFormat); + + auto control = std::make_shared<CSettingControlSpinner>(); + control->SetFormat("string"); + control->SetFormatLabel(valueFormat); + setting->SetControl(control); + + return setting; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingSlider(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue) +{ + // parse range + double min = 0.0, max = 100.0, step = 1.0; + const auto range = StringUtils::Split(XMLUtils::GetAttribute(settingElement, "range"), ','); + + if (range.size() > 1) + { + min = strtod(range[0].c_str(), nullptr); + + if (range.size() > 2) + { + max = strtod(range[2].c_str(), nullptr); + step = strtod(range[1].c_str(), nullptr); + } + else + max = strtod(range[1].c_str(), nullptr); + } + + // parse option + auto option = XMLUtils::GetAttribute(settingElement, "option"); + if (option.empty() || StringUtils::EqualsNoCase(option, "float")) + { + auto setting = std::make_shared<CSettingNumber>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + setting->SetMinimum(min); + setting->SetStep(step); + setting->SetMaximum(max); + + auto control = std::make_shared<CSettingControlSlider>(); + control->SetFormat("number"); + control->SetPopup(false); + setting->SetControl(control); + + return setting; + } + + if (StringUtils::EqualsNoCase(option, "int") || StringUtils::EqualsNoCase(option, "percent")) + { + auto setting = std::make_shared<CSettingInt>(settingId, GetSettingsManager()); + if (setting->FromString(defaultValue)) + setting->SetDefault(setting->GetValue()); + + setting->SetMinimum(static_cast<int>(min)); + setting->SetStep(static_cast<int>(step)); + setting->SetMaximum(static_cast<int>(max)); + + auto control = std::make_shared<CSettingControlSlider>(); + control->SetFormat(StringUtils::EqualsNoCase(option, "int") ? "integer" : "percentage"); + control->SetPopup(false); + setting->SetControl(control); + + return setting; + } + + m_logger->warn("ignoring old setting definition for \"{}\" of type \"slider\" because of unknown " + "option \"{}\"", + settingId, option); + + return nullptr; +} + +SettingPtr CAddonSettings::InitializeFromOldSettingFileWithSource( + const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + std::string source) +{ + auto setting = std::make_shared<CSettingPath>(settingId, GetSettingsManager()); + setting->SetDefault(defaultValue); + + if (source.find("$PROFILE") != std::string::npos) + StringUtils::Replace(source, "$PROFILE", m_addonProfile); + else + source = URIUtils::AddFileToFolder(m_addonPath, source); + + setting->SetSources({source}); + + // process the path/file mask + setting->SetMasking(XMLUtils::GetAttribute(settingElement, "mask")); + + // process option + std::string option = XMLUtils::GetAttribute(settingElement, "option"); + setting->SetHideExtension(StringUtils::EqualsNoCase(option, "hideext")); + + setting->SetOptionsFiller(FileEnumSettingOptionsFiller); + + return setting; +} + +bool CAddonSettings::LoadOldSettingValues(const CXBMCTinyXML& doc, + std::map<std::string, std::string>& settings) const +{ + if (!doc.RootElement()) + return false; + + const TiXmlElement* category = doc.RootElement()->FirstChildElement("category"); + if (category == nullptr) + category = doc.RootElement(); + + while (category != nullptr) + { + const TiXmlElement* setting = category->FirstChildElement("setting"); + while (setting != nullptr) + { + const char* id = setting->Attribute("id"); + const char* value = setting->Attribute("value"); + if (id != nullptr && value != nullptr) + settings[id] = value; + + setting = setting->NextSiblingElement("setting"); + } + + category = category->NextSiblingElement("category"); + } + + return !settings.empty(); +} + +bool CAddonSettings::ParseOldLabel(const TiXmlElement* element, + const std::string& settingId, + int& labelId) +{ + labelId = -1; + if (element == nullptr) + return false; + + // label value as a string + std::string labelString; + element->QueryStringAttribute("label", &labelString); + + bool parsed = !labelString.empty(); + + // try to parse the label as a pure number, i.e. a localized string + if (parsed) + { + char* endptr; + labelId = std::strtol(labelString.c_str(), &endptr, 10); + if (endptr == nullptr || *endptr == '\0') + return true; + } + // make sure the label string is not empty + else + labelString = " "; + + labelId = m_unknownSettingLabelId; + m_unknownSettingLabelId += 1; + m_unknownSettingLabels.emplace(labelId, labelString); + + return parsed; +} + +bool CAddonSettings::ParseOldCondition(const std::shared_ptr<const CSetting>& setting, + const std::vector<std::shared_ptr<const CSetting>>& settings, + const std::string& condition, + CSettingDependency& dependeny) const +{ + if (setting == nullptr) + return false; + + if (condition.empty()) + return true; + + // find the index of the setting in the list of all settings of the category + auto settingIt = std::find_if(settings.cbegin(), settings.cend(), + [setting](const SettingConstPtr& otherSetting) { + if (otherSetting == nullptr) + return false; + + return setting->GetId() == otherSetting->GetId(); + }); + if (settingIt == settings.cend()) + { + m_logger->warn("failed to parse old setting conditions \"{}\" for \"{}\"", condition, + setting->GetId()); + return false; + } + int32_t currentSettingIndex = std::distance(settings.cbegin(), settingIt); + + CSettingDependencyConditionCombinationPtr dependencyCombination; + std::vector<std::string> conditions; + if (condition.find('+') != std::string::npos) + { + StringUtils::Tokenize(condition, conditions, '+'); + dependencyCombination = dependeny.And(); + } + else + { + StringUtils::Tokenize(condition, conditions, '|'); + dependencyCombination = dependeny.Or(); + } + + bool error = false; + for (const auto& cond : conditions) + { + ConditionExpression expression; + if (!ParseOldConditionExpression(cond, expression)) + continue; + + // determine the absolute setting index + int32_t absoluteSettingIndex = currentSettingIndex + expression.m_relativeSettingIndex; + + // we cannot handle relative indices pointing to settings not belonging to the same category + if (absoluteSettingIndex < 0 || static_cast<size_t>(absoluteSettingIndex) >= settings.size()) + { + m_logger->warn("cannot reference setting (relative index: {}; absolute index: {}) in another " + "category in old setting condition \"{}\" for \"{}\"", + expression.m_relativeSettingIndex, absoluteSettingIndex, cond, + setting->GetId()); + error = true; + continue; + } + + const SettingConstPtr& referencedSetting = settings.at(absoluteSettingIndex); + if (referencedSetting == nullptr) + { + m_logger->warn( + "cannot reference separator setting in old setting condition \"{}\" for \"{}\"", cond, + setting->GetId()); + error = true; + continue; + } + + // try to handle some odd cases where the setting is of type string but the comparison value references the index of the value in the list of options + if (referencedSetting->GetType() == SettingType::String && + StringUtils::IsNaturalNumber(expression.m_value)) + { + // try to parse the comparison value + size_t valueIndex = static_cast<size_t>(strtoul(expression.m_value.c_str(), nullptr, 10)); + + const auto referencedSettingString = + std::static_pointer_cast<const CSettingString>(referencedSetting); + switch (referencedSettingString->GetOptionsType()) + { + case SettingOptionsType::Static: + { + const auto& options = referencedSettingString->GetOptions(); + if (options.size() > valueIndex) + expression.m_value = options.at(valueIndex).value; + break; + } + + case SettingOptionsType::StaticTranslatable: + { + const auto& options = referencedSettingString->GetTranslatableOptions(); + if (options.size() > valueIndex) + expression.m_value = options.at(valueIndex).second; + break; + } + + default: + break; + } + } + + // add the condition to the value of the referenced setting + dependencyCombination->Add(std::make_shared<CSettingDependencyCondition>( + referencedSetting->GetId(), expression.m_value, expression.m_operator, expression.m_negated, + GetSettingsManager())); + } + + // if the condition doesn't depend on other settings it might be an infobool expression + if (!error && dependencyCombination->GetOperations().empty() && + dependencyCombination->GetValues().empty()) + dependencyCombination->Add(std::make_shared<CSettingDependencyCondition>( + "InfoBool", condition, "", false, GetSettingsManager())); + + return !error; +} + +bool CAddonSettings::ParseOldConditionExpression(std::string str, ConditionExpression& expression) +{ + StringUtils::Trim(str); + + size_t posOpen = str.find('('); + size_t posSep = str.find(',', posOpen); + size_t posClose = str.find(')', posSep); + + if (posOpen == std::string::npos || posSep == std::string::npos || posClose == std::string::npos) + return false; + + auto op = str.substr(0, posOpen); + + // check if the operator is negated + expression.m_negated = StringUtils::StartsWith(op, "!"); + if (expression.m_negated) + op = op.substr(1); + + // parse the operator + if (StringUtils::EqualsNoCase(op, "eq")) + expression.m_operator = SettingDependencyOperator::Equals; + else if (StringUtils::EqualsNoCase(op, "gt")) + expression.m_operator = SettingDependencyOperator::GreaterThan; + else if (StringUtils::EqualsNoCase(op, "lt")) + expression.m_operator = SettingDependencyOperator::LessThan; + else + return false; + + expression.m_relativeSettingIndex = static_cast<int32_t>( + strtol(str.substr(posOpen + 1, posSep - posOpen - 1).c_str(), nullptr, 10)); + expression.m_value = str.substr(posSep + 1, posClose - posSep - 1); + + return true; +} + +void CAddonSettings::FileEnumSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + if (setting == nullptr) + return; + + auto settingPath = std::dynamic_pointer_cast<const CSettingPath>(setting); + if (settingPath == nullptr) + return; + + if (settingPath->GetSources().empty()) + return; + + const std::string& masking = settingPath->GetMasking(CServiceBroker::GetFileExtensionProvider()); + + // fetch the matching files/directories + CFileItemList items; + XFILE::CDirectory::GetDirectory(settingPath->GetSources().front(), items, masking, + XFILE::DIR_FLAG_NO_FILE_DIRS); + + // process the matching files/directories + for (const auto& item : items) + { + if ((masking == "/" && item->m_bIsFolder) || !item->m_bIsFolder) + { + if (settingPath->HideExtension()) + item->RemoveExtension(); + list.emplace_back(item->GetLabel(), item->GetLabel()); + } + } +} + +} // namespace ADDON diff --git a/xbmc/addons/settings/AddonSettings.h b/xbmc/addons/settings/AddonSettings.h new file mode 100644 index 0000000..811ded1 --- /dev/null +++ b/xbmc/addons/settings/AddonSettings.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2017-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 "settings/SettingControl.h" +#include "settings/SettingCreator.h" +#include "settings/SettingsBase.h" +#include "settings/lib/ISettingCallback.h" + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +enum class SettingDependencyOperator; + +class CSettingCategory; +class CSettingGroup; +class CSettingDependency; +class CXBMCTinyXML; + +struct StringSettingOption; + +namespace ADDON +{ + +class IAddon; +class IAddonInstanceHandler; + +class CAddonSettings : public CSettingControlCreator, + public CSettingCreator, + public CSettingsBase, + public ISettingCallback +{ +public: + CAddonSettings(const std::shared_ptr<const IAddon>& addon, AddonInstanceId instanceId); + ~CAddonSettings() override = default; + + // specialization of CSettingsBase + bool Initialize() override { return false; } + + // implementations of CSettingsBase + bool Load() override { return false; } + bool Save() override { return false; } + + // specialization of CSettingCreator + std::shared_ptr<CSetting> CreateSetting( + const std::string& settingType, + const std::string& settingId, + CSettingsManager* settingsManager = nullptr) const override; + + // implementation of ISettingCallback + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + const std::string& GetAddonId() const { return m_addonId; } + + bool Initialize(const CXBMCTinyXML& doc, bool allowEmpty = false); + bool Load(const CXBMCTinyXML& doc); + bool Save(CXBMCTinyXML& doc) const; + + bool HasSettings() const; + + std::string GetSettingLabel(int label) const; + + std::shared_ptr<CSetting> AddSetting(const std::string& settingId, bool value); + std::shared_ptr<CSetting> AddSetting(const std::string& settingId, int value); + std::shared_ptr<CSetting> AddSetting(const std::string& settingId, double value); + std::shared_ptr<CSetting> AddSetting(const std::string& settingId, const std::string& value); + +protected: + // specializations of CSettingsBase + void InitializeSettingTypes() override; + void InitializeControls() override; + void InitializeConditions() override; + + // implementation of CSettingsBase + bool InitializeDefinitions() override { return false; } + +private: + bool AddInstanceSettings(); + bool InitializeDefinitions(const CXBMCTinyXML& doc); + + bool ParseSettingVersion(const CXBMCTinyXML& doc, uint32_t& version) const; + + std::shared_ptr<CSettingGroup> ParseOldSettingElement( + const TiXmlElement* categoryElement, + const std::shared_ptr<CSettingCategory>& category, + std::set<std::string>& settingIds); + + std::shared_ptr<CSettingCategory> ParseOldCategoryElement(uint32_t& categoryId, + const TiXmlElement* categoryElement, + std::set<std::string>& settingIds); + + bool InitializeFromOldSettingDefinitions(const CXBMCTinyXML& doc); + std::shared_ptr<CSetting> InitializeFromOldSettingAction(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue); + std::shared_ptr<CSetting> InitializeFromOldSettingLabel(); + std::shared_ptr<CSetting> InitializeFromOldSettingBool(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue); + std::shared_ptr<CSetting> InitializeFromOldSettingTextIpAddress( + const std::string& settingId, + const std::string& settingType, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel); + std::shared_ptr<CSetting> InitializeFromOldSettingNumber(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel); + std::shared_ptr<CSetting> InitializeFromOldSettingPath(const std::string& settingId, + const std::string& settingType, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel); + std::shared_ptr<CSetting> InitializeFromOldSettingDate(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel); + std::shared_ptr<CSetting> InitializeFromOldSettingTime(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel); + std::shared_ptr<CSetting> InitializeFromOldSettingSelect( + const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel, + const std::string& settingValues, + const std::vector<std::string>& settingLValues); + std::shared_ptr<CSetting> InitializeFromOldSettingAddon(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const int settingLabel); + std::shared_ptr<CSetting> InitializeFromOldSettingEnums( + const std::string& settingId, + const std::string& settingType, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const std::string& settingValues, + const std::vector<std::string>& settingLValues); + std::shared_ptr<CSetting> InitializeFromOldSettingFileEnum(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + const std::string& settingValues); + std::shared_ptr<CSetting> InitializeFromOldSettingRangeOfNum(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue); + std::shared_ptr<CSetting> InitializeFromOldSettingSlider(const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue); + std::shared_ptr<CSetting> InitializeFromOldSettingFileWithSource( + const std::string& settingId, + const TiXmlElement* settingElement, + const std::string& defaultValue, + std::string source); + + bool LoadOldSettingValues(const CXBMCTinyXML& doc, + std::map<std::string, std::string>& settings) const; + + struct ConditionExpression + { + SettingDependencyOperator m_operator; + bool m_negated; + int32_t m_relativeSettingIndex; + std::string m_value; + }; + + bool ParseOldLabel(const TiXmlElement* element, const std::string& settingId, int& labelId); + bool ParseOldCondition(const std::shared_ptr<const CSetting>& setting, + const std::vector<std::shared_ptr<const CSetting>>& settings, + const std::string& condition, + CSettingDependency& dependeny) const; + static bool ParseOldConditionExpression(std::string str, ConditionExpression& expression); + + static void FileEnumSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + + // store these values so that we don't always have to access the weak pointer + const std::string m_addonId; + const std::string m_addonPath; + const std::string m_addonProfile; + const AddonInstanceId m_instanceId{ADDON_SETTINGS_ID}; + + uint32_t m_unidentifiedSettingId; + int m_unknownSettingLabelId; + std::map<int, std::string> m_unknownSettingLabels; + + Logger m_logger; +}; + +} // namespace ADDON diff --git a/xbmc/addons/settings/CMakeLists.txt b/xbmc/addons/settings/CMakeLists.txt new file mode 100644 index 0000000..6774f68 --- /dev/null +++ b/xbmc/addons/settings/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES AddonSettings.cpp + SettingUrlEncodedString.cpp) + +set(HEADERS AddonSettings.h + SettingUrlEncodedString.h) + +core_add_library(addons_settings) diff --git a/xbmc/addons/settings/SettingUrlEncodedString.cpp b/xbmc/addons/settings/SettingUrlEncodedString.cpp new file mode 100644 index 0000000..f09875f --- /dev/null +++ b/xbmc/addons/settings/SettingUrlEncodedString.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-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 "SettingUrlEncodedString.h" + +#include "URL.h" +#include "settings/lib/SettingsManager.h" + +namespace ADDON +{ + +CSettingUrlEncodedString::CSettingUrlEncodedString( + const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : CSettingString(id, settingsManager) +{ } + +CSettingUrlEncodedString::CSettingUrlEncodedString( + const std::string& id, + int label, + const std::string& value, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingString(id, label, value, settingsManager) +{ } + +CSettingUrlEncodedString::CSettingUrlEncodedString(const std::string &id, const CSettingUrlEncodedString &setting) + : CSettingString(id, setting) +{ } + +std::string CSettingUrlEncodedString::GetDecodedValue() const +{ + return CURL::Decode(CSettingString::GetValue()); +} + +bool CSettingUrlEncodedString::SetDecodedValue(const std::string &decodedValue) +{ + return CSettingString::SetValue(CURL::Encode(decodedValue)); +} + +} /* namespace ADDON */ diff --git a/xbmc/addons/settings/SettingUrlEncodedString.h b/xbmc/addons/settings/SettingUrlEncodedString.h new file mode 100644 index 0000000..80bad4d --- /dev/null +++ b/xbmc/addons/settings/SettingUrlEncodedString.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017-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 "settings/lib/Setting.h" + +class CSettingsManager; + +namespace ADDON +{ + class CSettingUrlEncodedString : public CSettingString + { + public: + CSettingUrlEncodedString(const std::string& id, CSettingsManager* settingsManager = nullptr); + CSettingUrlEncodedString(const std::string& id, + int label, + const std::string& value, + CSettingsManager* settingsManager = nullptr); + CSettingUrlEncodedString(const std::string &id, const CSettingUrlEncodedString &setting); + ~CSettingUrlEncodedString() override = default; + + SettingPtr Clone(const std::string &id) const override { return std::make_shared<CSettingUrlEncodedString>(id, *this); } + + std::string GetDecodedValue() const; + bool SetDecodedValue(const std::string& decodedValue); + }; +} |