ezEngine  Milestone 7
DirectoryWatcher_win.h
1 #pragma once
2 
3 #include <Foundation/IO/DirectoryWatcher.h>
4 #include <Foundation/Containers/DynamicArray.h>
5 
7 {
8  void DoRead();
9 
10  HANDLE m_directoryHandle;
11  HANDLE m_completionPort;
12  bool m_watchSubdirs;
13  DWORD m_filter;
14  OVERLAPPED m_overlapped;
16 };
17 
18 ezDirectoryWatcher::ezDirectoryWatcher()
19  : m_bDirectoryOpen(false)
21 {
22 }
23 
25 {
26  m_pImpl->m_watchSubdirs = whatToWatch.IsSet(Watch::Subdirectories);
27  m_pImpl->m_filter = 0;
28  if (whatToWatch.IsSet(Watch::Reads))
29  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
30  if (whatToWatch.IsSet(Watch::Writes))
31  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
32  if (whatToWatch.IsSet(Watch::Creates))
33  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_CREATION;
34  if (whatToWatch.IsSet(Watch::Renames))
35  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME;
36 
37  m_pImpl->m_directoryHandle = CreateFileW(
38  ezStringWChar(path).GetData(),
39  FILE_LIST_DIRECTORY,
40  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
41  nullptr,
42  OPEN_EXISTING,
43  FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
44  nullptr);
45  if (m_pImpl->m_directoryHandle == INVALID_HANDLE_VALUE)
46  {
47  return EZ_FAILURE;
48  }
49 
50  m_pImpl->m_completionPort = CreateIoCompletionPort(m_pImpl->m_directoryHandle, nullptr, 0, 1);
51  if (m_pImpl->m_completionPort == INVALID_HANDLE_VALUE)
52  {
53  return EZ_FAILURE;
54  }
55 
56  m_pImpl->DoRead();
57  m_bDirectoryOpen = true;
58 
59  return EZ_SUCCESS;
60 }
61 
63 {
64  if (m_bDirectoryOpen)
65  {
66  CancelIo(m_pImpl->m_directoryHandle);
67  CloseHandle(m_pImpl->m_completionPort);
68  CloseHandle(m_pImpl->m_directoryHandle);
69  m_bDirectoryOpen = false;
70  }
71 }
72 
73 ezDirectoryWatcher::~ezDirectoryWatcher()
74 {
76  EZ_DEFAULT_DELETE(m_pImpl);
77 }
78 
79 void ezDirectoryWatcherImpl::DoRead()
80 {
81  memset(&m_overlapped, 0, sizeof(m_overlapped));
82  ReadDirectoryChangesW(m_directoryHandle, m_buffer.GetData(), m_buffer.GetCount(), m_watchSubdirs,
83  m_filter, nullptr, &m_overlapped, nullptr);
84 }
85 
86 void ezDirectoryWatcher::EnumerateChanges(ezDelegate<void(const char* filename, Action action)> func)
87 {
88  EZ_ASSERT_DEV(m_bDirectoryOpen, "No directory opened!");
89  OVERLAPPED* lpOverlapped;
90  DWORD numberOfBytes;
91  ULONG_PTR completionKey;
92  while (GetQueuedCompletionStatus(m_pImpl->m_completionPort, &numberOfBytes, &completionKey, &lpOverlapped, 0) != 0)
93  {
94  //Copy the buffer
95  EZ_ASSERT_DEBUG(numberOfBytes > 0, "GetQueuedCompletionStatus failed");
97  buffer.SetCount(numberOfBytes);
98  buffer.GetArrayPtr().CopyFrom(m_pImpl->m_buffer.GetArrayPtr().GetSubArray(0, numberOfBytes));
99 
100  //Reissue the read request
101  m_pImpl->DoRead();
102 
103  //Progress the messages
104  auto info = (const FILE_NOTIFY_INFORMATION*)buffer.GetData();
105  while (true)
106  {
107  auto directory = ezArrayPtr<const WCHAR>(info->FileName, info->FileNameLength / sizeof(WCHAR));
108  int bytesNeeded = WideCharToMultiByte(CP_UTF8, 0, directory.GetPtr(), directory.GetCount(), nullptr, 0, nullptr, nullptr);
109  if (bytesNeeded > 0)
110  {
112  dir.SetCount(bytesNeeded);
113  WideCharToMultiByte(CP_UTF8, 0, directory.GetPtr(), directory.GetCount(), dir.GetData(), dir.GetCount(), nullptr, nullptr);
114  Action action;
115  switch (info->Action)
116  {
117  case FILE_ACTION_ADDED:
118  action = Action::Added;
119  break;
120  case FILE_ACTION_REMOVED:
121  action = Action::Removed;
122  break;
123  case FILE_ACTION_MODIFIED:
124  action = Action::Modified;
125  break;
126  case FILE_ACTION_RENAMED_OLD_NAME:
127  action = Action::RenamedOldName;
128  break;
129  case FILE_ACTION_RENAMED_NEW_NAME:
130  action = Action::RenamedNewName;
131  break;
132  }
133  func(dir.GetData(), action);
134  }
135  if (info->NextEntryOffset == 0)
136  break;
137  else
138  info = (const FILE_NOTIFY_INFORMATION*)(((ezUInt8*)info) + info->NextEntryOffset);
139  }
140  }
141 }