diff --git a/doc/Settings.md b/doc/Settings.md index e17085bd0e..8e463f7b28 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -78,6 +78,17 @@ The `locale` behavior affects the choice of installer based on installer locale. } }, ``` +### Architectures + +The `architectures` behavior affects what architectures will be selected when installing a package. The matching parameter is `--architecture`. Note that only architectures compatible with your system can be selected. + +```json + "installBehavior": { + "preferences": { + "architectures": ["x64", "arm64"] + } + }, +``` ## Telemetry diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json index 3b00fa66f4..2a9ab5bbd8 100644 --- a/schemas/JSON/settings/settings.schema.0.2.json +++ b/schemas/JSON/settings/settings.schema.0.2.json @@ -54,6 +54,23 @@ }, "minItems": 1, "maxItems": 10 + }, + "architectures": { + "description": "The architecture(s) to use for a package install", + "type": "array", + "items": { + "uniqueItems": "true", + "type": "string", + "enum": [ + "neutral", + "x64", + "x86", + "arm64", + "arm" + ], + "minItems": 1, + "maxItems": 4 + } } } }, diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index fe6d602d4e..3c10a3cd4f 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -962,14 +962,33 @@ namespace AppInstaller::CLI::Workflow bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); IPackageVersion::Metadata installationMetadata; + if (isUpdate) { installationMetadata = context.Get()->GetMetadata(); } if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) { + // arguments override settings. context.Add({ Utility::ConvertToArchitectureEnum(std::string(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))) }); } + else + { + std::vector requiredArchitectures = Settings::User().Get(); + std::vector optionalArchitectures = Settings::User().Get(); + + + if (!requiredArchitectures.empty()) + { + context.Add({ requiredArchitectures.begin(), requiredArchitectures.end() }); + } + else if (!optionalArchitectures.empty()) + { + optionalArchitectures.emplace_back(Utility::Architecture::Unknown); + context.Add({ optionalArchitectures.begin(), optionalArchitectures.end() }); + + } + } ManifestComparator manifestComparator(context, installationMetadata); auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(context.Get()); diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index fc5147c39a..7cf95b2ec1 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -13,6 +13,8 @@ #include #include +#include "AppInstallerArchitecture.h" + using namespace std::chrono_literals; using namespace std::string_view_literals; @@ -54,6 +56,7 @@ namespace AppInstaller::Settings DeliveryOptimization, }; + // Enum of settings. // Must start at 0 to enable direct access to variant in UserSettings. // Max must be last and unused. @@ -73,6 +76,8 @@ namespace AppInstaller::Settings InstallScopeRequirement, NetworkDownloader, NetworkDOProgressTimeoutInSeconds, + InstallArchitecturePreference, + InstallArchitectureRequirement, InstallLocalePreference, InstallLocaleRequirement, EFDirectMSI, @@ -116,6 +121,8 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDependencies, bool, bool, false, ".experimentalFeatures.dependencies"sv); SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitecturePreference, std::vector, std::vector, {}, ".installBehavior.preferences.architectures"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitectureRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.architectures"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopePreference, std::string, ScopePreference, ScopePreference::User, ".installBehavior.preferences.scope"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopeRequirement, std::string, ScopePreference, ScopePreference::None, ".installBehavior.requirements.scope"sv); SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 62dd69ea6a..8ea3b34585 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -1,334 +1,355 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerRuntime.h" -#include "AppInstallerLanguageUtilities.h" -#include "AppInstallerLogging.h" -#include "JsonUtil.h" -#include "winget/Settings.h" -#include "winget/UserSettings.h" -#include "winget/Locale.h" - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - using namespace Runtime; - using namespace Utility; - - static constexpr std::string_view s_SettingEmpty = - R"({ - "$schema": "https://aka.ms/winget-settings.schema.json", - - // For documentation on these settings, see: https://aka.ms/winget-settings - // "source": { - // "autoUpdateIntervalInMinutes": 5 - // }, -})"sv; - - namespace - { - template - inline std::string GetValueString(T value) - { - std::string convertedValue; - - if constexpr (std::is_arithmetic_v) - { - convertedValue = std::to_string(value); - } - else - { - convertedValue = value; - } - - return convertedValue; - } - - template<> - inline std::string GetValueString(std::vector value) - { - std::string convertedValue = "["; - - bool first = true; - for (auto const& entry : value) - { - if (first) - { - first = false; - } - else - { - convertedValue += ", "; - } - - convertedValue += entry; - } - - convertedValue += ']'; - - return convertedValue; - } - - std::optional ParseFile(const StreamDefinition& setting, std::vector& warnings) - { - auto stream = Stream{ setting }.Get(); - if (stream) - { - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); - - std::string settingsContentStr = Utility::ReadEntireStream(*stream); - std::string error; - - if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) - { - return root; - } - - AICLI_LOG(Core, Error, << "Error parsing " << setting.Name << ": " << error); - warnings.emplace_back(StringResource::String::SettingsWarningParseError, setting.Name, error, false); - } - - return {}; - } - - template - std::optional::json_t> GetValueFromPolicy() - { - return GroupPolicies().GetValue::Policy>(); - } - - template - void Validate( - Json::Value& root, - std::map& settings, - std::vector& warnings) - { - // jsoncpp doesn't support std::string_view yet. - auto path = std::string(details::SettingMapping::Path); - - // Settings set by Group Policy override anything else. See if there is one. - auto policyValue = GetValueFromPolicy(); - if (policyValue.has_value()) - { - // If the value is valid, use it. - // Otherwise, fall back to default. - // In any case, we do not need to read the setting from the JSON. - auto validatedValue = details::SettingMapping::Validate(policyValue.value()); - if (validatedValue.has_value()) - { - // Add it to the map - settings[S].emplace( - std::forward::value_t>(validatedValue.value())); - AICLI_LOG(Core, Verbose, << "Valid setting from Group Policy. Field: " << path << " Value: " << GetValueString(policyValue.value())); - } - else - { - auto valueAsString = GetValueString(policyValue.value()); - AICLI_LOG(Core, Error, << "Invalid setting from Group Policy. Field: " << path << " Value: " << valueAsString); - warnings.emplace_back(StringResource::String::SettingsWarningInvalidValueFromPolicy, path, valueAsString); - } - - return; - } - - const Json::Path jsonPath(path); - Json::Value result = jsonPath.resolve(root); - if (!result.isNull()) - { - auto jsonValue = GetValue::json_t>(result); - - if (jsonValue.has_value()) - { - auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); - - if (validatedValue.has_value()) - { - // Finally add it to the map - settings[S].emplace( - std::forward::value_t>(validatedValue.value())); - AICLI_LOG(Core, Verbose, << "Valid setting. Field: " << path << " Value: " << GetValueString(jsonValue.value())); - } - else - { - auto valueAsString = GetValueString(jsonValue.value()); - AICLI_LOG(Core, Error, << "Invalid field value. Field: " << path << " Value: " << valueAsString); - warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldValue, path, valueAsString); - } - } - else - { - AICLI_LOG(Core, Error, << "Invalid field format. Field: " << path << " Using default"); - warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldFormat, path); - } - } - else - { - AICLI_LOG(Core, Verbose, << "Setting " << path << " not found. Using default"); - } - } - - template - void ValidateAll( - Json::Value& root, - std::map& settings, - std::vector& warnings, - std::index_sequence) - { - // Use folding to call each setting validate function. - (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); - } - } - - namespace details - { -#define WINGET_VALIDATE_SIGNATURE(_setting_) \ - std::optional::value_t> \ - SettingMapping::Validate(const SettingMapping::json_t& value) - - // Stamps out a validate function that simply returns the input value. -#define WINGET_VALIDATE_PASS_THROUGH(_setting_) \ - WINGET_VALIDATE_SIGNATURE(_setting_) \ - { \ - return value; \ - } - - WINGET_VALIDATE_SIGNATURE(AutoUpdateTimeInMinutes) - { - return std::chrono::minutes(value); - } - - WINGET_VALIDATE_SIGNATURE(ProgressBarVisualStyle) - { - // progressBar property possible values - static constexpr std::string_view s_progressBar_Accent = "accent"; - static constexpr std::string_view s_progressBar_Rainbow = "rainbow"; - static constexpr std::string_view s_progressBar_Retro = "retro"; - - if (Utility::CaseInsensitiveEquals(value, s_progressBar_Accent)) - { - return VisualStyle::Accent; - } - else if (Utility::CaseInsensitiveEquals(value, s_progressBar_Rainbow)) - { - return VisualStyle::Rainbow; - } - else if (Utility::CaseInsensitiveEquals(value, s_progressBar_Retro)) - { - return VisualStyle::Retro; - } - - return {}; - } - - WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) - WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) - WINGET_VALIDATE_PASS_THROUGH(EFDependencies) - WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) - WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) - WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) - - WINGET_VALIDATE_SIGNATURE(InstallScopePreference) - { - static constexpr std::string_view s_scope_user = "user"; - static constexpr std::string_view s_scope_machine = "machine"; - - if (Utility::CaseInsensitiveEquals(value, s_scope_user)) - { - return ScopePreference::User; - } - else if (Utility::CaseInsensitiveEquals(value, s_scope_machine)) - { - return ScopePreference::Machine; - } - - return {}; - } - - WINGET_VALIDATE_SIGNATURE(InstallScopeRequirement) - { - return SettingMapping::Validate(value); - } - - WINGET_VALIDATE_SIGNATURE(InstallLocalePreference) - { - for (auto const& entry : value) - { - if (!Locale::IsWellFormedBcp47Tag(entry)) - { - return {}; - } - } - - return value; - } - - WINGET_VALIDATE_SIGNATURE(InstallLocaleRequirement) - { - return SettingMapping::Validate(value); - } - - WINGET_VALIDATE_SIGNATURE(NetworkDownloader) - { - static constexpr std::string_view s_downloader_default = "default"; - static constexpr std::string_view s_downloader_wininet = "wininet"; - static constexpr std::string_view s_downloader_do = "do"; - - if (Utility::CaseInsensitiveEquals(value, s_downloader_default)) - { - return InstallerDownloader::Default; - } - else if (Utility::CaseInsensitiveEquals(value, s_downloader_wininet)) - { - return InstallerDownloader::WinInet; - } - else if (Utility::CaseInsensitiveEquals(value, s_downloader_do)) - { - return InstallerDownloader::DeliveryOptimization; - } - - return {}; - } - - WINGET_VALIDATE_SIGNATURE(NetworkDOProgressTimeoutInSeconds) - { - return std::chrono::seconds(value); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static UserSettings* s_UserSettings_Override = nullptr; - - void SetUserSettingsOverride(UserSettings* value) - { - s_UserSettings_Override = value; - } -#endif - - static std::atomic_bool s_userSettingsInitialized{ false }; - static std::atomic_bool s_userSettingsInInitialization{ false }; - - UserSettings const& UserSettings::Instance() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_UserSettings_Override) - { - return *s_UserSettings_Override; - } -#endif - if (!s_userSettingsInitialized) - { - s_userSettingsInInitialization = true; - } - - static UserSettings userSettings; - s_userSettingsInitialized = true; - s_userSettingsInInitialization = false; - - return userSettings; - } - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerRuntime.h" +#include "AppInstallerLanguageUtilities.h" +#include "AppInstallerLogging.h" +#include "JsonUtil.h" +#include "winget/Settings.h" +#include "winget/UserSettings.h" + +#include "AppInstallerArchitecture.h" +#include "winget/Locale.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Runtime; + using namespace Utility; + + static constexpr std::string_view s_SettingEmpty = + R"({ + "$schema": "https://aka.ms/winget-settings.schema.json", + + // For documentation on these settings, see: https://aka.ms/winget-settings + // "source": { + // "autoUpdateIntervalInMinutes": 5 + // }, +})"sv; + + namespace + { + template + inline std::string GetValueString(T value) + { + std::string convertedValue; + + if constexpr (std::is_arithmetic_v) + { + convertedValue = std::to_string(value); + } + else + { + convertedValue = value; + } + + return convertedValue; + } + + template<> + inline std::string GetValueString(std::vector value) + { + std::string convertedValue = "["; + + bool first = true; + for (auto const& entry : value) + { + if (first) + { + first = false; + } + else + { + convertedValue += ", "; + } + + convertedValue += entry; + } + + convertedValue += ']'; + + return convertedValue; + } + + std::optional ParseFile(const StreamDefinition& setting, std::vector& warnings) + { + auto stream = Stream{ setting }.Get(); + if (stream) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + + std::string settingsContentStr = Utility::ReadEntireStream(*stream); + std::string error; + + if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) + { + return root; + } + + AICLI_LOG(Core, Error, << "Error parsing " << setting.Name << ": " << error); + warnings.emplace_back(StringResource::String::SettingsWarningParseError, setting.Name, error, false); + } + + return {}; + } + + template + std::optional::json_t> GetValueFromPolicy() + { + return GroupPolicies().GetValue::Policy>(); + } + + template + void Validate( + Json::Value& root, + std::map& settings, + std::vector& warnings) + { + // jsoncpp doesn't support std::string_view yet. + auto path = std::string(details::SettingMapping::Path); + + // Settings set by Group Policy override anything else. See if there is one. + auto policyValue = GetValueFromPolicy(); + if (policyValue.has_value()) + { + // If the value is valid, use it. + // Otherwise, fall back to default. + // In any case, we do not need to read the setting from the JSON. + auto validatedValue = details::SettingMapping::Validate(policyValue.value()); + if (validatedValue.has_value()) + { + // Add it to the map + settings[S].emplace( + std::forward::value_t>(validatedValue.value())); + AICLI_LOG(Core, Verbose, << "Valid setting from Group Policy. Field: " << path << " Value: " << GetValueString(policyValue.value())); + } + else + { + auto valueAsString = GetValueString(policyValue.value()); + AICLI_LOG(Core, Error, << "Invalid setting from Group Policy. Field: " << path << " Value: " << valueAsString); + warnings.emplace_back(StringResource::String::SettingsWarningInvalidValueFromPolicy, path, valueAsString); + } + + return; + } + + const Json::Path jsonPath(path); + Json::Value result = jsonPath.resolve(root); + if (!result.isNull()) + { + auto jsonValue = GetValue::json_t>(result); + + if (jsonValue.has_value()) + { + auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); + + if (validatedValue.has_value()) + { + // Finally add it to the map + settings[S].emplace( + std::forward::value_t>(validatedValue.value())); + AICLI_LOG(Core, Verbose, << "Valid setting. Field: " << path << " Value: " << GetValueString(jsonValue.value())); + } + else + { + auto valueAsString = GetValueString(jsonValue.value()); + AICLI_LOG(Core, Error, << "Invalid field value. Field: " << path << " Value: " << valueAsString); + warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldValue, path, valueAsString); + } + } + else + { + AICLI_LOG(Core, Error, << "Invalid field format. Field: " << path << " Using default"); + warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldFormat, path); + } + } + else + { + AICLI_LOG(Core, Verbose, << "Setting " << path << " not found. Using default"); + } + } + + template + void ValidateAll( + Json::Value& root, + std::map& settings, + std::vector& warnings, + std::index_sequence) + { + // Use folding to call each setting validate function. + (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); + } + } + + namespace details + { +#define WINGET_VALIDATE_SIGNATURE(_setting_) \ + std::optional::value_t> \ + SettingMapping::Validate(const SettingMapping::json_t& value) + + // Stamps out a validate function that simply returns the input value. +#define WINGET_VALIDATE_PASS_THROUGH(_setting_) \ + WINGET_VALIDATE_SIGNATURE(_setting_) \ + { \ + return value; \ + } + + WINGET_VALIDATE_SIGNATURE(AutoUpdateTimeInMinutes) + { + return std::chrono::minutes(value); + } + + WINGET_VALIDATE_SIGNATURE(ProgressBarVisualStyle) + { + // progressBar property possible values + static constexpr std::string_view s_progressBar_Accent = "accent"; + static constexpr std::string_view s_progressBar_Rainbow = "rainbow"; + static constexpr std::string_view s_progressBar_Retro = "retro"; + + if (Utility::CaseInsensitiveEquals(value, s_progressBar_Accent)) + { + return VisualStyle::Accent; + } + else if (Utility::CaseInsensitiveEquals(value, s_progressBar_Rainbow)) + { + return VisualStyle::Rainbow; + } + else if (Utility::CaseInsensitiveEquals(value, s_progressBar_Retro)) + { + return VisualStyle::Retro; + } + + return {}; + } + + WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) + WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) + WINGET_VALIDATE_PASS_THROUGH(EFDependencies) + WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) + WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) + WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) + + WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference) + { + std::vector archs; + for (auto const& i : value) { + Utility::Architecture arch = Utility::ConvertToArchitectureEnum(i); + if (Utility::IsApplicableArchitecture(arch) == Utility::InapplicableArchitecture) + { + return {}; + } + archs.emplace_back(arch); + } + return archs; + } + + WINGET_VALIDATE_SIGNATURE(InstallArchitectureRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(InstallScopePreference) + { + static constexpr std::string_view s_scope_user = "user"; + static constexpr std::string_view s_scope_machine = "machine"; + + if (Utility::CaseInsensitiveEquals(value, s_scope_user)) + { + return ScopePreference::User; + } + else if (Utility::CaseInsensitiveEquals(value, s_scope_machine)) + { + return ScopePreference::Machine; + } + + return {}; + } + + WINGET_VALIDATE_SIGNATURE(InstallScopeRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(InstallLocalePreference) + { + for (auto const& entry : value) + { + if (!Locale::IsWellFormedBcp47Tag(entry)) + { + return {}; + } + } + + return value; + } + + WINGET_VALIDATE_SIGNATURE(InstallLocaleRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(NetworkDownloader) + { + static constexpr std::string_view s_downloader_default = "default"; + static constexpr std::string_view s_downloader_wininet = "wininet"; + static constexpr std::string_view s_downloader_do = "do"; + + if (Utility::CaseInsensitiveEquals(value, s_downloader_default)) + { + return InstallerDownloader::Default; + } + else if (Utility::CaseInsensitiveEquals(value, s_downloader_wininet)) + { + return InstallerDownloader::WinInet; + } + else if (Utility::CaseInsensitiveEquals(value, s_downloader_do)) + { + return InstallerDownloader::DeliveryOptimization; + } + + return {}; + } + + WINGET_VALIDATE_SIGNATURE(NetworkDOProgressTimeoutInSeconds) + { + return std::chrono::seconds(value); + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static UserSettings* s_UserSettings_Override = nullptr; + + void SetUserSettingsOverride(UserSettings* value) + { + s_UserSettings_Override = value; + } +#endif + + static std::atomic_bool s_userSettingsInitialized{ false }; + static std::atomic_bool s_userSettingsInInitialization{ false }; + + UserSettings const& UserSettings::Instance() + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_UserSettings_Override) + { + return *s_UserSettings_Override; + } +#endif + if (!s_userSettingsInitialized) + { + s_userSettingsInInitialization = true; + } + + static UserSettings userSettings; + s_userSettingsInitialized = true; + s_userSettingsInInitialization = false; + + return userSettings; + } + const UserSettings* TryGetUser() { if (s_userSettingsInitialized) @@ -348,82 +369,82 @@ namespace AppInstaller::Settings UserSettings const& User() { return UserSettings::Instance(); - } - - UserSettings::UserSettings() : m_type(UserSettingsType::Default) - { - Json::Value settingsRoot = Json::Value::nullSingleton(); - - // Settings can be loaded from settings.json or settings.json.backup files. - // 0 - Use default (empty) settings if disabled by group policy. - // 1 - Use settings.json if exists and passes parsing. - // 2 - Use settings.backup.json if settings.json fails to parse. - // 3 - Use default (empty) if both settings files fail to load. - - if (!GroupPolicies().IsEnabled(TogglePolicy::Policy::Settings)) - { - AICLI_LOG(Core, Info, << "Ignoring settings file due to group policy. Using default values."); - return; - } - - auto settingsJson = ParseFile(Stream::PrimaryUserSettings, m_warnings); - if (settingsJson.has_value()) - { - AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::PrimaryUserSettings.Name); - m_type = UserSettingsType::Standard; - settingsRoot = settingsJson.value(); - } - - // Settings didn't parse or doesn't exist, try with backup. - if (settingsRoot.isNull()) - { - auto settingsBackupJson = ParseFile(Stream::BackupUserSettings, m_warnings); - if (settingsBackupJson.has_value()) - { - AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::BackupUserSettings.Name); - m_warnings.emplace_back(StringResource::String::SettingsWarningLoadedBackupSettings); - m_type = UserSettingsType::Backup; - settingsRoot = settingsBackupJson.value(); - } - } - - if (!settingsRoot.isNull()) - { - ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence(Setting::Max)>()); - } - else - { - AICLI_LOG(Core, Info, << "Valid settings file not found. Using default values."); - } - } - - void UserSettings::PrepareToShellExecuteFile() const - { - UserSettingsType userSettingType = GetType(); - - if (userSettingType == UserSettingsType::Default) - { - Stream primarySettings{ Stream::PrimaryUserSettings }; - - // Create settings file if it doesn't exist. - if (!std::filesystem::exists(primarySettings.GetPath())) - { - std::ignore = primarySettings.Set(s_SettingEmpty); - AICLI_LOG(Core, Info, << "Created new settings file"); - } - } - else if (userSettingType == UserSettingsType::Standard) - { - // Settings file was loaded correctly, create backup. - auto from = SettingsFilePath(); - auto to = Stream{ Stream::BackupUserSettings }.GetPath(); - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); - AICLI_LOG(Core, Info, << "Copied settings to backup file"); - } - } - - std::filesystem::path UserSettings::SettingsFilePath() - { - return Stream{ Stream::PrimaryUserSettings }.GetPath(); - } -} + } + + UserSettings::UserSettings() : m_type(UserSettingsType::Default) + { + Json::Value settingsRoot = Json::Value::nullSingleton(); + + // Settings can be loaded from settings.json or settings.json.backup files. + // 0 - Use default (empty) settings if disabled by group policy. + // 1 - Use settings.json if exists and passes parsing. + // 2 - Use settings.backup.json if settings.json fails to parse. + // 3 - Use default (empty) if both settings files fail to load. + + if (!GroupPolicies().IsEnabled(TogglePolicy::Policy::Settings)) + { + AICLI_LOG(Core, Info, << "Ignoring settings file due to group policy. Using default values."); + return; + } + + auto settingsJson = ParseFile(Stream::PrimaryUserSettings, m_warnings); + if (settingsJson.has_value()) + { + AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::PrimaryUserSettings.Name); + m_type = UserSettingsType::Standard; + settingsRoot = settingsJson.value(); + } + + // Settings didn't parse or doesn't exist, try with backup. + if (settingsRoot.isNull()) + { + auto settingsBackupJson = ParseFile(Stream::BackupUserSettings, m_warnings); + if (settingsBackupJson.has_value()) + { + AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::BackupUserSettings.Name); + m_warnings.emplace_back(StringResource::String::SettingsWarningLoadedBackupSettings); + m_type = UserSettingsType::Backup; + settingsRoot = settingsBackupJson.value(); + } + } + + if (!settingsRoot.isNull()) + { + ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence(Setting::Max)>()); + } + else + { + AICLI_LOG(Core, Info, << "Valid settings file not found. Using default values."); + } + } + + void UserSettings::PrepareToShellExecuteFile() const + { + UserSettingsType userSettingType = GetType(); + + if (userSettingType == UserSettingsType::Default) + { + Stream primarySettings{ Stream::PrimaryUserSettings }; + + // Create settings file if it doesn't exist. + if (!std::filesystem::exists(primarySettings.GetPath())) + { + std::ignore = primarySettings.Set(s_SettingEmpty); + AICLI_LOG(Core, Info, << "Created new settings file"); + } + } + else if (userSettingType == UserSettingsType::Standard) + { + // Settings file was loaded correctly, create backup. + auto from = SettingsFilePath(); + auto to = Stream{ Stream::BackupUserSettings }.GetPath(); + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + AICLI_LOG(Core, Info, << "Copied settings to backup file"); + } + } + + std::filesystem::path UserSettings::SettingsFilePath() + { + return Stream{ Stream::PrimaryUserSettings }.GetPath(); + } +}