From 3248dd50f91daef996aea745d89a705c1962d021 Mon Sep 17 00:00:00 2001 From: Mariusz Wisniewski Date: Mon, 16 Aug 2021 14:31:21 +0200 Subject: [PATCH] Added LinuxFileSystemWatcher --- Source/Engine/Platform/FileSystemWatcher.h | 2 +- .../Platform/Linux/LinuxFileSystemWatcher.cpp | 247 ++++++++++++++++++ .../Platform/Linux/LinuxFileSystemWatcher.h | 41 +++ Source/Engine/Platform/Types.h | 4 +- 4 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp create mode 100644 Source/Engine/Platform/Linux/LinuxFileSystemWatcher.h diff --git a/Source/Engine/Platform/FileSystemWatcher.h b/Source/Engine/Platform/FileSystemWatcher.h index bd14f4241..035775b6c 100644 --- a/Source/Engine/Platform/FileSystemWatcher.h +++ b/Source/Engine/Platform/FileSystemWatcher.h @@ -7,7 +7,7 @@ #elif PLATFORM_UWP #include "Base/FileSystemWatcherBase.h" #elif PLATFORM_LINUX -#include "Base/FileSystemWatcherBase.h" +#include "Linux/LinuxFileSystemWatcher.h" #elif PLATFORM_PS4 #include "Base/FileSystemWatcherBase.h" #elif PLATFORM_XBOX_SCARLETT diff --git a/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp b/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp new file mode 100644 index 000000000..afc00726a --- /dev/null +++ b/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp @@ -0,0 +1,247 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_LINUX + +#include "LinuxFileSystemWatcher.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Platform/Thread.h" +#include "Engine/Threading/ThreadSpawner.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Types/Pair.h" +#include +#include +#include +#include + +namespace FileSystemWatchers +{ + CriticalSection Locker; + Dictionary RootWatchers; + Dictionary>> Watchers; + + LinuxThread* Thread = nullptr; + bool ThreadActive; + int WacherFileDescriptor; + + constexpr int EVENT_SIZE = sizeof (inotify_event); + constexpr int BUF_LEN = 1024 * ( EVENT_SIZE + 16 ); + + void AddDirWatcher(const int rootFileDesc, const String& path); + void DeleteDirWatcher(const int parentWacherFileDesc, String& dirName); + LinuxFileSystemWatcher* GetRootWatcher(int watcherFileDesc); + + int32 Run() + { + char buffer[BUF_LEN]; + fd_set set; + timeval timeout; + + while (ThreadActive) + { + FD_ZERO(&set); + FD_SET(WacherFileDescriptor, &set); + + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + + int retVal = select(WacherFileDescriptor + 1, &set, NULL, NULL, &timeout); + + if(retVal == -1) + LOG(Error, "WacherFileDescriptor select failed."); + else if(retVal == 0) + { + continue; + } + else + { + int length = read( WacherFileDescriptor, buffer, BUF_LEN ); + + if ( length < 0 ) + { + return 0; + LOG(Error, "Failed to read WacherFileDescriptor."); + } + + int event_offset = 0; + while ( event_offset < length ) + { + + LinuxFileSystemWatcher* watcher; + auto action = FileSystemAction::Unknown; + + inotify_event* event = reinterpret_cast(&buffer[ event_offset ]); + if ( event->len ) + { + auto wacher = Watchers[event->wd]; + String name(event->name); + + if ( event->mask & IN_CREATE ) + { + if ( event->mask & IN_ISDIR ) + { + AddDirWatcher(GetRootWatcher(event->wd)->GetWachedDirectoryDescriptor(), wacher.Second.First/name); + } else + { + GetRootWatcher(event->wd)->OnEvent(name, FileSystemAction::Create); + } + } + else if ( event->mask & IN_DELETE ) + { + if ( event->mask & IN_ISDIR ) + { + DeleteDirWatcher(event->wd, name); + } else + { + GetRootWatcher(event->wd)->OnEvent(name, FileSystemAction::Delete); + } + } + else if ( event->mask & IN_MODIFY ) + { + if ( event->mask & IN_ISDIR ) + { + // Do we need this? + } else + { + GetRootWatcher(event->wd)->OnEvent(name, FileSystemAction::Modify); + } + } + } + event_offset += EVENT_SIZE + event->len; + } + } + } + return 0; + } + + void AddSubDirWatcher (const int rootFileDesc, const String& parent) + { + DIR *dir; + struct dirent *entry; + + char c_path[parent.Length() + 1]; + Platform::MemoryCopy(c_path, parent.ToStringAnsi().GetText(),parent.Length()); + c_path[parent.Length()]=0; + + if (!(dir = opendir(c_path))) + return; + + while ((entry = readdir(dir)) != NULL) + { + if (entry->d_type == DT_DIR) + { + char path[1024]; + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + AddDirWatcher(rootFileDesc, parent / String(entry->d_name)); + } + } + closedir(dir); + }; + + LinuxFileSystemWatcher* GetRootWatcher(int watcherFileDesc) + { + if (RootWatchers.ContainsKey(watcherFileDesc)) return RootWatchers[watcherFileDesc]; + + return RootWatchers[Watchers[watcherFileDesc].First]; + } + + void AddDirWatcher(const int rootFileDesc, const String& path) + { + auto watcher = New(path, false, rootFileDesc); + Pair subDir = Pair(path, watcher); + } + + void DeleteDirWatcher(const int parentWacherFileDesc, String& dirName) + { + if ( Watchers.ContainsKey(parentWacherFileDesc)) + { + auto fullPath = Watchers[parentWacherFileDesc].Second.Second->Directory/dirName; + + for (auto& watcher : Watchers) + { + if(watcher.Value.Second.First.Compare(fullPath) == 0) + { + Delete(watcher.Value.Second.Second); + } + } + } + } +}; + +LinuxFileSystemWatcher::LinuxFileSystemWatcher(const String& directory, bool withSubDirs, int rootWatcher) + : FileSystemWatcherBase(directory, withSubDirs) +{ + // Register + FileSystemWatchers::Locker.Lock(); + + if(withSubDirs) + { + for (const auto& watcher : FileSystemWatchers::Watchers) + { + LOG(Info, "Watcher:{} .", watcher.Key); + } + } + if (!FileSystemWatchers::Thread) + { + FileSystemWatchers::WacherFileDescriptor = inotify_init(); + + if ( FileSystemWatchers::WacherFileDescriptor > 0 ) + { + FileSystemWatchers::ThreadActive = true; + FileSystemWatchers::Thread = ThreadSpawner::Start(FileSystemWatchers::Run, TEXT("File System Watchers"), ThreadPriority::BelowNormal); + } + } + + if ( FileSystemWatchers::WacherFileDescriptor > 0 ) + { + char c_path[directory.Length() + 1]; + Platform::MemoryCopy(c_path, directory.ToStringAnsi().GetText(),directory.Length()); + c_path[directory.Length()]=0; + + LOG(Info, "Adding watcher for directory {}.", Directory); + WachedDirectory = inotify_add_watch( FileSystemWatchers::WacherFileDescriptor, c_path, IN_MODIFY | IN_CREATE | IN_DELETE ); + + FileSystemWatchers::Watchers[WachedDirectory] = Pair>(rootWatcher, Pair(directory, this)); + + if (withSubDirs) + { + FileSystemWatchers::RootWatchers[WachedDirectory] = this; + FileSystemWatchers::AddSubDirWatcher(WachedDirectory, directory); + } + } + FileSystemWatchers::Locker.Unlock(); +} + +LinuxFileSystemWatcher::~LinuxFileSystemWatcher() +{ + if (FileSystemWatchers::WacherFileDescriptor > 0) + { + LOG(Info, "Removing watcher for directory {}.", Directory); + inotify_rm_watch( FileSystemWatchers::WacherFileDescriptor, WachedDirectory ); + + FileSystemWatchers::Locker.Lock(); + if (FileSystemWatchers::RootWatchers.ContainsKey(WachedDirectory)) + { + FileSystemWatchers::RootWatchers.Remove(WachedDirectory); + } + FileSystemWatchers::Watchers.Remove(WachedDirectory); + FileSystemWatchers::Locker.Unlock(); + } + + FileSystemWatchers::Locker.Lock(); + if (FileSystemWatchers::Watchers.IsEmpty() && FileSystemWatchers::Thread) + { + FileSystemWatchers::ThreadActive = false; + FileSystemWatchers::Thread->Join(); + Delete(FileSystemWatchers::Thread); + FileSystemWatchers::Thread = nullptr; + close( FileSystemWatchers::WacherFileDescriptor ); + } + FileSystemWatchers::Locker.Unlock(); +} + +#endif + diff --git a/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.h b/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.h new file mode 100644 index 000000000..be90def28 --- /dev/null +++ b/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_LINUX + +#include "Engine/Platform/Base/FileSystemWatcherBase.h" + +/// +/// Linux platform implementation of the file system watching object. +/// +class FLAXENGINE_API LinuxFileSystemWatcher : public FileSystemWatcherBase +{ +public: + + /// + /// Initializes a new instance of the class. + /// + /// The directory to watch. + /// True if monitor the directory tree rooted at the specified directory or just a given directory. + /// Linux specific root directory watcher file descriptor. + LinuxFileSystemWatcher(const String& directory, bool withSubDirs, int root= -1); + + /// + /// Finalizes an instance of the class. + /// + ~LinuxFileSystemWatcher(); + +public: + + FORCE_INLINE int GetWachedDirectoryDescriptor() + { + return WachedDirectory; + } + +private: + + int WachedDirectory; +}; + +#endif diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index e7ec0308c..44a347286 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -58,8 +58,8 @@ class UnixConditionVariable; typedef UnixConditionVariable ConditionVariable; class LinuxFileSystem; typedef LinuxFileSystem FileSystem; -class FileSystemWatcherBase; -typedef FileSystemWatcherBase FileSystemWatcher; +class LinuxFileSystemWatcher; +typedef LinuxFileSystemWatcher FileSystemWatcher; class UnixFile; typedef UnixFile File; class LinuxPlatform;