From eee657b5026d665f75bd0f3273d2a2620a32b5ed Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 22 Sep 2021 14:35:02 -0500 Subject: [PATCH] fix hot reloading for this file --- src/cascadia/TerminalApp/AppLogic.cpp | 12 +++--- .../ApplicationState.cpp | 40 ++++++++++++++++--- .../TerminalSettingsModel/ApplicationState.h | 15 ++++--- .../ApplicationState.idl | 2 +- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 78c6c82a3..e1d2aeee2 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -908,17 +908,15 @@ namespace winrt::TerminalApp::implementation // editors, who will write a temp file, then rename it to be the // actual file you wrote. So listen for that too. wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime, - [this, settingsPath](wil::FolderChangeEvent, PCWSTR fileModified) { - // TODO! - static const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } }; + [this, settingsBasename = settingsPath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) { + static const auto appState{ ApplicationState::SharedInstance() }; + const winrt::hstring modifiedBasename{ std::filesystem::path{ fileModified }.filename().c_str() }; - const auto modifiedBasename = std::filesystem::path{ fileModified }.filename(); - - if (modifiedBasename == settingsPath.filename()) + if (modifiedBasename == settingsBasename) { _reloadSettings->Run(); } - else if (modifiedBasename == statePath.filename()) + else if (appState.IsStatePath(modifiedBasename)) { _reloadState(); } diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 8d063eb2a..2e58586a4 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -87,14 +87,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _read(); } - // Returns the state.json path on the disk. - winrt::hstring ApplicationState::FilePath() const noexcept + bool ApplicationState::IsStatePath(const winrt::hstring& filename) { - return winrt::hstring{ _sharedPath.wstring() }; + static const auto sharedPath{ _sharedPath.filename() }; + static const auto elevatedPath{ _elevatedPath.filename() }; + static const auto userPath{ _userPath.filename() }; + return filename == sharedPath || filename == elevatedPath || filename == userPath; } - // TODO! - // Deserializes the state.json at _path into this ApplicationState. + // Deserializes the state.json and user-state (or elevated-state if + // elevated) 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 @@ -103,6 +105,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::string errs; std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; + // First get shared state out of `state.json` into us const auto sharedData = _readSharedContents().value_or(std::string{}); if (!sharedData.empty()) { @@ -114,6 +117,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation FromJson(root, FileSource::Shared); } + + // Then, try and get anything in user-state/elevated-state if (const auto localData{ _readLocalContents().value_or(std::string{}) }; !localData.empty()) { Json::Value root; @@ -149,6 +154,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return *state; } + // Method Description: + // - Loads data from the given json blob. Will only read the data that's in + // the specified parseSource - so if we're reading the Local state file, + // we won't destroy previously parsed Shared data. + // - READ: there's no layering for app state. void ApplicationState::FromJson(const Json::Value& root, FileSource parseSource) const noexcept { auto state = _state.lock(); @@ -202,11 +212,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) #undef MTSM_APPLICATION_STATE_GEN + // Method Description: + // - Read the contents of our "shared" state - state that should be shared + // for elevated and unelevated instances. This is things like the list of + // generated profiles, the cmdpal commandlines. std::optional ApplicationState::_readSharedContents() const { return ReadUTF8FileIfExists(_sharedPath); } + // Method Description: + // - Read the contents of our "local" state - state that should be kept in + // separate files for elevated and unelevated instances. This is things + // like the persisted window state, and the approved commandlines (though, + // those don't matter when unelevated). + // - When elevated, this will DELETE `elevated-state.json` if it has bad + // permissions, so we don't potentially read malicious data. std::optional ApplicationState::_readLocalContents() const { return ::Microsoft::Console::Utils::IsElevated() ? @@ -214,11 +235,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ReadUTF8FileIfExists(_userPath, false); } + // Method Description: + // - Write the contents of our "shared" state - state that should be shared + // for elevated and unelevated instances. This will atomically write to + // `state.json` void ApplicationState::_writeSharedContents(const std::string_view content) const { WriteUTF8FileAtomic(_sharedPath, content); } + // Method Description: + // - Write the contents of our "local" state - state that should be kept in + // separate files for elevated and unelevated instances. When elevated, + // this will write to `elevated-state.json`, and when unelevated, this + // will atomically write to `user-state.json` void ApplicationState::_writeLocalContents(const std::string_view content) const { if (::Microsoft::Console::Utils::IsElevated()) diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index f1e328efb..396a1bfea 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -18,19 +18,22 @@ Abstract: #include #include -// This macro generates all getters and setters for ApplicationState. -// It provides X with the following arguments: -// (type, function name, JSON key, ...variadic construction arguments) namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + // If a property is Shared, then it'll be stored in `state.json`, and used + // in both elevated and unelevated instances of the Terminal. If a property + // is marked Local, then it will have separate values for elevated and + // unelevated instances. enum FileSource : int { Shared = 0x1, - Local = 0x2, - // ElevatedOnly + Local = 0x2 }; DEFINE_ENUM_FLAG_OPERATORS(FileSource); +// This macro generates all getters and setters for ApplicationState. +// It provides X with the following arguments: +// (source, type, function name, JSON key, ...variadic construction arguments) #define MTSM_APPLICATION_STATE_FIELDS(X) \ X(FileSource::Shared, std::unordered_set, GeneratedProfiles, "generatedProfiles") \ X(FileSource::Local, Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") \ @@ -60,7 +63,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ToJson(FileSource parseSource) const noexcept; // General getters/setters - winrt::hstring FilePath() const noexcept; + bool IsStatePath(const winrt::hstring& filename); // State getters/setters #define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \ diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index 5d27f0e4c..765f44b78 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -25,7 +25,7 @@ namespace Microsoft.Terminal.Settings.Model void Reload(); - String FilePath { get; }; + Boolean IsStatePath(String filename); Windows.Foundation.Collections.IVector PersistedWindowLayouts { get; set; };