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:
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
}