diff --git a/doc/Settings.md b/doc/Settings.md new file mode 100644 index 0000000000..9b25e3a8e2 --- /dev/null +++ b/doc/Settings.md @@ -0,0 +1,46 @@ +# WinGet CLI Settings + +You can configure WinGet by editing the `settings.json` file. The file can be open with the default json editor by running `winget settings`. If no editor is configure, notepad.exe will be used. + +## File Location + +Settings file is located in %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json + +If you are using the non-packaged winget version by building it from source code the file will %LOCALAPPDATA%\Microsoft\WinGet\Settings\settings.json + +## Source + +These settings involve configuration to the WinGet source. + +``` + "source": { + "autoUpdateIntervalInMinutes": 3 + }, +``` + +### autoUpdateIntervalInMinutes + +Positive integer that represents the interval in minutes of how often to automatically check for updates to a WinGet source. The check for updates only happens when a source is used, and if no update is available the interval will be reset. + +- Minimum: 0 +- Default: 5 + +To manually update the source use `winget source update` + +## Visual + +These settings involve visual elements that are displayed by WinGet + +``` + "visual": { + "progressBar": "accent" + } +``` + +### progressBar + +Color of the progress bar that WinGet displays when not specified by arguments. + +- accent (default) +- retro +- rainbow \ No newline at end of file diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj index db70e78cec..e656d737b0 100644 --- a/src/AppInstallerCLI/AppInstallerCLI.vcxproj +++ b/src/AppInstallerCLI/AppInstallerCLI.vcxproj @@ -208,6 +208,9 @@ {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + {82b39fda-e86b-4713-a873-9d56de00247a} + {8bb94bb8-374f-4294-bca1-c7811514a6b7} diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 116a992020..9b6fd840be 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -122,9 +122,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -139,7 +139,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -152,10 +152,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -181,6 +181,7 @@ + @@ -209,6 +210,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 0e1ecbb044..e4405dbbbc 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -102,6 +102,9 @@ Header Files + + Commands + @@ -170,6 +173,9 @@ Source Files + + Commands + diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 78448f53ab..c51aa16105 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -72,8 +72,8 @@ namespace AppInstaller::CLI return Argument{ "no-vt", None, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::RainbowStyle: return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; - case Args::Type::PlainStyle: - return Argument{ "plain", None, Args::Type::PlainStyle, Resource::String::PlainArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; + case Args::Type::RetroStyle: + return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::Force: return Argument{ "force", None, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag }; case Args::Type::VerboseLogs: @@ -88,7 +88,7 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::Help)); args.push_back(ForType(Args::Type::NoVT)); args.push_back(ForType(Args::Type::RainbowStyle)); - args.push_back(ForType(Args::Type::PlainStyle)); + args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); } } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index 45b3422f3f..7fdd0ba269 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -9,6 +9,7 @@ #include "SearchCommand.h" #include "HashCommand.h" #include "ValidateCommand.h" +#include "SettingsCommand.h" #include "Resources.h" #include "TableOutput.h" @@ -26,6 +27,7 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), + std::make_unique(FullName()), }); } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp new file mode 100644 index 0000000000..ddf3dc4310 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SettingsCommand.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" +#include + +namespace AppInstaller::CLI +{ + using namespace Utility::literals; + using namespace AppInstaller::Settings; + + using namespace std::string_view_literals; + + std::vector SettingsCommand::GetArguments() const + { + return {}; + } + + Resource::LocString SettingsCommand::ShortDescription() const + { + return { Resource::String::SettingsCommandShortDescription }; + } + + Resource::LocString SettingsCommand::LongDescription() const + { + return { Resource::String::SettingsCommandLongDescription }; + } + + std::string SettingsCommand::HelpLink() const + { + return "https://aka.ms/winget-settings"; + } + + void SettingsCommand::ExecuteInternal(Execution::Context& context) const + { + // Show warnings only when the setting command is executed. + if (!User().GetWarnings().empty()) + { + context.Reporter.Warn() << Resource::String::SettingLoadFailure << std::endl; + for (const auto& warning : User().GetWarnings()) + { + context.Reporter.Warn() << warning << std::endl; + } + } + + User().PrepareToShellExecuteFile(); + + auto filePathUTF16 = UserSettings::SettingsFilePath().wstring(); + + // Some versions of windows will fail if no file extension association exists, other will pop up the dialog + // to make the user pick their default. + // Kudos to the terminal team for this work around. + HINSTANCE res = ShellExecuteW(nullptr, nullptr, filePathUTF16.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast(reinterpret_cast(res)) <= 32) + { + // User doesn't have file type association. Default to notepad + ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); + } + } +} diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.h b/src/AppInstallerCLICore/Commands/SettingsCommand.h new file mode 100644 index 0000000000..a170543d88 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct SettingsCommand final : public Command + { + SettingsCommand(std::string_view parent) : Command("settings", parent) {} + + virtual std::vector GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index f0b9b8a41b..519309fef9 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -4,6 +4,7 @@ #include "Public/AppInstallerCLICore.h" #include "Commands/RootCommand.h" #include "ExecutionContext.h" +#include using namespace winrt; using namespace winrt::Windows::Foundation; @@ -110,6 +111,11 @@ namespace AppInstaller::CLI try { + if (!Settings::User().GetWarnings().empty()) + { + context.Reporter.Warn() << Resource::String::SettingsWarnings << std::endl; + } + command->Execute(context); } // Exceptions that may occur in the process of executing an arbitrary command diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index dbeb5e3b4d..58fa72f845 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -54,7 +54,7 @@ namespace AppInstaller::CLI::Execution Force, // Generic flag to enable a command to skip some check ListVersions, // Used in Show command to list all available versions of an app NoVT, // Disable VirtualTerminal outputs - PlainStyle, // Makes progress display as plain + RetroStyle, // Makes progress display as retro RainbowStyle, // Makes progress display as a rainbow Help, // Show command usage Info, // Show general info about WinGet diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index caf964e2fe..4d6558d6b7 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -2,10 +2,12 @@ // Licensed under the MIT License. #include "pch.h" #include "ExecutionContext.h" - +#include "winget/UserSettings.h" namespace AppInstaller::CLI::Execution { + using namespace Settings; + namespace { // The context that will receive CTRL signals @@ -80,14 +82,18 @@ namespace AppInstaller::CLI::Execution { Reporter.SetStyle(VisualStyle::NoVT); } - else if (Args.Contains(Args::Type::PlainStyle)) + else if (Args.Contains(Args::Type::RetroStyle)) { - Reporter.SetStyle(VisualStyle::Plain); + Reporter.SetStyle(VisualStyle::Retro); } else if (Args.Contains(Args::Type::RainbowStyle)) { Reporter.SetStyle(VisualStyle::Rainbow); } + else + { + Reporter.SetStyle(User().Get()); + } } void Context::Terminate(HRESULT hr) diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index 157dc53887..3912f5ec71 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -37,7 +37,7 @@ namespace AppInstaller::CLI::Workflow namespace AppInstaller::CLI::Execution { - // Names a peice of data stored in the context by a workflow step. + // Names a piece of data stored in the context by a workflow step. // Must start at 0 to enable direct access to variant in Context. // Max must be last and unused. enum class Data : size_t diff --git a/src/AppInstallerCLICore/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp index 3d81c7cfdc..609f04db54 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.cpp +++ b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -3,9 +3,9 @@ #include "pch.h" #include "ExecutionProgress.h" - namespace AppInstaller::CLI::Execution { + using namespace Settings; using namespace VirtualTerminal; using namespace std::string_view_literals; @@ -135,10 +135,10 @@ namespace AppInstaller::CLI::Execution { switch (m_style) { - case AppInstaller::CLI::Execution::VisualStyle::NoVT: + case VisualStyle::NoVT: // No VT means no style set break; - case AppInstaller::CLI::Execution::VisualStyle::Plain: + case VisualStyle::Retro: if (enabled) { m_out << TextFormat::Default; @@ -148,10 +148,10 @@ namespace AppInstaller::CLI::Execution m_out << TextFormat::Negative; } break; - case AppInstaller::CLI::Execution::VisualStyle::Accent: + case VisualStyle::Accent: SetColor(m_out, TextFormat::Color::GetAccentColor(), enabled); break; - case AppInstaller::CLI::Execution::VisualStyle::Rainbow: + case VisualStyle::Rainbow: SetRainbowColor(m_out, i, max, enabled); break; default: diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h index 326452ab46..f3eec53623 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.h +++ b/src/AppInstallerCLICore/ExecutionProgress.h @@ -3,6 +3,7 @@ #pragma once #include "VTSupport.h" #include +#include #include @@ -13,18 +14,8 @@ #include #include - namespace AppInstaller::CLI::Execution { - // The visual style of the progress bar. - enum class VisualStyle - { - NoVT, - Plain, - Accent, - Rainbow, - }; - namespace details { // Shared functionality for progress visualizers. @@ -33,13 +24,13 @@ namespace AppInstaller::CLI::Execution ProgressVisualizerBase(std::ostream& stream, bool enableVT) : m_out(stream), m_enableVT(enableVT) {} - void SetStyle(VisualStyle style) { m_style = style; } + void SetStyle(AppInstaller::Settings::VisualStyle style) { m_style = style; } protected: std::ostream& m_out; - VisualStyle m_style = VisualStyle::Accent; + Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent; - bool UseVT() const { return m_enableVT && m_style != VisualStyle::NoVT; } + bool UseVT() const { return m_enableVT && m_style != AppInstaller::Settings::VisualStyle::NoVT; } // Applies the selected visual style. void ApplyStyle(size_t i, size_t max, bool enabled); @@ -80,7 +71,7 @@ namespace AppInstaller::CLI::Execution void EndProgress(bool hideProgressWhenDone); - void SetStyle(VisualStyle style) { m_style = style; } + void SetStyle(AppInstaller::Settings::VisualStyle style) { m_style = style; } private: std::atomic m_isVisible = false; diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 0c980fd2a1..b6e3fae8e4 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -6,6 +6,7 @@ namespace AppInstaller::CLI::Execution { + using namespace Settings; using namespace VirtualTerminal; const Sequence& HelpCommandEmphasis = TextFormat::Foreground::BrightWhite; diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index 8febfc33c4..7b37d4ba25 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -124,7 +124,7 @@ namespace AppInstaller::CLI::Execution void EmptyLine() { m_out << std::endl; } // Sets the visual style (mostly for progress currently) - void SetStyle(VisualStyle style); + void SetStyle(AppInstaller::Settings::VisualStyle style); // Prompts the user, return true if they consented. bool PromptForBoolResponse(const std::string& msg, Level level = Level::Info); diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index c87c3433f1..a4fedb61ae 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -79,14 +79,18 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Package); WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); - WINGET_DEFINE_RESOURCE_STRINGID(PlainArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PreviewVersion); WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); + WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index cb41df768e..cddc07cf40 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -281,9 +281,6 @@ Oops, we forgot to do this... - - Progress display as the default color - Preview @@ -299,12 +296,27 @@ Required argument not provided + + Progress display as the default color + Searches for applications from configured sources. Find and show basic info of apps + + The following failures were found validating the settings: + + + Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings + + + Open settings + + + Unexpected error while loading settings. Please verify your settings by running the settings command. + Shows information on a specific application. diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index bacf7d0b8f..62fc8f0cc5 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -108,7 +108,7 @@ Disabled _DEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -126,7 +126,7 @@ WIN32;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -145,8 +145,8 @@ true true NDEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true @@ -184,6 +184,7 @@ + @@ -393,6 +394,9 @@ {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + {82b39fda-e86b-4713-a873-9d56de00247a} + {8bb94bb8-374f-4294-bca1-c7811514a6b7} @@ -410,4 +414,4 @@ - + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index a4dbd348e8..d348d410a9 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -86,6 +86,9 @@ Source Files + + Source Files + diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp new file mode 100644 index 0000000000..74d8851f06 --- /dev/null +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +#include + +#include +#include +#include + +using namespace AppInstaller::Settings; +using namespace AppInstaller::Runtime; +using namespace std::string_view_literals; +using namespace std::chrono_literals; + +namespace +{ + static constexpr std::string_view s_goodJson = "{}"; + static constexpr std::string_view s_badJson = "{"; + static constexpr std::string_view s_settings = "settings.json"sv; + static constexpr std::string_view s_settingsBackup = "settings.json.backup"sv; + + std::filesystem::path GetBackupPath() + { + return GetPathTo(PathName::UserFileSettings) / s_settingsBackup; + } + + void DeleteUserSettingsFiles() + { + auto settingsPath = UserSettings::SettingsFilePath(); + if (std::filesystem::exists(settingsPath)) + { + std::filesystem::remove(settingsPath); + } + + auto settingsBackupPath = GetBackupPath(); + if (std::filesystem::exists(settingsBackupPath)) + { + std::filesystem::remove(settingsBackupPath); + } + } + + struct UserSettingsTest : UserSettings + { + }; +} + +TEST_CASE("UserSettingsFilePaths", "[settings]") +{ + auto settingsPath = UserSettings::SettingsFilePath(); + auto expectedPath = GetPathTo(PathName::UserFileSettings) / "settings.json"; + REQUIRE(settingsPath == expectedPath); +} + +TEST_CASE("UserSettingsType", "[settings]") +{ + // These are all the possible combinations between (7 of them are impossible): + // 1 - No settings.json file exists + // 2 - Bad settings.json file + // 3 - No settings.json.backup file exists + // 4 - Bad settings.json.backup file exists. + DeleteUserSettingsFiles(); + + SECTION("No setting.json No setting.json.backup") + { + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); + } + SECTION("No setting.json Bad setting.json.backup") + { + SetSetting(Type::UserFile, s_settingsBackup, s_badJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); + } + SECTION("No setting.json Good setting.json.backup") + { + SetSetting(Type::UserFile, s_settingsBackup, s_goodJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Backup); + } + SECTION("Bad setting.json No setting.json.backup") + { + SetSetting(Type::UserFile, s_settings, s_badJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); + } + SECTION("Bad setting.json Bad setting.json.backup") + { + SetSetting(Type::UserFile, s_settings, s_badJson); + SetSetting(Type::UserFile, s_settingsBackup, s_badJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); + } + SECTION("Bad setting.json Good setting.json.backup") + { + SetSetting(Type::UserFile, s_settings, s_badJson); + SetSetting(Type::UserFile, s_settingsBackup, s_goodJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Backup); + } + SECTION("Good setting.json No setting.json.backup") + { + SetSetting(Type::UserFile, s_settings, s_goodJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); + } + SECTION("Good setting.json Bad setting.json.backup") + { + SetSetting(Type::UserFile, s_settings, s_goodJson); + SetSetting(Type::UserFile, s_settingsBackup, s_badJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); + } + SECTION("Good setting.json Good setting.json.backup") + { + SetSetting(Type::UserFile, s_settings, s_goodJson); + SetSetting(Type::UserFile, s_settingsBackup, s_goodJson); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); + } +} + +TEST_CASE("UserSettingsCreateFiles", "[settings]") +{ + DeleteUserSettingsFiles(); + + auto settingsPath = UserSettings::SettingsFilePath(); + auto settingsBackupPath = GetBackupPath(); + + SECTION("No settings.json create new") + { + REQUIRE(!std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); + userSettingTest.PrepareToShellExecuteFile(); + + REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); + } + SECTION("Good settings.json create new backup") + { + SetSetting(Type::UserFile, s_settings, s_goodJson); + REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); + + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); + userSettingTest.PrepareToShellExecuteFile(); + + REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(std::filesystem::exists(settingsBackupPath)); + } +} + +TEST_CASE("SettingProgressBar", "[settings]") +{ + DeleteUserSettingsFiles(); + + SECTION("Default value") + { + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Accent") + { + std::string_view json = R"({ "visual": { "progressBar": "accent" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Rainbow") + { + std::string_view json = R"({ "visual": { "progressBar": "rainbow" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Rainbow); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("retro") + { + std::string_view json = R"({ "visual": { "progressBar": "retro" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Retro); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Bad value") + { + std::string_view json = R"({ "visual": { "progressBar": "fake" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } + SECTION("Bad value type") + { + std::string_view json = R"({ "visual": { "progressBar": 5 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} + +TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") +{ + DeleteUserSettingsFiles(); + + constexpr static auto cinq = 5min; + constexpr static auto cero = 0min; + constexpr static auto threehundred = 300min; + + SECTION("Default value") + { + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == cinq); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Valid value") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": 0 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == cero); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Valid value 0") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": 300 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == threehundred); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Invalid type negative integer") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": -20 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == cinq); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } + SECTION("Invalid type string") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": "not a number" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == cinq); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 29cc0f1337..9ed88a5854 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -122,9 +122,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -139,7 +139,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -152,10 +152,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -175,6 +175,7 @@ + @@ -195,9 +196,11 @@ + + @@ -211,6 +214,7 @@ + @@ -222,6 +226,7 @@ + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 05aeee0d27..458aa9d182 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -105,6 +105,15 @@ Public\winget + + Public\winget + + + Header Files + + + Header Files + @@ -167,9 +176,15 @@ Source Files + + Source Files + Source Files + + Source Files + diff --git a/src/AppInstallerCommonCore/JsonUtil.cpp b/src/AppInstallerCommonCore/JsonUtil.cpp new file mode 100644 index 0000000000..ba4f41e82f --- /dev/null +++ b/src/AppInstallerCommonCore/JsonUtil.cpp @@ -0,0 +1,36 @@ +// Copyright(c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "JsonUtil.h" +#include "AppInstallerStrings.h" + +namespace AppInstaller::Utility +{ + template<> + std::optional GetValue(const Json::Value& node) + { + std::optional value = std::nullopt; + + if (node.isString()) + { + value = node.asString(); + } + + return value; + } + + template<> + std::optional GetValue(const Json::Value& node) + { + std::optional value = std::nullopt; + + if (node.isUInt()) + { + value = node.asUInt(); + } + + return value; + } + +} + diff --git a/src/AppInstallerCommonCore/JsonUtil.h b/src/AppInstallerCommonCore/JsonUtil.h new file mode 100644 index 0000000000..247c79af40 --- /dev/null +++ b/src/AppInstallerCommonCore/JsonUtil.h @@ -0,0 +1,21 @@ +#pragma once +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include + +namespace AppInstaller::Utility +{ + template + std::optional GetValue(const Json::Value& node); + + template<> + std::optional GetValue(const Json::Value& node); + + template<> + std::optional GetValue(const Json::Value& node); + +} diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h new file mode 100644 index 0000000000..a7356dc7cc --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerStrings.h" + +#include +#include +#include +#include + +namespace AppInstaller::Settings +{ + using namespace std::chrono_literals; + using namespace std::string_view_literals; + + // The type of argument. + enum class UserSettingsType + { + // Settings files don't exist. A file is created on the first call to the settings command. + Default, + // Loaded settings.json + Standard, + // Loaded settings.json.backup + Backup, + }; + + // The visual style of the progress bar. + enum class VisualStyle + { + NoVT, + Retro, + Accent, + Rainbow, + }; + + // Enum of settings. + // Must start at 0 to enable direct access to variant in UserSettings. + // Max must be last and unused. + // How to add a setting + // 1 - Add to enum. + // 2 - Implement SettingMap specialization + // Validate will be called by ValidateAll without any more changes. + enum class Setting : size_t + { + ProgressBarVisualStyle, + AutoUpdateTimeInMinutes, + Max + }; + + namespace details + { + template + struct SettingMapping + { + // json_t - type the setting in json. + // value_t - the type of this setting. + // DefaultValue - the value_t default value when setting is absent or semantically wrong. + // Path - json path to the property. See Json::Path in json.h for syntax. So far, this is sufficient + // but since is "brief" and "untested" we might implement our own if needed. + // Validate - Function that does semantic validation. + }; + + template <> + struct SettingMapping + { + using json_t = std::string; + using value_t = VisualStyle; + + static constexpr value_t DefaultValue = VisualStyle::Accent; + static constexpr std::string_view Path = ".visual.progressBar"sv; + + static std::optional Validate(const json_t& value); + }; + + template <> + struct SettingMapping + { + using json_t = uint32_t; + using value_t = std::chrono::minutes; + + static constexpr std::chrono::minutes DefaultValue = 5min; + static constexpr std::string_view Path = ".source.autoUpdateIntervalInMinutes"sv; + + static std::optional Validate(const json_t& value); + }; + + // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. + template + inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } + + // Holds data of any type listed in a SettingMapping. + using SettingVariant = decltype(Deduce(std::make_index_sequence(Setting::Max)>())); + + // Gets the index into the variant for the given Setting. + constexpr inline size_t SettingIndex(Setting s) { return static_cast(s) + 1; } + } + + + // Representation of the parsed settings file. + struct UserSettings + { + static UserSettings const& Instance() + { + static UserSettings userSettings; + return userSettings; + } + + static std::filesystem::path SettingsFilePath(); + + UserSettings(const UserSettings&) = delete; + UserSettings& operator=(const UserSettings&) = delete; + + UserSettings(UserSettings&&) = delete; + UserSettings& operator=(UserSettings&&) = delete; + + UserSettingsType GetType() const { return m_type; } + std::vector const& GetWarnings() const { return m_warnings; } + + void PrepareToShellExecuteFile() const; + + // Gets setting value, if its not in the map it returns the default value. + template + typename details::SettingMapping::value_t Get() const + { + auto itr = m_settings.find(S); + if (itr == m_settings.end()) + { + return details::SettingMapping::DefaultValue; + } + + return std::get(itr->second); + } + + private: + UserSettingsType m_type = UserSettingsType::Default; + std::vector m_warnings; + + std::map m_settings; + + protected: + UserSettings(); + ~UserSettings() = default; + + }; + + inline UserSettings const& User() + { + return UserSettings::Instance(); + } +} diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp new file mode 100644 index 0000000000..6564d02274 --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "AppInstallerLanguageUtilities.h" +#include "JsonUtil.h" +#include "winget/Settings.h" +#include "winget/UserSettings.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Runtime; + using namespace Utility; + + static constexpr std::string_view s_SettingFileName = "settings.json"sv; + static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; + + static constexpr std::string_view s_SettingEmpty = + R"({ + // For documentation on these settings, see: https://aka.ms/winget-settings + // "source": { + // "autoUpdateIntervalInMinutes": 5 + // } +})"sv; + + namespace SettingsWarnings + { + const char* const Field = " Field: "; + const char* const Value = " Value: "; + const char* const InvalidFieldValue = "Invalid field value."; + const char* const InvalidFieldFormat = "Invalid field format."; + constexpr std::string_view LoadedBackupSettings = "Loaded settings from backup file."sv; + } + + namespace + { + // Jsoncpp doesn't provide line number and column for an individual Json::Value node. + inline std::string GetSettingsMessage(const std::string& message, const std::string& path) + { + return message + SettingsWarnings::Field + path; + } + + template + inline std::string GetSettingsMessage(const std::string& message, const std::string& path, T value) + { + std::string convertedValue; + + // This won't work when there's a json_t with bool. Change when this happens. + if constexpr (std::is_arithmetic_v) + { + convertedValue = std::to_string(value); + } + else + { + convertedValue = value; + } + + return GetSettingsMessage(message, path) + SettingsWarnings::Value + convertedValue; + } + + + std::filesystem::path SettingsBackupFilePath() + { + return GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; + } + + std::optional ParseFile(const std::filesystem::path& fileName, std::vector& warnings) + { + auto stream = GetSettingStream(Type::UserFile, fileName); + 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; + } + + warnings.emplace_back(fileName.u8string()); + warnings.emplace_back(error); + } + + return {}; + } + + 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); + + 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())); + } + else + { + warnings.push_back(GetSettingsMessage(SettingsWarnings::InvalidFieldValue, path, jsonValue.value())); + } + } + else + { + warnings.emplace_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, path)); + } + } + } + + 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 + { + std::optional::value_t> + SettingMapping::Validate(const SettingMapping::json_t& value) + { + return std::chrono::minutes(value); + } + + std::optional::value_t> + SettingMapping::Validate(const SettingMapping::json_t& value) + { + // 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 {}; + } + } + + UserSettings::UserSettings() : m_type(UserSettingsType::Default) + { + Json::Value settingsRoot = Json::Value::nullSingleton(); + + // Settings can be loaded from settings.json or settings.json.backup files. + // 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. + + auto settingsJson = ParseFile(s_SettingFileName, m_warnings); + if (settingsJson.has_value()) + { + m_type = UserSettingsType::Standard; + settingsRoot = settingsJson.value(); + } + + // Settings didn't parse or doesn't exist, try with backup. + if (settingsRoot.isNull()) + { + auto settingsBackupJson = ParseFile(s_SettingBackupFileName, m_warnings); + if (settingsBackupJson.has_value()) + { + m_warnings.emplace_back(SettingsWarnings::LoadedBackupSettings); + m_type = UserSettingsType::Backup; + settingsRoot = settingsBackupJson.value(); + } + } + + if (!settingsRoot.isNull()) + { + ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence(Setting::Max)>()); + } + } + + void UserSettings::PrepareToShellExecuteFile() const + { + UserSettingsType userSettingType = GetType(); + + if (userSettingType == UserSettingsType::Default) + { + // Create settings file if it doesn't exist. + if (!std::filesystem::exists(UserSettings::SettingsFilePath())) + { + SetSetting(Type::UserFile, s_SettingFileName, s_SettingEmpty); + } + } + else if (userSettingType == UserSettingsType::Standard) + { + // Settings file was loaded correctly, create backup. + auto from = SettingsFilePath(); + auto to = SettingsBackupFilePath(); + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + } + } + + std::filesystem::path UserSettings::SettingsFilePath() + { + return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; + } +} diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 859883daee..a84e31e81f 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -122,9 +122,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -139,7 +139,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -152,10 +152,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index bee4acf35d..d899931741 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -2,12 +2,15 @@ // Licensed under the MIT License. #include "pch.h" #include "Public/AppInstallerRepositorySource.h" +#include #include "SourceFactory.h" #include "Microsoft/PreIndexedPackageSourceFactory.h" namespace AppInstaller::Repository { + using namespace Settings; + using namespace std::chrono_literals; using namespace std::string_view_literals; constexpr std::string_view s_RepositorySettings_UserSources = "usersources"sv; @@ -258,16 +261,21 @@ namespace AppInstaller::Repository return true; } - // TODO: Enable some amount of user control over this. - constexpr static auto s_DefaultAutoUpdateTime = 5min; + constexpr static auto s_ZeroMins = 0min; + auto autoUpdateTime = User().Get(); - auto timeSinceLastUpdate = std::chrono::system_clock::now() - details.LastUpdateTime; - if (timeSinceLastUpdate > s_DefaultAutoUpdateTime) + // A value of zero means no auto update, to get update the source run `winget update` + if (autoUpdateTime != s_ZeroMins) { - AICLI_LOG(Repo, Info, << "Source past auto update time [" << - std::chrono::duration_cast(s_DefaultAutoUpdateTime).count() << " mins]; it has been at least " << - std::chrono::duration_cast(timeSinceLastUpdate).count() << " mins"); - return true; + auto autoUpdateTimeMins = std::chrono::minutes(autoUpdateTime); + auto timeSinceLastUpdate = std::chrono::system_clock::now() - details.LastUpdateTime; + if (timeSinceLastUpdate > autoUpdateTimeMins) + { + AICLI_LOG(Repo, Info, << "Source past auto update time [" << + std::chrono::duration_cast(autoUpdateTimeMins).count() << " mins]; it has been at least " << + std::chrono::duration_cast(timeSinceLastUpdate).count() << " mins"); + return true; + } } return false; diff --git a/src/WinGetUtil/WinGetUtil.vcxproj b/src/WinGetUtil/WinGetUtil.vcxproj index d34a514ea7..f12d45877e 100644 --- a/src/WinGetUtil/WinGetUtil.vcxproj +++ b/src/WinGetUtil/WinGetUtil.vcxproj @@ -124,9 +124,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -156,7 +156,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -235,6 +235,9 @@ {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + {82b39fda-e86b-4713-a873-9d56de00247a} + {8bb94bb8-374f-4294-bca1-c7811514a6b7}