1#include <Foundation/FoundationInternal.h>
2PL_FOUNDATION_INTERNAL_HEADER
4#include <Foundation/IO/DirectoryWatcher.h>
6#if __has_include(<sys/inotify.h>)
9# include <sys/inotify.h>
11# include <Foundation/IO/Implementation/FileSystemMirror.h>
12# include <Foundation/IO/OSFile.h>
13# include <Foundation/Logging/Log.h>
15# if PL_DISABLED(PL_SUPPORTS_FILE_ITERATORS)
16# error The directory watcher implementation needs file iterators
22# ifdef DEBUG_FILE_WATCHER
23# define DEBUG_LOG(...) plLog::Debug(__VA_ARGS__)
25# define DEBUG_LOG(...)
30 bool IsFile(uint32_t mask)
32 return (mask & IN_ISDIR) == 0;
35 bool IsDirectory(uint32_t mask)
37 return mask & IN_ISDIR;
43 bool isDirectory =
false;
58 struct RenamedDirectory
67# ifndef _PL_DEFINED_POLLFD_POD
68# define _PL_DEFINED_POLLFD_POD
69PL_DEFINE_AS_POD_TYPE(
struct pollfd);
79 uint32_t m_inotifyWatchMask = 0;
88 void WatchNewDirectory(
const plStringBuilder& path, plDirectoryWatcher::EnumerateChangesFunction& func,
bool fireEvent)
91 EnsureTrailingSlash(tmpPath);
93 bool directoryAlreadyExists =
false;
94 if (m_fileSystemMirror)
96 m_fileSystemMirror->AddDirectory(tmpPath, &directoryAlreadyExists).AssertSuccess();
99 if (m_whatToWatch.
IsSet(plDirectoryWatcher::Watch::Creates) && !directoryAlreadyExists && fireEvent)
101 RemoveTrailingSlash(tmpPath);
102 func(tmpPath, plDirectoryWatcherAction::Added, plDirectoryWatcherType::Directory);
103 EnsureTrailingSlash(tmpPath);
106 if (!m_whatToWatch.
IsSet(plDirectoryWatcher::Watch::Subdirectories))
117 RemoveTrailingSlash(tmpPath);
118 int wd = inotify_add_watch(m_inotifyFd, tmpPath.
GetData(), m_inotifyWatchMask);
121 DEBUG_LOG(
"Now watching {}", tmpPath);
122 EnsureTrailingSlash(tmpPath);
123 m_pathToWd.
Insert(tmpPath, wd);
124 m_wdToPath.
Insert(wd, tmpPath);
131 m_whatToWatch.
IsSet(plDirectoryWatcher::Watch::Subdirectories)
132 ? plFileSystemIteratorFlags::ReportFilesAndFoldersRecursive
142 directoryAlreadyExists =
false;
143 if (m_fileSystemMirror)
145 m_fileSystemMirror->AddDirectory(tmpPath2, &directoryAlreadyExists).AssertSuccess();
148 if (m_whatToWatch.
IsSet(plDirectoryWatcher::Watch::Creates) && !directoryAlreadyExists)
150 func(tmpPath2, plDirectoryWatcherAction::Added, plDirectoryWatcherType::Directory);
153 EnsureTrailingSlash(tmpPath2);
156 RemoveTrailingSlash(tmpPath2);
157 int wd = inotify_add_watch(m_inotifyFd, tmpPath2.
GetData(), m_inotifyWatchMask);
160 DEBUG_LOG(
"Now watching {}", tmpPath2);
161 EnsureTrailingSlash(tmpPath2);
162 m_pathToWd.
Insert(tmpPath2, wd);
163 m_wdToPath.
Insert(wd, tmpPath2);
167 m_pendingDirectories.
Insert(tmpPath2);
173 bool fileExistsAlready =
false;
174 if (m_fileSystemMirror)
176 m_fileSystemMirror->AddFile(tmpPath2,
false, &fileExistsAlready,
nullptr).AssertSuccess();
178 if (m_whatToWatch.
IsSet(plDirectoryWatcher::Watch::Creates) && !fileExistsAlready)
180 func(tmpPath2, plDirectoryWatcherAction::Added, plDirectoryWatcherType::File);
187 m_pendingDirectories.
Insert(tmpPath);
192plDirectoryWatcher::plDirectoryWatcher()
195 m_pImpl->m_buffer.SetCountUninitialized(4 * 1024);
198plDirectoryWatcher::~plDirectoryWatcher()
201 PL_DEFAULT_DELETE(m_pImpl);
206 if (m_pImpl->m_inotifyFd >= 0)
211 m_pImpl->m_inotifyFd = inotify_init();
212 if (m_pImpl->m_inotifyFd < 0)
218 int flags = fcntl(m_pImpl->m_inotifyFd, F_GETFL, 0);
219 if (fcntl(m_pImpl->m_inotifyFd, F_SETFL, flags | O_NONBLOCK) != 0)
221 close(m_pImpl->m_inotifyFd);
222 m_pImpl->m_inotifyFd = -1;
227 EnsureTrailingSlash(folder);
229 m_pImpl->m_topLevelPath = folder;
231 const int inotifyFd = m_pImpl->m_inotifyFd;
233 uint32_t watchMask = IN_MOVE_SELF;
238 if (whatToWatch.
IsSet(Watch::Subdirectories))
240 watchMask |= IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO;
242 if (whatToWatch.
IsSet(Watch::Creates))
244 watchMask |= IN_CREATE;
246 if (whatToWatch.
IsSet(Watch::Renames))
248 watchMask |= IN_MOVED_FROM | IN_MOVED_TO;
250 if (whatToWatch.
IsSet(Watch::Writes))
252 watchMask |= IN_CLOSE_WRITE | IN_CREATE;
254 if (whatToWatch.
IsSet(Watch::Deletes))
256 watchMask |= IN_DELETE;
259 int wd = inotify_add_watch(inotifyFd, folder.
GetData(), watchMask);
262 close(m_pImpl->m_inotifyFd);
263 m_pImpl->m_inotifyFd = -1;
267 if ((whatToWatch.
IsSet(Watch::Deletes) && whatToWatch.
IsSet(Watch::Subdirectories)) || whatToWatch.
IsSet(Watch::Writes))
271 m_pImpl->m_fileSystemMirror = PL_DEFAULT_NEW(plFileSystemMirrorType);
272 m_pImpl->m_fileSystemMirror->AddDirectory(folder.
GetData()).AssertSuccess();
275 m_pImpl->m_whatToWatch = whatToWatch;
276 m_pImpl->m_inotifyWatchMask = watchMask;
278 DEBUG_LOG(
"Now watching {}", folder);
279 m_pImpl->m_wdToPath.Insert(wd, folder);
280 m_pImpl->m_pathToWd.Insert(folder, wd);
283 dirIt.
StartSearch(folder.
GetData(), plFileSystemIteratorFlags::ReportFoldersRecursive);
288 wd = inotify_add_watch(inotifyFd, subFolderPath.
GetData(), watchMask);
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);
302void plDirectoryWatcher::CloseDirectory()
304 const int inotifyFd = m_pImpl->m_inotifyFd;
307 for (
auto& paths : m_pImpl->m_wdToPath)
309 inotify_rm_watch(inotifyFd, paths.Key());
311 m_pImpl->m_inotifyFd = -1;
314 m_pImpl->m_wdToPath.Clear();
315 m_pImpl->m_pathToWd.Clear();
316 m_pImpl->m_fileSystemMirror =
nullptr;
320void plDirectoryWatcher::EnumerateChanges(EnumerateChangesFunction func,
plTime waitUpTo)
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();
329 plFileSystemMirrorType* mirror = m_pImpl->m_fileSystemMirror.Borrow();
331 MoveEvent lastMoveFrom;
336 auto processOrphanedMoveFrom = [&](MoveEvent& moveFrom)
338 if (whatToWatch.
IsSet(Watch::Deletes))
340 if (!moveFrom.isDirectory)
342 func(moveFrom.path.GetData(), plDirectoryWatcherAction::Removed, plDirectoryWatcherType::File);
343 mirror->RemoveFile(moveFrom.path).AssertSuccess();
344 m_pImpl->m_pendingDirectories.Remove(moveFrom.path);
349 mirror->Enumerate(moveFrom.path, [&](
plStringView sPath,
typename plFileSystemMirrorType::Type type)
351 if (type == plFileSystemMirrorType::Type::File)
353 func(sPath, plDirectoryWatcherAction::Removed, plDirectoryWatcherType::File);
357 func(sPath, plDirectoryWatcherAction::Removed, plDirectoryWatcherType::Directory);
359 EnsureTrailingSlash(dirPath);
360 auto it = m_pImpl->m_pathToWd.Find(dirPath);
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);
372 mirror->RemoveDirectory(moveFrom.path).AssertSuccess();
374 func(moveFrom.path, plDirectoryWatcherAction::Removed, plDirectoryWatcherType::Directory);
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");
382 DEBUG_LOG(
"No longer watching {}", it.Key());
383 m_pImpl->m_wdToPath.Remove(it.Value());
384 m_pImpl->m_pathToWd.Remove(it);
396 struct pollfd pollFor = {inotifyFd, POLLIN, 0};
397 int pollResult = poll(&pollFor, 1, timeout);
401 plLog::Error(
"Unexpected result from poll when watching directory {}", m_sDirectoryPath);
405 else if (pollResult == 0)
411 ssize_t numBytesRead = 0;
412 while ((numBytesRead = read(inotifyFd, buffer, bufferSize)) > 0)
415 while (curPos < numBytesRead)
417 const struct inotify_event*
event = (
struct inotify_event*)(buffer + curPos);
419 auto it = m_pImpl->m_wdToPath.Find(event->wd);
420 if (it.IsValid() &&
event->len > 0)
422 tmpPath = it.Value();
425 const char* type =
"file";
426 PL_IGNORE_UNUSED(type);
427 if (IsDirectory(event->mask))
432 if (event->mask & IN_CREATE)
434 DEBUG_LOG(
"IN_CREATE {} {} {} {}", type, tmpPath, event->cookie, event->mask);
436 if (IsDirectory(event->mask))
438 m_pImpl->WatchNewDirectory(tmpPath, func,
true);
442 bool fileExistsAlready =
false;
443 if (mirror !=
nullptr)
445 mirror->AddFile(tmpPath,
true, &fileExistsAlready,
nullptr).AssertSuccess();
448 if (whatToWatch.
IsSet(Watch::Creates) && !fileExistsAlready)
450 func(tmpPath.
GetData(), plDirectoryWatcherAction::Added, plDirectoryWatcherType::File);
454 else if (event->mask & IN_CLOSE_WRITE)
456 DEBUG_LOG(
"IN_CLOSE_WRITE {} {} {} {}", type, tmpPath, event->cookie, event->mask);
457 if (whatToWatch.
IsSet(Watch::Writes) && IsFile(event->mask))
459 bool addPending =
false;
462 mirror->AddFile(tmpPath,
false,
nullptr, &addPending).AssertSuccess();
466 func(tmpPath.
GetData(), plDirectoryWatcherAction::Modified, plDirectoryWatcherType::File);
470 else if (event->mask & IN_DELETE)
472 DEBUG_LOG(
"IN_DELETE {} {} {}", type, tmpPath, event->cookie);
474 if (IsDirectory(event->mask))
476 m_pImpl->m_pendingDirectories.Remove(tmpPath);
480 mirror->RemoveDirectory(tmpPath).AssertSuccess();
483 if (whatToWatch.
IsSet(Watch::Deletes))
485 func(tmpPath.
GetData(), plDirectoryWatcherAction::Removed, plDirectoryWatcherType::Directory);
488 if (whatToWatch.
IsSet(Watch::Subdirectories))
490 EnsureTrailingSlash(tmpPath);
491 auto deletedDirIt = m_pImpl->m_pathToWd.Find(tmpPath);
492 if (deletedDirIt.IsValid())
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);
506 mirror->RemoveFile(tmpPath).AssertSuccess();
509 if (whatToWatch.
IsSet(Watch::Deletes))
511 func(tmpPath.
GetData(), plDirectoryWatcherAction::Removed, plDirectoryWatcherType::File);
515 else if (event->mask & IN_MOVED_FROM)
517 DEBUG_LOG(
"IN_MOVED_FROM {} {} {}", type, tmpPath, event->cookie);
519 if (!lastMoveFrom.IsEmpty())
521 processOrphanedMoveFrom(lastMoveFrom);
524 lastMoveFrom.path = tmpPath;
525 lastMoveFrom.cookie =
event->cookie;
526 lastMoveFrom.isDirectory = IsDirectory(event->mask);
528 else if (event->mask & IN_MOVED_TO)
530 DEBUG_LOG(
"IN_MOVED_TO {} {} {}", type, tmpPath, event->cookie);
532 if (!lastMoveFrom.IsEmpty() && lastMoveFrom.cookie != event->cookie)
535 processOrphanedMoveFrom(lastMoveFrom);
538 if (lastMoveFrom.IsEmpty())
541 if (IsFile(event->mask))
543 bool fileAlreadyExists =
false;
546 mirror->AddFile(tmpPath,
false, &fileAlreadyExists,
nullptr).AssertSuccess();
549 if (whatToWatch.
IsSet(Watch::Creates) && !fileAlreadyExists)
551 func(tmpPath.
GetData(), plDirectoryWatcherAction::Added, plDirectoryWatcherType::File);
556 m_pImpl->WatchNewDirectory(tmpPath, func,
true);
562 if (whatToWatch.
IsSet(Watch::Renames))
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);
568 if (IsDirectory(event->mask))
570 if (m_pImpl->m_pendingDirectories.Contains(lastMoveFrom.path))
572 m_pImpl->m_pendingDirectories.Remove(lastMoveFrom.path);
573 m_pImpl->WatchNewDirectory(tmpPath, func,
false);
578 EnsureTrailingSlash(moveFromPath);
579 EnsureTrailingSlash(tmpPath);
582 for (
auto it = m_pImpl->m_pathToWd.GetIterator(); it.IsValid();)
584 if (it.Key().StartsWith(moveFromPath))
586 m_pImpl->m_wdToPath.Remove(it.Value());
590 renamedDirectories.
PushBack({fixedPath, it.Value()});
591 it = m_pImpl->m_pathToWd.Remove(it);
599 for (
auto& renamedDirectory : renamedDirectories)
601 m_pImpl->m_pathToWd.Insert(renamedDirectory.path, renamedDirectory.wd);
602 m_pImpl->m_wdToPath.Insert(renamedDirectory.wd, std::move(renamedDirectory.path));
609 if (IsFile(event->mask))
611 mirror->RemoveFile(lastMoveFrom.path).AssertSuccess();
612 mirror->AddFile(tmpPath,
false,
nullptr,
nullptr).AssertSuccess();
616 mirror->MoveDirectory(lastMoveFrom.path, tmpPath).AssertSuccess();
619 lastMoveFrom.Clear();
625 DEBUG_LOG(
"UNKNOWN {}", event->mask);
628 curPos +=
sizeof(
struct inotify_event) + event->len;
633 if (!lastMoveFrom.IsEmpty())
635 processOrphanedMoveFrom(lastMoveFrom);
647 for (plUInt32 i = 0; i < watchers.
GetCount(); ++i)
649 plDirectoryWatcher* curWatcher = watchers[i];
650 pollFor[i] = {curWatcher->m_pImpl->m_inotifyFd, POLLIN, 0};
658 plLog::Error(
"Unexpected result from poll enumerating multiple watchers");
661 else if (pollResult == 0)
667 for (plDirectoryWatcher* watcher : watchers)
669 watcher->EnumerateChanges(func);
680plDirectoryWatcher::plDirectoryWatcher()
690void plDirectoryWatcher::CloseDirectory()
694plDirectoryWatcher::~plDirectoryWatcher()
698void plDirectoryWatcher::EnumerateChanges(EnumerateChangesFunction func,
plTime waitUpTo)
700 plLog::Warning(
"plDirectoryWatcher not supported on this Linux system");
705 plLog::Warning(
"plDirectoryWatcher not supported on this Linux system");
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
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
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
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