ezEngine  Milestone 9
DirectoryWatcher_win.h
1 #pragma once
2 
3 #include <Foundation/Containers/DynamicArray.h>
4 #include <Foundation/IO/DirectoryWatcher.h>
5 #include <Foundation/Logging/Log.h>
6 
8 {
9  void DoRead();
10 
11  HANDLE m_directoryHandle;
12  HANDLE m_completionPort;
13  bool m_watchSubdirs;
14  DWORD m_filter;
15  OVERLAPPED m_overlapped;
16  ezDynamicArray<ezUInt8> m_buffer;
17 };
18 
19 ezDirectoryWatcher::ezDirectoryWatcher()
21 {
22  m_pImpl->m_buffer.SetCountUninitialized(1024 * 1024);
23 }
24 
26 {
27  EZ_ASSERT_DEV(m_sDirectoryPath.IsEmpty(), "Directory already open, call CloseDirectory first!");
28  ezStringBuilder sPath(absolutePath);
29  sPath.MakeCleanPath();
30  sPath.ReplaceAll("/", "\\");
31  sPath.Trim("\\");
32 
33  m_pImpl->m_watchSubdirs = whatToWatch.IsSet(Watch::Subdirectories);
34  m_pImpl->m_filter = 0;
35  if (whatToWatch.IsSet(Watch::Reads))
36  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
37  if (whatToWatch.IsSet(Watch::Writes))
38  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
39  if (whatToWatch.IsSet(Watch::Creates))
40  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_CREATION;
41  if (whatToWatch.IsSet(Watch::Renames))
42  m_pImpl->m_filter |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME;
43 
44  m_pImpl->m_directoryHandle = CreateFileW(
45  ezStringWChar(sPath).GetData(),
46  FILE_LIST_DIRECTORY,
47  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
48  nullptr,
49  OPEN_EXISTING,
50  FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
51  nullptr);
52  if (m_pImpl->m_directoryHandle == INVALID_HANDLE_VALUE)
53  {
54  return EZ_FAILURE;
55  }
56 
57  m_pImpl->m_completionPort = CreateIoCompletionPort(m_pImpl->m_directoryHandle, nullptr, 0, 1);
58  if (m_pImpl->m_completionPort == INVALID_HANDLE_VALUE)
59  {
60  return EZ_FAILURE;
61  }
62 
63  m_pImpl->DoRead();
64  m_sDirectoryPath = sPath;
65 
66  return EZ_SUCCESS;
67 }
68 
70 {
71  if (!m_sDirectoryPath.IsEmpty())
72  {
73  CancelIo(m_pImpl->m_directoryHandle);
74  CloseHandle(m_pImpl->m_completionPort);
75  CloseHandle(m_pImpl->m_directoryHandle);
76  m_sDirectoryPath.Clear();
77  }
78 }
79 
80 ezDirectoryWatcher::~ezDirectoryWatcher()
81 {
83  EZ_DEFAULT_DELETE(m_pImpl);
84 }
85 
86 void ezDirectoryWatcherImpl::DoRead()
87 {
88  memset(&m_overlapped, 0, sizeof(m_overlapped));
89  BOOL success = ReadDirectoryChangesW(m_directoryHandle, m_buffer.GetData(), m_buffer.GetCount(), m_watchSubdirs,
90  m_filter, nullptr, &m_overlapped, nullptr);
91  EZ_ASSERT_DEV(success, "ReadDirectoryChangesW failed.");
92 }
93 
94 void ezDirectoryWatcher::EnumerateChanges(ezDelegate<void(const char* filename, ezDirectoryWatcherAction action)> func)
95 {
96  EZ_ASSERT_DEV(!m_sDirectoryPath.IsEmpty(), "No directory opened!");
97  OVERLAPPED* lpOverlapped;
98  DWORD numberOfBytes;
99  ULONG_PTR completionKey;
100  while (GetQueuedCompletionStatus(m_pImpl->m_completionPort, &numberOfBytes, &completionKey, &lpOverlapped, 10) != 0)
101  {
102  if (numberOfBytes <= 0)
103  {
104  ezLog::Debug("GetQueuedCompletionStatus with size 0. Should not happen according to msdn.");
105  m_pImpl->DoRead();
106  continue;
107  }
108  //Copy the buffer
109 
111  buffer.SetCountUninitialized(numberOfBytes);
112  buffer.GetArrayPtr().CopyFrom(m_pImpl->m_buffer.GetArrayPtr().GetSubArray(0, numberOfBytes));
113 
114  //Reissue the read request
115  m_pImpl->DoRead();
116 
117  //Progress the messages
118  auto info = (const FILE_NOTIFY_INFORMATION*)buffer.GetData();
119  while (true)
120  {
121  auto directory = ezArrayPtr<const WCHAR>(info->FileName, info->FileNameLength / sizeof(WCHAR));
122  int bytesNeeded = WideCharToMultiByte(CP_UTF8, 0, directory.GetPtr(), directory.GetCount(), nullptr, 0, nullptr, nullptr);
123  if (bytesNeeded > 0)
124  {
126  dir.SetCountUninitialized(bytesNeeded + 1);
127  WideCharToMultiByte(CP_UTF8, 0, directory.GetPtr(), directory.GetCount(), dir.GetData(), dir.GetCount(), nullptr, nullptr);
128  dir[bytesNeeded] = '\0';
129  ezDirectoryWatcherAction action = ezDirectoryWatcherAction::None;
130  switch (info->Action)
131  {
132  case FILE_ACTION_ADDED:
133  action = ezDirectoryWatcherAction::Added;
134  break;
135  case FILE_ACTION_REMOVED:
136  action = ezDirectoryWatcherAction::Removed;
137  break;
138  case FILE_ACTION_MODIFIED:
139  action = ezDirectoryWatcherAction::Modified;
140  break;
141  case FILE_ACTION_RENAMED_OLD_NAME:
142  action = ezDirectoryWatcherAction::RenamedOldName;
143  break;
144  case FILE_ACTION_RENAMED_NEW_NAME:
145  action = ezDirectoryWatcherAction::RenamedNewName;
146  break;
147  }
148  func(dir.GetData(), action);
149  }
150  if (info->NextEntryOffset == 0)
151  break;
152  else
153  info = (const FILE_NOTIFY_INFORMATION*)(((ezUInt8*)info) + info->NextEntryOffset);
154  }
155  }
156 
157  if (lpOverlapped != nullptr)
158  {
159  EZ_ASSERT_DEV(false, "GetQueuedCompletionStatus returned false but lpOverlapped is not null");
160  }
161 
162  DWORD dwError = GetLastError();
163  EZ_ASSERT_DEV(dwError == WAIT_TIMEOUT, "GetQueuedCompletionStatus gave an error");
164 }
165 
ezUInt32 GetCount() const
Returns the number of active elements in the array.
Definition: ArrayBase_inl.h:165
Watch for renames.
Definition: DirectoryWatcher.h:37
Watch for newly created files.
Definition: DirectoryWatcher.h:36
Definition: String.h:136
void Clear()
Resets this string to an empty string.
Definition: String_inl.h:63
void CopyFrom(const ezArrayPtr< const T > &other)
Copies the data from other into this array. The arrays must have the exact same size.
Definition: ArrayPtr.h:280
void SetCountUninitialized(ezUInt32 uiCount)
Resizes the array to have exactly uiCount elements. Extra elements might be uninitialized.
Definition: ArrayBase_inl.h:145
Watch for writes.
Definition: DirectoryWatcher.h:35
ezStringBuilder is a class that is meant for creating and modifying strings.
Definition: StringBuilder.h:34
A very simple string class that should only be used to temporarily convert text to the OSes native wc...
Definition: StringConversion.h:16
#define EZ_ASSERT_DEV(bCondition, szErrorMsg,...)
Macro to raise an error, if a condition is not met.
Definition: Assert.h:116
#define EZ_DEFAULT_NEW(type,...)
creates a new instance of type using the default allocator
Definition: AllocatorBase.h:92
T * GetData()
Returns a pointer to the array data, or nullptr if the array is empty.
Definition: ArrayBase_inl.h:397
The ezBitflags class allows you to work with type-safe bitflags.
Definition: Bitflags.h:80
#define EZ_DEFAULT_DELETE(ptr)
deletes the instance stored in ptr using the default allocator and sets ptr to nullptr ...
Definition: AllocatorBase.h:95
Watch files in subdirectories recursively.
Definition: DirectoryWatcher.h:38
bool IsEmpty() const
Returns whether the string is an empty string.
Definition: StringBase_inl.h:25
void CloseDirectory()
Closes the currently watched directory if any.
Definition: DirectoryWatcher_posix.h:20
EZ_ALWAYS_INLINE bool IsSet(Enum flag) const
Checks if certain flags are set within the bitfield.
Definition: Bitflags.h:122
Watch for reads.
Definition: DirectoryWatcher.h:34
Definition: DirectoryWatcher_posix.h:5
ezResult OpenDirectory(const ezString &absolutePath, ezBitflags< Watch > whatToWatch)
Opens the directory at absolutePath for watching. whatToWatch controls what exactly should be watched...
Definition: DirectoryWatcher_posix.h:14
A generic delegate class which supports static functions and member functions.
Definition: MathExpression.h:11
ezArrayPtr< T > GetArrayPtr()
Returns an array pointer to the array data, or an empty array pointer if the array is empty...
Definition: ArrayBase_inl.h:415
Default enum for returning failure or success, instead of using a bool.
Definition: Types.h:51
static void Debug(ezLogInterface *pInterface, const ezFormatString &string)
Status information during debugging. Very verbose. Usually only temporarily added to the code...
void EnumerateChanges(ezDelegate< void(const char *filename, ezDirectoryWatcherAction action)> func)
Calls the callback func for each change since the last call. For each change the filename and the act...
Definition: DirectoryWatcher_posix.h:27
This class encapsulates an array and it&#39;s size. It is recommended to use this class instead of plain ...
Definition: ArrayPtr.h:82