-
-
Save NocturnDragon/3e94b5fad8a85b488582 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FileWatcher | |
{ | |
public: | |
FileWatcher(const Path& path) | |
: m_Path(path) | |
, m_DirHandle(INVALID_HANDLE_VALUE) | |
, m_BufferSize(CORE_KB(100)) | |
, m_Buffer(nullptr) | |
, m_ChangedFiles(1024) | |
{ | |
memset(&m_Overlapped, 0, sizeof(m_Overlapped)); | |
m_Buffer = new u8[m_BufferSize]; | |
} | |
~FileWatcher() | |
{ | |
// Abort any pending watches | |
if (m_DirHandle != INVALID_HANDLE_VALUE) | |
{ | |
CancelIo(m_DirHandle); | |
DWORD nb_bytes; | |
GetOverlappedResult(m_DirHandle, &m_Overlapped, &nb_bytes, TRUE); | |
} | |
if (m_DirHandle != INVALID_HANDLE_VALUE) | |
CloseHandle(m_DirHandle); | |
delete [] m_Buffer; | |
} | |
bool Init() | |
{ | |
// Get a handle to the directory being watched | |
m_DirHandle = CreateFileA( | |
m_Path.c_str(), | |
FILE_LIST_DIRECTORY, | |
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
nullptr, | |
OPEN_EXISTING, | |
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, | |
0); | |
if (m_DirHandle == INVALID_HANDLE_VALUE) | |
return false; | |
// Create an event for async waiting | |
// The even handle is unused when using completion routines | |
// Use it to store the this pointer | |
m_Overlapped.hEvent = this; | |
// Kick-off the initial watch | |
if (!Watch()) | |
return false; | |
return true; | |
} | |
void Update(core::Vector<Path>& changed_files) | |
{ | |
rmt_ScopedCPUSample(DirectoryWatcherUpdate); | |
core::Milliseconds cur_time = core::GetLowResTimer(); | |
// Walk all changed files in the hash table | |
core::Vector<u32> expired_changes; | |
changed_files.clear_resize(0); | |
for (core::HashTableIterator i(m_ChangedFiles); i.IsValid(); i.MoveNext()) | |
{ | |
ChangedFile& changed_file = *(ChangedFile*)i.GetPtr(); | |
// Pass filename back to the caller if not done before | |
if (!changed_file.processed) | |
{ | |
changed_files.push_back(changed_file.path); | |
changed_file.processed = true; | |
} | |
// Track which changes are safe to discard | |
if (cur_time - changed_file.first_change_time > core::Milliseconds(500)) | |
expired_changes.push_back(i.GetHash()); | |
} | |
// Remove all expired file changes | |
for (u32 i = 0; i < expired_changes.size(); i++) | |
m_ChangedFiles.remove(expired_changes[i]); | |
// Make the thread alertable so that IO events are processed | |
MsgWaitForMultipleObjectsEx(0, NULL, 0, QS_ALLINPUT, MWMO_ALTERTABLE); | |
} | |
private: | |
void AddFile(const file::Path& path) | |
{ | |
// Don't add if it's already been added | |
ChangedFile change(path); | |
if (m_ChangedFiles.find(change.hash)) | |
return; | |
core::String512 text; | |
text.setv("FILE WATCHER: Changed file %s", path.c_str()); | |
rmt_LogText(text.c_str()); | |
// Hash table owns the new entry | |
m_ChangedFiles.insert(change.hash, new ChangedFile(change)); | |
} | |
static void CALLBACK WatchCallback(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) | |
{ | |
if (dwNumberOfBytesTransferred == 0) | |
return; | |
if (dwErrorCode != 0) | |
return; | |
// Alias event in overlapped structure to get this pointer | |
FileWatcher* watcher = (FileWatcher*)lpOverlapped->hEvent; | |
if (watcher == nullptr) | |
return; | |
// Walk all notifications | |
DWORD offset = 0; | |
while (true) | |
{ | |
FILE_NOTIFY_INFORMATION* notify = (FILE_NOTIFY_INFORMATION*)(watcher->m_Buffer + offset); | |
// Hacky way to convert wchar path to ascii | |
file::Path path; | |
for (DWORD i = 0; i < notify->FileNameLength; i += 2) | |
path.append(((char*)notify->FileName)[i]); | |
watcher->AddFile(path); | |
if (notify->NextEntryOffset == 0) | |
break; | |
offset += notify->NextEntryOffset; | |
} | |
// Kick-off the next watch | |
watcher->Watch(); | |
} | |
bool Watch() | |
{ | |
return ReadDirectoryChangesW( | |
m_DirHandle, | |
m_Buffer, | |
m_BufferSize, | |
TRUE, | |
FILE_NOTIFY_CHANGE_LAST_WRITE, | |
NULL, | |
&m_Overlapped, | |
&WatchCallback) != 0; | |
} | |
OVERLAPPED m_Overlapped; | |
Path m_Path; | |
HANDLE m_DirHandle; | |
const u32 m_BufferSize; | |
u8* m_Buffer; | |
// Hash table of recent changed files for filtering out double-notifications | |
// from the file system | |
core::HashTable m_ChangedFiles; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment