winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit cc7904cd23bf1cdb1178a9dd8e634edf82d51e68
parent d978596b999a76a56764e72a065c462eb30a96f7
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Tue, 16 Jun 2020 10:32:16 -0700

Move settings to their own file, and enable differentiation of settings types (#427)


Diffstat:
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 2+-
Msrc/AppInstallerCLITests/PreIndexedPackageSource.cpp | 12+++++++-----
Msrc/AppInstallerCLITests/Settings.cpp | 34+++++++++++++++++++++++++---------
Msrc/AppInstallerCLITests/Sources.cpp | 30++++++++++++++++--------------
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 6++++++
Msrc/AppInstallerCommonCore/FileLogger.cpp | 2+-
Msrc/AppInstallerCommonCore/Public/AppInstallerRuntime.h | 36++++++++++++++++++------------------
Asrc/AppInstallerCommonCore/Public/winget/Settings.h | 30++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Runtime.cpp | 222++++++++++++++++++++-----------------------------------------------------------
Asrc/AppInstallerCommonCore/Settings.cpp | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp | 4++--
Msrc/AppInstallerRepositoryCore/RepositorySource.cpp | 8++++----
Msrc/AppInstallerRepositoryCore/pch.h | 1+
14 files changed, 362 insertions(+), 220 deletions(-)

diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -78,7 +78,7 @@ namespace AppInstaller::CLI::Workflow const auto& manifest = context.Get<Execution::Data::Manifest>(); const auto& installer = context.Get<Execution::Data::Installer>().value(); - std::filesystem::path tempInstallerPath = Runtime::GetPathToTemp(); + std::filesystem::path tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); tempInstallerPath /= manifest.Id + '.' + manifest.Version; AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); diff --git a/src/AppInstallerCLITests/PreIndexedPackageSource.cpp b/src/AppInstallerCLITests/PreIndexedPackageSource.cpp @@ -6,6 +6,7 @@ #include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> #include <Microsoft/PreIndexedPackageSourceFactory.h> +#include <winget/Settings.h> using namespace std::string_literals; using namespace std::string_view_literals; @@ -13,6 +14,7 @@ using namespace TestCommon; using namespace AppInstaller; using namespace AppInstaller::Repository; using namespace AppInstaller::Runtime; +using namespace AppInstaller::Settings; using namespace AppInstaller::Utility; namespace fs = std::filesystem; @@ -39,7 +41,7 @@ void CopyIndexFileToDirectory(const fs::path& from, const fs::path& to) fs::path GetPathToFileDir() { - fs::path result = GetPathToLocalState(); + fs::path result = GetPathTo(Runtime::PathName::LocalState); result /= AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); result /= s_Msix_FamilyName; return result; @@ -54,7 +56,7 @@ std::string GetContents(const fs::path& file) TEST_CASE("PIPS_Add", "[pips]") { - RemoveSetting(s_RepositorySettings_UserSources); + RemoveSetting(Type::Standard, s_RepositorySettings_UserSources); TempDirectory dir("pipssource"); TestDataFile index(s_MsixFile_1); @@ -83,7 +85,7 @@ TEST_CASE("PIPS_Add", "[pips]") TEST_CASE("PIPS_UpdateSameVersion", "[pips]") { - RemoveSetting(s_RepositorySettings_UserSources); + RemoveSetting(Type::Standard, s_RepositorySettings_UserSources); TempDirectory dir("pipssource"); TestDataFile index(s_MsixFile_1); @@ -108,7 +110,7 @@ TEST_CASE("PIPS_UpdateSameVersion", "[pips]") TEST_CASE("PIPS_UpdateNewVersion", "[pips]") { - RemoveSetting(s_RepositorySettings_UserSources); + RemoveSetting(Type::Standard, s_RepositorySettings_UserSources); TempDirectory dir("pipssource"); TestDataFile indexMsix1(s_MsixFile_1); @@ -150,7 +152,7 @@ TEST_CASE("PIPS_UpdateNewVersion", "[pips]") TEST_CASE("PIPS_Remove", "[pips]") { - RemoveSetting(s_RepositorySettings_UserSources); + RemoveSetting(Type::Standard, s_RepositorySettings_UserSources); TempDirectory dir("pipssource"); TestDataFile index(s_MsixFile_1); diff --git a/src/AppInstallerCLITests/Settings.cpp b/src/AppInstallerCLITests/Settings.cpp @@ -5,15 +5,17 @@ #include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> +#include <winget/Settings.h> using namespace AppInstaller::Runtime; +using namespace AppInstaller::Settings; using namespace AppInstaller::Utility; TEST_CASE("ReadEmptySetting", "[settings]") { std::string name = "nonexistentsetting"; - auto result = GetSettingStream(name); + auto result = GetSettingStream(Type::Standard, name); REQUIRE(!result); } @@ -22,9 +24,9 @@ TEST_CASE("SetAndReadSetting", "[settings]") std::string name = "testsettingname"; std::string value = "This is the test setting value"; - SetSetting(name, value); + SetSetting(Type::Standard, name, value); - auto result = GetSettingStream(name); + auto result = GetSettingStream(Type::Standard, name); REQUIRE(static_cast<bool>(result)); std::string settingValue = ReadEntireStream(*result); @@ -36,9 +38,9 @@ TEST_CASE("SetAndReadSettingInContainer", "[settings]") std::string name = "testcontainer/testsettingname"; std::string value = "This is the test setting value from inside a container"; - SetSetting(name, value); + SetSetting(Type::Standard, name, value); - auto result = GetSettingStream(name); + auto result = GetSettingStream(Type::Standard, name); REQUIRE(static_cast<bool>(result)); std::string settingValue = ReadEntireStream(*result); @@ -50,18 +52,32 @@ TEST_CASE("RemoveSetting", "[settings]") std::string name = "testsettingname"; std::string value = "This is the test setting value to be removed"; - SetSetting(name, value); + SetSetting(Type::Standard, name, value); { - auto result = GetSettingStream(name); + auto result = GetSettingStream(Type::Standard, name); REQUIRE(static_cast<bool>(result)); std::string settingValue = ReadEntireStream(*result); REQUIRE(value == settingValue); } - RemoveSetting(name); + RemoveSetting(Type::Standard, name); - auto result = GetSettingStream(name); + auto result = GetSettingStream(Type::Standard, name); REQUIRE(!static_cast<bool>(result)); } + +TEST_CASE("SetAndReadUserFileSetting", "[settings]") +{ + std::string name = "userfilesetting"; + std::string value = "This is the test setting value for a user file"; + + SetSetting(Type::UserFile, name, value); + + auto result = GetSettingStream(Type::UserFile, name); + REQUIRE(static_cast<bool>(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); +} diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp @@ -8,10 +8,12 @@ #include <AppInstallerDateTime.h> #include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> +#include <winget/Settings.h> using namespace AppInstaller; using namespace AppInstaller::Runtime; using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; using namespace AppInstaller::Utility; // Duplicating here because a change to these values in the product *REALLY* needs to be thought through. @@ -142,7 +144,7 @@ struct TestSourceFactory : public ISourceFactory TEST_CASE("RepoSources_UserSettingDoesNotExist", "[sources]") { - RemoveSetting(s_RepositorySettings_UserSources); + RemoveSetting(Type::Standard, s_RepositorySettings_UserSources); std::vector<SourceDetails> sources = GetSources(); // The default source is added when no source exists @@ -152,7 +154,7 @@ TEST_CASE("RepoSources_UserSettingDoesNotExist", "[sources]") TEST_CASE("RepoSources_EmptySourcesList", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_EmptySources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_EmptySources); std::vector<SourceDetails> sources = GetSources(); REQUIRE(sources.empty()); @@ -160,7 +162,7 @@ TEST_CASE("RepoSources_EmptySourcesList", "[sources]") TEST_CASE("RepoSources_SingleSource", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_SingleSource); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_SingleSource); std::vector<SourceDetails> sources = GetSources(); REQUIRE(sources.size() == 1); @@ -174,7 +176,7 @@ TEST_CASE("RepoSources_SingleSource", "[sources]") TEST_CASE("RepoSources_ThreeSources", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_ThreeSources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_ThreeSources); std::vector<SourceDetails> sources = GetSources(); REQUIRE(sources.size() == 3); @@ -195,21 +197,21 @@ TEST_CASE("RepoSources_ThreeSources", "[sources]") TEST_CASE("RepoSources_InvalidYAML", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, "Name: Value : BAD"); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, "Name: Value : BAD"); REQUIRE_THROWS_HR(GetSources(), APPINSTALLER_CLI_ERROR_SOURCES_INVALID); } TEST_CASE("RepoSources_MissingField", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_SingleSource_MissingArg); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_SingleSource_MissingArg); REQUIRE_THROWS_HR(GetSources(), APPINSTALLER_CLI_ERROR_SOURCES_INVALID); } TEST_CASE("RepoSources_AddSource", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_EmptySources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_EmptySources); TestHook_ClearSourceFactoryOverrides(); std::string name = "thisIsTheName"; @@ -239,7 +241,7 @@ TEST_CASE("RepoSources_AddSource", "[sources]") TEST_CASE("RepoSources_AddMultipleSources", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_EmptySources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_EmptySources); std::string name = "thisIsTheName"; std::string type = "thisIsTheType"; @@ -288,7 +290,7 @@ TEST_CASE("RepoSources_UpdateSource", "[sources]") { using namespace std::chrono_literals; - SetSetting(s_RepositorySettings_UserSources, s_EmptySources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_EmptySources); TestHook_ClearSourceFactoryOverrides(); std::string name = "thisIsTheName"; @@ -338,7 +340,7 @@ TEST_CASE("RepoSources_UpdateSourceRetries", "[sources]") { using namespace std::chrono_literals; - SetSetting(s_RepositorySettings_UserSources, s_EmptySources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_EmptySources); TestHook_ClearSourceFactoryOverrides(); std::string name = "thisIsTheName"; @@ -373,7 +375,7 @@ TEST_CASE("RepoSources_UpdateSourceRetries", "[sources]") TEST_CASE("RepoSources_RemoveSource", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_EmptySources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_EmptySources); TestHook_ClearSourceFactoryOverrides(); std::string name = "thisIsTheName"; @@ -417,7 +419,7 @@ TEST_CASE("RepoSources_UpdateOnOpen", "[sources]") factory.m_Update = [&](SourceDetails& sd) { updateCalledOnFactory = true; sd.Data = data; }; TestHook_SetSourceFactoryOverride(type, factory); - SetSetting(s_RepositorySettings_UserSources, s_SingleSource); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_SingleSource); ProgressCallback progress; auto source = OpenSource(name, progress); @@ -436,7 +438,7 @@ TEST_CASE("RepoSources_UpdateOnOpen", "[sources]") TEST_CASE("RepoSources_DropSourceByName", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_ThreeSources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_ThreeSources); std::vector<SourceDetails> sources = GetSources(); REQUIRE(sources.size() == 3); @@ -462,7 +464,7 @@ TEST_CASE("RepoSources_DropSourceByName", "[sources]") TEST_CASE("RepoSources_DropAllSources", "[sources]") { - SetSetting(s_RepositorySettings_UserSources, s_ThreeSources); + SetSetting(Type::Standard, s_RepositorySettings_UserSources, s_ThreeSources); std::vector<SourceDetails> sources = GetSources(); REQUIRE(sources.size() == 3); diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -194,6 +194,7 @@ <ClInclude Include="Public\AppInstallerVersions.h" /> <ClInclude Include="Public\winget\ExtensionCatalog.h" /> <ClInclude Include="Public\winget\LocIndependent.h" /> + <ClInclude Include="Public\winget\Settings.h" /> <ClInclude Include="Telemetry\MicrosoftTelemetry.h" /> <ClInclude Include="Telemetry\TraceLogging.h" /> <ClInclude Include="Telemetry\WinEventLogLevels.h" /> @@ -216,6 +217,7 @@ <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> <ClCompile Include="AppInstallerTelemetry.cpp" /> + <ClCompile Include="Settings.cpp" /> <ClCompile Include="SHA256.cpp" /> <ClCompile Include="Synchronization.cpp" /> <ClCompile Include="Telemetry\TraceLogging.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -102,6 +102,9 @@ <ClInclude Include="Public\winget\LocIndependent.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\Settings.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -164,6 +167,9 @@ <ClCompile Include="ExtensionCatalog.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="Settings.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/FileLogger.cpp b/src/AppInstallerCommonCore/FileLogger.cpp @@ -17,7 +17,7 @@ namespace AppInstaller::Logging if (filePath.empty()) { m_name = "file"; - m_filePath = Runtime::GetPathToDefaultLogLocation(); + m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); m_filePath /= AICLI_FILELOGGER_DEFAULT_FILE_PREFIX + Utility::GetCurrentTimeForFilename() + AICLI_FILELOGGER_DEFAULT_FILE_EXT; } else diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -23,24 +23,24 @@ namespace AppInstaller::Runtime // Gets a string representation of the OS version for debugging purposes. Utility::LocIndString GetOSVersion(); - // Gets the path to the temp location. - std::filesystem::path GetPathToTemp(); - - // Gets the path to the local state location. - std::filesystem::path GetPathToLocalState(); - - // Gets the path to the default log location. - std::filesystem::path GetPathToDefaultLogLocation(); - - // Gets a stream containing the named setting's value, if present. - // If the setting does not exist, returns an empty value. - std::unique_ptr<std::istream> GetSettingStream(std::filesystem::path name); - - // Sets the named setting to the given value. - void SetSetting(std::filesystem::path name, std::string_view value); - - // Deletes the given setting. - void RemoveSetting(std::filesystem::path name); + // A path to be retrieved based on the runtime. + enum class PathName + { + // The temporary file location. + Temp, + // The local state (file) storage location. + LocalState, + // The default location where log files are located. + DefaultLogLocation, + // The location that standard type settings are stored. + // In a packaged context, this returns a prepend value for the container name. + StandardSettings, + // The location that user file type settings are stored. + UserFileSettings, + }; + + // Gets the path to the requested location. + std::filesystem::path GetPathTo(PathName path); // Determines whether the current OS version is >= the given one. // We treat the given Version struct as a standard 4 part Windows OS version. diff --git a/src/AppInstallerCommonCore/Public/winget/Settings.h b/src/AppInstallerCommonCore/Public/winget/Settings.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <filesystem> +#include <memory> +#include <string> +#include <string_view> + +namespace AppInstaller::Settings +{ + // Allows settings to be classified and treated differently base on any number of factors. + // Names should still be unique, as there is no guarantee made about types mapping to unique roots. + enum class Type + { + // A Standard setting stream has no special requirements. + Standard, + // A UserFile setting stream should be located in a file that is easily editable by the user. + UserFile, + }; + + // Gets a stream containing the named setting's value, if present. + // If the setting does not exist, returns an empty value. + std::unique_ptr<std::istream> GetSettingStream(Type type, const std::filesystem::path& name); + + // Sets the named setting to the given value. + void SetSetting(Type type, const std::filesystem::path& name, std::string_view value); + + // Deletes the given setting. + void RemoveSetting(Type type, const std::filesystem::path& name); +} diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -73,41 +73,6 @@ namespace AppInstaller::Runtime static std::filesystem::path s_Settings_TestHook_ForcedContainerPrepend; #endif - void ValidateSettingNamePath(std::filesystem::path& name) - { - THROW_HR_IF(E_INVALIDARG, !name.has_relative_path()); - THROW_HR_IF(E_INVALIDARG, name.has_root_path()); - THROW_HR_IF(E_INVALIDARG, !name.has_filename()); - } - - // Gets the container within LocalSettings for the given path. - auto GetLocalSettingsContainerForPath(const std::filesystem::path& name) - { - auto result = winrt::Windows::Storage::ApplicationData::Current().LocalSettings(); - - std::filesystem::path pathToUse; - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (!s_Settings_TestHook_ForcedContainerPrepend.empty()) - { - pathToUse = s_Settings_TestHook_ForcedContainerPrepend; - pathToUse /= name; - } - else -#endif - { - pathToUse = name; - } - - for (const auto& part : pathToUse.parent_path()) - { - auto partHstring = winrt::to_hstring(part.c_str()); - result = result.CreateContainer(partHstring, winrt::Windows::Storage::ApplicationDataCreateDisposition::Always); - } - - return result; - } - // Gets the path to the appdata root. // *Only used by non packaged version!* std::filesystem::path GetPathToAppDataRoot() @@ -147,32 +112,6 @@ namespace AppInstaller::Runtime std::filesystem::path result = GetPathToAppDataRoot(); result /= relative; - if (std::filesystem::exists(result)) - { - if (!std::filesystem::is_directory(result)) - { - // STATUS_NOT_A_DIRECTORY: A requested opened file is not a directory. - THROW_NTSTATUS_MSG(0xC0000103, "AppData location is not a directory"); - } - } - else - { - std::filesystem::create_directories(result); - } - - return result; - } - - // Gets the path to the settings directory for the given setting. - // Creates the directory if it does not already exist. - std::filesystem::path GetPathToSettings(const std::filesystem::path& name) - { - std::filesystem::path result = GetPathToAppDataDir(s_AppDataDir_Settings); - if (name.has_parent_path()) - { - result /= name.parent_path(); - std::filesystem::create_directories(result); - } return result; } } @@ -262,135 +201,86 @@ namespace AppInstaller::Runtime return LocIndString{ strstr.str() }; } - std::filesystem::path GetPathToTemp() + std::filesystem::path GetPathTo(PathName path) { std::filesystem::path result; if (IsRunningInPackagedContext()) { - result.assign(winrt::Windows::Storage::ApplicationData::Current().TemporaryFolder().Path().c_str()); - } - else - { - wchar_t tempPath[MAX_PATH + 1]; - DWORD tempChars = GetTempPathW(ARRAYSIZE(tempPath), tempPath); - result.assign(std::wstring_view{ tempPath, static_cast<size_t>(tempChars) }); - } + auto appStorage = winrt::Windows::Storage::ApplicationData::Current(); - result /= AICLI_DEFAULT_TEMP_DIRECTORY; - - std::filesystem::create_directories(result); - - return result; - } - - std::filesystem::path GetPathToLocalState() - { - if (IsRunningInPackagedContext()) - { - std::filesystem::path result = winrt::Windows::Storage::ApplicationData::Current().LocalFolder().Path().c_str(); + switch (path) + { + case PathName::Temp: + result.assign(appStorage.TemporaryFolder().Path().c_str()); + result /= AICLI_DEFAULT_TEMP_DIRECTORY; + break; + case PathName::LocalState: + case PathName::UserFileSettings: + result.assign(appStorage.LocalFolder().Path().c_str()); #ifndef AICLI_DISABLE_TEST_HOOKS - if (!s_Settings_TestHook_ForcedContainerPrepend.empty()) - { - result /= s_Settings_TestHook_ForcedContainerPrepend; - } + if (!s_Settings_TestHook_ForcedContainerPrepend.empty()) + { + result /= s_Settings_TestHook_ForcedContainerPrepend; + } #endif - - return result; - } - else - { - return GetPathToAppDataDir(s_AppDataDir_State); - } - } - - std::filesystem::path GetPathToDefaultLogLocation() - { - if (IsRunningInPackagedContext()) - { - // To enable UIF collection through Feedback hub, we must put our logs here. - auto result = GetPathToLocalState() / WINGET_DEFAULT_LOG_DIRECTORY; - - std::filesystem::create_directories(result); - - return result; + break; + case PathName::DefaultLogLocation: + // To enable UIF collection through Feedback hub, we must put our logs here. + result.assign(appStorage.LocalFolder().Path().c_str()); + result /= WINGET_DEFAULT_LOG_DIRECTORY; + break; + case PathName::StandardSettings: +#ifndef AICLI_DISABLE_TEST_HOOKS + result = s_Settings_TestHook_ForcedContainerPrepend; +#endif + break; + default: + THROW_HR(E_UNEXPECTED); + } } else { - return GetPathToTemp(); - } - } - - std::unique_ptr<std::istream> GetSettingStream(std::filesystem::path name) - { - ValidateSettingNamePath(name); - - if (IsRunningInPackagedContext()) - { - auto container = GetLocalSettingsContainerForPath(name); - auto filenameHstring = winrt::to_hstring(name.filename().c_str()); - auto settingsValues = container.Values(); - if (settingsValues.HasKey(filenameHstring)) + switch (path) { - auto value = winrt::unbox_value<winrt::hstring>(settingsValues.Lookup(filenameHstring)); - return std::make_unique<std::istringstream>(Utility::ConvertToUTF8(value.c_str())); - } - else + case PathName::Temp: + case PathName::DefaultLogLocation: { - return {}; + wchar_t tempPath[MAX_PATH + 1]; + DWORD tempChars = GetTempPathW(ARRAYSIZE(tempPath), tempPath); + result.assign(std::wstring_view{ tempPath, static_cast<size_t>(tempChars) }); + } + break; + case PathName::LocalState: + result = GetPathToAppDataDir(s_AppDataDir_State); + break; + case PathName::StandardSettings: + case PathName::UserFileSettings: + result = GetPathToAppDataDir(s_AppDataDir_Settings); + break; + default: + THROW_HR(E_UNEXPECTED); } } - else - { - auto settingFileName = GetPathToSettings(name); - settingFileName /= name.filename(); - if (std::filesystem::exists(settingFileName)) + if (result.is_absolute()) + { + if (std::filesystem::exists(result)) { - return std::make_unique<std::ifstream>(settingFileName); + if (!std::filesystem::is_directory(result)) + { + // STATUS_NOT_A_DIRECTORY: A requested opened file is not a directory. + THROW_NTSTATUS_MSG(0xC0000103, "Location is not a directory"); + } } else { - return {}; + std::filesystem::create_directories(result); } } - } - - void SetSetting(std::filesystem::path name, std::string_view value) - { - ValidateSettingNamePath(name); - - if (IsRunningInPackagedContext()) - { - GetLocalSettingsContainerForPath(name).Values(). - Insert(winrt::to_hstring(name.filename().c_str()), winrt::box_value(winrt::to_hstring(value))); - } - else - { - auto settingFileName = GetPathToSettings(name); - settingFileName /= name.filename(); - - std::ofstream stream(settingFileName, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); - stream << value << std::flush; - } - } - - void RemoveSetting(std::filesystem::path name) - { - ValidateSettingNamePath(name); - if (IsRunningInPackagedContext()) - { - GetLocalSettingsContainerForPath(name).Values().Remove(winrt::to_hstring(name.filename().c_str())); - } - else - { - auto settingFileName = GetPathToSettings(name); - settingFileName /= name.filename(); - - std::filesystem::remove(settingFileName); - } + return result; } bool IsCurrentOSVersionGreaterThanOrEqual(const Utility::Version& version) diff --git a/src/AppInstallerCommonCore/Settings.cpp b/src/AppInstallerCommonCore/Settings.cpp @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Settings.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Settings +{ + using namespace Runtime; + using namespace Utility; + + namespace + { + void ValidateSettingNamePath(const std::filesystem::path& name) + { + THROW_HR_IF(E_INVALIDARG, !name.has_relative_path()); + THROW_HR_IF(E_INVALIDARG, name.has_root_path()); + THROW_HR_IF(E_INVALIDARG, !name.has_filename()); + } + + // A settings container. + struct ISettingsContainer + { + virtual ~ISettingsContainer() = default; + + // Gets a stream containing the named setting's value, if present. + // If the setting does not exist, returns an empty value. + virtual std::unique_ptr<std::istream> Get(const std::filesystem::path& name) = 0; + + // Sets the named setting to the given value. + virtual void Set(const std::filesystem::path& name, std::string_view value) = 0; + + // Deletes the given setting. + virtual void Remove(const std::filesystem::path& name) = 0; + }; + + // A settings container backed by the ApplicationDataContainer functionality. + struct ApplicationDataSettingsContainer : public ISettingsContainer + { + using Container = winrt::Windows::Storage::ApplicationDataContainer; + + ApplicationDataSettingsContainer(Container&& container) : m_root(std::move(container)) {} + + static Container GetRelativeContainer(const Container& container, const std::filesystem::path& offset) + { + auto result = container; + + for (const auto& part : offset) + { + auto partHstring = winrt::to_hstring(part.c_str()); + result = result.CreateContainer(partHstring, winrt::Windows::Storage::ApplicationDataCreateDisposition::Always); + } + + return result; + } + + std::unique_ptr<std::istream> Get(const std::filesystem::path& name) override + { + Container parent = GetRelativeContainer(m_root, name.parent_path()); + + auto filenameHstring = winrt::to_hstring(name.filename().c_str()); + auto settingsValues = parent.Values(); + if (settingsValues.HasKey(filenameHstring)) + { + auto value = winrt::unbox_value<winrt::hstring>(settingsValues.Lookup(filenameHstring)); + return std::make_unique<std::istringstream>(Utility::ConvertToUTF8(value.c_str())); + } + else + { + return {}; + } + } + + void Set(const std::filesystem::path& name, std::string_view value) override + { + Container parent = GetRelativeContainer(m_root, name.parent_path()); + parent.Values().Insert(winrt::to_hstring(name.filename().c_str()), winrt::box_value(winrt::to_hstring(value))); + } + + void Remove(const std::filesystem::path& name) override + { + Container parent = GetRelativeContainer(m_root, name.parent_path()); + parent.Values().Remove(winrt::to_hstring(name.filename().c_str())); + } + + private: + Container m_root; + }; + + // A settings container backed by the filesystem. + struct FileSettingsContainer : public ISettingsContainer + { + using Container = winrt::Windows::Storage::ApplicationDataContainer; + + FileSettingsContainer(std::filesystem::path root) : m_root(std::move(root)) {} + + std::filesystem::path GetRelativePath(const std::filesystem::path& name) + { + std::filesystem::path result = m_root; + if (name.has_parent_path()) + { + result /= name.parent_path(); + std::filesystem::create_directories(result); + } + return result; + } + + std::unique_ptr<std::istream> Get(const std::filesystem::path& name) override + { + std::filesystem::path settingFileName = GetRelativePath(name); + settingFileName /= name.filename(); + + if (std::filesystem::exists(settingFileName)) + { + return std::make_unique<std::ifstream>(settingFileName); + } + else + { + return {}; + } + } + + void Set(const std::filesystem::path& name, std::string_view value) override + { + std::filesystem::path settingFileName = GetRelativePath(name); + settingFileName /= name.filename(); + + std::ofstream stream(settingFileName, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + stream << value << std::flush; + } + + void Remove(const std::filesystem::path& name) override + { + std::filesystem::path settingFileName = GetRelativePath(name); + settingFileName /= name.filename(); + + std::filesystem::remove(settingFileName); + } + + private: + std::filesystem::path m_root; + }; + + std::unique_ptr<ISettingsContainer> GetSettingsContainer(Type type) + { + if (IsRunningInPackagedContext()) + { + switch (type) + { + case AppInstaller::Settings::Type::Standard: + return std::make_unique<ApplicationDataSettingsContainer>( + ApplicationDataSettingsContainer::GetRelativeContainer( + winrt::Windows::Storage::ApplicationData::Current().LocalSettings(), GetPathTo(PathName::StandardSettings))); + case AppInstaller::Settings::Type::UserFile: + return std::make_unique<FileSettingsContainer>(GetPathTo(PathName::UserFileSettings)); + default: + THROW_HR(E_UNEXPECTED); + } + } + else + { + switch (type) + { + case AppInstaller::Settings::Type::Standard: + return std::make_unique<FileSettingsContainer>(GetPathTo(PathName::StandardSettings)); + case AppInstaller::Settings::Type::UserFile: + return std::make_unique<FileSettingsContainer>(GetPathTo(PathName::UserFileSettings)); + default: + THROW_HR(E_UNEXPECTED); + } + } + } + } + + std::unique_ptr<std::istream> GetSettingStream(Type type, const std::filesystem::path& name) + { + ValidateSettingNamePath(name); + return GetSettingsContainer(type)->Get(name); + } + + void SetSetting(Type type, const std::filesystem::path& name, std::string_view value) + { + ValidateSettingNamePath(name); + GetSettingsContainer(type)->Set(name, value); + } + + void RemoveSetting(Type type, const std::filesystem::path& name) + { + ValidateSettingNamePath(name); + GetSettingsContainer(type)->Remove(name); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -190,7 +190,7 @@ namespace AppInstaller::Repository::Microsoft if (download) { - tempFile = Runtime::GetPathToTemp(); + tempFile = Runtime::GetPathTo(Runtime::PathName::Temp); tempFile /= GetPackageFullNameFromDetails(details) + ".msix"; Utility::Download(packageLocation, tempFile, progress); @@ -227,7 +227,7 @@ namespace AppInstaller::Repository::Microsoft // Constructs the location that we will write files to. std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) { - std::filesystem::path result = Runtime::GetPathToLocalState(); + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); result /= PreIndexedPackageSourceFactory::Type(); result /= GetPackageFamilyNameFromDetails(details); return result; diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -122,7 +122,7 @@ namespace AppInstaller::Repository // Gets the source details from a particular setting, or an empty optional if no setting exists. std::optional<std::vector<SourceDetails>> TryGetSourcesFromSetting(std::string_view settingName) { - auto sourcesStream = Runtime::GetSettingStream(settingName); + auto sourcesStream = Settings::GetSettingStream(Settings::Type::Standard, settingName); if (!sourcesStream) { // Handle first run scenario and configure default source(s). @@ -172,7 +172,7 @@ namespace AppInstaller::Repository out << YAML::EndSeq; out << YAML::EndMap; - Runtime::SetSetting(settingName, out.c_str()); + Settings::SetSetting(Settings::Type::Standard, settingName, out.c_str()); } // Finds a source from the given vector by its name. @@ -323,7 +323,7 @@ namespace AppInstaller::Repository // If there is no setting value at all, adds the default sources. void AddDefaultSourcesIfNeeded() { - auto sourcesStream = Runtime::GetSettingStream(s_RepositorySettings_UserSources); + auto sourcesStream = Settings::GetSettingStream(Settings::Type::Standard, s_RepositorySettings_UserSources); if (!sourcesStream) { // We have to set an initial, empty list of sources or the add will create an infinite loop. @@ -457,7 +457,7 @@ namespace AppInstaller::Repository { if (name.empty()) { - Runtime::RemoveSetting(s_RepositorySettings_UserSources); + Settings::RemoveSetting(Settings::Type::Standard, s_RepositorySettings_UserSources); return true; } else diff --git a/src/AppInstallerRepositoryCore/pch.h b/src/AppInstallerRepositoryCore/pch.h @@ -18,6 +18,7 @@ #include <AppInstallerSynchronization.h> #include <AppInstallerVersions.h> #include <winget/ExtensionCatalog.h> +#include <winget/Settings.h> #include <yaml-cpp/yaml.h> #include <wil/result_macros.h>