-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce a basic ApplicationState class #10513
Conversation
This comment has been minimized.
This comment has been minimized.
_dialogLock{}, | ||
_loadedInitialSettings{ false }, | ||
_settingsLoadedResult{ S_OK } | ||
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the existing settings-reload throttler, this does the same but for state.json.
Since til::throttled_func
cannot be copied or moved, this type must be initialized in the member list initializer (instead of more ergonomically in the constructor function body).
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(_root->Dispatcher(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { | ||
if (auto self{ weakSelf.get() }) | ||
{ | ||
self->_ReloadSettings(); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This replaces the previous, manual settings.json load delay & throttling we had before.
// settings.json. This second case will oftentimes happen with text | ||
// 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We previously listened for wil::FolderChangeEvents::All
and then filtered the events, but this isn't necessary...
FileName
maps toFILE_NOTIFY_CHANGE_FILE_NAME
and includes renaming, creating, or deleting a file name.LastWriteTime
maps toFILE_NOTIFY_CHANGE_LAST_WRITE
and includes any writes that are flushed to disk.
// create the defaults. | ||
LOG_LAST_ERROR(); | ||
return std::nullopt; | ||
std::filesystem::rename(pathToLegacySettingsFile, pathToSettingsFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::filesystem::rename
attempts to be atomic and we don't have to use Win32 for that.
|
||
namespace Microsoft::Terminal::Settings::Model | ||
{ | ||
std::filesystem::path GetBaseSettingsPath() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an almost 1:1 copy of CascadiaSettings::SettingsPath()
.
return baseSettingsPath; | ||
} | ||
|
||
std::string ReadUTF8File(const std::filesystem::path& path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function unifies our many, many manual ways to read files from the disk.
This ensures we focus unsafe API usage in a single function and can easily introduce features like:
- Preventing us from returning cut-off files / files that were changed while reading
- Retrying in case something goes wrong temporarily.
{ | ||
if (exception.GetErrorCode() == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) | ||
{ | ||
return {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I know exceptions for control flow are bad, but I didn't have a good idea how to write this without being too roundabout.
} | ||
} | ||
|
||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay. 🎉
auto tmpPath = path; | ||
tmpPath += L".tmp"; | ||
|
||
WriteUTF8File(tmpPath, content); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the path with .tmp suffix already exists? What if the rename fails?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming the user will never create a .tmp file themselves...
Overwriting it is absolutely fine since the actually important file (state.json
) will only be overwritten by the rename operation. And the rename is basically an atomic operation so it shouldn't ever fail midway.
|
||
if (til::starts_with(buffer, Utf8Bom)) | ||
{ | ||
buffer.erase(0, Utf8Bom.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Man I really hope this doesn't reallocate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but it does a slow 🍑 memmove
! I just don't think a lot of people will have a UTF-8 BOM and I don't want to plan for it any more than necessary, because BOMs in UTF8 are an aBOMination. *ba dum tss*
// It provides X with the following arguments: | ||
// (type, function name, JSON key, ...variadic construction arguments) | ||
#define MTSM_APPLICATION_STATE_FIELDS(X) \ | ||
X(std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory, more things go here later, right? And you need more Traits to convert them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah all fields will just go into this macro (and into the .idl).
But hopefully we won't need more converters. This new one is just a hack, because I wanted to have an unordered set for later, but I don't think we'll need it anywhere else.
|
||
std::string TypeDescription() const | ||
{ | ||
return fmt::format("std::unordered_set<{}>", ConversionTrait<GUID>{}.TypeDescription()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message is intended for display to the user, but at the same time, the state deserializer is meant to be silent and discard-on-failure 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you suggest? Should I remove the implementation of this function and return an empty string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my recommendation would be something less C++y and more javascripty. "array (of {})" or something similar. I don't wholly mind if you leave it like this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or even like, "list of guids"
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
@DHowett It's interesting how I cannot reply to your comment. |
technically correct, which "is the best kind of correct" |
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a short abstract and slap your name on it haha
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by our header styles: til uses this brief format, while some files uses the verbose one. Can I use the brief one in new code? Or should it always be the verbose one outside of til?
But I'm also not sure if anyone reads the verbose variant to be honest...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, generally, we should put the verbose one in the header, but the brief one in the cpp.
For files/classes that are pretty self-explanatory, the brief one is fine imo. Like, this one is probably fine because "FileUtils" is pretty clear that this is just a bunch of useful functions when working with files.
For the other one (ApplicationState), it's more useful because...
- it's more complicated of a concept. What is this class used for?
- if we recognize the name, it's easier to reach out to them and find them (easier than a git blame, but not by much)
Hello @lhecker! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
This commit introduces a basic ApplicationState class, without being used for anything yet to aid reviewers. At a later point actual usages of this new class may be added separately.
References
This commit is an initial step towards implementing #8324.
PR Checklist
Validation Steps Performed
state.json
with{"generatedProfiles":["{53e75ed9-2b63-4118-856d-0510c4f6b97e}"]}
updates the ApplicationState, as observed through a debugger ✔️