Plasma Engine  2.0
Loading...
Searching...
No Matches
DirectoryWatcher_Posix.h
1#include <Foundation/FoundationInternal.h>
2PL_FOUNDATION_INTERNAL_HEADER
3
4#include <Foundation/IO/DirectoryWatcher.h>
5
6#if __has_include(<sys/inotify.h>)
7# include <fcntl.h>
8# include <poll.h>
9# include <sys/inotify.h>
10
11# include <Foundation/IO/Implementation/FileSystemMirror.h>
12# include <Foundation/IO/OSFile.h>
13# include <Foundation/Logging/Log.h>
14
15# if PL_DISABLED(PL_SUPPORTS_FILE_ITERATORS)
16# error The directory watcher implementation needs file iterators
17# endif
18
19// Comment in to get verbose output on the function of the directory watcher
20// # define DEBUG_FILE_WATCHER
21
22# ifdef DEBUG_FILE_WATCHER
23# define DEBUG_LOG(...) plLog::Debug(__VA_ARGS__)
24# else
25# define DEBUG_LOG(...)
26# endif
27
28namespace
29{
30 bool IsFile(uint32_t mask)
31 {
32 return (mask & IN_ISDIR) == 0;
33 }
34
35 bool IsDirectory(uint32_t mask)
36 {
37 return mask & IN_ISDIR;
38 }
39
40 struct MoveEvent
41 {
42 plString path;
43 bool isDirectory = false;
44 uint32_t cookie = 0; // Two related events have the same cookie
45
46 void Clear()
47 {
48 path.Clear();
49 cookie = 0;
50 }
51
52 bool IsEmpty()
53 {
54 return path.IsEmpty();
55 }
56 };
57
58 struct RenamedDirectory
59 {
60 plString path;
61 int wd;
62 };
63
64 using plFileSystemMirrorType = plFileSystemMirror<bool>;
65} // namespace
66
67# ifndef _PL_DEFINED_POLLFD_POD
68# define _PL_DEFINED_POLLFD_POD
69PL_DEFINE_AS_POD_TYPE(struct pollfd);
70# endif
71
73{
75 plMap<plString, int> m_pathToWd;
76 plString m_topLevelPath;
77
78 int m_inotifyFd = -1;
79 uint32_t m_inotifyWatchMask = 0;
82
83 plUniquePtr<plFileSystemMirrorType> m_fileSystemMirror;
84 // List of directories we could not watch yet.
85 // This might happen if the directory was moved / renamed before we could add a watch to it.
86 plHashSet<plString> m_pendingDirectories;
87
88 void WatchNewDirectory(const plStringBuilder& path, plDirectoryWatcher::EnumerateChangesFunction& func, bool fireEvent)
89 {
90 plStringBuilder tmpPath = path;
91 EnsureTrailingSlash(tmpPath);
92
93 bool directoryAlreadyExists = false;
94 if (m_fileSystemMirror)
95 {
96 m_fileSystemMirror->AddDirectory(tmpPath, &directoryAlreadyExists).AssertSuccess();
97 }
98
99 if (m_whatToWatch.IsSet(plDirectoryWatcher::Watch::Creates) && !directoryAlreadyExists && fireEvent)
100 {
101 RemoveTrailingSlash(tmpPath);
102 func(tmpPath, plDirectoryWatcherAction::Added, plDirectoryWatcherType::Directory);
103 EnsureTrailingSlash(tmpPath);
104 }
105
106 if (!m_whatToWatch.IsSet(plDirectoryWatcher::Watch::Subdirectories))
107 {
108 return;
109 }
110
111 if (m_pathToWd.Contains(tmpPath))
112 {
113 // Already watching, skip
114 return;
115 }
116
117 RemoveTrailingSlash(tmpPath);
118 int wd = inotify_add_watch(m_inotifyFd, tmpPath.GetData(), m_inotifyWatchMask);
119 if (wd >= 0)
120 {
121 DEBUG_LOG("Now watching {}", tmpPath);
122 EnsureTrailingSlash(tmpPath);
123 m_pathToWd.Insert(tmpPath, wd);
124 m_wdToPath.Insert(wd, tmpPath);
125
126 // Whenever we add a directory we might be "to late" to see changes inside it.
127 // So iterate the file system and make sure we track all files / subdirectories
128 plFileSystemIterator subdirIt;
129
130 subdirIt.StartSearch(tmpPath.GetData(),
131 m_whatToWatch.IsSet(plDirectoryWatcher::Watch::Subdirectories)
132 ? plFileSystemIteratorFlags::ReportFilesAndFoldersRecursive
133 : plFileSystemIteratorFlags::ReportFiles);
134
135 plStringBuilder tmpPath2;
136 for (; subdirIt.IsValid(); subdirIt.Next())
137 {
138 const plFileStats& stats = subdirIt.GetStats();
139 stats.GetFullPath(tmpPath2);
140 if (stats.m_bIsDirectory)
141 {
142 directoryAlreadyExists = false;
143 if (m_fileSystemMirror)
144 {
145 m_fileSystemMirror->AddDirectory(tmpPath2, &directoryAlreadyExists).AssertSuccess();
146 }
147
148 if (m_whatToWatch.IsSet(plDirectoryWatcher::Watch::Creates) && !directoryAlreadyExists)
149 {
150 func(tmpPath2, plDirectoryWatcherAction::Added, plDirectoryWatcherType::Directory);
151 }
152
153 EnsureTrailingSlash(tmpPath2);
154 if (!m_pathToWd.Contains(tmpPath2))
155 {
156 RemoveTrailingSlash(tmpPath2);
157 int wd = inotify_add_watch(m_inotifyFd, tmpPath2.GetData(), m_inotifyWatchMask);
158 if (wd >= 0)
159 {
160 DEBUG_LOG("Now watching {}", tmpPath2);
161 EnsureTrailingSlash(tmpPath2);
162 m_pathToWd.Insert(tmpPath2, wd);
163 m_wdToPath.Insert(wd, tmpPath2);
164 }
165 else
166 {
167 m_pendingDirectories.Insert(tmpPath2);
168 }
169 }
170 }
171 else
172 {
173 bool fileExistsAlready = false;
174 if (m_fileSystemMirror)
175 {
176 m_fileSystemMirror->AddFile(tmpPath2, false, &fileExistsAlready, nullptr).AssertSuccess();
177 }
178 if (m_whatToWatch.IsSet(plDirectoryWatcher::Watch::Creates) && !fileExistsAlready)
179 {
180 func(tmpPath2, plDirectoryWatcherAction::Added, plDirectoryWatcherType::File);
181 }
182 }
183 }
184 }
185 else
186 {
187 m_pendingDirectories.Insert(tmpPath);
188 }
189 }
190};
191
192plDirectoryWatcher::plDirectoryWatcher()
193 : m_pImpl(PL_DEFAULT_NEW(plDirectoryWatcherImpl))
194{
195 m_pImpl->m_buffer.SetCountUninitialized(4 * 1024);
196}
197
198plDirectoryWatcher::~plDirectoryWatcher()
199{
200 CloseDirectory();
201 PL_DEFAULT_DELETE(m_pImpl);
202}
203
204plResult plDirectoryWatcher::OpenDirectory(plStringView sAbsolutePath, plBitflags<Watch> whatToWatch)
205{
206 if (m_pImpl->m_inotifyFd >= 0)
207 {
208 return PL_FAILURE; // already open
209 }
210
211 m_pImpl->m_inotifyFd = inotify_init();
212 if (m_pImpl->m_inotifyFd < 0)
213 {
214 return PL_FAILURE; // init failure
215 }
216
217 // Configure the file descriptor to be non-blocking
218 int flags = fcntl(m_pImpl->m_inotifyFd, F_GETFL, 0);
219 if (fcntl(m_pImpl->m_inotifyFd, F_SETFL, flags | O_NONBLOCK) != 0)
220 {
221 close(m_pImpl->m_inotifyFd);
222 m_pImpl->m_inotifyFd = -1;
223 }
224
225 plStringBuilder folder = sAbsolutePath;
226 folder.MakeCleanPath();
227 EnsureTrailingSlash(folder);
228
229 m_pImpl->m_topLevelPath = folder;
230
231 const int inotifyFd = m_pImpl->m_inotifyFd;
232
233 uint32_t watchMask = IN_MOVE_SELF;
234 // TODO add IN_MOVE_SELF handling
235
236
237 // If we need to watch subdirectories, we always need to be notified if a subdirectory was created or deleted
238 if (whatToWatch.IsSet(Watch::Subdirectories))
239 {
240 watchMask |= IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO;
241 }
242 if (whatToWatch.IsSet(Watch::Creates))
243 {
244 watchMask |= IN_CREATE;
245 }
246 if (whatToWatch.IsSet(Watch::Renames))
247 {
248 watchMask |= IN_MOVED_FROM | IN_MOVED_TO;
249 }
250 if (whatToWatch.IsSet(Watch::Writes))
251 {
252 watchMask |= IN_CLOSE_WRITE | IN_CREATE;
253 }
254 if (whatToWatch.IsSet(Watch::Deletes))
255 {
256 watchMask |= IN_DELETE;
257 }
258
259 int wd = inotify_add_watch(inotifyFd, folder.GetData(), watchMask);
260 if (wd < 0)
261 {
262 close(m_pImpl->m_inotifyFd);
263 m_pImpl->m_inotifyFd = -1;
264 return PL_FAILURE;
265 }
266
267 if ((whatToWatch.IsSet(Watch::Deletes) && whatToWatch.IsSet(Watch::Subdirectories)) || whatToWatch.IsSet(Watch::Writes))
268 {
269 // When a sub-folder is moved out of view. We need to trigger delete events for all files inside it.
270 // Thus we need to keep a in memory copy of the file system.
271 m_pImpl->m_fileSystemMirror = PL_DEFAULT_NEW(plFileSystemMirrorType);
272 m_pImpl->m_fileSystemMirror->AddDirectory(folder.GetData()).AssertSuccess();
273 }
274
275 m_pImpl->m_whatToWatch = whatToWatch;
276 m_pImpl->m_inotifyWatchMask = watchMask;
277
278 DEBUG_LOG("Now watching {}", folder);
279 m_pImpl->m_wdToPath.Insert(wd, folder);
280 m_pImpl->m_pathToWd.Insert(folder, wd);
281
283 dirIt.StartSearch(folder.GetData(), plFileSystemIteratorFlags::ReportFoldersRecursive);
284 plStringBuilder subFolderPath;
285 while (dirIt.IsValid())
286 {
287 dirIt.GetStats().GetFullPath(subFolderPath);
288 wd = inotify_add_watch(inotifyFd, subFolderPath.GetData(), watchMask);
289 if (wd >= 0)
290 {
291 EnsureTrailingSlash(subFolderPath);
292 DEBUG_LOG("Now watching {}", subFolderPath);
293 m_pImpl->m_wdToPath.Insert(wd, subFolderPath);
294 m_pImpl->m_pathToWd.Insert(subFolderPath, wd);
295 }
296 dirIt.Next();
297 }
298
299 return PL_SUCCESS;
300}
301
302void plDirectoryWatcher::CloseDirectory()
303{
304 const int inotifyFd = m_pImpl->m_inotifyFd;
305 if (inotifyFd >= 0)
306 {
307 for (auto& paths : m_pImpl->m_wdToPath)
308 {
309 inotify_rm_watch(inotifyFd, paths.Key());
310 }
311 m_pImpl->m_inotifyFd = -1;
312 close(inotifyFd);
313 }
314 m_pImpl->m_wdToPath.Clear();
315 m_pImpl->m_pathToWd.Clear();
316 m_pImpl->m_fileSystemMirror = nullptr;
317}
318
319
320void plDirectoryWatcher::EnumerateChanges(EnumerateChangesFunction func, plTime waitUpTo)
321{
322 const int inotifyFd = m_pImpl->m_inotifyFd;
323 uint8_t* const buffer = m_pImpl->m_buffer.GetData();
324 const size_t bufferSize = m_pImpl->m_buffer.GetCount();
325
326 plStringBuilder tmpPath;
327
328 const plBitflags<Watch> whatToWatch = m_pImpl->m_whatToWatch;
329 plFileSystemMirrorType* mirror = m_pImpl->m_fileSystemMirror.Borrow();
330
331 MoveEvent lastMoveFrom;
332
333 // An orpahend move from is when we see a move from, but no move to
334 // This means the file was moved outside of our view of the file system
335 // tread this as a delete.
336 auto processOrphanedMoveFrom = [&](MoveEvent& moveFrom)
337 {
338 if (whatToWatch.IsSet(Watch::Deletes))
339 {
340 if (!moveFrom.isDirectory)
341 {
342 func(moveFrom.path.GetData(), plDirectoryWatcherAction::Removed, plDirectoryWatcherType::File);
343 mirror->RemoveFile(moveFrom.path).AssertSuccess();
344 m_pImpl->m_pendingDirectories.Remove(moveFrom.path);
345 }
346 else
347 {
348 plStringBuilder dirPath;
349 mirror->Enumerate(moveFrom.path, [&](plStringView sPath, typename plFileSystemMirrorType::Type type)
350 {
351 if (type == plFileSystemMirrorType::Type::File)
352 {
353 func(sPath, plDirectoryWatcherAction::Removed, plDirectoryWatcherType::File);
354 }
355 else
356 {
357 func(sPath, plDirectoryWatcherAction::Removed, plDirectoryWatcherType::Directory);
358 dirPath = sPath;
359 EnsureTrailingSlash(dirPath);
360 auto it = m_pImpl->m_pathToWd.Find(dirPath);
361 if (it.IsValid())
362 {
363 DEBUG_LOG("No longer watching {}", it.Key());
364 inotify_rm_watch(inotifyFd, it.Value());
365 m_pImpl->m_wdToPath.Remove(it.Value());
366 m_pImpl->m_pathToWd.Remove(it);
367 }
368 }
369 //
370 })
371 .AssertSuccess();
372 mirror->RemoveDirectory(moveFrom.path).AssertSuccess();
373
374 func(moveFrom.path, plDirectoryWatcherAction::Removed, plDirectoryWatcherType::Directory);
375
376 dirPath = moveFrom.path;
377 EnsureTrailingSlash(dirPath);
378 auto it = m_pImpl->m_pathToWd.Find(dirPath);
379 PL_ASSERT_DEBUG(it.IsValid(), "path should exist");
380 if (it.IsValid())
381 {
382 DEBUG_LOG("No longer watching {}", it.Key());
383 m_pImpl->m_wdToPath.Remove(it.Value());
384 m_pImpl->m_pathToWd.Remove(it);
385 }
386 }
387 }
388 moveFrom.Clear();
389 };
390
391 if (inotifyFd >= 0)
392 {
393 int timeout = static_cast<int>(waitUpTo.GetMilliseconds());
394 if (timeout > 0)
395 {
396 struct pollfd pollFor = {inotifyFd, POLLIN, 0};
397 int pollResult = poll(&pollFor, 1, timeout);
398 if (pollResult < 0)
399 {
400 // Error, stop
401 plLog::Error("Unexpected result from poll when watching directory {}", m_sDirectoryPath);
402 CloseDirectory();
403 return;
404 }
405 else if (pollResult == 0)
406 {
407 return; // timeout, no results
408 }
409 }
410
411 ssize_t numBytesRead = 0;
412 while ((numBytesRead = read(inotifyFd, buffer, bufferSize)) > 0)
413 {
414 size_t curPos = 0;
415 while (curPos < numBytesRead)
416 {
417 const struct inotify_event* event = (struct inotify_event*)(buffer + curPos);
418
419 auto it = m_pImpl->m_wdToPath.Find(event->wd);
420 if (it.IsValid() && event->len > 0)
421 {
422 tmpPath = it.Value();
423 tmpPath.AppendPath(event->name);
424
425 const char* type = "file";
426 PL_IGNORE_UNUSED(type);
427 if (IsDirectory(event->mask))
428 {
429 type = "folder";
430 }
431
432 if (event->mask & IN_CREATE)
433 {
434 DEBUG_LOG("IN_CREATE {} {} {} {}", type, tmpPath, event->cookie, event->mask);
435
436 if (IsDirectory(event->mask))
437 {
438 m_pImpl->WatchNewDirectory(tmpPath, func, true);
439 }
440 else
441 {
442 bool fileExistsAlready = false;
443 if (mirror != nullptr)
444 {
445 mirror->AddFile(tmpPath, true, &fileExistsAlready, nullptr).AssertSuccess();
446 }
447
448 if (whatToWatch.IsSet(Watch::Creates) && !fileExistsAlready)
449 {
450 func(tmpPath.GetData(), plDirectoryWatcherAction::Added, plDirectoryWatcherType::File);
451 }
452 }
453 }
454 else if (event->mask & IN_CLOSE_WRITE)
455 {
456 DEBUG_LOG("IN_CLOSE_WRITE {} {} {} {}", type, tmpPath, event->cookie, event->mask);
457 if (whatToWatch.IsSet(Watch::Writes) && IsFile(event->mask))
458 {
459 bool addPending = false;
460 if (mirror)
461 {
462 mirror->AddFile(tmpPath, false, nullptr, &addPending).AssertSuccess();
463 }
464 if (!addPending)
465 {
466 func(tmpPath.GetData(), plDirectoryWatcherAction::Modified, plDirectoryWatcherType::File);
467 }
468 }
469 }
470 else if (event->mask & IN_DELETE)
471 {
472 DEBUG_LOG("IN_DELETE {} {} {}", type, tmpPath, event->cookie);
473
474 if (IsDirectory(event->mask))
475 {
476 m_pImpl->m_pendingDirectories.Remove(tmpPath);
477
478 if (mirror)
479 {
480 mirror->RemoveDirectory(tmpPath).AssertSuccess();
481 }
482
483 if (whatToWatch.IsSet(Watch::Deletes))
484 {
485 func(tmpPath.GetData(), plDirectoryWatcherAction::Removed, plDirectoryWatcherType::Directory);
486 }
487
488 if (whatToWatch.IsSet(Watch::Subdirectories))
489 {
490 EnsureTrailingSlash(tmpPath);
491 auto deletedDirIt = m_pImpl->m_pathToWd.Find(tmpPath);
492 if (deletedDirIt.IsValid())
493 {
494 int deletedWd = deletedDirIt.Value();
495 deletedDirIt = m_pImpl->m_pathToWd.Remove(deletedDirIt);
496 inotify_rm_watch(inotifyFd, deletedWd);
497 m_pImpl->m_wdToPath.Remove(deletedWd);
498 DEBUG_LOG("No longer watching {}", tmpPath);
499 }
500 }
501 }
502 else
503 {
504 if (mirror)
505 {
506 mirror->RemoveFile(tmpPath).AssertSuccess();
507 }
508
509 if (whatToWatch.IsSet(Watch::Deletes))
510 {
511 func(tmpPath.GetData(), plDirectoryWatcherAction::Removed, plDirectoryWatcherType::File);
512 }
513 }
514 }
515 else if (event->mask & IN_MOVED_FROM)
516 {
517 DEBUG_LOG("IN_MOVED_FROM {} {} {}", type, tmpPath, event->cookie);
518
519 if (!lastMoveFrom.IsEmpty())
520 {
521 processOrphanedMoveFrom(lastMoveFrom);
522 }
523
524 lastMoveFrom.path = tmpPath;
525 lastMoveFrom.cookie = event->cookie;
526 lastMoveFrom.isDirectory = IsDirectory(event->mask);
527 }
528 else if (event->mask & IN_MOVED_TO)
529 {
530 DEBUG_LOG("IN_MOVED_TO {} {} {}", type, tmpPath, event->cookie);
531
532 if (!lastMoveFrom.IsEmpty() && lastMoveFrom.cookie != event->cookie)
533 {
534 // orphaned move from
535 processOrphanedMoveFrom(lastMoveFrom);
536 }
537
538 if (lastMoveFrom.IsEmpty())
539 {
540 // Orphaned move to, treat as add
541 if (IsFile(event->mask))
542 {
543 bool fileAlreadyExists = false;
544 if (mirror)
545 {
546 mirror->AddFile(tmpPath, false, &fileAlreadyExists, nullptr).AssertSuccess();
547 }
548
549 if (whatToWatch.IsSet(Watch::Creates) && !fileAlreadyExists)
550 {
551 func(tmpPath.GetData(), plDirectoryWatcherAction::Added, plDirectoryWatcherType::File);
552 }
553 }
554 else
555 {
556 m_pImpl->WatchNewDirectory(tmpPath, func, true);
557 }
558 }
559 else
560 {
561 // regular move
562 if (whatToWatch.IsSet(Watch::Renames))
563 {
564 func(lastMoveFrom.path.GetData(), plDirectoryWatcherAction::RenamedOldName, IsFile(event->mask) ? plDirectoryWatcherType::File : plDirectoryWatcherType::Directory);
565 func(tmpPath.GetData(), plDirectoryWatcherAction::RenamedNewName, IsFile(event->mask) ? plDirectoryWatcherType::File : plDirectoryWatcherType::Directory);
566 }
567
568 if (IsDirectory(event->mask))
569 {
570 if (m_pImpl->m_pendingDirectories.Contains(lastMoveFrom.path))
571 {
572 m_pImpl->m_pendingDirectories.Remove(lastMoveFrom.path);
573 m_pImpl->WatchNewDirectory(tmpPath, func, false);
574 }
575 else
576 {
577 plStringBuilder moveFromPath = lastMoveFrom.path;
578 EnsureTrailingSlash(moveFromPath);
579 EnsureTrailingSlash(tmpPath);
580
581 plDynamicArray<RenamedDirectory> renamedDirectories;
582 for (auto it = m_pImpl->m_pathToWd.GetIterator(); it.IsValid();)
583 {
584 if (it.Key().StartsWith(moveFromPath))
585 {
586 m_pImpl->m_wdToPath.Remove(it.Value());
587 plStringBuilder fixedPath = it.Key();
588 fixedPath.Shrink(moveFromPath.GetCharacterCount(), 0);
589 fixedPath.Prepend(tmpPath);
590 renamedDirectories.PushBack({fixedPath, it.Value()});
591 it = m_pImpl->m_pathToWd.Remove(it);
592 }
593 else
594 {
595 it.Next();
596 }
597 }
598
599 for (auto& renamedDirectory : renamedDirectories)
600 {
601 m_pImpl->m_pathToWd.Insert(renamedDirectory.path, renamedDirectory.wd);
602 m_pImpl->m_wdToPath.Insert(renamedDirectory.wd, std::move(renamedDirectory.path));
603 }
604 }
605 }
606
607 if (mirror)
608 {
609 if (IsFile(event->mask))
610 {
611 mirror->RemoveFile(lastMoveFrom.path).AssertSuccess();
612 mirror->AddFile(tmpPath, false, nullptr, nullptr).AssertSuccess();
613 }
614 else
615 {
616 mirror->MoveDirectory(lastMoveFrom.path, tmpPath).AssertSuccess();
617 }
618 }
619 lastMoveFrom.Clear();
620 }
621 }
622 }
623 else
624 {
625 DEBUG_LOG("UNKNOWN {}", event->mask);
626 }
627
628 curPos += sizeof(struct inotify_event) + event->len;
629 }
630 }
631 }
632
633 if (!lastMoveFrom.IsEmpty())
634 {
635 processOrphanedMoveFrom(lastMoveFrom);
636 }
637}
638
639void plDirectoryWatcher::EnumerateChanges(plArrayPtr<plDirectoryWatcher*> watchers, EnumerateChangesFunction func, plTime waitUpTo)
640{
641 int timeout = static_cast<int>(waitUpTo.GetMilliseconds());
642 if (timeout > 0)
643 {
645 pollFor.SetCount(watchers.GetCount());
646
647 for (plUInt32 i = 0; i < watchers.GetCount(); ++i)
648 {
649 plDirectoryWatcher* curWatcher = watchers[i];
650 pollFor[i] = {curWatcher->m_pImpl->m_inotifyFd, POLLIN, 0};
651 }
652
653
654 int pollResult = poll(pollFor.GetData(), pollFor.GetCount(), timeout);
655 if (pollResult < 0)
656 {
657 // Error, stop
658 plLog::Error("Unexpected result from poll enumerating multiple watchers");
659 return;
660 }
661 else if (pollResult == 0)
662 {
663 return; // timeout, no results
664 }
665 }
666
667 for (plDirectoryWatcher* watcher : watchers)
668 {
669 watcher->EnumerateChanges(func);
670 }
671}
672
673# undef DEBUG_LOG
674
675#else // <sys/inotify.h> is missing
677{
678};
679
680plDirectoryWatcher::plDirectoryWatcher()
681 : m_pImpl(nullptr)
682{
683}
684
685plResult plDirectoryWatcher::OpenDirectory(const plString& path, plBitflags<Watch> whatToWatch)
686{
687 return PL_FAILURE;
688}
689
690void plDirectoryWatcher::CloseDirectory()
691{
692}
693
694plDirectoryWatcher::~plDirectoryWatcher()
695{
696}
697
698void plDirectoryWatcher::EnumerateChanges(EnumerateChangesFunction func, plTime waitUpTo)
699{
700 plLog::Warning("plDirectoryWatcher not supported on this Linux system");
701}
702
703void plDirectoryWatcher::EnumerateChanges(plArrayPtr<plDirectoryWatcher*> watchers, EnumerateChangesFunction func, plTime waitUpTo)
704{
705 plLog::Warning("plDirectoryWatcher not supported on this Linux system");
706}
707
708#endif
void PushBack(const T &value)
Pushes value at the end of the array.
Definition ArrayBase_inl.h:333
T * GetData()
Returns a pointer to the array data, or nullptr if the array is empty.
Definition ArrayBase_inl.h:423
void SetCount(plUInt32 uiCount)
Resizes the array to have exactly uiCount elements. Default constructs extra elements if the array is...
Definition ArrayBase_inl.h:106
plUInt32 GetCount() const
Returns the number of active elements in the array.
Definition ArrayBase_inl.h:172
This class encapsulates an array and it's size. It is recommended to use this class instead of plain ...
Definition ArrayPtr.h:37
PL_ALWAYS_INLINE plUInt32 GetCount() const
Returns the number of elements in the array.
Definition ArrayPtr.h:142
Definition DynamicArray.h:81
An plFileSystemIterator allows to iterate over all files in a certain directory.
Definition OSFile.h:94
bool IsValid() const
Returns true if the iterator currently points to a valid file entry.
void StartSearch(plStringView sSearchTerm, plBitflags< plFileSystemIteratorFlags > flags=plFileSystemIteratorFlags::Default)
Starts a search at the given folder. Use * and ? as wildcards.
void Next()
Advances the iterator to the next file object. Might recurse into sub-folders.
const plFileStats & GetStats() const
Returns the file stats of the current object that the iterator points to.
Definition OSFile.h:130
Definition FileSystemMirror.h:13
bool Insert(CompatibleKeyType &&key)
Inserts the key. Returns whether the key was already existing.
Definition HashSet_inl.h:270
Definition HashSet.h:191
bool Insert(CompatibleKeyType &&key, CompatibleValueType &&value, ValueType *out_pOldValue=nullptr)
Inserts the key value pair or replaces value if an entry with the given key already exists.
Definition HashTable.h:333
A hybrid array uses in-place storage to handle the first few elements without any allocation....
Definition HybridArray.h:12
static void Error(plLogInterface *pInterface, const plFormatString &string)
An error that needs to be fixed as soon as possible.
Definition Log.cpp:375
static void Warning(plLogInterface *pInterface, const plFormatString &string)
A potential problem or a performance warning. Might be possible to ignore it.
Definition Log.cpp:391
bool Contains(const CompatibleKeyType &key) const
Checks whether the given key is in the container.
Iterator Insert(CompatibleKeyType &&key, CompatibleValueType &&value)
Inserts the key/value pair into the tree and returns an Iterator to it. O(log n) operation.
Definition Map_inl.h:535
Definition Map.h:408
plStringBuilder is a class that is meant for creating and modifying strings.
Definition StringBuilder.h:35
const char * GetData() const
Returns a char pointer to the internal Utf8 data.
Definition StringBuilder_inl.h:148
void Shrink(plUInt32 uiShrinkCharsFront, plUInt32 uiShrinkCharsBack)
Removes the first n and last m characters from this string.
Definition StringBuilder.cpp:393
void Prepend(plUInt32 uiChar)
Prepends a single Utf32 character.
Definition StringBuilder_inl.h:111
plUInt32 GetCharacterCount() const
Returns the number of characters of which this string consists. Might be less than GetElementCount,...
Definition StringBuilder_inl.h:83
void MakeCleanPath()
Removes "../" where possible, replaces all path separators with /, removes double slashes.
Definition StringBuilder.cpp:751
void AppendPath(plStringView sPath1, plStringView sPath2={}, plStringView sPath3={}, plStringView sPath4={})
Appends several path pieces. Makes sure they are always properly separated by a slash.
Definition StringBuilder.cpp:866
plStringView represent a read-only sub-string of a larger string, as it can store a dedicated string ...
Definition StringView.h:34
A Unique ptr manages an object and destroys that object when it goes out of scope....
Definition UniquePtr.h:10
The plBitflags class allows you to work with type-safe bitflags.
Definition Bitflags.h:82
PL_ALWAYS_INLINE bool IsSet(Enum flag) const
Checks if certain flags are set within the bitfield.
Definition Bitflags.h:127
Definition DirectoryWatcher_Posix.h:677
Holds the stats for a file.
Definition OSFile.h:34
bool m_bIsDirectory
Whether the file object is a file or folder.
Definition OSFile.h:56
void GetFullPath(plStringBuilder &ref_sPath) const
Stores the concatenated m_sParentPath and m_sName in path.
Definition OSFile.cpp:16
Definition OSFile.h:64
void Clear()
Resets this string to an empty string.
Definition String_inl.h:49
Default enum for returning failure or success, instead of using a bool.
Definition Types.h:54
bool IsEmpty() const
Returns whether the string is an empty string.
Definition StringBase_inl.h:25
The time class encapsulates a double value storing the time in seconds.
Definition Time.h:12
constexpr double GetMilliseconds() const
Returns the milliseconds value.
Definition Time_inl.h:25