Skip to content
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

Merged
merged 4 commits into from
Jun 30, 2021
Merged

Introduce a basic ApplicationState class #10513

merged 4 commits into from
Jun 30, 2021

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Jun 25, 2021

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

  • I work here
  • Tests added/passed

Validation Steps Performed

  • Creating a state.json with {"generatedProfiles":["{53e75ed9-2b63-4118-856d-0510c4f6b97e}"]} updates the ApplicationState, as observed through a debugger ✔️
  • Deleting the "generatedProfiles" field sets the corresponding field back to nullopt ✔️

@github-actions

This comment has been minimized.

_dialogLock{},
_loadedInitialSettings{ false },
_settingsLoadedResult{ S_OK }
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
Copy link
Member Author

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).

Comment on lines +206 to +211
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(_root->Dispatcher(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->_ReloadSettings();
}
});
Copy link
Member Author

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,
Copy link
Member Author

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 to FILE_NOTIFY_CHANGE_FILE_NAME and includes renaming, creating, or deleting a file name.
  • LastWriteTime maps to FILE_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);
Copy link
Member Author

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()
Copy link
Member Author

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)
Copy link
Member Author

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 {};
Copy link
Member Author

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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay. 🎉

@lhecker lhecker marked this pull request as ready for review June 28, 2021 15:54
auto tmpPath = path;
tmpPath += L".tmp";

WriteUTF8File(tmpPath, content);
Copy link
Member

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?

Copy link
Member Author

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());
Copy link
Member

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.

Copy link
Member Author

@lhecker lhecker Jun 28, 2021

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")
Copy link
Member

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?

Copy link
Member Author

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());
Copy link
Member

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 😄

Copy link
Member Author

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?

Copy link
Member

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

Copy link
Member

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"

@lhecker
Copy link
Member Author

lhecker commented Jun 29, 2021

@DHowett It's interesting how I cannot reply to your comment.
Either way: I'll revert the change to GetModuleFileNameW. The prior version is technically more correct, right?

@DHowett
Copy link
Member

DHowett commented Jun 29, 2021

The prior version is technically more correct

technically correct, which "is the best kind of correct"

Comment on lines +1 to +2
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
Copy link
Member

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

Copy link
Member Author

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...

Copy link
Member

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...

  1. it's more complicated of a concept. What is this class used for?
  2. 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)

src/cascadia/TerminalSettingsModel/FileUtils.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/ApplicationState.h Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/ApplicationState.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/ApplicationState.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsModel/ApplicationState.cpp Outdated Show resolved Hide resolved
@lhecker lhecker added Area-Settings Issues related to settings and customizability, for console or terminal Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal. AutoMerge Marked for automatic merge by the bot when requirements are met labels Jun 30, 2021
@ghost
Copy link

ghost commented Jun 30, 2021

Hello @lhecker!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

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 (@msftbot) and give me an instruction to get started! Learn more here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Settings Issues related to settings and customizability, for console or terminal AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants