Files
UnrealEngine/Engine/Source/Programs/TestPAL/Private/TestDirectoryWatcher.cpp
2025-05-18 13:04:45 +08:00

202 lines
6.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TestDirectoryWatcher.h"
#if USE_DIRECTORY_WATCHER
#include "IDirectoryWatcher.h"
#include "DirectoryWatcherModule.h"
#include "LaunchEngineLoop.h" // GEngineLoop
#include "Modules/ModuleManager.h"
#include "TestPALLog.h"
#include "HAL/PlatformFile.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/PlatformProcess.h"
struct FChangeDetector
{
void OnDirectoryChangedFunc(const FString& Title, const TArray<FFileChangeData>& FileChanges)
{
UE_LOG(LogTestPAL, Display, TEXT(" -- %s %d change(s) detected"), *Title, static_cast<int32>(FileChanges.Num()));
int ChangeIdx = 0;
for (const auto& ThisEntry : FileChanges)
{
UE_LOG(LogTestPAL, Display, TEXT(" Change %d: %s was %s"),
++ChangeIdx,
*ThisEntry.Filename,
ThisEntry.Action == FFileChangeData::FCA_Added ? TEXT("added") :
(ThisEntry.Action == FFileChangeData::FCA_Removed ? TEXT("removed") :
(ThisEntry.Action == FFileChangeData::FCA_Modified ? TEXT("modified") : TEXT("??? (unknown)")
)
)
);
}
UE_LOG(LogTestPAL, Display, TEXT(""));
}
void OnDirectoryChanged(const TArray<FFileChangeData>& FileChanges)
{
OnDirectoryChangedFunc(TEXT("OnDirectoryChanged1"), FileChanges);
}
void OnDirectoryChanged2(const TArray<FFileChangeData>& FileChanges)
{
OnDirectoryChangedFunc(TEXT("OnDirectoryChanged2"), FileChanges);
}
};
static void DoTick(IDirectoryWatcher* DirectoryWatcher, float Dt = 0.2f)
{
DirectoryWatcher->Tick(Dt);
FPlatformProcess::Sleep(Dt);
DirectoryWatcher->Tick(Dt);
}
static void DoCommand(IDirectoryWatcher *DirectoryWatcher, const FString& Command, const FString& Arg)
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (Command == TEXT("CreateDirectory"))
{
UE_LOG(LogTestPAL, Display, TEXT("Creating DIRECTORY '%s'"), *Arg);
verify(PlatformFile.CreateDirectory(*Arg));
}
else if (Command == TEXT("DeleteDirectory"))
{
UE_LOG(LogTestPAL, Display, TEXT("Deleting DIRECTORY '%s'"), *Arg);
verify(PlatformFile.DeleteDirectory(*Arg));
}
else if (Command == TEXT("CreateModifyFile"))
{
// create file
UE_LOG(LogTestPAL, Display, TEXT("Creating FILE '%s'"), *Arg);
IFileHandle* DummyFile = PlatformFile.OpenWrite(*Arg);
check(DummyFile);
DoTick(DirectoryWatcher);
// modify file
UE_LOG(LogTestPAL, Display, TEXT("Modifying FILE '%s'"), *Arg);
uint8 Contents = 0x32;
DummyFile->Write(&Contents, sizeof(Contents));
DoTick(DirectoryWatcher);
// close the file
UE_LOG(LogTestPAL, Display, TEXT("Closing FILE '%s'"), *Arg);
delete DummyFile;
DummyFile = nullptr;
DoTick(DirectoryWatcher);
}
else if (Command == TEXT("DeleteFile"))
{
// delete file
UE_LOG(LogTestPAL, Display, TEXT("Deleting FILE '%s'"), *Arg);
verify(PlatformFile.DeleteFile(*Arg));
DoTick(DirectoryWatcher);
}
else
{
checkf(false, TEXT("Unknown command"));
}
DoTick(DirectoryWatcher);
DirectoryWatcher->DumpStats();
}
/**
* Kicks directory watcher test/
*/
int32 DirectoryWatcherTest(const TCHAR* CommandLine)
{
FPlatformMisc::SetCrashHandler(NULL);
FPlatformMisc::SetGracefulTerminationHandler();
GEngineLoop.PreInit(CommandLine);
UE_LOG(LogTestPAL, Display, TEXT("Running directory watcher test."));
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
const FString TestDir = FString::Printf(TEXT("%s/DirectoryWatcherTest-%d"), FPlatformProcess::UserTempDir(), FPlatformProcess::GetCurrentProcessId());
const FString SubtestDir = TestDir + TEXT("/subtest");
if (PlatformFile.CreateDirectory(*TestDir) && PlatformFile.CreateDirectory(*SubtestDir))
{
FChangeDetector Detector, Detector2;
FDelegateHandle DirectoryChangedHandle, DirectoryChangedHandle2;
IDirectoryWatcher* DirectoryWatcher = FModuleManager::Get().LoadModuleChecked<FDirectoryWatcherModule>(TEXT("DirectoryWatcher")).Get();
if (DirectoryWatcher)
{
/** Whether to include notifications for changes to actual directories (such as directories being created or removed). */
// IDirectoryWatcher::WatchOptions::IgnoreChangesInSubtree
/** Whether changes in subdirectories need to be reported. */
// IDirectoryWatcher::WatchOptions::IncludeDirectoryChanges
uint32 Flags = IDirectoryWatcher::WatchOptions::IncludeDirectoryChanges;
auto Callback = IDirectoryWatcher::FDirectoryChanged::CreateRaw(&Detector, &FChangeDetector::OnDirectoryChanged);
DirectoryWatcher->RegisterDirectoryChangedCallback_Handle(TestDir, Callback, DirectoryChangedHandle, Flags);
UE_LOG(LogTestPAL, Display, TEXT("Registered callback for changes in '%s'"), *TestDir);
auto Callback2 = IDirectoryWatcher::FDirectoryChanged::CreateRaw(&Detector2, &FChangeDetector::OnDirectoryChanged2);
DirectoryWatcher->RegisterDirectoryChangedCallback_Handle(SubtestDir, Callback2, DirectoryChangedHandle2, Flags);
UE_LOG(LogTestPAL, Display, TEXT("Registered callback 2 for changes in '%s'"), *SubtestDir);
}
else
{
UE_LOG(LogTestPAL, Fatal, TEXT("Could not get DirectoryWatcher module"));
}
DoTick(DirectoryWatcher);
// create and remove directory
DoCommand(DirectoryWatcher, "CreateDirectory", (TestDir + TEXT("/test")));
DoCommand(DirectoryWatcher, "DeleteDirectory", (TestDir + TEXT("/test")));
// create and remove in a sub directory
DoCommand(DirectoryWatcher, "CreateDirectory", (SubtestDir + TEXT("/blah")));
UE_LOG(LogTestPAL, Display, TEXT("Moving DIRECTORY '%s' -> '%s'"), *(SubtestDir + TEXT("/blah")), *(SubtestDir + TEXT("/blah2")));
verify(PlatformFile.MoveFile(*(SubtestDir + TEXT("/blah2")), *(SubtestDir + TEXT("/blah"))));
DoTick(DirectoryWatcher);
DoCommand(DirectoryWatcher, "DeleteDirectory", (SubtestDir + TEXT("/blah2")));
DoCommand(DirectoryWatcher, "CreateModifyFile", TestDir + TEXT("/test file.bin"));
DoCommand(DirectoryWatcher, "DeleteFile", TestDir + TEXT("/test file.bin"));
// now the same in a grandchild directory
{
FString GrandChildDir = SubtestDir + TEXT("/grandchild");
DoCommand(DirectoryWatcher, "CreateDirectory", GrandChildDir);
DoCommand(DirectoryWatcher, "CreateModifyFile", (GrandChildDir + TEXT("/test file.bin")));
DoCommand(DirectoryWatcher, "DeleteFile", (GrandChildDir + TEXT("/test file.bin")));
DoCommand(DirectoryWatcher, "DeleteDirectory", GrandChildDir);
}
// remove main dirs as another test case
DoCommand(DirectoryWatcher, "DeleteDirectory", SubtestDir);
DoCommand(DirectoryWatcher, "DeleteDirectory", TestDir);
// Clean up
verify(DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(TestDir, DirectoryChangedHandle));
verify(DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(SubtestDir, DirectoryChangedHandle2));
// Unregister requests get moved to a list for deletion during tick
DoTick(DirectoryWatcher);
UE_LOG(LogTestPAL, Display, TEXT("End of test"));
}
else
{
UE_LOG(LogTestPAL, Fatal, TEXT("Could not create test directory %s."), *TestDir);
}
FEngineLoop::AppPreExit();
FEngineLoop::AppExit();
return 0;
}
#endif /* USE_DIRECTORY_WATCHER */