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}