/* * 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 #include #include 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 SettingPtr InitializeFromOldSettingWithoutDefinition(ADDON::CAddonSettings& settings, const std::string& settingId, const typename TSetting::Value& defaultValue) { std::shared_ptr setting = std::make_shared(settingId, settings.GetSettingsManager()); setting->SetLevel(SettingLevel::Internal); setting->SetVisible(false); setting->SetDefault(defaultValue); return setting; } template<> SettingPtr InitializeFromOldSettingWithoutDefinition( ADDON::CAddonSettings& settings, const std::string& settingId, const typename CSettingString::Value& defaultValue) { std::shared_ptr setting = std::make_shared(settingId, settings.GetSettingsManager()); setting->SetLevel(SettingLevel::Internal); setting->SetVisible(false); setting->SetDefault(defaultValue); setting->SetAllowEmpty(true); return setting; } template 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(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("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("0", settings.GetSettingsManager()); else group = groups.back(); // create a new setting on-the-fly auto setting = InitializeFromOldSettingWithoutDefinition(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& 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 CAddonSettings::CreateSetting( const std::string& settingType, const std::string& settingId, CSettingsManager* settingsManager /* = nullptr */) const { if (StringUtils::EqualsNoCase(settingType, "urlencodedstring")) return std::make_shared(settingId, settingsManager); return CSettingCreator::CreateSetting(settingType, settingId, settingsManager); } void CAddonSettings::OnSettingAction(const std::shared_ptr& 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(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(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(ADDON_SETTING_INSTANCE_GROUP, mgr); group->SetLabel(10017); // Add-on configuration category->AddGroupToFront(group); } const std::shared_ptr name = std::make_shared(ADDON_SETTING_INSTANCE_NAME_VALUE, 551, "", mgr); // Name name->SetAllowEmpty(false); name->SetControl(std::make_shared()); if (!mgr->AddSetting(name, section, category, group)) return false; const std::shared_ptr enabled = std::make_shared( ADDON_SETTING_INSTANCE_ENABLED_VALUE, 305, true, mgr); // Enabled enabled->SetControl(std::make_shared()); if (!mgr->AddSetting(enabled, section, category, group)) return false; return true; } bool CAddonSettings::Initialize(const CXBMCTinyXML& doc, bool allowEmpty /* = false */) { std::unique_lock 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 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 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(*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 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 CAddonSettings::AddSetting(const std::string& settingId, bool value) { return AddSettingWithoutDefinition(*this, settingId, value, m_logger); } std::shared_ptr CAddonSettings::AddSetting(const std::string& settingId, int value) { return AddSettingWithoutDefinition(*this, settingId, value, m_logger); } std::shared_ptr CAddonSettings::AddSetting(const std::string& settingId, double value) { return AddSettingWithoutDefinition(*this, settingId, value, m_logger); } std::shared_ptr CAddonSettings::AddSetting(const std::string& settingId, const std::string& value) { return AddSettingWithoutDefinition(*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& 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 tag"); return false; } version = GetSettingsManager()->ParseVersion(root); return true; } std::shared_ptr CAddonSettings::ParseOldSettingElement( const TiXmlElement* categoryElement, const std::shared_ptr& category, std::set& settingIds) { // build a vector of settings from the same category std::vector> categorySettings; // prepare for settings with enable/visible conditions struct SettingWithConditions { SettingPtr setting; std::string enableCondition; std::string visibleCondition; SettingDependencies deps; }; std::vector settingsWithConditions; auto group = std::make_shared("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 CAddonSettings::ParseOldCategoryElement( uint32_t& categoryId, const TiXmlElement* categoryElement, std::set& settingIds) { // create the category auto category = std::make_shared(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 section = std::make_shared(m_addonId, GetSettingsManager()); std::shared_ptr category; uint32_t categoryId = 0; // Settings id set std::set 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(); 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(actionSettingId, GetSettingsManager()); settingAction->SetData(action); setting = settingAction; } else { // assume that the setting might store a value as a string auto settingString = std::make_shared(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 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(labelSettingId, GetSettingsManager()); // create the setting's control settingLabel->SetControl(std::make_shared()); return settingLabel; } SettingPtr CAddonSettings::InitializeFromOldSettingBool(const std::string& settingId, const TiXmlElement* settingElement, const std::string& defaultValue) { auto setting = std::make_shared(settingId, GetSettingsManager()); if (setting->FromString(defaultValue)) setting->SetDefault(setting->GetValue()); setting->SetControl(std::make_shared()); 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 setting; auto control = std::make_shared(); control->SetHeading(settingLabel); // get any options std::string option = XMLUtils::GetAttribute(settingElement, "option"); if (settingType == "ipaddress") { setting = std::make_shared(settingId, GetSettingsManager()); control->SetFormat("ip"); } else if (settingType == "text") { if (StringUtils::EqualsNoCase(option, "urlencoded")) { setting = std::make_shared(settingId, GetSettingsManager()); control->SetFormat("urlencoded"); } else { setting = std::make_shared(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(settingId, GetSettingsManager()); if (setting->FromString(defaultValue)) setting->SetDefault(setting->GetValue()); auto control = std::make_shared(); 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(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(); 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(settingId, GetSettingsManager()); if (setting->FromString(defaultValue)) setting->SetDefault(setting->GetValue()); auto control = std::make_shared(); 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(settingId, GetSettingsManager()); if (setting->FromString(defaultValue)) setting->SetDefault(setting->GetValue()); auto control = std::make_shared(); 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& settingLValues) { // process values and lvalues std::vector 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(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(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(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(); 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 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(settingId, GetSettingsManager()); settingAddon->SetAddonType(*addonTypes.begin()); SettingPtr setting = settingAddon; if (multiselect) { auto settingList = std::make_shared(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(); 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& settingLValues) { // process values and lvalues std::vector 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(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(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(strtol(values[i].c_str(), nullptr, 0)); int value = i; if (settingEntries.size() > i) value = static_cast(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(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(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(); 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(); 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(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(); 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(settingId, GetSettingsManager()); if (setting->FromString(defaultValue)) setting->SetDefault(setting->GetValue()); setting->SetMinimum(min); setting->SetStep(step); setting->SetMaximum(max); auto control = std::make_shared(); 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(settingId, GetSettingsManager()); if (setting->FromString(defaultValue)) setting->SetDefault(setting->GetValue()); setting->SetMinimum(static_cast(min)); setting->SetStep(static_cast(step)); setting->SetMaximum(static_cast(max)); auto control = std::make_shared(); 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(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& 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& setting, const std::vector>& 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 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(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(strtoul(expression.m_value.c_str(), nullptr, 10)); const auto referencedSettingString = std::static_pointer_cast(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( 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( "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( 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& setting, std::vector& list, std::string& current, void* data) { if (setting == nullptr) return; auto settingPath = std::dynamic_pointer_cast(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