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

Font Install #5042

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Font Install #5042

wants to merge 14 commits into from

Conversation

ryfu-msft
Copy link
Contributor

@ryfu-msft ryfu-msft commented Dec 5, 2024

Related to: #392, #4842

Changes:
Adds Support for installing a font.

  • Created a FontInstaller that does two things based on the scope. place the file in the fonts directory, and writes the subkey entry in the Fonts Registry based on the title of the file.
  • Added a IsFontSupported function that calls upon the Dwrite Analyze function to determine if the system can support installing the font. In the flow, if this fails, we error out with no way to override.
  • Adds a new font install command but currently only supports installing from a local manifest. This means that the same font manifest will work for regular install.

Tests:

  • Added tests to verify if the installation was successful and if the correct error message is displayed if the font file is invalid.
Microsoft Reviewers: Open in CodeFlow

This comment has been minimized.

This comment has been minimized.

@ryfu-msft ryfu-msft marked this pull request as ready for review December 10, 2024 17:30
@ryfu-msft ryfu-msft requested a review from a team as a code owner December 10, 2024 17:30
src/AppInstallerCLICore/FontInstaller.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/FontInstaller.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/FontInstaller.cpp Outdated Show resolved Hide resolved
AICLI_LOG(CLI, Info, << "Creating font subkey with name: " << AppInstaller::Utility::ConvertToUTF8(title));
if (m_scope == Manifest::ScopeEnum::Machine)
{
m_key.SetValue(title, fileName, REG_SZ);
Copy link
Member

Choose a reason for hiding this comment

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

Is it truly always a 1:1 registry value to file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I believe so. Whenever I do a manual install of a font file, it creates a new registry value. I haven't seen an example where that hasn't happened yet.

Copy link
Member

Choose a reason for hiding this comment

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

The value names must be unique, so we know that isn't an issue. But that leaves actual question in a more verbose form "Is it possible for multiple values to point to the same file, for instance a .ttc?". Maybe it is acceptable for us to install it as a single entry and the DWrite APIs take care of exposing the contents. But have you installed a TTC via explorer and examined the resulting registry changes?

Copy link
Contributor Author

@ryfu-msft ryfu-msft Dec 19, 2024

Choose a reason for hiding this comment

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

A TTC also only has exactly one registry value when installed. Having multiple registry values point to the same font file doesn't appear to have any effect on how the font appears in the system.

src/AppInstallerCLICore/Workflows/DownloadFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/FontFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/FontFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/FontFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerSharedLib/Errors.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/FontInstaller.cpp Outdated Show resolved Hide resolved
AICLI_LOG(CLI, Info, << "Creating font subkey with name: " << AppInstaller::Utility::ConvertToUTF8(title));
if (m_scope == Manifest::ScopeEnum::Machine)
{
m_key.SetValue(title, fileName, REG_SZ);
Copy link
Member

Choose a reason for hiding this comment

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

The value names must be unique, so we know that isn't an issue. But that leaves actual question in a more verbose form "Is it possible for multiple values to point to the same file, for instance a .ttc?". Maybe it is acceptable for us to install it as a single entry and the DWrite APIs take care of exposing the contents. But have you installed a TTC via explorer and examined the resulting registry changes?

@@ -69,6 +85,9 @@ namespace AppInstaller::CLI::Font
{
m_key.SetValue(title, destinationPath, REG_SZ);
}

AICLI_LOG(CLI, Info, << "Moving font file to: " << destinationPath);
AppInstaller::Filesystem::RenameFile(filePath, destinationPath);
Copy link
Member

Choose a reason for hiding this comment

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

Does this handle the case where the target file already exists? If it deletes that file, this is probably bad. We should choose a new name if it does already exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a check to see if the installation will overwrite an existing font file. If that is true, I show an error message. This is only a temporary solution for now, we need a proper way to bring back those font files that we remove.

if (std::filesystem::exists(existingFontFilePath))
{
AICLI_LOG(CLI, Info, << "Removing existing font file at:" << existingFontFilePath);
std::filesystem::remove(existingFontFilePath);
Copy link
Member

Choose a reason for hiding this comment

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

If the operation fails, we cannot revert this. Much like my comment on the rest of the operation being reverted on failure, we want some mechanism to put things back the way they were.

I would think that to make it maximally recoverable, one would need to:

  1. Create a temporary portable tracking database for the file and registry changes (probably requires some amount of addition to the tracked things)
  2. Set up a resume that can leverage the database to revert to the previous state

But I don't think we need to be that extreme (handling full on process termination). A more modest approach would have a vector of IOperationSteps that you populate as you go. On exiting the operation, you either invoke Complete or Revert on all of the items, depending on success or failure. In this case, Complete deletes the renamed file, while Revert renames it back to its old state.

const auto& destinationPath = m_installLocation / fileName;
if (m_key[fontFile.Title].has_value())
{
if (!std::filesystem::exists(m_key[fontFile.Title]->GetValue<Registry::Value::Type::String>()))
Copy link
Member

Choose a reason for hiding this comment

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

Retrieve m_key[fontFile.Title] once rather than in both conditions.

AICLI_LOG(CLI, Info, << "Existing font subkey found:" << AppInstaller::Utility::ConvertToUTF8(title));
std::filesystem::path existingFontFilePath = { m_key[title]->GetValue<Registry::Value::Type::String>() };
AICLI_LOG(CLI, Info, << "Existing font value found:" << AppInstaller::Utility::ConvertToUTF8(fontFile.Title));
std::filesystem::path existingFontFilePath = { m_key[fontFile.Title]->GetValue<Registry::Value::Type::String>() };
Copy link
Member

Choose a reason for hiding this comment

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

You will want to assign the path from a wide string; I think there is a registry value to get that directly.


for (const auto& file : filePaths)
for (std::filesystem::path filePath : filePaths)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (std::filesystem::path filePath : filePaths)
for (const std::filesystem::path& filePath : filePaths)

Did we need a copy?

@@ -132,6 +132,20 @@ namespace AppInstaller::Registry

return true;
}

bool TryDeleteRegistryValueData(const wil::shared_hkey& key, const std::wstring& valueName)
Copy link
Member

Choose a reason for hiding this comment

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

This should probably be called DeleteRegistryValueIfPresent. Try shouldn't throw, even on failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants