Brenta Engine 1.2
Loading...
Searching...
No Matches
fswatcher_windows.cpp
1// SPDX-License-Identifier: MIT
2// Author: Giovanni Santini
3// Mail: giovanni.santini@proton.me
4// Github: @San7o
5
6#ifdef _WIN32
7
8#define NOMINMAX
9#define WIN32_LEAN_AND_MEAN
10#include <windows.h>
11
12#include <brenta/fswatcher.hpp>
13
14namespace brenta
15{
16
17struct FsWatcherWindowsDirList
18{
19 HANDLE hDir;
20 WCHAR pathW[MAX_PATH];
21 char path[MAX_PATH];
22 BYTE buffer[1024 * 64];
23 DWORD filter;
24 OVERLAPPED overlapped;
25 FsWatcherWindowsDirList *next;
26};
27
28struct FsWatcherWindows
29{
30 HANDLE iocp;
31 FsWatcherWindowsDirList *dir_list;
32};
33
34} // namespace brenta
35
36using namespace brenta;
37
38FilesystemWatcher::~FilesystemWatcher()
39{
40 this->destroy();
41}
42
43bool FilesystemWatcher::init()
44{
45 FsWatcherWindows *fw_windows = new FsWatcherWindows();
46
47 fw_windows->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
48 NULL,
49 0,
50 0);
51 if (!fw_windows->iocp)
52 {
53 delete fw_windows;
54 return false;
55 }
56
57 this->internal = static_cast<void*>(fw_windows);
58 return true;
59}
60
61void FilesystemWatcher::destroy()
62{
63 if (!this->internal) return;
64
65 FsWatcherWindows *fw_windows = static_cast<FsWatcherWindows*>(this->internal);
66
67 FsWatcherWindowsDirList *it = fw_windows->dir_list;
68 FsWatcherWindowsDirList *next = NULL;
69 while (it)
70 {
71 next = it->next;
72 CloseHandle(it->hDir);
73 // Send exit signal
74 PostQueuedCompletionStatus(fw_windows->iocp, 0, (ULONG_PTR)NULL, NULL);
75 it = next;
76 }
77
78 CloseHandle(fw_windows->iocp);
79
80 delete static_cast<FsWatcherWindows*>(this->internal);
81 this->internal = nullptr;
82}
83
84bool FilesystemWatcher::add(const std::filesystem::path &path,
85 tenno::vector<Event> events)
86{
87 if (!this->internal) return false;
88
89 FsWatcherWindows *fw_windows = static_cast<FsWatcherWindows*>(this->internal);
90
91 DWORD filter = 0;
92 for (auto& event : events)
93 {
94 switch(event)
95 {
96 case FilesystemWatcher::Event::Modify:
97 filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
98 break;
99 default:
100 break;
101 }
102 }
103
104 // Convert to wide char string
105 int len = MultiByteToWideChar(CP_UTF8, 0, path.string().c_str(), -1, NULL, 0);
106 WCHAR pathW[MAX_PATH];
107 MultiByteToWideChar(CP_UTF8, 0, path.string().c_str(), -1, pathW, len);
108
109 wchar_t full_path[MAX_PATH];
110 wchar_t *file_part = NULL;
111
112 if (GetFullPathNameW(pathW,
113 MAX_PATH,
114 full_path,
115 &file_part) == 0)
116 return false;
117
118 FsWatcherWindowsDirList *fw_dir = new FsWatcherWindowsDirList();
119 fw_dir->filter = filter;
120 strcpy(fw_dir->path, path.string().c_str());
121
122 if (file_part != NULL)
123 {
124 wcscpy(fw_dir->pathW, file_part);
125 *file_part = L'\0';
126 }
127
128 HANDLE hDir = CreateFileW(full_path,
129 FILE_LIST_DIRECTORY,
130 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
131 NULL,
132 OPEN_EXISTING,
133 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
134 NULL);
135 if (hDir == INVALID_HANDLE_VALUE)
136 {
137 delete fw_dir;
138 return false;
139 }
140
141 fw_dir->hDir = hDir;
142
143 // Associate the handle with the port
144 if (CreateIoCompletionPort(fw_dir->hDir,
145 fw_windows->iocp,
146 (ULONG_PTR) fw_dir,
147 0) == NULL)
148 {
149 delete fw_dir;
150 return false;
151 }
152
153 // Tell windows to listen for changes on this directory
154 if (ReadDirectoryChangesW(fw_dir->hDir,
155 fw_dir->buffer,
156 sizeof(fw_dir->buffer),
157 FALSE,
158 filter,
159 NULL,
160 &fw_dir->overlapped, NULL) == 0)
161 {
162 delete fw_dir;
163 return false;
164 }
165
166 // Add dir to list of watched files
167 FsWatcherWindowsDirList *it = fw_windows->dir_list;
168 if (!it)
169 {
170 fw_windows->dir_list = fw_dir;
171 return true;
172 }
173 while(it->next) { it = it->next; }
174 it->next = fw_dir;
175 return true;
176}
177
178bool FilesystemWatcher::add(const tenno::vector<std::filesystem::path> &paths,
179 tenno::vector<Event> events)
180{
181 bool out = true;
182 for (auto& path : paths)
183 {
184 out |= this->add(path, events);
185 }
186 return out;
187}
188
189bool FilesystemWatcher::rm(const std::filesystem::path &path)
190{
191 if (!this->internal) return false;
192 FsWatcherWindows *fw_windows = static_cast<FsWatcherWindows*>(this->internal);
193 FsWatcherWindowsDirList **curr = &fw_windows->dir_list;
194
195 while (*curr)
196 {
197 if ((*curr)->path == path)
198 {
199 FsWatcherWindowsDirList *to_remove = *curr;
200
201 *curr = to_remove->next;
202
203 CancelIoEx(to_remove->hDir, &to_remove->overlapped);
204 CloseHandle(to_remove->hDir);
205
206 return true;
207 }
208 curr = &((*curr)->next);
209 }
210
211 return false;
212}
213
214bool FilesystemWatcher::rm(const tenno::vector<std::filesystem::path> &paths)
215{
216 bool out = true;
217 for (auto& path : paths)
218 {
219 out |= this->rm(path);
220 }
221 return out;
222}
223
224std::optional<std::filesystem::path> FilesystemWatcher::watch()
225{
226 if (!this->internal) return {};
227 FsWatcherWindows *fw_windows = static_cast<FsWatcherWindows*>(this->internal);
228
229 DWORD bytes;
230 ULONG_PTR key;
231 LPOVERLAPPED overlapped;
232
233 while(GetQueuedCompletionStatus(fw_windows->iocp,
234 &bytes,
235 &key,
236 &overlapped,
237 INFINITE))
238 {
239 if (key == 0) return {}; // Global exit signal
240
241 FsWatcherWindowsDirList* fw_dir = (FsWatcherWindowsDirList*)key;
242
243 if (bytes == 0)
244 {
245 delete fw_dir;
246 continue;
247 }
248
249 BYTE* pRaw = fw_dir->buffer;
250 FILE_NOTIFY_INFORMATION* info = NULL;
251
252 do
253 {
254 info = (FILE_NOTIFY_INFORMATION*)pRaw;
255
256 if (wcsncmp(info->FileName,
257 fw_dir->pathW,
258 info->FileNameLength / sizeof(WCHAR)) == 0)
259 {
260 ReadDirectoryChangesW(fw_dir->hDir,
261 fw_dir->buffer,
262 sizeof(fw_dir->buffer),
263 FALSE,
264 fw_dir->filter,
265 NULL,
266 &fw_dir->overlapped,
267 NULL);
268
269 return fw_dir->path;
270 }
271
272 pRaw += info->NextEntryOffset;
273
274 } while (info->NextEntryOffset != 0);
275
276 ReadDirectoryChangesW(fw_dir->hDir,
277 fw_dir->buffer,
278 sizeof(fw_dir->buffer),
279 FALSE,
280 fw_dir->filter, NULL,
281 &fw_dir->overlapped,
282 NULL);
283 }
284 return {};
285}
286
287
288#endif // _WIN32