winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit baf0a49230cf4f55e7efa48d1fe7f6ac0b0a89f1
parent 156282cf7efc2124380cda3014c0144220f528a5
Author: Ruben Guerrero <rubengu@microsoft.com>
Date:   Thu, 21 Jul 2022 16:39:35 -0700

Implement FolderFileWatcher (#2336)

* FolderFileWatcher

* c_str

* Remove header

* Internal compiler error

* VS 22 hates me

* Remove string()
Diffstat:
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 1+
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 3+++
Asrc/AppInstallerCLITests/FolderFileWatcher.cpp | 387+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/TestCommon.cpp | 26++++++++++++++++++++++----
Msrc/AppInstallerCLITests/TestCommon.h | 3+++
Msrc/AppInstallerCLITests/pch.h | 1+
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 10++++++++--
Asrc/AppInstallerCommonCore/FolderFileWatcher.cpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h | 32++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/pch.h | 2++
Mtools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 | 2+-
12 files changed, 532 insertions(+), 7 deletions(-)

diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -197,6 +197,7 @@ <ClCompile Include="Downloader.cpp" /> <ClCompile Include="ExperimentalFeature.cpp" /> <ClCompile Include="Filesystem.cpp" /> + <ClCompile Include="FolderFileWatcher.cpp" /> <ClCompile Include="GroupPolicy.cpp" /> <ClCompile Include="HashCommand.cpp" /> <ClCompile Include="HttpClientHelper.cpp" /> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -218,6 +218,9 @@ <ClCompile Include="Filesystem.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="FolderFileWatcher.cpp"> + <Filter>Source Files\Common</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLITests/FolderFileWatcher.cpp b/src/AppInstallerCLITests/FolderFileWatcher.cpp @@ -0,0 +1,387 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include <winget/FolderFileWatcher.h> + +using namespace TestCommon; +using namespace AppInstaller; + +namespace +{ + void WriteText(const std::filesystem::path& path) + { + std::ofstream fileStream{ path }; + fileStream << "text"; + } + + std::filesystem::path RemoveRoot(const std::filesystem::path& prefix, const std::filesystem::path& source) + { + auto prefixItr = prefix.begin(); + auto sourceItr = source.begin(); + + while (prefixItr != prefix.end() && sourceItr != source.end()) + { + if (*prefixItr != *sourceItr) + { + break; + } + + ++prefixItr; + ++sourceItr; + } + + std::filesystem::path result{}; + if (prefixItr == prefix.end()) + { + for (; sourceItr != source.end(); ++sourceItr) + { + if (result.empty()) + { + result = *sourceItr; + } + else + { + result /= *sourceItr; + } + } + } + + return result; + } +} + +TEST_CASE("FolderFileWatcher_CreateNewFiles", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_CreateNewFiles_", true); + + Utility::FolderFileWatcher folderFileWatcher(dirToWatch.GetPath()); + folderFileWatcher.Start(); + + TempFile tempFile1(dirToWatch.GetPath(), "file1_", ".txt"); + WriteText(tempFile1.GetPath()); + + TempFile tempFile2(dirToWatch.GetPath(), "file2_", ".txt"); + WriteText(tempFile2.GetPath()); + + std::filesystem::path newTestDir = dirToWatch.GetPath(); + newTestDir /= "testDir"; + std::filesystem::create_directories(newTestDir); + + TempFile tempFile3(newTestDir, "file3_", ".txt"); + WriteText(tempFile3.GetPath()); + + std::this_thread::sleep_for(100ms); + folderFileWatcher.Stop(); + + auto& watchedFiles = folderFileWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1.GetPath()); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2.GetPath()); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 != watchedFiles.cend()); + + auto tempFile3RelativePath = RemoveRoot(dirToWatch, tempFile3.GetPath()); + auto foundTempFile3 = watchedFiles.find(tempFile3RelativePath); + REQUIRE(foundTempFile3 != watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_CreateAfterStop", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_CreateAfterStop_", true); + + Utility::FolderFileWatcher folderFileWatcher(dirToWatch.GetPath()); + folderFileWatcher.Start(); + + TempFile tempFile1(dirToWatch.GetPath(), "file1_", ".txt"); + WriteText(tempFile1.GetPath()); + + std::this_thread::sleep_for(100ms); + folderFileWatcher.Stop(); + + TempFile tempFile2(dirToWatch.GetPath(), "file2_", ".txt"); + WriteText(tempFile2.GetPath()); + + auto& watchedFiles = folderFileWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1.GetPath()); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2.GetPath()); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 == watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_CreateNewFilesAndRename", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_CreateNewFilesAndRename_", true); + + Utility::FolderFileWatcher folderFileWatcher(dirToWatch.GetPath()); + folderFileWatcher.Start(); + + std::filesystem::path tempFile1Path = dirToWatch.GetPath() / "file1.txt"; + TempFile tempFile1(tempFile1Path); + WriteText(tempFile1Path); + + std::filesystem::path newTestDir = dirToWatch.GetPath(); + newTestDir /= "testDir"; + std::filesystem::create_directories(newTestDir); + + std::filesystem::path tempFile2Path = newTestDir / "file2.txt"; + TempFile tempFile2(tempFile2Path); + WriteText(tempFile2Path); + + std::filesystem::path tempFile1PathRenamed = dirToWatch.GetPath() / "file1_renamed.txt"; + std::filesystem::path tempFile2PathRenamed = newTestDir / "file2_renamed.txt"; + + tempFile1.Rename(tempFile1PathRenamed); + tempFile2.Rename(tempFile2PathRenamed); + + std::this_thread::sleep_for(100ms); + folderFileWatcher.Stop(); + + auto& watchedFiles = folderFileWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1Path); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 == watchedFiles.cend()); + + auto tempFile1RenamedRelativePath = RemoveRoot(dirToWatch, tempFile1PathRenamed); + auto foundTempFile1Renamed = watchedFiles.find(tempFile1RenamedRelativePath); + REQUIRE(foundTempFile1Renamed != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2Path); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 == watchedFiles.cend()); + + auto tempFile2RenamedRelativePath = RemoveRoot(dirToWatch, tempFile2PathRenamed); + auto foundTempFile2Renamed = watchedFiles.find(tempFile2RenamedRelativePath); + REQUIRE(foundTempFile2Renamed != watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_CreateNewFilesAndDelete", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_CreateNewFilesAndDelete_", true); + + Utility::FolderFileWatcher folderFileWatcher(dirToWatch.GetPath()); + folderFileWatcher.Start(); + + TempFile tempFile1(dirToWatch.GetPath(), "file1_", ".txt"); + WriteText(tempFile1.GetPath()); + + std::filesystem::path newTestDir = dirToWatch.GetPath(); + newTestDir /= "testDir"; + std::filesystem::create_directories(newTestDir); + + TempFile tempFile2(newTestDir, "file2_", ".txt"); + WriteText(tempFile2.GetPath()); + + // Create files and delete them. + std::filesystem::path tempFile3Path; + std::filesystem::path tempFile4Path; + { + TempFile tempFile3(dirToWatch.GetPath(), "file3_", ".txt"); + tempFile3Path = tempFile3.GetPath(); + WriteText(tempFile3Path); + + TempFile tempFile4(newTestDir, "file4_", ".txt"); + tempFile4Path = tempFile4.GetPath(); + WriteText(tempFile4Path); + } + + std::this_thread::sleep_for(100ms); + folderFileWatcher.Stop(); + + auto& watchedFiles = folderFileWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1.GetPath()); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2.GetPath()); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 != watchedFiles.cend()); + + auto tempFile3RelativePath = RemoveRoot(dirToWatch, tempFile3Path); + auto foundTempFile3 = watchedFiles.find(tempFile3RelativePath); + REQUIRE(foundTempFile3 == watchedFiles.cend()); + + auto tempFile4RelativePath = RemoveRoot(dirToWatch, tempFile4Path); + auto foundTempFile4 = watchedFiles.find(tempFile4RelativePath); + REQUIRE(foundTempFile4 == watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_Extension_CreateNewFiles", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_Extension_CreateNewFiles", true); + + Utility::FolderFileWatcher folderFileExtensionWatcher(dirToWatch.GetPath(), ".yaml"); + folderFileExtensionWatcher.Start(); + + TempFile tempFile1(dirToWatch.GetPath(), "file1_", ".txt"); + WriteText(tempFile1.GetPath()); + + TempFile tempFile2(dirToWatch.GetPath(), "file2_", ".yaml"); + WriteText(tempFile2.GetPath()); + + std::filesystem::path newTestDir = dirToWatch.GetPath(); + newTestDir /= "testDir"; + std::filesystem::create_directories(newTestDir); + + TempFile tempFile3(newTestDir, "file3_", ".txt"); + WriteText(tempFile3.GetPath()); + + TempFile tempFile4(newTestDir, "file4_", ".yaml"); + WriteText(tempFile4.GetPath()); + + std::this_thread::sleep_for(100ms); + folderFileExtensionWatcher.Stop(); + + auto& watchedFiles = folderFileExtensionWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1.GetPath()); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 == watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2.GetPath()); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 != watchedFiles.cend()); + + auto tempFile3RelativePath = RemoveRoot(dirToWatch, tempFile3.GetPath()); + auto foundTempFile3 = watchedFiles.find(tempFile3RelativePath); + REQUIRE(foundTempFile3 == watchedFiles.cend()); + + auto tempFile4RelativePath = RemoveRoot(dirToWatch, tempFile4.GetPath()); + auto foundTempFile4 = watchedFiles.find(tempFile4RelativePath); + REQUIRE(foundTempFile4 != watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_Extension_CreateAfterStop", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_Extension_CreateAfterStop", true); + + Utility::FolderFileWatcher folderFileExtensionWatcher(dirToWatch.GetPath(), ".txt"); + folderFileExtensionWatcher.Start(); + + TempFile tempFile1(dirToWatch.GetPath(), "file1_", ".txt"); + WriteText(tempFile1.GetPath()); + + std::this_thread::sleep_for(100ms); + folderFileExtensionWatcher.Stop(); + + TempFile tempFile2(dirToWatch.GetPath(), "file2_", ".txt"); + WriteText(tempFile2.GetPath()); + + auto& watchedFiles = folderFileExtensionWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1.GetPath()); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2.GetPath()); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 == watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_Extension_CreateNewFilesAndRename", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_Extension_CreateNewFilesAndRename", true); + + Utility::FolderFileWatcher folderFileExtensionWatcher(dirToWatch.GetPath(), ".txt"); + folderFileExtensionWatcher.Start(); + + std::filesystem::path tempFile1Path = dirToWatch.GetPath() / "file1.txt"; + TempFile tempFile1(tempFile1Path); + WriteText(tempFile1Path); + + std::filesystem::path newTestDir = dirToWatch.GetPath(); + newTestDir /= "testDir"; + std::filesystem::create_directories(newTestDir); + + std::filesystem::path tempFile2Path = newTestDir / "file2.txt"; + TempFile tempFile2(tempFile2Path); + WriteText(tempFile2Path); + + std::filesystem::path tempFile1PathRenamed = dirToWatch.GetPath() / "file1_renamed.txt"; + std::filesystem::path tempFile2PathRenamed = newTestDir / "file2_renamed.txt"; + + tempFile1.Rename(tempFile1PathRenamed); + tempFile2.Rename(tempFile2PathRenamed); + + std::this_thread::sleep_for(100ms); + folderFileExtensionWatcher.Stop(); + + auto& watchedFiles = folderFileExtensionWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1Path); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 == watchedFiles.cend()); + + auto tempFile1RenamedRelativePath = RemoveRoot(dirToWatch, tempFile1PathRenamed); + auto foundTempFile1Renamed = watchedFiles.find(tempFile1RenamedRelativePath); + REQUIRE(foundTempFile1Renamed != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2Path); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 == watchedFiles.cend()); + + auto tempFile2RenamedRelativePath = RemoveRoot(dirToWatch, tempFile2PathRenamed); + auto foundTempFile2Renamed = watchedFiles.find(tempFile2RenamedRelativePath); + REQUIRE(foundTempFile2Renamed != watchedFiles.cend()); +} + +TEST_CASE("FolderFileWatcher_Extension_CreateNewFilesAndDelete", "[FolderFileWatcher]") +{ + TempDirectory dirToWatch("FolderFileWatcher_Extension_CreateNewFilesAndDelete", true); + + Utility::FolderFileWatcher folderFileExtensionWatcher(dirToWatch.GetPath(), ".txt"); + folderFileExtensionWatcher.Start(); + + TempFile tempFile1(dirToWatch.GetPath(), "file1_", ".txt"); + WriteText(tempFile1.GetPath()); + + std::filesystem::path newTestDir = dirToWatch.GetPath(); + newTestDir /= "testDir"; + std::filesystem::create_directories(newTestDir); + + TempFile tempFile2(newTestDir, "file2_", ".txt"); + WriteText(tempFile2.GetPath()); + + // Create files and delete them. + std::filesystem::path tempFile3Path; + std::filesystem::path tempFile4Path; + { + TempFile tempFile3(dirToWatch.GetPath(), "file3_", ".txt"); + tempFile3Path = tempFile3.GetPath(); + WriteText(tempFile3Path); + + TempFile tempFile4(newTestDir, "file4_", ".txt"); + tempFile4Path = tempFile4.GetPath(); + WriteText(tempFile4Path); + } + + std::this_thread::sleep_for(100ms); + folderFileExtensionWatcher.Stop(); + + auto& watchedFiles = folderFileExtensionWatcher.files(); + + auto tempFile1RelativePath = RemoveRoot(dirToWatch, tempFile1.GetPath()); + auto foundTempFile1 = watchedFiles.find(tempFile1RelativePath); + REQUIRE(foundTempFile1 != watchedFiles.cend()); + + auto tempFile2RelativePath = RemoveRoot(dirToWatch, tempFile2.GetPath()); + auto foundTempFile2 = watchedFiles.find(tempFile2RelativePath); + REQUIRE(foundTempFile2 != watchedFiles.cend()); + + auto tempFile3RelativePath = RemoveRoot(dirToWatch, tempFile3Path); + auto foundTempFile3 = watchedFiles.find(tempFile3RelativePath); + REQUIRE(foundTempFile3 == watchedFiles.cend()); + + auto tempFile4RelativePath = RemoveRoot(dirToWatch, tempFile4Path); + auto foundTempFile4 = watchedFiles.find(tempFile4RelativePath); + REQUIRE(foundTempFile4 == watchedFiles.cend()); +} diff --git a/src/AppInstallerCLITests/TestCommon.cpp b/src/AppInstallerCLITests/TestCommon.cpp @@ -24,13 +24,16 @@ namespace TestCommon return randStart++; } + inline std::filesystem::path GetFilePath(std::filesystem::path path, const std::string& baseName, const std::string& baseExt) + { + path /= baseName + std::to_string(getRand()) + baseExt; + return path; + } + inline std::filesystem::path GetTempFilePath(const std::string& baseName, const std::string& baseExt) { std::filesystem::path tempFilePath = std::filesystem::temp_directory_path(); - - tempFilePath /= baseName + std::to_string(getRand()) + baseExt; - - return tempFilePath; + return GetFilePath(tempFilePath, baseName, baseExt); } static TempFileDestructionBehavior s_TempFileDestructorBehavior = TempFileDestructionBehavior::Delete; @@ -54,6 +57,15 @@ namespace TestCommon } } + TempFile::TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, bool deleteFileOnConstruction) + { + _filepath = GetFilePath(parent, baseName, baseExt); + if (deleteFileOnConstruction) + { + std::filesystem::remove(_filepath); + } + } + TempFile::TempFile(const std::filesystem::path& filePath, bool deleteFileOnConstruction) { if (filePath.is_relative()) @@ -86,6 +98,12 @@ namespace TestCommon } } + void TempFile::Rename(const std::filesystem::path& newFilePath) + { + std::filesystem::rename(GetPath(), newFilePath); + _filepath = newFilePath; + } + void TempFile::SetDestructorBehavior(TempFileDestructionBehavior behavior) { s_TempFileDestructorBehavior = behavior; diff --git a/src/AppInstallerCLITests/TestCommon.h b/src/AppInstallerCLITests/TestCommon.h @@ -26,6 +26,7 @@ namespace TestCommon struct TempFile { TempFile(const std::string& baseName, const std::string& baseExt, bool deleteFileOnConstruction = true); + TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, bool deleteFileOnConstruction = true); TempFile(const std::filesystem::path& filePath, bool deleteFileOnConstruction = true); TempFile(const TempFile&) = delete; @@ -40,6 +41,8 @@ namespace TestCommon operator const std::filesystem::path& () const { return _filepath; } operator const std::string() const { return _filepath.u8string(); } + void Rename(const std::filesystem::path& newFilePath); + static void SetDestructorBehavior(TempFileDestructionBehavior behavior); static void SetTestFailed(bool failed); diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h @@ -17,6 +17,7 @@ #include <winrt/Windows.Globalization.h> #include <winrt/Windows.Management.Deployment.h> +#include <wil/filesystem.h> #include <wil/resource.h> #include <wil/result_macros.h> #include <wil/token_helpers.h> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -278,6 +278,7 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="DODownloader.h" /> + <ClInclude Include="Public\winget\FolderFileWatcher.h" /> <ClInclude Include="Public\winget\MsixManifest.h" /> <ClInclude Include="Public\winget\AdminSettings.h" /> <ClInclude Include="Public\winget\Debugging.h" /> @@ -342,6 +343,7 @@ <ClCompile Include="DependenciesGraph.cpp" /> <ClCompile Include="DODownloader.cpp" /> <ClCompile Include="Filesystem.cpp" /> + <ClCompile Include="FolderFileWatcher.cpp" /> <ClCompile Include="GroupPolicy.cpp"> <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> </ClCompile> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -201,9 +201,12 @@ <ClInclude Include="Public\winget\MsixManifest.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="Public\winget\FolderFileWatcher.h"> + <Filter>Public\winget</Filter> + </ClInclude> <ClInclude Include="Public\winget\Archive.h"> <Filter>Public\winget</Filter> - </ClInclude> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -353,9 +356,12 @@ <ClCompile Include="MsixManifest.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="FolderFileWatcher.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="Archive.cpp"> <Filter>Source Files</Filter> - </ClCompile> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/FolderFileWatcher.cpp b/src/AppInstallerCommonCore/FolderFileWatcher.cpp @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/FolderFileWatcher.h" + +namespace AppInstaller::Utility +{ + FolderFileWatcher::FolderFileWatcher(const std::filesystem::path& path, const std::optional<std::string>& ext) : + m_path(path), m_ext(ext), m_changeReader{} + { + } + + void FolderFileWatcher::Start() + { + m_files.clear(); + m_changeReader = wil::make_folder_change_reader(m_path.c_str(), + true, + wil::FolderChangeEvents::FileName, + [this](wil::FolderChangeEvent changeEvent, PCWSTR filePath) + { + switch (changeEvent) + { + // The file was added to the directory. + case wil::FolderChangeEvent::Added: + // The file was renamed and this is the new name. + case wil::FolderChangeEvent::RenameNewName: + { + std::filesystem::path path(filePath); + if (!m_ext.has_value() || + (m_ext.has_value() && path.extension() == m_ext)) + { + m_files.emplace(path); + } + break; + } + + // The file was removed from the directory. + case wil::FolderChangeEvent::Removed: + // The file was renamed and this is the old name. + case wil::FolderChangeEvent::RenameOldName: + { + std::filesystem::path path(filePath); + if (!m_ext.has_value() || + (m_ext.has_value() && path.extension() == m_ext)) + { + auto it = m_files.find(path); + if (it != m_files.cend()) + { + m_files.erase(it); + } + } + break; + } + + // The file was modified. This can be a change in the time stamp or attributes. + case wil::FolderChangeEvent::Modified: + // A change happens but it got lost. The result of the IoCompletionCallback is ERROR_NOTIFY_ENUM_DIR. + case wil::FolderChangeEvent::ChangesLost: + default: + break; + } + }); + } + + void FolderFileWatcher::Stop() + { + m_changeReader.reset(); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h b/src/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "pch.h" + +namespace AppInstaller::Utility +{ + // Watch for new/renamed files recursively in a given directory with an optional extension. + struct FolderFileWatcher + { + FolderFileWatcher(const std::filesystem::path& path, const std::optional<std::string>& ext = std::nullopt); + ~FolderFileWatcher() {}; + + FolderFileWatcher(const FolderFileWatcher&) = delete; + FolderFileWatcher& operator=(const FolderFileWatcher&) = delete; + + FolderFileWatcher(FolderFileWatcher&&) = delete; + FolderFileWatcher& operator=(FolderFileWatcher&&) = delete; + + void Start(); + void Stop(); + + const std::unordered_set<std::filesystem::path>& files() { return m_files; } + + private: + std::filesystem::path m_path; + std::optional<std::string> m_ext; + std::unordered_set<std::filesystem::path> m_files; + wil::unique_folder_change_reader m_changeReader; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -56,6 +56,7 @@ #include <stack> #include <string_view> #include <type_traits> +#include <unordered_set> #include <vector> #pragma warning( push ) @@ -66,6 +67,7 @@ #include <wil/safecast.h> #include <wil/token_helpers.h> #include <wil/com.h> +#include <wil/filesystem.h> #pragma warning( pop ) #ifndef WINGET_DISABLE_FOR_FUZZING diff --git a/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 b/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 @@ -240,7 +240,7 @@ if (-not $UseDev) # Extract Microsoft.UI.Xaml from zip (if freshly downloaded). # This is a workaround until https://github.com/microsoft/winget-cli/issues/1861 is resolved. - if (-Not (Test-Path (Join-Path -Path $tampFolder -ChildPath \Microsoft.UI.Xaml.2.7\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx))) + if (-Not (Test-Path (Join-Path -Path $tempFolder -ChildPath \Microsoft.UI.Xaml.2.7\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx))) { Expand-Archive -Path $uiLibsUwp.file -DestinationPath ($tempFolder + "\Microsoft.UI.Xaml.2.7") -Force }