192 lines
5.5 KiB
C++
192 lines
5.5 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#if PLATFORM_WINDOWS
|
|
|
|
#include "WindowsFileSystemWatcher.h"
|
|
#include "Engine/Platform/CriticalSection.h"
|
|
#include "Engine/Platform/Thread.h"
|
|
#include "Engine/Threading/ThreadSpawner.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Math/Math.h"
|
|
#include "Engine/Core/Collections/Array.h"
|
|
#include "../Win32/IncludeWindowsHeaders.h"
|
|
|
|
BOOL RefreshWatch(WindowsFileSystemWatcher* watcher);
|
|
|
|
namespace FileSystemWatchers
|
|
{
|
|
CriticalSection Locker;
|
|
Array<WindowsFileSystemWatcher*, InlinedAllocation<16>> Watchers;
|
|
Win32Thread* Thread = nullptr;
|
|
bool ThreadActive;
|
|
|
|
int32 Run()
|
|
{
|
|
while (ThreadActive)
|
|
{
|
|
SleepEx(INFINITE, true);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void CALLBACK StopProc(ULONG_PTR arg)
|
|
{
|
|
ThreadActive = false;
|
|
}
|
|
|
|
static void CALLBACK AddDirectoryProc(ULONG_PTR arg)
|
|
{
|
|
const auto watcher = (FileSystemWatcher*)arg;
|
|
RefreshWatch(watcher);
|
|
}
|
|
};
|
|
|
|
VOID CALLBACK NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
|
|
{
|
|
auto watcher = (FileSystemWatcher*)lpOverlapped->hEvent;
|
|
if (dwErrorCode == ERROR_OPERATION_ABORTED ||
|
|
dwNumberOfBytesTransfered <= 0 ||
|
|
!watcher)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Swap buffers
|
|
watcher->CurrentBuffer = (watcher->CurrentBuffer + 1) % 2;
|
|
|
|
// Get the new read issued as fast as possible
|
|
if (!watcher->StopNow)
|
|
{
|
|
RefreshWatch(watcher);
|
|
}
|
|
|
|
// Process notifications
|
|
auto notify = (FILE_NOTIFY_INFORMATION*)watcher->Buffer[(watcher->CurrentBuffer + 1) % 2];
|
|
do
|
|
{
|
|
// Convert action type
|
|
auto action = FileSystemAction::Unknown;
|
|
switch (notify->Action)
|
|
{
|
|
case FILE_ACTION_RENAMED_NEW_NAME:
|
|
case FILE_ACTION_ADDED:
|
|
action = FileSystemAction::Create;
|
|
break;
|
|
case FILE_ACTION_RENAMED_OLD_NAME:
|
|
case FILE_ACTION_REMOVED:
|
|
action = FileSystemAction::Delete;
|
|
break;
|
|
case FILE_ACTION_MODIFIED:
|
|
action = FileSystemAction::Modify;
|
|
break;
|
|
default:
|
|
action = FileSystemAction::Unknown;
|
|
break;
|
|
}
|
|
if (action != FileSystemAction::Unknown)
|
|
{
|
|
// Build path
|
|
String path(notify->FileName, notify->FileNameLength / sizeof(WCHAR));
|
|
path = watcher->Directory / path;
|
|
|
|
// Send event
|
|
watcher->OnEvent(path, action);
|
|
}
|
|
|
|
// Move to the next notify
|
|
notify = (FILE_NOTIFY_INFORMATION*)((byte*)notify + notify->NextEntryOffset);
|
|
} while (notify->NextEntryOffset != 0);
|
|
}
|
|
|
|
// Refreshes the directory monitoring
|
|
BOOL RefreshWatch(WindowsFileSystemWatcher* watcher)
|
|
{
|
|
DWORD dwBytesReturned = 0;
|
|
return ReadDirectoryChangesW(
|
|
watcher->DirectoryHandle,
|
|
watcher->Buffer[watcher->CurrentBuffer],
|
|
FileSystemWatcher::BufferSize,
|
|
watcher->WithSubDirs ? TRUE : FALSE,
|
|
FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
|
|
&dwBytesReturned,
|
|
(OVERLAPPED*)&watcher->Overlapped,
|
|
NotificationCompletion
|
|
);
|
|
}
|
|
|
|
WindowsFileSystemWatcher::WindowsFileSystemWatcher(const String& directory, bool withSubDirs)
|
|
: FileSystemWatcherBase(directory, withSubDirs)
|
|
, StopNow(false)
|
|
, CurrentBuffer(0)
|
|
{
|
|
// Setup
|
|
Platform::MemoryClear(&Overlapped, sizeof(Overlapped));
|
|
((OVERLAPPED&)Overlapped).hEvent = this;
|
|
|
|
// Create directory handle for events handling
|
|
DirectoryHandle = CreateFileW(
|
|
*directory,
|
|
FILE_LIST_DIRECTORY,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
|
nullptr
|
|
);
|
|
if (DirectoryHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
LOG_WIN32_LAST_ERROR;
|
|
return;
|
|
}
|
|
|
|
// Register
|
|
FileSystemWatchers::Locker.Lock();
|
|
FileSystemWatchers::Watchers.Add(this);
|
|
if (!FileSystemWatchers::Thread)
|
|
{
|
|
FileSystemWatchers::ThreadActive = true;
|
|
FileSystemWatchers::Thread = ThreadSpawner::Start(FileSystemWatchers::Run, TEXT("File System Watchers"), ThreadPriority::BelowNormal);
|
|
}
|
|
FileSystemWatchers::Locker.Unlock();
|
|
|
|
// Issue the first read
|
|
QueueUserAPC(FileSystemWatchers::AddDirectoryProc, FileSystemWatchers::Thread->GetHandle(), (ULONG_PTR)this);
|
|
}
|
|
|
|
WindowsFileSystemWatcher::~WindowsFileSystemWatcher()
|
|
{
|
|
FileSystemWatchers::Locker.Lock();
|
|
FileSystemWatchers::Watchers.Remove(this);
|
|
FileSystemWatchers::Locker.Unlock();
|
|
|
|
if (DirectoryHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
StopNow = true;
|
|
|
|
#if WINVER >= 0x600
|
|
CancelIoEx(DirectoryHandle, (OVERLAPPED*)&Overlapped);
|
|
#else
|
|
CancelIo(DirectoryHandle);
|
|
#endif
|
|
|
|
const HANDLE handle = DirectoryHandle;
|
|
DirectoryHandle = INVALID_HANDLE_VALUE;
|
|
WaitForSingleObjectEx(handle, 0, true);
|
|
|
|
CloseHandle(handle);
|
|
}
|
|
|
|
FileSystemWatchers::Locker.Lock();
|
|
if (FileSystemWatchers::Watchers.IsEmpty() && FileSystemWatchers::Thread)
|
|
{
|
|
FileSystemWatchers::ThreadActive = false;
|
|
QueueUserAPC(FileSystemWatchers::StopProc, FileSystemWatchers::Thread->GetHandle(), 0);
|
|
FileSystemWatchers::Thread->Join();
|
|
Delete(FileSystemWatchers::Thread);
|
|
FileSystemWatchers::Thread = nullptr;
|
|
}
|
|
FileSystemWatchers::Locker.Unlock();
|
|
}
|
|
|
|
#endif
|