diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 73f3e770f..b63c4b33b 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -64,13 +64,81 @@ using namespace ::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ApplicationState::ApplicationState(std::filesystem::path path) noexcept : - BaseApplicationState{ path } {} + _path{ std::move(path) }, + _throttler{ std::chrono::seconds(1), [this]() { _write(); } } + { + _read(); + } + + // The destructor ensures that the last write is flushed to disk before returning. + ApplicationState::~ApplicationState() + { + // This will ensure that we not just cancel the last outstanding timer, + // but instead force it to run as soon as possible and wait for it to complete. + _throttler.flush(); + } + + // Re-read the state.json from disk. + void ApplicationState::Reload() const noexcept + { + _read(); + } + + // Returns the state.json path on the disk. + winrt::hstring ApplicationState::FilePath() const noexcept + { + return winrt::hstring{ _path.wstring() }; + } + + // Deserializes the state.json at _path into this ApplicationState. + // * ANY errors during app state will result in the creation of a new empty state. + // * ANY errors during runtime will result in changes being partially ignored. + void ApplicationState::_read() const noexcept + try + { + // Use the derived class's implementation of _readFileContents to get the + // actual contents of the file. + const auto data = _readFileContents().value_or(std::string{}); + if (data.empty()) + { + return; + } + + std::string errs; + std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; + + Json::Value root; + if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs)) + { + throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); + } + + FromJson(root); + } + CATCH_LOG() + + // Serialized this ApplicationState (in `context`) into the state.json at _path. + // * Errors are only logged. + // * _state->_writeScheduled is set to false, signaling our + // setters that _synchronize() needs to be called again. + void ApplicationState::_write() const noexcept + try + { + Json::Value root{ this->ToJson() }; + + Json::StreamWriterBuilder wbuilder; + const auto content = Json::writeString(wbuilder, root); + + // Use the derived class's implementation of _writeFileContents to write the + // file to disk. + _writeFileContents(content); + } + CATCH_LOG() // Returns the application-global ApplicationState object. Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance() { static auto state = winrt::make_self(GetBaseSettingsPath() / (::Microsoft::Console::Utils::IsElevated() ? elevatedStateFileName : stateFileName)); - state->Reload(); return *state; } diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index cd4d245d0..b31090768 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -12,7 +12,6 @@ Abstract: --*/ #pragma once -#include "BaseApplicationState.h" #include "ApplicationState.g.h" #include "WindowLayout.g.h" @@ -40,14 +39,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait; }; - struct ApplicationState : public BaseApplicationState, ApplicationStateT + struct ApplicationState : public ApplicationStateT { static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance(); ApplicationState(std::filesystem::path path) noexcept; + ~ApplicationState(); - virtual void FromJson(const Json::Value& root) const noexcept override; - virtual Json::Value ToJson() const noexcept override; + // Methods + void Reload() const noexcept; + void FromJson(const Json::Value& root) const noexcept; + Json::Value ToJson() const noexcept; + + // General getters/setters + winrt::hstring FilePath() const noexcept; // State getters/setters #define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \ @@ -64,9 +69,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation #undef MTSM_APPLICATION_STATE_GEN }; til::shared_mutex _state; + std::filesystem::path _path; + til::throttled_func_trailing<> _throttler; - virtual std::optional _readFileContents() const override; - virtual void _writeFileContents(const std::string_view content) const override; + void _write() const noexcept; + void _read() const noexcept; + + std::optional _readFileContents() const; + void _writeFileContents(const std::string_view content) const; }; } diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp deleted file mode 100644 index a45df5865..000000000 --- a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "BaseApplicationState.h" -#include "CascadiaSettings.h" - -#include "JsonUtils.h" -#include "FileUtils.h" - -using namespace ::Microsoft::Terminal::Settings::Model; - -BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept : - _path{ std::move(path) }, - _throttler{ std::chrono::seconds(1), [this]() { _write(); } } -{ - // DON'T _read() here! _read() will call FromJson, which is virtual, and - // needs to be implemented in a derived class. Classes that derive from - // BaseApplicationState should make sure to call Reload() after construction - // to ensure the data is loaded. -} - -// The destructor ensures that the last write is flushed to disk before returning. -BaseApplicationState::~BaseApplicationState() -{ - // This will ensure that we not just cancel the last outstanding timer, - // but instead force it to run as soon as possible and wait for it to complete. - _throttler.flush(); -} - -// Re-read the state.json from disk. -void BaseApplicationState::Reload() const noexcept -{ - _read(); -} - -// Returns the state.json path on the disk. -winrt::hstring BaseApplicationState::FilePath() const noexcept -{ - return winrt::hstring{ _path.wstring() }; -} - -// Deserializes the state.json at _path into this ApplicationState. -// * ANY errors during app state will result in the creation of a new empty state. -// * ANY errors during runtime will result in changes being partially ignored. -void BaseApplicationState::_read() const noexcept -try -{ - // Use the derived class's implementation of _readFileContents to get the - // actual contents of the file. - const auto data = _readFileContents().value_or(std::string{}); - if (data.empty()) - { - return; - } - - std::string errs; - std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; - - Json::Value root; - if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs)) - { - throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); - } - - FromJson(root); -} -CATCH_LOG() - -// Serialized this ApplicationState (in `context`) into the state.json at _path. -// * Errors are only logged. -// * _state->_writeScheduled is set to false, signaling our -// setters that _synchronize() needs to be called again. -void BaseApplicationState::_write() const noexcept -try -{ - Json::Value root{ this->ToJson() }; - - Json::StreamWriterBuilder wbuilder; - const auto content = Json::writeString(wbuilder, root); - - // Use the derived class's implementation of _writeFileContents to write the - // file to disk. - _writeFileContents(content); -} -CATCH_LOG() diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.h b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h deleted file mode 100644 index 35bc54bc6..000000000 --- a/src/cascadia/TerminalSettingsModel/BaseApplicationState.h +++ /dev/null @@ -1,38 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- BaseApplicationState.h - -Abstract: -- This is the common core for both ApplicationState and ElevatedState. This - handles more of the mechanics of serializing these structures to/from json, as - well as the mechanics of loading the file. ---*/ -#pragma once - -struct BaseApplicationState -{ - BaseApplicationState(std::filesystem::path path) noexcept; - ~BaseApplicationState(); - - // Methods - void Reload() const noexcept; - - // General getters/setters - winrt::hstring FilePath() const noexcept; - - virtual void FromJson(const Json::Value& root) const noexcept = 0; - virtual Json::Value ToJson() const noexcept = 0; - -protected: - virtual std::optional _readFileContents() const = 0; - virtual void _writeFileContents(const std::string_view content) const = 0; - - void _write() const noexcept; - void _read() const noexcept; - - std::filesystem::path _path; - til::throttled_func_trailing<> _throttler; -}; diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp deleted file mode 100644 index de8136571..000000000 --- a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "ElevatedState.h" -#include "CascadiaSettings.h" -#include "ElevatedState.g.cpp" - -#include "JsonUtils.h" -#include "FileUtils.h" - -#include - -constexpr std::wstring_view stateFileName{ L"elevated-state.json" }; - -using namespace ::Microsoft::Terminal::Settings::Model; - -namespace winrt::Microsoft::Terminal::Settings::Model::implementation -{ - ElevatedState::ElevatedState(std::filesystem::path path) noexcept : - BaseApplicationState{ path } {} - - // Returns the application-global ElevatedState object. - Microsoft::Terminal::Settings::Model::ElevatedState ElevatedState::SharedInstance() - { - static auto state = winrt::make_self(GetBaseSettingsPath() / stateFileName); - state->Reload(); - return *state; - } - - void ElevatedState::FromJson(const Json::Value& root) const noexcept - { - auto state = _state.lock(); - // GetValueForKey() comes in two variants: - // * take a std::optional reference - // * return std::optional by value - // At the time of writing the former version skips missing fields in the json, - // but we want to explicitly clear state fields that were removed from state.json. -#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); - MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -#undef MTSM_ELEVATED_STATE_GEN - } - Json::Value ElevatedState::ToJson() const noexcept - { - Json::Value root{ Json::objectValue }; - - { - auto state = _state.lock_shared(); -#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); - MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -#undef MTSM_ELEVATED_STATE_GEN - } - return root; - } - - // Generate all getter/setters -#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \ - type ElevatedState::name() const noexcept \ - { \ - const auto state = _state.lock_shared(); \ - const auto& value = state->name; \ - return value ? *value : type{ __VA_ARGS__ }; \ - } \ - \ - void ElevatedState::name(const type& value) noexcept \ - { \ - { \ - auto state = _state.lock(); \ - state->name.emplace(value); \ - } \ - \ - _throttler(); \ - } - MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -#undef MTSM_ELEVATED_STATE_GEN - - void ElevatedState::_writeFileContents(const std::string_view content) const - { - // DON'T use WriteUTF8FileAtomic, which will write to a temporary file - // then rename that file to the final filename. That actually lets us - // overwrite the elevate file's contents even when unelevated, because - // we're effectively deleting the original file, then renaming a - // different file in it's place. - // - // We're not worried about someone else doing that though, if they do - // that with the wrong permissions, then we'll just ignore the file and - // start over. - WriteUTF8File(_path, content, true); - } - - std::optional ElevatedState::_readFileContents() const - { - return ReadUTF8FileIfExists(_path, true); - } -} diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.h b/src/cascadia/TerminalSettingsModel/ElevatedState.h deleted file mode 100644 index d0eb8381f..000000000 --- a/src/cascadia/TerminalSettingsModel/ElevatedState.h +++ /dev/null @@ -1,60 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- ElevatedState.h - -Abstract: -- If the CascadiaSettings class were AppData, then this class would be LocalAppData. - Put anything in here that you wouldn't want to be stored next to user-editable settings. -- Modify ElevatedState.idl and MTSM_ELEVATED_STATE_FIELDS to add new fields. ---*/ -#pragma once - -#include "BaseApplicationState.h" -#include "ElevatedState.g.h" -#include - -// This macro generates all getters and setters for ElevatedState. -// It provides X with the following arguments: -// (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_ELEVATED_STATE_FIELDS(X) \ - X(Windows::Foundation::Collections::IVector, AllowedCommandlines, "allowedCommandlines") - -namespace winrt::Microsoft::Terminal::Settings::Model::implementation -{ - struct ElevatedState : ElevatedStateT, public BaseApplicationState - { - static Microsoft::Terminal::Settings::Model::ElevatedState SharedInstance(); - - ElevatedState(std::filesystem::path path) noexcept; - - void FromJson(const Json::Value& root) const noexcept override; - Json::Value ToJson() const noexcept override; - - // State getters/setters -#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \ - type name() const noexcept; \ - void name(const type& value) noexcept; - MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -#undef MTSM_ELEVATED_STATE_GEN - - private: - struct state_t - { -#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) std::optional name{ __VA_ARGS__ }; - MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -#undef MTSM_ELEVATED_STATE_GEN - }; - til::shared_mutex _state; - - virtual std::optional _readFileContents() const override; - virtual void _writeFileContents(const std::string_view content) const override; - }; -} - -namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation -{ - BASIC_FACTORY(ElevatedState); -} diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.idl b/src/cascadia/TerminalSettingsModel/ElevatedState.idl deleted file mode 100644 index 77654383a..000000000 --- a/src/cascadia/TerminalSettingsModel/ElevatedState.idl +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -namespace Microsoft.Terminal.Settings.Model -{ - [default_interface] runtimeclass ElevatedState { - static ElevatedState SharedInstance(); - - void Reload(); - - String FilePath { get; }; - - } -} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 8203476de..971096375 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -21,7 +21,6 @@ IconPathConverter.idl - ActionArgs.idl @@ -93,7 +92,6 @@ IconPathConverter.idl - Create @@ -267,4 +265,4 @@ - \ No newline at end of file +