/src/Fast-DDS/thirdparty/filewatch/FileWatch.hpp
Line | Count | Source (jump to first uncovered line) |
1 | | // MIT License |
2 | | // |
3 | | // Copyright(c) 2017 Thomas Monkman |
4 | | // |
5 | | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | | // of this software and associated documentation files(the "Software"), to deal |
7 | | // in the Software without restriction, including without limitation the rights |
8 | | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | | // copies of the Software, and to permit persons to whom the Software is |
10 | | // furnished to do so, subject to the following conditions : |
11 | | // |
12 | | // The above copyright notice and this permission notice shall be included in all |
13 | | // copies or substantial portions of the Software. |
14 | | // |
15 | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE |
18 | | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | | // SOFTWARE. |
22 | | |
23 | | #ifndef FILEWATCHER_H |
24 | | #define FILEWATCHER_H |
25 | | |
26 | | #ifdef _WIN32 |
27 | | #define WIN32_LEAN_AND_MEAN |
28 | | #define stat _stat |
29 | | #ifndef NOMINMAX |
30 | | #define NOMINMAX |
31 | | #endif |
32 | | #include <windows.h> |
33 | | #include <stdlib.h> |
34 | | #include <stdio.h> |
35 | | #include <tchar.h> |
36 | | #include <Pathcch.h> |
37 | | #include <shlwapi.h> |
38 | | #endif // WIN32 |
39 | | |
40 | | #if __unix__ |
41 | | #include <stdio.h> |
42 | | #include <stdlib.h> |
43 | | #include <errno.h> |
44 | | #include <sys/types.h> |
45 | | #include <sys/inotify.h> |
46 | | #include <sys/stat.h> |
47 | | #include <unistd.h> |
48 | | #endif // __unix__ |
49 | | |
50 | | #include <algorithm> |
51 | | #include <array> |
52 | | #include <atomic> |
53 | | #include <chrono> |
54 | | #include <condition_variable> |
55 | | #include <functional> |
56 | | #include <future> |
57 | | #include <iostream> |
58 | | #include <map> |
59 | | #include <mutex> |
60 | | #include <regex> |
61 | | #include <string> |
62 | | #include <system_error> |
63 | | #include <thread> |
64 | | #include <type_traits> |
65 | | #include <utility> |
66 | | #include <vector> |
67 | | |
68 | | namespace filewatch { |
69 | | enum class Event { |
70 | | added, |
71 | | removed, |
72 | | modified, |
73 | | renamed_old, |
74 | | renamed_new |
75 | | }; |
76 | | |
77 | | /** |
78 | | * \class FileWatch |
79 | | * |
80 | | * \brief Watches a folder or file, and will notify of changes via function callback. |
81 | | * |
82 | | * \author Thomas Monkman |
83 | | * |
84 | | */ |
85 | | template<class T> |
86 | | class FileWatch |
87 | | { |
88 | | typedef typename T::value_type C; |
89 | | typedef std::basic_string<C, std::char_traits<C>> UnderpinningString; |
90 | | typedef std::basic_regex<C, std::regex_traits<C>> UnderpinningRegex; |
91 | | |
92 | | public: |
93 | | |
94 | | FileWatch(T path, UnderpinningRegex pattern, std::function<void(const T& file, const Event event_type)> callback) : |
95 | | _path(path), |
96 | | _pattern(pattern), |
97 | | _callback(callback), |
98 | | _directory(get_directory(path)) |
99 | 0 | { |
100 | 0 | init(); |
101 | 0 | } |
102 | | |
103 | | FileWatch(T path, std::function<void(const T& file, const Event event_type)> callback) : |
104 | 0 | FileWatch<T>(path, UnderpinningRegex(_regex_all), callback) {} |
105 | | |
106 | 0 | ~FileWatch() { |
107 | 0 | destroy(); |
108 | 0 | } |
109 | | |
110 | | FileWatch(const FileWatch<T>& other) : FileWatch<T>(other._path, other._callback) {} |
111 | | |
112 | | FileWatch<T>& operator=(const FileWatch<T>& other) |
113 | | { |
114 | | if (this == &other) { return *this; } |
115 | | |
116 | | destroy(); |
117 | | _path = other._path; |
118 | | _callback = other._callback; |
119 | | _directory = get_directory(other._path); |
120 | | init(); |
121 | | return *this; |
122 | | } |
123 | | |
124 | | // Const memeber varibles don't let me implent moves nicely, if moves are really wanted std::unique_ptr should be used and move that. |
125 | | FileWatch<T>(FileWatch<T>&&) = delete; |
126 | | FileWatch<T>& operator=(FileWatch<T>&&) & = delete; |
127 | | |
128 | | private: |
129 | | static constexpr C _regex_all[] = { '.', '*', '\0' }; |
130 | | static constexpr C _this_directory[] = { '.', '/', '\0' }; |
131 | | |
132 | | struct PathParts |
133 | | { |
134 | 0 | PathParts(T directory, T filename) : directory(directory), filename(filename) {} |
135 | | T directory; |
136 | | T filename; |
137 | | }; |
138 | | const T _path; |
139 | | |
140 | | UnderpinningRegex _pattern; |
141 | | |
142 | | static constexpr std::size_t _buffer_size = { 1024 * 256 }; |
143 | | |
144 | | // only used if watch a single file |
145 | | bool _watching_single_file = { false }; |
146 | | T _filename; |
147 | | |
148 | | std::atomic<bool> _destory = { false }; |
149 | | std::function<void(const T& file, const Event event_type)> _callback; |
150 | | |
151 | | std::thread _watch_thread; |
152 | | |
153 | | std::condition_variable _cv; |
154 | | std::mutex _callback_mutex; |
155 | | std::vector<std::pair<T, Event>> _callback_information; |
156 | | std::thread _callback_thread; |
157 | | |
158 | | std::promise<void> _running; |
159 | | |
160 | | std::chrono::time_point<std::chrono::system_clock> last_write_time_; |
161 | | unsigned long last_size_; |
162 | | |
163 | | #ifdef _WIN32 |
164 | | HANDLE _directory = { nullptr }; |
165 | | HANDLE _close_event = { nullptr }; |
166 | | |
167 | | const DWORD _listen_filters = |
168 | | FILE_NOTIFY_CHANGE_SECURITY | |
169 | | FILE_NOTIFY_CHANGE_CREATION | |
170 | | FILE_NOTIFY_CHANGE_LAST_ACCESS | |
171 | | FILE_NOTIFY_CHANGE_LAST_WRITE | |
172 | | FILE_NOTIFY_CHANGE_SIZE | |
173 | | FILE_NOTIFY_CHANGE_ATTRIBUTES | |
174 | | FILE_NOTIFY_CHANGE_DIR_NAME | |
175 | | FILE_NOTIFY_CHANGE_FILE_NAME; |
176 | | |
177 | | const std::map<DWORD, Event> _event_type_mapping = { |
178 | | { FILE_ACTION_ADDED, Event::added }, |
179 | | { FILE_ACTION_REMOVED, Event::removed }, |
180 | | { FILE_ACTION_MODIFIED, Event::modified }, |
181 | | { FILE_ACTION_RENAMED_OLD_NAME, Event::renamed_old }, |
182 | | { FILE_ACTION_RENAMED_NEW_NAME, Event::renamed_new } |
183 | | }; |
184 | | |
185 | | // time epoch translation |
186 | | std::pair<ULARGE_INTEGER, std::chrono::time_point<std::chrono::system_clock>> base_; |
187 | | #endif // WIN32 |
188 | | |
189 | | #if __unix__ |
190 | | struct FolderInfo { |
191 | | int folder; |
192 | | int watch; |
193 | | }; |
194 | | |
195 | | FolderInfo _directory; |
196 | | |
197 | | const std::uint32_t _listen_filters = IN_MODIFY | IN_CREATE | IN_DELETE; |
198 | | |
199 | | const static std::size_t event_size = (sizeof(struct inotify_event)); |
200 | | #endif // __unix__ |
201 | | |
202 | | void init() |
203 | 0 | { |
204 | | #ifdef _WIN32 |
205 | | _close_event = CreateEvent(NULL, TRUE, FALSE, NULL); |
206 | | if (!_close_event) { |
207 | | throw std::system_error(GetLastError(), std::system_category()); |
208 | | } |
209 | | #endif // WIN32 |
210 | 0 | _callback_thread = std::move(std::thread([this]() { |
211 | 0 | try { |
212 | 0 | callback_thread(); |
213 | 0 | } catch (...) { |
214 | 0 | try { |
215 | 0 | _running.set_exception(std::current_exception()); |
216 | 0 | } |
217 | 0 | catch (...) {} // set_exception() may throw too |
218 | 0 | } |
219 | 0 | })); |
220 | 0 | _watch_thread = std::move(std::thread([this]() { |
221 | 0 | try { |
222 | 0 | monitor_directory(); |
223 | 0 | } catch (...) { |
224 | 0 | try { |
225 | 0 | _running.set_exception(std::current_exception()); |
226 | 0 | } |
227 | 0 | catch (...) {} // set_exception() may throw too |
228 | 0 | } |
229 | 0 | })); |
230 | |
|
231 | 0 | std::future<void> future = _running.get_future(); |
232 | 0 | future.get(); //block until the monitor_directory is up and running |
233 | 0 | } |
234 | | |
235 | | void destroy() |
236 | 0 | { |
237 | 0 | _destory = true; |
238 | 0 | _running = std::promise<void>(); |
239 | | #ifdef _WIN32 |
240 | | SetEvent(_close_event); |
241 | | #elif __unix__ |
242 | 0 | inotify_rm_watch(_directory.folder, _directory.watch); |
243 | 0 | #endif // __unix__ |
244 | 0 | _cv.notify_all(); |
245 | 0 | _watch_thread.join(); |
246 | 0 | _callback_thread.join(); |
247 | | #ifdef _WIN32 |
248 | | CloseHandle(_directory); |
249 | | #elif __unix__ |
250 | 0 | close(_directory.folder); |
251 | 0 | #endif // __unix__ |
252 | 0 | } |
253 | | |
254 | | const PathParts split_directory_and_file(const T& path) const |
255 | 0 | { |
256 | 0 | const auto predict = [](C character) { |
257 | | #ifdef _WIN32 |
258 | | return character == C('\\') || character == C('/'); |
259 | | #elif __unix__ |
260 | 0 | return character == C('/'); |
261 | 0 | #endif // __unix__ |
262 | 0 | }; |
263 | |
|
264 | 0 | UnderpinningString path_string = path; |
265 | 0 | const auto pivot = std::find_if(path_string.rbegin(), path_string.rend(), predict).base(); |
266 | | //if the path is something like "test.txt" there will be no directory part, however we still need one, so insert './' |
267 | 0 | const T directory = [&]() { |
268 | 0 | const auto extracted_directory = UnderpinningString(path_string.begin(), pivot); |
269 | 0 | return (extracted_directory.size() > 0) ? extracted_directory : UnderpinningString(_this_directory); |
270 | 0 | }(); |
271 | 0 | const T filename = UnderpinningString(pivot, path_string.end()); |
272 | 0 | return PathParts(directory, filename); |
273 | 0 | } |
274 | | |
275 | | bool pass_filter(const UnderpinningString& file_path) |
276 | 0 | { |
277 | 0 | if (_watching_single_file) { |
278 | 0 | const UnderpinningString extracted_filename = { split_directory_and_file(file_path).filename }; |
279 | | //if we are watching a single file, only that file should trigger action |
280 | 0 | return extracted_filename == _filename; |
281 | 0 | } |
282 | 0 | return std::regex_match(file_path, _pattern); |
283 | 0 | } |
284 | | |
285 | | #ifdef _WIN32 |
286 | | template<typename... Args> DWORD GetFileAttributesX(const char* lpFileName, Args... args) { |
287 | | return GetFileAttributesA(lpFileName, args...); |
288 | | } |
289 | | template<typename... Args> DWORD GetFileAttributesX(const wchar_t* lpFileName, Args... args) { |
290 | | return GetFileAttributesW(lpFileName, args...); |
291 | | } |
292 | | |
293 | | template<typename... Args> HANDLE CreateFileX(const char* lpFileName, Args... args) { |
294 | | return CreateFileA(lpFileName, args...); |
295 | | } |
296 | | template<typename... Args> HANDLE CreateFileX(const wchar_t* lpFileName, Args... args) { |
297 | | return CreateFileW(lpFileName, args...); |
298 | | } |
299 | | |
300 | | HANDLE get_directory(const T& path) |
301 | | { |
302 | | auto file_info = GetFileAttributesX(path.c_str()); |
303 | | |
304 | | if (file_info == INVALID_FILE_ATTRIBUTES) |
305 | | { |
306 | | throw std::system_error(GetLastError(), std::system_category()); |
307 | | } |
308 | | _watching_single_file = (file_info & FILE_ATTRIBUTE_DIRECTORY) == false; |
309 | | |
310 | | const T watch_path = [this, &path]() { |
311 | | if (_watching_single_file) |
312 | | { |
313 | | const auto parsed_path = split_directory_and_file(path); |
314 | | _filename = parsed_path.filename; |
315 | | return parsed_path.directory; |
316 | | } |
317 | | else |
318 | | { |
319 | | return path; |
320 | | } |
321 | | }(); |
322 | | |
323 | | HANDLE directory = CreateFileX( |
324 | | watch_path.c_str(), // pointer to the file name |
325 | | FILE_LIST_DIRECTORY, // access (read/write) mode |
326 | | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // share mode |
327 | | nullptr, // security descriptor |
328 | | OPEN_EXISTING, // how to create |
329 | | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // file attributes |
330 | | HANDLE(0)); // file with attributes to copy |
331 | | |
332 | | if (directory == INVALID_HANDLE_VALUE) |
333 | | { |
334 | | throw std::system_error(GetLastError(), std::system_category()); |
335 | | } |
336 | | |
337 | | init_last_write_time(); |
338 | | |
339 | | return directory; |
340 | | } |
341 | | |
342 | | void convert_wstring(const std::wstring& wstr, std::string& out) |
343 | | { |
344 | | int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); |
345 | | out.resize(size_needed, '\0'); |
346 | | WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &out[0], size_needed, NULL, NULL); |
347 | | } |
348 | | |
349 | | void convert_wstring(const std::wstring& wstr, std::wstring& out) |
350 | | { |
351 | | out = wstr; |
352 | | } |
353 | | |
354 | | void monitor_directory() |
355 | | { |
356 | | std::vector<BYTE> buffer(_buffer_size); |
357 | | DWORD bytes_returned = 0; |
358 | | OVERLAPPED overlapped_buffer{ 0 }; |
359 | | |
360 | | overlapped_buffer.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
361 | | if (!overlapped_buffer.hEvent) { |
362 | | std::cerr << "Error creating monitor event" << std::endl; |
363 | | } |
364 | | |
365 | | std::array<HANDLE, 2> handles{ overlapped_buffer.hEvent, _close_event }; |
366 | | |
367 | | auto async_pending = false; |
368 | | _running.set_value(); |
369 | | do { |
370 | | std::vector<std::pair<T, Event>> parsed_information; |
371 | | ReadDirectoryChangesW( |
372 | | _directory, |
373 | | buffer.data(), static_cast<DWORD>(buffer.size()), |
374 | | TRUE, |
375 | | _listen_filters, |
376 | | &bytes_returned, |
377 | | &overlapped_buffer, NULL); |
378 | | |
379 | | async_pending = true; |
380 | | |
381 | | switch (WaitForMultipleObjects(2, handles.data(), FALSE, INFINITE)) |
382 | | { |
383 | | case WAIT_OBJECT_0: |
384 | | { |
385 | | if (!GetOverlappedResult(_directory, &overlapped_buffer, &bytes_returned, TRUE)) { |
386 | | throw std::system_error(GetLastError(), std::system_category()); |
387 | | } |
388 | | async_pending = false; |
389 | | |
390 | | // Get current time |
391 | | _WIN32_FILE_ATTRIBUTE_DATA att; |
392 | | GetFileAttributesExA(_path.c_str(), GetFileExInfoStandard, &att); |
393 | | |
394 | | unsigned long current_size = att.nFileSizeLow; |
395 | | auto current_time = base_.second |
396 | | + std::chrono::duration< |
397 | | typename std::chrono::time_point<std::chrono::system_clock>::rep, |
398 | | std::ratio_multiply<std::hecto, typename std::chrono::nanoseconds::period>>( |
399 | | reinterpret_cast<ULARGE_INTEGER*>(&att.ftLastWriteTime)->QuadPart - base_.first.QuadPart); |
400 | | |
401 | | if (bytes_returned == 0 || (current_time == last_write_time_) && current_size == last_size_ ) { |
402 | | break; |
403 | | } |
404 | | |
405 | | FILE_NOTIFY_INFORMATION *file_information = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]); |
406 | | do |
407 | | { |
408 | | std::wstring changed_file_w{ file_information->FileName, file_information->FileNameLength / sizeof(file_information->FileName[0]) }; |
409 | | UnderpinningString changed_file; |
410 | | convert_wstring(changed_file_w, changed_file); |
411 | | if (pass_filter(changed_file)) |
412 | | { |
413 | | parsed_information.emplace_back(T{ changed_file }, _event_type_mapping.at(file_information->Action)); |
414 | | } |
415 | | |
416 | | last_write_time_ = current_time; |
417 | | last_size_ = current_size; |
418 | | |
419 | | if (file_information->NextEntryOffset == 0) { |
420 | | break; |
421 | | } |
422 | | |
423 | | file_information = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(file_information) + file_information->NextEntryOffset); |
424 | | } while (true); |
425 | | break; |
426 | | } |
427 | | case WAIT_OBJECT_0 + 1: |
428 | | // quit |
429 | | break; |
430 | | case WAIT_FAILED: |
431 | | break; |
432 | | } |
433 | | //dispatch callbacks |
434 | | { |
435 | | std::lock_guard<std::mutex> lock(_callback_mutex); |
436 | | _callback_information.insert(_callback_information.end(), parsed_information.begin(), parsed_information.end()); |
437 | | } |
438 | | _cv.notify_all(); |
439 | | } while (_destory == false); |
440 | | |
441 | | if (async_pending) |
442 | | { |
443 | | //clean up running async io |
444 | | CancelIo(_directory); |
445 | | GetOverlappedResult(_directory, &overlapped_buffer, &bytes_returned, TRUE); |
446 | | } |
447 | | } |
448 | | #endif // WIN32 |
449 | | |
450 | | #if __unix__ |
451 | | |
452 | | bool is_file(const T& path) const |
453 | 0 | { |
454 | 0 | struct stat statbuf = {}; |
455 | 0 | if (stat(path.c_str(), &statbuf) != 0) |
456 | 0 | { |
457 | 0 | throw std::system_error(errno, std::system_category()); |
458 | 0 | } |
459 | 0 | return S_ISREG(statbuf.st_mode); |
460 | 0 | } |
461 | | |
462 | | FolderInfo get_directory(const T& path) |
463 | 0 | { |
464 | 0 | const auto folder = inotify_init(); |
465 | 0 | if (folder < 0) |
466 | 0 | { |
467 | 0 | throw std::system_error(errno, std::system_category()); |
468 | 0 | } |
469 | | //const auto listen_filters = _listen_filters; |
470 | | |
471 | 0 | _watching_single_file = is_file(path); |
472 | |
|
473 | 0 | const T watch_path = [this, &path]() { |
474 | 0 | if (_watching_single_file) |
475 | 0 | { |
476 | 0 | const auto parsed_path = split_directory_and_file(path); |
477 | 0 | _filename = parsed_path.filename; |
478 | 0 | return parsed_path.directory; |
479 | 0 | } |
480 | 0 | else |
481 | 0 | { |
482 | 0 | return path; |
483 | 0 | } |
484 | 0 | }(); |
485 | |
|
486 | 0 | const auto watch = inotify_add_watch(folder, watch_path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE ); |
487 | 0 | if (watch < 0) |
488 | 0 | { |
489 | 0 | throw std::system_error(errno, std::system_category()); |
490 | 0 | } |
491 | | |
492 | 0 | init_last_write_time(); |
493 | |
|
494 | 0 | return { folder, watch }; |
495 | 0 | } |
496 | | |
497 | | void monitor_directory() |
498 | 0 | { |
499 | 0 | std::vector<char> buffer(_buffer_size); |
500 | |
|
501 | 0 | _running.set_value(); |
502 | 0 | while (_destory == false) |
503 | 0 | { |
504 | 0 | const auto length = read(_directory.folder, static_cast<void*>(buffer.data()), buffer.size()); |
505 | |
|
506 | 0 | struct stat result; |
507 | 0 | stat(_path.c_str(), &result); |
508 | |
|
509 | 0 | using clock = std::chrono::system_clock; |
510 | 0 | using duration = clock::duration; |
511 | 0 | std::chrono::time_point<clock> current_time; |
512 | 0 | current_time += std::chrono::duration_cast<duration>(std::chrono::seconds(result.st_mtim.tv_sec)); |
513 | 0 | current_time += std::chrono::duration_cast<duration>(std::chrono::nanoseconds(result.st_mtim.tv_nsec)); |
514 | |
|
515 | 0 | unsigned long current_size = result.st_size; |
516 | |
|
517 | 0 | if (length > 0 && (current_time != last_write_time_ || current_size != last_size_)) |
518 | 0 | { |
519 | 0 | int i = 0; |
520 | 0 | last_write_time_ = current_time; |
521 | 0 | last_size_ = current_size; |
522 | 0 | std::vector<std::pair<T, Event>> parsed_information; |
523 | 0 | bool already_modified = false; |
524 | 0 | while (i < length) |
525 | 0 | { |
526 | 0 | struct inotify_event *event = reinterpret_cast<struct inotify_event *>(&buffer[i]); // NOLINT |
527 | 0 | if (event->len) |
528 | 0 | { |
529 | 0 | const UnderpinningString changed_file{ event->name }; |
530 | 0 | if (pass_filter(changed_file)) |
531 | 0 | { |
532 | 0 | if (event->mask & IN_CREATE) |
533 | 0 | { |
534 | 0 | parsed_information.emplace_back(T{ changed_file }, Event::added); |
535 | 0 | } |
536 | 0 | else if (event->mask & IN_DELETE) |
537 | 0 | { |
538 | 0 | parsed_information.emplace_back(T{ changed_file }, Event::removed); |
539 | 0 | } |
540 | 0 | else if (event->mask & IN_MODIFY && !already_modified) |
541 | 0 | { |
542 | 0 | already_modified = true; |
543 | 0 | parsed_information.emplace_back(T{ changed_file }, Event::modified); |
544 | 0 | } |
545 | 0 | } |
546 | 0 | } |
547 | 0 | i += event_size + event->len; |
548 | 0 | } |
549 | | //dispatch callbacks |
550 | 0 | { |
551 | 0 | std::lock_guard<std::mutex> lock(_callback_mutex); |
552 | 0 | _callback_information.insert(_callback_information.end(), parsed_information.begin(), parsed_information.end()); |
553 | 0 | } |
554 | 0 | _cv.notify_all(); |
555 | 0 | } |
556 | 0 | } |
557 | 0 | } |
558 | | #endif // __unix__ |
559 | | |
560 | | void callback_thread() |
561 | 0 | { |
562 | 0 | while (_destory == false) { |
563 | 0 | std::unique_lock<std::mutex> lock(_callback_mutex); |
564 | 0 | if (_callback_information.empty() && _destory == false) { |
565 | 0 | _cv.wait(lock, [this] { return _callback_information.size() > 0 || _destory; }); |
566 | 0 | } |
567 | 0 | decltype(_callback_information) callback_information = {}; |
568 | 0 | std::swap(callback_information, _callback_information); |
569 | 0 | lock.unlock(); |
570 | |
|
571 | 0 | for (const auto& file : callback_information) { |
572 | 0 | if (_callback) { |
573 | 0 | try |
574 | 0 | { |
575 | 0 | _callback(file.first, file.second); |
576 | 0 | } |
577 | 0 | catch (const std::exception&) |
578 | 0 | { |
579 | 0 | } |
580 | 0 | } |
581 | 0 | } |
582 | 0 | } |
583 | 0 | } |
584 | | |
585 | | void init_last_write_time() |
586 | 0 | { |
587 | | #ifdef _WIN32 |
588 | | // Define epoch reference |
589 | | GetSystemTimeAsFileTime((LPFILETIME)&base_.first); |
590 | | base_.second = std::chrono::system_clock::now(); |
591 | | |
592 | | // Initialize last_write_time_ |
593 | | _WIN32_FILE_ATTRIBUTE_DATA att; |
594 | | GetFileAttributesExA(_path.c_str(), GetFileExInfoStandard, &att); |
595 | | |
596 | | last_write_time_ = base_.second |
597 | | + std::chrono::duration< |
598 | | typename std::chrono::time_point<std::chrono::system_clock>::rep, |
599 | | std::ratio_multiply<std::hecto, typename std::chrono::nanoseconds::period>>( |
600 | | reinterpret_cast<ULARGE_INTEGER*>(&att.ftLastWriteTime)->QuadPart - base_.first.QuadPart); |
601 | | |
602 | | // Initialize filesize |
603 | | last_size_ = att.nFileSizeLow; |
604 | | #else |
605 | | // Initialize last_write_time_ |
606 | 0 | struct stat result; |
607 | 0 | stat(_path.c_str(), &result); |
608 | |
|
609 | 0 | using duration = std::chrono::system_clock::duration; |
610 | 0 | last_write_time_ += std::chrono::duration_cast<duration>(std::chrono::seconds(result.st_mtim.tv_sec)); |
611 | 0 | last_write_time_ += std::chrono::duration_cast<duration>(std::chrono::nanoseconds(result.st_mtim.tv_nsec)); |
612 | | |
613 | | // Initialize filesize |
614 | 0 | last_size_ = result.st_size; |
615 | 0 | #endif |
616 | | |
617 | 0 | } |
618 | | |
619 | | }; |
620 | | |
621 | | template<class T> constexpr typename FileWatch<T>::C FileWatch<T>::_regex_all[]; |
622 | | template<class T> constexpr typename FileWatch<T>::C FileWatch<T>::_this_directory[]; |
623 | | } |
624 | | #endif |