From 0816bf093aa589519be25f1215e81fb531b8b611 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 09:56:28 +0300 Subject: [PATCH 1/7] FileSystemWatcher added --- thirdparty/next/inc/os/filesystemwatcher.h | 53 ++ thirdparty/next/src/os/filesystemwatcher.cpp | 496 +++++++++++++++++++ 2 files changed, 549 insertions(+) create mode 100644 thirdparty/next/inc/os/filesystemwatcher.h create mode 100644 thirdparty/next/src/os/filesystemwatcher.cpp diff --git a/thirdparty/next/inc/os/filesystemwatcher.h b/thirdparty/next/inc/os/filesystemwatcher.h new file mode 100644 index 000000000..ddbf7d6e3 --- /dev/null +++ b/thirdparty/next/inc/os/filesystemwatcher.h @@ -0,0 +1,53 @@ +/* + This file is part of Thunder Next. + + Thunder Next is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + Thunder Next is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with Thunder Next. If not, see . + + Copyright: 2008-2026 Evgeniy Prikazchikov +*/ + +#ifndef FILESYSTEMWATCHER_H +#define FILESYSTEMWATCHER_H + +#include + +class FileSystemWatcherPrivate; + +class NEXT_LIBRARY_EXPORT FileSystemWatcher : Object { + A_OBJECT(FileSystemWatcher, Object, Core) + + A_METHODS( + A_SIGNAL(FileSystemWatcher::fileChanged), + A_SIGNAL(FileSystemWatcher::directoryChanged) + ) + +public: + FileSystemWatcher(); + ~FileSystemWatcher(); + + bool addPath(const TString &path); + bool addPaths(const StringList &paths); + + bool removePath(const TString &path); + +public: // signals + void fileChanged(const TString &path); + + void directoryChanged(const TString &path); + +private: + FileSystemWatcherPrivate *m_ptr; + +}; + +#endif // FILESYSTEMWATCHER_H diff --git a/thirdparty/next/src/os/filesystemwatcher.cpp b/thirdparty/next/src/os/filesystemwatcher.cpp new file mode 100644 index 000000000..a985de689 --- /dev/null +++ b/thirdparty/next/src/os/filesystemwatcher.cpp @@ -0,0 +1,496 @@ +#include "os/filesystemwatcher.h" + +#include + +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +class FileSystemWatcherPrivate { +public: + struct FileInfo { + TString path; + std::filesystem::file_time_type lastWriteTime; + uintmax_t size = 0; + }; + + FileSystemWatcherPrivate(FileSystemWatcher *ptr) : + running(false), + pollingMode(false), + p_ptr(ptr) { + + } + + bool addPath(const TString &path) { + std::lock_guard lock(mutex); + + if(!File::exists(path)) { + return false; + } + + if(std::find(paths.begin(), paths.end(), path) != paths.end()) { + return true; + } + + paths.push_back(path); + lastWriteTime[path] = getLastWriteTime(path); + + if(!pollingMode) { + if(!initNativeWatcher(path)) { + pollingMode = true; + } + } + + return true; + } + + bool removePath(const TString &path) { + std::lock_guard lock(mutex); + + auto it = std::find(paths.begin(), paths.end(), path); + if(it != paths.end()) { + size_t index = std::distance(paths.begin(), it); + paths.erase(it); + lastWriteTime.erase(path); + +#ifdef _WIN32 + if(index < handles.size()) { + CancelIo(handles[index]); + CloseHandle(handles[index]); + if (index < overlapped.size()) { + CloseHandle(overlapped[index].hEvent); + } + handles.erase(handles.begin() + index); + if (index < overlapped.size()) + overlapped.erase(overlapped.begin() + index); + } +#elif defined(__linux__) + for(auto& pair : watchDescriptors) { + if (pair.second == path) { + inotify_rm_watch(inotifyFD, pair.first); + break; + } + } + auto it_desc = std::find_if(watchDescriptors.begin(), + watchDescriptors.end(), + [&path](const auto& p) { return p.second == path; }); + if(it_desc != watchDescriptors.end()) { + watchDescriptors.erase(it_desc); + } +#elif defined(__APPLE__) + if(paths.empty() && running) { + if(watcherThread.joinable()) { + running = false; + cv.notify_all(); + watcherThread.join(); + } + } +#endif + return true; + } + return false; + } + + static std::filesystem::file_time_type getLastWriteTime(const TString &path) { + try { + if(File::exists(path)) { + return std::filesystem::last_write_time(path.toStdString()); + } + } catch (...) {} + return std::filesystem::file_time_type(); + } + + void start() { + if(running) { + return; + } + + running = true; + + if(pollingMode || !isNativeAvailable()) { + watcherThread = std::thread(&FileSystemWatcherPrivate::pollingLoop, this); + } else { +#ifdef _WIN32 + watcherThread = std::thread(&FileSystemWatcherPrivate::windowsWatchLoop, this); +#elif defined(__linux__) + watcherThread = std::thread(&FileSystemWatcherPrivate::linuxWatchLoop, this); +#elif defined(__APPLE__) + watcherThread = std::thread(&FileSystemWatcherPrivate::macosWatchLoop, this); +#endif + } + } + + void stop() { + if(!running) { + return; + } + + running = false; + +#ifdef _WIN32 + for(auto& ov : overlapped) { + if(ov.hEvent) { + SetEvent(ov.hEvent); + } + } +#elif defined(__APPLE__) + cv.notify_all(); +#endif + + if(watcherThread.joinable()) { + watcherThread.join(); + } + +#ifdef _WIN32 + for(auto h : handles) { + CloseHandle(h); + } + for(auto& ov : overlapped) { + CloseHandle(ov.hEvent); + } + handles.clear(); + overlapped.clear(); +#elif defined(__linux__) + if(inotifyFD >= 0) { + close(inotifyFD); + inotifyFD = -1; + } + watchDescriptors.clear(); +#endif + } + + bool isNativeAvailable() const { +#ifdef _WIN32 + return true; +#elif defined(__linux__) + return true; +#elif defined(__APPLE__) + return true; +#else + return false; +#endif + } + + bool initNativeWatcher(const TString &path) { +#ifdef _WIN32 + HANDLE hDir = CreateFileA( + path.data(), FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); + + if(hDir == INVALID_HANDLE_VALUE) { + return false; + } + + handles.push_back(hDir); + overlapped.push_back(OVERLAPPED()); + overlapped.back().hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if(overlapped.back().hEvent == NULL) { + CloseHandle(hDir); + return false; + } + + return true; +#elif defined(__linux__) + if(inotifyFD < 0) { + inotifyFD = inotify_init1(IN_NONBLOCK); + if (inotifyFD < 0) { + return false; + } + } + + int wd = inotify_add_watch(inotifyFD, path.data(), IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE); + if(wd < 0) { + return false; + } + + watchDescriptors[wd] = path; + return true; +#elif defined(__APPLE__) + return true; +#else + return false; +#endif + } + + void checkDirectory(const TString &dirPath) { + try { + if(!File::exists(dirPath)) { + // Directory removed + p_ptr->directoryChanged(dirPath); + return; + } + + for(const auto &entry : File::list(dirPath)) { + try { + auto currentTime = getLastWriteTime(entry); + auto lastTime = lastWriteTime[entry]; + + if (currentTime > lastTime) { + // File modified + p_ptr->fileChanged(entry); + lastWriteTime[entry] = currentTime; + } + } catch (...) {} + } + } catch (const std::filesystem::filesystem_error& e) {} + } + + void pollingLoop() { + while(running) { + { + std::lock_guard lock(mutex); + for (const auto& path : paths) { + checkDirectory(path); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + +#ifdef _WIN32 + void windowsWatchLoop() { + std::vector buffer(65536); + + while (running) { + for (size_t i = 0; i < paths.size() && running; ++i) { + if (i >= handles.size()) continue; + + DWORD bytesReturned; + if (ReadDirectoryChangesW( + handles[i], buffer.data(), static_cast(buffer.size()), TRUE, + FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION, + &bytesReturned, &overlapped[i], NULL)) { + + DWORD waitResult = WaitForSingleObject(overlapped[i].hEvent, 1000); + + if (waitResult == WAIT_OBJECT_0 && bytesReturned > 0) { + processWindowsEvents(i, buffer.data(), bytesReturned); + } + + ResetEvent(overlapped[i].hEvent); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + void processWindowsEvents(size_t index, void* buffer, DWORD bufferSize) { + FILE_NOTIFY_INFORMATION* fni = static_cast(buffer); + + do { + std::wstring wname(fni->FileName, fni->FileNameLength / sizeof(WCHAR)); + std::string name(wname.begin(), wname.end()); + TString fullPath = paths[index] + "\\" + name; + + switch (fni->Action) { + case FILE_ACTION_ADDED: + case FILE_ACTION_REMOVED: + case FILE_ACTION_MODIFIED: + case FILE_ACTION_RENAMED_OLD_NAME: + case FILE_ACTION_RENAMED_NEW_NAME: + p_ptr->fileChanged(fullPath); + break; + default: break; + } + + if (fni->NextEntryOffset == 0) break; + fni = reinterpret_cast( + reinterpret_cast(fni) + fni->NextEntryOffset); + } while (true); + } +#elif defined(__linux__) + void linuxWatchLoop() { + char buffer[4096]; + + while (running) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(inotifyFD, &fds); + + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + + int ret = select(inotifyFD + 1, &fds, NULL, NULL, &tv); + + if (ret > 0 && FD_ISSET(inotifyFD, &fds)) { + ssize_t length = read(inotifyFD, buffer, sizeof(buffer)); + + if (length > 0) { + processLinuxEvents(buffer, length); + } + } + } + } + + void processLinuxEvents(char* buffer, ssize_t length) { + for (char* ptr = buffer; ptr < buffer + length; ) { + struct inotify_event* event = reinterpret_cast(ptr); + + std::string eventPath; + auto it = watchDescriptors.find(event->wd); + if (it != watchDescriptors.end()) { + eventPath = it->second; + if (event->len > 0 && event->name[0] != '\0') { + eventPath += "/" + std::string(event->name); + } + } + + if(event->mask & IN_CREATE || event->mask & IN_DELETE || event->mask & IN_MODIFY || + event->mask & IN_MOVED_FROM || event->mask & IN_MOVED_TO) { + if(!eventPath.empty()) { + p_ptr->fileChanged(fullPath); + } + } + + ptr += sizeof(struct inotify_event) + event->len; + } + } +#elif defined(__APPLE__) + void macosWatchLoop() { + auto lastState = getCurrentState(); + + while(running) { + { + std::unique_lock lock(mutex); + if(cv.wait_for(lock, std::chrono::milliseconds(200), [this] { return !running; })) { + break; + } + } + + auto currentState = getCurrentState(); + detectChanges(lastState, currentState); + lastState = std::move(currentState); + } + } + + std::unordered_map getCurrentState() { + std::unordered_map state; + + for(const auto& path : paths) { + if(File::exists(path)) { + collectFiles(path, state); + } + } + + return state; + } + + void collectFiles(const TString &path, std::unordered_map& state) { + try { + if(File::isDir(path)) { + for(const auto& entry : File::list(path)) { + try { + FileInfo info; + info.path = entry.path().string(); + info.lastWriteTime = std::filesystem::last_write_time(entry.path()); + info.size = std::filesystem::file_size(entry.path()); + state[info.path] = info; + } catch (...) {} + } + } else if(File::isFile(path)) { + FileInfo info; + info.path = path; + info.lastWriteTime = std::filesystem::last_write_time(path); + info.size = std::filesystem::file_size(path); + state[info.path] = info; + } + } catch (...) {} + } + + void detectChanges(const std::unordered_map& oldState, + const std::unordered_map& newState) { + for(const auto& [path, info] : newState) { + if(oldState.find(path) == oldState.end()) { + // Created + p_ptr->fileChanged(path); + } + } + + for(const auto& [path, info] : oldState) { + if(newState.find(path) == newState.end()) { + // Deleted + p_ptr->fileChanged(path); + } + } + + for(const auto& [path, info] : newState) { + auto it = oldState.find(path); + if(it != oldState.end()) { + if(info.lastWriteTime != it->second.lastWriteTime || info.size != it->second.size) { + // Modified + p_ptr->fileChanged(path); + } + } + } + } +#endif + +public: + std::vector paths; + std::unordered_map lastWriteTime; + std::atomic running; + std::atomic pollingMode; + std::thread watcherThread; + std::mutex mutex; + std::condition_variable cv; + + FileSystemWatcher *p_ptr = nullptr; + +#ifdef _WIN32 + std::vector handles; + std::vector overlapped; +#elif defined(__linux__) + int inotifyFD = -1; + std::unordered_map watchDescriptors; +#endif +}; + +FileSystemWatcher::FileSystemWatcher() : + m_ptr(new FileSystemWatcherPrivate(this)) { + +} + +FileSystemWatcher::~FileSystemWatcher() { + m_ptr->stop(); + delete m_ptr; +} + +bool FileSystemWatcher::addPath(const TString &path) { + return m_ptr->addPath(path); +} + +bool FileSystemWatcher::addPaths(const StringList &paths) { + bool allSuccess = true; + for(const auto& path : paths) { + if(!addPath(path)) { + allSuccess = false; + } + } + return allSuccess; +} + +bool FileSystemWatcher::removePath(const TString &path) { + return m_ptr->removePath(path); +} + +void FileSystemWatcher::fileChanged(const TString &path) { + emitSignal(_SIGNAL(fileChanged(TString)), path); +} + +void FileSystemWatcher::directoryChanged(const TString &path) { + emitSignal(_SIGNAL(directoryChanged(TString)), path); +} From eeb3ca34cac0b3d751f3aa7092808cc8e1fbd1db Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 10:21:12 +0300 Subject: [PATCH 2/7] update --- thirdparty/next/inc/os/filesystemwatcher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/next/inc/os/filesystemwatcher.h b/thirdparty/next/inc/os/filesystemwatcher.h index ddbf7d6e3..4809fef36 100644 --- a/thirdparty/next/inc/os/filesystemwatcher.h +++ b/thirdparty/next/inc/os/filesystemwatcher.h @@ -23,7 +23,7 @@ class FileSystemWatcherPrivate; -class NEXT_LIBRARY_EXPORT FileSystemWatcher : Object { +class NEXT_LIBRARY_EXPORT FileSystemWatcher : public Object { A_OBJECT(FileSystemWatcher, Object, Core) A_METHODS( From f165842d7803dabba2c04c4b9413f3db4e46c18c Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 10:35:22 +0300 Subject: [PATCH 3/7] update --- thirdparty/next/src/os/filesystemwatcher.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/thirdparty/next/src/os/filesystemwatcher.cpp b/thirdparty/next/src/os/filesystemwatcher.cpp index a985de689..ca380923a 100644 --- a/thirdparty/next/src/os/filesystemwatcher.cpp +++ b/thirdparty/next/src/os/filesystemwatcher.cpp @@ -3,6 +3,10 @@ #include #include +#include +#include +#include +#include #ifdef _WIN32 #include @@ -389,23 +393,23 @@ class FileSystemWatcherPrivate { return state; } - void collectFiles(const TString &path, std::unordered_map& state) { + void collectFiles(const TString &path, std::unordered_map &state) { try { if(File::isDir(path)) { for(const auto& entry : File::list(path)) { try { FileInfo info; - info.path = entry.path().string(); - info.lastWriteTime = std::filesystem::last_write_time(entry.path()); - info.size = std::filesystem::file_size(entry.path()); + info.path = entry; + info.lastWriteTime = std::filesystem::last_write_time(entry.toStdString()); + info.size = std::filesystem::file_size(entry.toStdString()); state[info.path] = info; } catch (...) {} } } else if(File::isFile(path)) { FileInfo info; info.path = path; - info.lastWriteTime = std::filesystem::last_write_time(path); - info.size = std::filesystem::file_size(path); + info.lastWriteTime = std::filesystem::last_write_time(path.toStdString()); + info.size = std::filesystem::file_size(path.toStdString()); state[info.path] = info; } } catch (...) {} From cac3a555ad10e04cc56c297a3d25c4070adaf9f3 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 10:42:43 +0300 Subject: [PATCH 4/7] update --- thirdparty/next/src/os/filesystemwatcher.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/thirdparty/next/src/os/filesystemwatcher.cpp b/thirdparty/next/src/os/filesystemwatcher.cpp index ca380923a..e5eb4bccd 100644 --- a/thirdparty/next/src/os/filesystemwatcher.cpp +++ b/thirdparty/next/src/os/filesystemwatcher.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #endif From f5a1a2f914f9c995a639e04d5cde2d2b1936e6af Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 11:17:14 +0300 Subject: [PATCH 5/7] update --- thirdparty/next/src/os/filesystemwatcher.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/thirdparty/next/src/os/filesystemwatcher.cpp b/thirdparty/next/src/os/filesystemwatcher.cpp index e5eb4bccd..0b4048000 100644 --- a/thirdparty/next/src/os/filesystemwatcher.cpp +++ b/thirdparty/next/src/os/filesystemwatcher.cpp @@ -17,10 +17,13 @@ #include #include #include -#include #include #endif +#ifdef __linux__ +#include +#endif + class FileSystemWatcherPrivate { public: struct FileInfo { @@ -346,7 +349,7 @@ class FileSystemWatcherPrivate { for (char* ptr = buffer; ptr < buffer + length; ) { struct inotify_event* event = reinterpret_cast(ptr); - std::string eventPath; + TString eventPath; auto it = watchDescriptors.find(event->wd); if (it != watchDescriptors.end()) { eventPath = it->second; From 5d006562d0f139805429558c6b56364a3ac1c716 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 11:24:26 +0300 Subject: [PATCH 6/7] update --- thirdparty/next/src/os/filesystemwatcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thirdparty/next/src/os/filesystemwatcher.cpp b/thirdparty/next/src/os/filesystemwatcher.cpp index 0b4048000..d25a6fbae 100644 --- a/thirdparty/next/src/os/filesystemwatcher.cpp +++ b/thirdparty/next/src/os/filesystemwatcher.cpp @@ -360,8 +360,8 @@ class FileSystemWatcherPrivate { if(event->mask & IN_CREATE || event->mask & IN_DELETE || event->mask & IN_MODIFY || event->mask & IN_MOVED_FROM || event->mask & IN_MOVED_TO) { - if(!eventPath.empty()) { - p_ptr->fileChanged(fullPath); + if(!eventPath.isEmpty()) { + p_ptr->fileChanged(eventPath); } } From 538747338a6ffe6627465bfecee316d240faad74 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 8 May 2026 21:43:51 +0300 Subject: [PATCH 7/7] FileSystemWatcher replaced in BaseAssetProvider --- .../adapters/handlers/defaultfilehandler.h | 6 +- engine/includes/editor/baseassetprovider.h | 35 +- engine/src/editor/assetmanager.cpp | 26 +- engine/src/editor/baseassetprovider.cpp | 181 +++---- thirdparty/next/inc/core/astring.h | 2 + thirdparty/next/inc/core/objectsystem.h | 2 + thirdparty/next/inc/os/filesystemwatcher.h | 7 + thirdparty/next/src/core/astring.cpp | 10 + thirdparty/next/src/core/objectsystem.cpp | 7 +- thirdparty/next/src/os/filesystemwatcher.cpp | 455 ++++-------------- 10 files changed, 210 insertions(+), 521 deletions(-) diff --git a/engine/includes/adapters/handlers/defaultfilehandler.h b/engine/includes/adapters/handlers/defaultfilehandler.h index a404c4e1f..dbd475aa7 100644 --- a/engine/includes/adapters/handlers/defaultfilehandler.h +++ b/engine/includes/adapters/handlers/defaultfilehandler.h @@ -46,7 +46,11 @@ class DefaultFileHandler : public FileHandler { bool remove(const char *path) override { try { - return std::filesystem::remove(path); + if(isDir(path)) { + return std::filesystem::remove_all(path); + } else { + return std::filesystem::remove(path); + } } catch (const std::filesystem::filesystem_error &) { return false; } diff --git a/engine/includes/editor/baseassetprovider.h b/engine/includes/editor/baseassetprovider.h index 2f326fa17..9fafe0da9 100644 --- a/engine/includes/editor/baseassetprovider.h +++ b/engine/includes/editor/baseassetprovider.h @@ -1,40 +1,37 @@ #ifndef BASEASSETPROVIDER_H #define BASEASSETPROVIDER_H -#include - #include -class QFileSystemWatcher; +class FileSystemWatcher; + +class ENGINE_EXPORT BaseAssetProvider : public Object { + A_OBJECT(BaseAssetProvider, Object, Core) + + A_METHODS( + A_SLOT(BaseAssetProvider::onFileChanged), + A_SLOT(BaseAssetProvider::onDirectoryChanged) + ) -class ENGINE_EXPORT BaseAssetProvider : public QObject { - Q_OBJECT public: BaseAssetProvider(); - ~BaseAssetProvider(); - void init(); + void init(bool force); void renameResource(const TString &source, const TString &destination); void removeResource(const TString &source); void duplicateResource(const TString &source); - void cleanupBundle(); - -protected: - bool copyRecursively(const TString &sourceFolder, const TString &destFolder); - -public slots: - void onFileChanged(const QString &path); - void onFileChangedForce(const QString &path, bool force = false); +public: // slots + void onFileChanged(const TString &path); + void onFileChangedForce(const TString &path, bool force = false); - void onDirectoryChanged(const QString &path); - void onDirectoryChangedForce(const QString &path, bool force = false); + void onDirectoryChanged(const TString &path); + void onDirectoryChangedForce(const TString &path, bool force = false); private: - QFileSystemWatcher *m_dirWatcher; - QFileSystemWatcher *m_fileWatcher; + FileSystemWatcher *m_dirWatcher; }; diff --git a/engine/src/editor/assetmanager.cpp b/engine/src/editor/assetmanager.cpp index 4f866a729..f97e7be6f 100644 --- a/engine/src/editor/assetmanager.cpp +++ b/engine/src/editor/assetmanager.cpp @@ -119,7 +119,6 @@ void AssetManager::rescan() { TString target = m_projectManager->targetPath(); if(target.isEmpty()) { - bool update = m_projectManager->projectSdk() != SDK_VERSION; if(update) { getChangedUUIDs(); @@ -128,24 +127,11 @@ void AssetManager::rescan() { Engine::resourceSystem()->unloadBundle(TString()); m_force |= !Engine::resourceSystem()->loadBundle(TString()); m_force |= update; - - m_assetProvider->init(); } else { m_force = true; } - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/engine/materials").data(),m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/engine/textures").data(), m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/engine/meshes").data(), m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/engine/pipelines").data(),m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/engine/fonts").data(), m_force); -#ifndef BUILDER - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/editor/materials").data(),m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/editor/gizmos").data(), m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/editor/meshes").data(), m_force); - m_assetProvider->onDirectoryChangedForce((m_projectManager->resourcePath() + "/editor/textures").data(), m_force); -#endif - m_assetProvider->onDirectoryChangedForce(m_projectManager->contentPath().data(), m_force); + m_assetProvider->init(m_force); Engine::resourceSystem()->setCleanImport(m_force); @@ -175,7 +161,7 @@ TString AssetManager::assetTypeName(const TString &source) { } bool AssetManager::pushToImport(const TString &source) { - m_assetProvider->onFileChangedForce(source.data(), true); + m_assetProvider->onFileChangedForce(source, true); return true; } @@ -601,7 +587,13 @@ void AssetManager::onPerform() { } } - m_assetProvider->cleanupBundle(); + // Cleanup bundle + for(auto &path : File::list(ProjectSettings::instance()->importPath())) { + TString fileName(Url(path).name()); + if(!File::isDir(path) && fileName != gIndex && uuidToPath(fileName).isEmpty()) { + File::remove(path); + } + } auto tmp = m_indices; for(auto &index : tmp) { diff --git a/engine/src/editor/baseassetprovider.cpp b/engine/src/editor/baseassetprovider.cpp index e4745cba0..ef330e914 100644 --- a/engine/src/editor/baseassetprovider.cpp +++ b/engine/src/editor/baseassetprovider.cpp @@ -1,7 +1,6 @@ #include "editor/baseassetprovider.h" -#include -#include +#include #include "editor/projectsettings.h" #include "editor/assetmanager.h" @@ -10,60 +9,62 @@ #include "config.h" BaseAssetProvider::BaseAssetProvider() : - m_dirWatcher(new QFileSystemWatcher(this)), - m_fileWatcher(new QFileSystemWatcher(this)) { + m_dirWatcher(nullptr) { + FileSystemWatcher::registerClassFactory(&Engine::instance()); + + m_dirWatcher = Engine::objectCreate(); + + connect(m_dirWatcher, _SIGNAL(directoryChanged(TString)), this, _SLOT(onDirectoryChanged(TString))); + connect(m_dirWatcher, _SIGNAL(fileChanged(TString)), this, _SLOT(onFileChanged(TString))); } BaseAssetProvider::~BaseAssetProvider() { delete m_dirWatcher; - delete m_fileWatcher; } -void BaseAssetProvider::init() { - QStringList paths = m_dirWatcher->directories(); - if(!paths.isEmpty()) { +void BaseAssetProvider::init(bool force) { + StringList paths = m_dirWatcher->directories(); + if(!paths.empty()) { m_dirWatcher->removePaths(paths); } - connect(m_dirWatcher, &QFileSystemWatcher::directoryChanged, this, &BaseAssetProvider::onDirectoryChanged); - connect(m_dirWatcher, SIGNAL(directoryChanged(QString)), AssetManager::instance(), SIGNAL(directoryChanged(QString))); - connect(m_dirWatcher, SIGNAL(directoryChanged(QString)), AssetManager::instance(), SLOT(reimport())); - - connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &BaseAssetProvider::onFileChanged); - connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, AssetManager::instance(), &AssetManager::fileChanged); - connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, AssetManager::instance(), &AssetManager::reimport); + ProjectSettings *mgr = ProjectSettings::instance(); + TString resourcePath(ProjectSettings::instance()->resourcePath()); + + onDirectoryChangedForce(resourcePath + "/engine/materials",force); + onDirectoryChangedForce(resourcePath + "/engine/textures", force); + onDirectoryChangedForce(resourcePath + "/engine/meshes", force); + onDirectoryChangedForce(resourcePath + "/engine/pipelines",force); + onDirectoryChangedForce(resourcePath + "/engine/fonts", force); +#ifndef BUILDER + onDirectoryChangedForce(resourcePath + "/editor/materials",force); + onDirectoryChangedForce(resourcePath + "/editor/gizmos", force); + onDirectoryChangedForce(resourcePath + "/editor/meshes", force); + onDirectoryChangedForce(resourcePath + "/editor/textures", force); +#endif + onDirectoryChangedForce(mgr->contentPath(), force); } -void BaseAssetProvider::cleanupBundle() { - AssetManager *mgr = AssetManager::instance(); - - for(auto &path : File::list(ProjectSettings::instance()->importPath())) { - TString fileName(Url(path).name()); - if(!File::isDir(path) && fileName != gIndex && mgr->uuidToPath(fileName).isEmpty()) { - File::remove(path); - } - } -} - -void BaseAssetProvider::onFileChanged(const QString &path) { +void BaseAssetProvider::onFileChanged(const TString &path) { onFileChangedForce(path); -} -void BaseAssetProvider::onFileChangedForce(const QString &path, bool force) { - AssetManager *mgr = AssetManager::instance(); + AssetManager::instance()->fileChanged(path.data()); + AssetManager::instance()->reimport(); +} - TString filePath(path.toStdString()); - if(File::exists(filePath) && Url(path.toStdString()).suffix() != gMetaExt) { - AssetConverterSettings *settings = mgr->fetchSettings(filePath); +void BaseAssetProvider::onFileChangedForce(const TString &path, bool force) { + if(File::exists(path) && Url(path).suffix() != gMetaExt) { + AssetManager *mgr = AssetManager::instance(); + AssetConverterSettings *settings = mgr->fetchSettings(path); if(settings) { if(force || settings->isOutdated()) { mgr->pushToImport(settings); } else { if(!settings->isCode()) { - mgr->registerAsset(filePath, settings->info()); + mgr->registerAsset(path, settings->info()); for(const TString &it : settings->subKeys()) { - mgr->registerAsset(filePath + "/" + it, settings->subItem(it)); + mgr->registerAsset(path + "/" + it, settings->subItem(it)); } } } @@ -71,26 +72,24 @@ void BaseAssetProvider::onFileChangedForce(const QString &path, bool force) { } } -void BaseAssetProvider::onDirectoryChanged(const QString &path) { +void BaseAssetProvider::onDirectoryChanged(const TString &path) { onDirectoryChangedForce(path); + + AssetManager::instance()->directoryChanged(path.data()); } -void BaseAssetProvider::onDirectoryChangedForce(const QString &path, bool force) { +void BaseAssetProvider::onDirectoryChangedForce(const TString &path, bool force) { m_dirWatcher->addPath(path); - for(auto &item : File::list(path.toStdString())) { + for(auto &item : File::list(path)) { if(Url(item).suffix() == gMetaExt) { continue; } - if(File::isDir(item)) { - m_dirWatcher->addPath(item.data()); - continue; + m_dirWatcher->addPath(item); + } else { + onFileChangedForce(item, force); } - - m_fileWatcher->addPath(item.data()); - - onFileChangedForce(item.data(), force); } } @@ -100,26 +99,9 @@ void BaseAssetProvider::removeResource(const TString &source) { } ProjectSettings *project = ProjectSettings::instance(); + AssetManager *asset = AssetManager::instance(); TString src(project->contentPath() + "/" + source); - if(File::isDir(src)) { - m_dirWatcher->removePath(src.data()); - QDir dir(project->contentPath().data()); - QDirIterator it(src.data(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - while(it.hasNext()) { - removeResource(dir.relativeFilePath(it.next()).toStdString()); - } - QDir().rmdir(src.data()); - return; - } - - AssetManager *asset = AssetManager::instance(); - CodeBuilder *builder = nullptr; - BuilderSettings *settings = dynamic_cast(asset->fetchSettings(src)); - if(settings) { - builder = settings->builder(); - } - m_fileWatcher->removePath(src.data()); Engine::unloadResource(source); TString uuid = asset->unregisterAsset(source); @@ -131,6 +113,11 @@ void BaseAssetProvider::removeResource(const TString &source) { File::remove(src + "." + gMetaExt); File::remove(src); + CodeBuilder *builder = nullptr; + BuilderSettings *settings = dynamic_cast(asset->fetchSettings(src)); + if(settings) { + builder = settings->builder(); + } if(builder) { builder->rescanSources(project->contentPath()); if(!builder->isEmpty()) { @@ -144,29 +131,26 @@ void BaseAssetProvider::removeResource(const TString &source) { void BaseAssetProvider::renameResource(const TString &oldName, const TString &newName) { AssetManager *asset = AssetManager::instance(); - ProjectSettings *project = ProjectSettings::instance(); ResourceSystem::Dictionary &indices(Engine::resourceSystem()->indices()); if(File::isDir(oldName)) { - QStringList dirs = m_dirWatcher->directories(); - QStringList files = m_fileWatcher->files(); - if(!dirs.isEmpty()) { + StringList dirs = m_dirWatcher->directories(); + if(!dirs.empty()) { m_dirWatcher->removePaths(dirs); - m_dirWatcher->addPaths(dirs.replaceInStrings(oldName.data(), newName.data())); - } - if(!files.isEmpty()) { - m_fileWatcher->removePaths(files); - m_fileWatcher->addPaths(files.replaceInStrings(oldName.data(), newName.data())); + for(auto &it : dirs) { + it.replace(oldName, newName); + } + m_dirWatcher->addPaths(dirs); } - QDir dir; - if(dir.rename(oldName.data(), newName.data())) { + if(File::rename(oldName, newName)) { std::map back; + ProjectSettings *project = ProjectSettings::instance(); for(auto it = indices.cbegin(); it != indices.cend();) { - QString path = (project->contentPath() + "/" + it->first).data(); - if(path.startsWith(oldName.data())) { + TString path(project->contentPath() + "/" + it->first); + if(path.startsWith(oldName)) { back[path.toStdString()] = it->second; it = indices.erase(it); } else { @@ -181,12 +165,9 @@ void BaseAssetProvider::renameResource(const TString &oldName, const TString &ne } asset->dumpBundle(); } else { - if(!dirs.isEmpty()) { + if(!dirs.empty()) { m_dirWatcher->addPaths(dirs); } - if(!files.isEmpty()) { - m_fileWatcher->addPaths(files); - } } } else { if(File::rename(oldName, newName) && @@ -203,7 +184,7 @@ void BaseAssetProvider::renameResource(const TString &oldName, const TString &ne AssetConverterSettings *settings = asset->fetchSettings(newName); if(settings) { AssetConverter *converter = asset->getConverter(newName); - converter->renameAsset(settings, Url(oldName).baseName(), Url(oldName).baseName()); + converter->renameAsset(settings, Url(oldName).baseName(), Url(newName).baseName()); } } } @@ -211,9 +192,8 @@ void BaseAssetProvider::renameResource(const TString &oldName, const TString &ne void BaseAssetProvider::duplicateResource(const TString &source) { AssetManager *asset = AssetManager::instance(); - ProjectSettings *project = ProjectSettings::instance(); - TString src(project->contentPath() + "/" + source); + TString src(ProjectSettings::instance()->contentPath() + "/" + source); Url info(src); @@ -250,38 +230,3 @@ void BaseAssetProvider::duplicateResource(const TString &source) { asset->dumpBundle(); } } - -// Copied from: https://forum.qt.io/topic/59245/is-there-any-api-to-recursively-copy-a-directory-and-all-it-s-sub-dirs-and-files/3 -bool BaseAssetProvider::copyRecursively(const TString &sourceFolder, const TString &destFolder) { - QDir sourceDir(sourceFolder.data()); - - if(!sourceDir.exists()) { - return false; - } - - QDir destDir(destFolder.data()); - if(!destDir.exists()) { - destDir.mkdir(destFolder.data()); - } - - QStringList files = sourceDir.entryList(QDir::Files); - for(int i = 0; i< files.count(); i++) { - TString srcName = sourceFolder + "/" + files[i].toStdString(); - TString destName = destFolder + "/" + files[i].toStdString(); - if(!QFile::copy(srcName.data(), destName.data())) { - return false; - } - } - - files.clear(); - files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); - for(int i = 0; i < files.count(); i++) { - TString srcName = sourceFolder + "/" + files[i].toStdString(); - TString destName = destFolder + "/" + files[i].toStdString(); - if(!copyRecursively(srcName, destName)) { - return false; - } - } - - return true; -} diff --git a/thirdparty/next/inc/core/astring.h b/thirdparty/next/inc/core/astring.h index 778752a85..32dd704f0 100644 --- a/thirdparty/next/inc/core/astring.h +++ b/thirdparty/next/inc/core/astring.h @@ -80,6 +80,8 @@ class NEXT_LIBRARY_EXPORT TString { bool isEmpty() const; + bool startsWith(const TString &str) const; + int indexOf(const TString &str) const; int indexOf(const char ch) const; diff --git a/thirdparty/next/inc/core/objectsystem.h b/thirdparty/next/inc/core/objectsystem.h index bd6403ff6..6a9ab9e34 100644 --- a/thirdparty/next/inc/core/objectsystem.h +++ b/thirdparty/next/inc/core/objectsystem.h @@ -89,6 +89,8 @@ class NEXT_LIBRARY_EXPORT ObjectSystem : public Object { static void removeInvalid(Invalid *invalid); + static void notify(Object *receiver, Event *event); + protected: virtual void factoryAdd(const TString &name, const TString &url, const MetaObject *meta); diff --git a/thirdparty/next/inc/os/filesystemwatcher.h b/thirdparty/next/inc/os/filesystemwatcher.h index 4809fef36..2dcbbf60b 100644 --- a/thirdparty/next/inc/os/filesystemwatcher.h +++ b/thirdparty/next/inc/os/filesystemwatcher.h @@ -39,12 +39,19 @@ class NEXT_LIBRARY_EXPORT FileSystemWatcher : public Object { bool addPaths(const StringList &paths); bool removePath(const TString &path); + bool removePaths(const StringList &paths); + + StringList directories() const; + StringList files() const; public: // signals void fileChanged(const TString &path); void directoryChanged(const TString &path); +private: + bool event(Event *event) override; + private: FileSystemWatcherPrivate *m_ptr; diff --git a/thirdparty/next/src/core/astring.cpp b/thirdparty/next/src/core/astring.cpp index 906c9902d..5580ad3a2 100644 --- a/thirdparty/next/src/core/astring.cpp +++ b/thirdparty/next/src/core/astring.cpp @@ -211,6 +211,16 @@ TString TString::join(const StringList &list, const char *separator) { bool TString::isEmpty() const { return m_data.empty(); } +/*! + Returns true if this string starts with the string \a str; otherwise returns false. + The comparison is case-sensitive. +*/ +bool TString::startsWith(const TString &str) const { + if (str.length() > length()) { + return false; + } + return m_data.compare(0, str.length(), str.m_data) == 0; +} /*! Returns the index position of the first occurrence of the string \a str in this string. Returns -1 if \a str is not found. */ diff --git a/thirdparty/next/src/core/objectsystem.cpp b/thirdparty/next/src/core/objectsystem.cpp index 662484543..6d5a7d075 100644 --- a/thirdparty/next/src/core/objectsystem.cpp +++ b/thirdparty/next/src/core/objectsystem.cpp @@ -556,7 +556,12 @@ Object::ObjectList &ObjectSystem::invalidObjects() { void ObjectSystem::removeInvalid(Invalid *invalid) { s_Invalids.remove(invalid); } - +/*! + Sends \a event to \a receiver. +*/ +void ObjectSystem::notify(Object *receiver, Event *event) { + receiver->postEvent(event); +} /*! Returns a list of objects with specified \a type. \warning This is very small function! diff --git a/thirdparty/next/src/os/filesystemwatcher.cpp b/thirdparty/next/src/os/filesystemwatcher.cpp index d25a6fbae..a11252432 100644 --- a/thirdparty/next/src/os/filesystemwatcher.cpp +++ b/thirdparty/next/src/os/filesystemwatcher.cpp @@ -1,63 +1,49 @@ #include "os/filesystemwatcher.h" #include +#include #include -#include #include #include #include -#include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif - -#ifdef __linux__ -#include -#endif +#include -class FileSystemWatcherPrivate { +class FileSystemWatcherEvent : public Event { public: - struct FileInfo { - TString path; - std::filesystem::file_time_type lastWriteTime; - uintmax_t size = 0; - }; + FileSystemWatcherEvent(uint32_t type, const TString &path) : + Event(type), + m_path(path) { + + } + + TString m_path; + +}; +class FileSystemWatcherPrivate { +public: FileSystemWatcherPrivate(FileSystemWatcher *ptr) : running(false), - pollingMode(false), p_ptr(ptr) { } bool addPath(const TString &path) { - std::lock_guard lock(mutex); - if(!File::exists(path)) { return false; } + { + std::lock_guard lock(mutex); + if(std::find(paths.begin(), paths.end(), path) != paths.end()) { + return true; + } - if(std::find(paths.begin(), paths.end(), path) != paths.end()) { - return true; + paths.push_back(path); + lastWriteTime[path] = getLastWriteTime(path); } - paths.push_back(path); - lastWriteTime[path] = getLastWriteTime(path); - - if(!pollingMode) { - if(!initNativeWatcher(path)) { - pollingMode = true; - } - } + start(); return true; } @@ -67,43 +53,9 @@ class FileSystemWatcherPrivate { auto it = std::find(paths.begin(), paths.end(), path); if(it != paths.end()) { - size_t index = std::distance(paths.begin(), it); paths.erase(it); lastWriteTime.erase(path); -#ifdef _WIN32 - if(index < handles.size()) { - CancelIo(handles[index]); - CloseHandle(handles[index]); - if (index < overlapped.size()) { - CloseHandle(overlapped[index].hEvent); - } - handles.erase(handles.begin() + index); - if (index < overlapped.size()) - overlapped.erase(overlapped.begin() + index); - } -#elif defined(__linux__) - for(auto& pair : watchDescriptors) { - if (pair.second == path) { - inotify_rm_watch(inotifyFD, pair.first); - break; - } - } - auto it_desc = std::find_if(watchDescriptors.begin(), - watchDescriptors.end(), - [&path](const auto& p) { return p.second == path; }); - if(it_desc != watchDescriptors.end()) { - watchDescriptors.erase(it_desc); - } -#elif defined(__APPLE__) - if(paths.empty() && running) { - if(watcherThread.joinable()) { - running = false; - cv.notify_all(); - watcherThread.join(); - } - } -#endif return true; } return false; @@ -124,18 +76,7 @@ class FileSystemWatcherPrivate { } running = true; - - if(pollingMode || !isNativeAvailable()) { - watcherThread = std::thread(&FileSystemWatcherPrivate::pollingLoop, this); - } else { -#ifdef _WIN32 - watcherThread = std::thread(&FileSystemWatcherPrivate::windowsWatchLoop, this); -#elif defined(__linux__) - watcherThread = std::thread(&FileSystemWatcherPrivate::linuxWatchLoop, this); -#elif defined(__APPLE__) - watcherThread = std::thread(&FileSystemWatcherPrivate::macosWatchLoop, this); -#endif - } + watcherThread = std::thread(&FileSystemWatcherPrivate::pollingLoop, this); } void stop() { @@ -145,98 +86,20 @@ class FileSystemWatcherPrivate { running = false; -#ifdef _WIN32 - for(auto& ov : overlapped) { - if(ov.hEvent) { - SetEvent(ov.hEvent); - } - } -#elif defined(__APPLE__) - cv.notify_all(); -#endif - if(watcherThread.joinable()) { watcherThread.join(); } - -#ifdef _WIN32 - for(auto h : handles) { - CloseHandle(h); - } - for(auto& ov : overlapped) { - CloseHandle(ov.hEvent); - } - handles.clear(); - overlapped.clear(); -#elif defined(__linux__) - if(inotifyFD >= 0) { - close(inotifyFD); - inotifyFD = -1; - } - watchDescriptors.clear(); -#endif - } - - bool isNativeAvailable() const { -#ifdef _WIN32 - return true; -#elif defined(__linux__) - return true; -#elif defined(__APPLE__) - return true; -#else - return false; -#endif - } - - bool initNativeWatcher(const TString &path) { -#ifdef _WIN32 - HANDLE hDir = CreateFileA( - path.data(), FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - - if(hDir == INVALID_HANDLE_VALUE) { - return false; - } - - handles.push_back(hDir); - overlapped.push_back(OVERLAPPED()); - overlapped.back().hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - if(overlapped.back().hEvent == NULL) { - CloseHandle(hDir); - return false; - } - - return true; -#elif defined(__linux__) - if(inotifyFD < 0) { - inotifyFD = inotify_init1(IN_NONBLOCK); - if (inotifyFD < 0) { - return false; - } - } - - int wd = inotify_add_watch(inotifyFD, path.data(), IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE); - if(wd < 0) { - return false; - } - - watchDescriptors[wd] = path; - return true; -#elif defined(__APPLE__) - return true; -#else - return false; -#endif } void checkDirectory(const TString &dirPath) { try { if(!File::exists(dirPath)) { - // Directory removed - p_ptr->directoryChanged(dirPath); + { + std::lock_guard lock(mutex); + paths.remove(dirPath); + } + TString path(Url(dirPath).absoluteDir()); + ObjectSystem::notify(p_ptr, new FileSystemWatcherEvent(Event::UserType, path)); return; } @@ -245,227 +108,40 @@ class FileSystemWatcherPrivate { auto currentTime = getLastWriteTime(entry); auto lastTime = lastWriteTime[entry]; - if (currentTime > lastTime) { - // File modified - p_ptr->fileChanged(entry); + if(currentTime > lastTime) { + ObjectSystem::notify(p_ptr, new FileSystemWatcherEvent(Event::UserType, entry)); lastWriteTime[entry] = currentTime; } } catch (...) {} } - } catch (const std::filesystem::filesystem_error& e) {} + } catch (const std::filesystem::filesystem_error &e) {} } void pollingLoop() { + StringList pathsCopy; while(running) { { std::lock_guard lock(mutex); - for (const auto& path : paths) { - checkDirectory(path); - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - -#ifdef _WIN32 - void windowsWatchLoop() { - std::vector buffer(65536); - - while (running) { - for (size_t i = 0; i < paths.size() && running; ++i) { - if (i >= handles.size()) continue; - - DWORD bytesReturned; - if (ReadDirectoryChangesW( - handles[i], buffer.data(), static_cast(buffer.size()), TRUE, - FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | - FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION, - &bytesReturned, &overlapped[i], NULL)) { - - DWORD waitResult = WaitForSingleObject(overlapped[i].hEvent, 1000); - - if (waitResult == WAIT_OBJECT_0 && bytesReturned > 0) { - processWindowsEvents(i, buffer.data(), bytesReturned); - } - - ResetEvent(overlapped[i].hEvent); - } + pathsCopy = paths; } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - void processWindowsEvents(size_t index, void* buffer, DWORD bufferSize) { - FILE_NOTIFY_INFORMATION* fni = static_cast(buffer); - - do { - std::wstring wname(fni->FileName, fni->FileNameLength / sizeof(WCHAR)); - std::string name(wname.begin(), wname.end()); - TString fullPath = paths[index] + "\\" + name; - - switch (fni->Action) { - case FILE_ACTION_ADDED: - case FILE_ACTION_REMOVED: - case FILE_ACTION_MODIFIED: - case FILE_ACTION_RENAMED_OLD_NAME: - case FILE_ACTION_RENAMED_NEW_NAME: - p_ptr->fileChanged(fullPath); - break; - default: break; + for(const auto &path : pathsCopy) { + checkDirectory(path); } - if (fni->NextEntryOffset == 0) break; - fni = reinterpret_cast( - reinterpret_cast(fni) + fni->NextEntryOffset); - } while (true); - } -#elif defined(__linux__) - void linuxWatchLoop() { - char buffer[4096]; - - while (running) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(inotifyFD, &fds); - - struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; - - int ret = select(inotifyFD + 1, &fds, NULL, NULL, &tv); - - if (ret > 0 && FD_ISSET(inotifyFD, &fds)) { - ssize_t length = read(inotifyFD, buffer, sizeof(buffer)); - - if (length > 0) { - processLinuxEvents(buffer, length); - } - } - } - } - - void processLinuxEvents(char* buffer, ssize_t length) { - for (char* ptr = buffer; ptr < buffer + length; ) { - struct inotify_event* event = reinterpret_cast(ptr); - - TString eventPath; - auto it = watchDescriptors.find(event->wd); - if (it != watchDescriptors.end()) { - eventPath = it->second; - if (event->len > 0 && event->name[0] != '\0') { - eventPath += "/" + std::string(event->name); - } - } - - if(event->mask & IN_CREATE || event->mask & IN_DELETE || event->mask & IN_MODIFY || - event->mask & IN_MOVED_FROM || event->mask & IN_MOVED_TO) { - if(!eventPath.isEmpty()) { - p_ptr->fileChanged(eventPath); - } - } - - ptr += sizeof(struct inotify_event) + event->len; - } - } -#elif defined(__APPLE__) - void macosWatchLoop() { - auto lastState = getCurrentState(); - - while(running) { - { - std::unique_lock lock(mutex); - if(cv.wait_for(lock, std::chrono::milliseconds(200), [this] { return !running; })) { - break; - } - } - - auto currentState = getCurrentState(); - detectChanges(lastState, currentState); - lastState = std::move(currentState); - } - } - - std::unordered_map getCurrentState() { - std::unordered_map state; - - for(const auto& path : paths) { - if(File::exists(path)) { - collectFiles(path, state); - } - } - - return state; - } - - void collectFiles(const TString &path, std::unordered_map &state) { - try { - if(File::isDir(path)) { - for(const auto& entry : File::list(path)) { - try { - FileInfo info; - info.path = entry; - info.lastWriteTime = std::filesystem::last_write_time(entry.toStdString()); - info.size = std::filesystem::file_size(entry.toStdString()); - state[info.path] = info; - } catch (...) {} - } - } else if(File::isFile(path)) { - FileInfo info; - info.path = path; - info.lastWriteTime = std::filesystem::last_write_time(path.toStdString()); - info.size = std::filesystem::file_size(path.toStdString()); - state[info.path] = info; - } - } catch (...) {} - } - - void detectChanges(const std::unordered_map& oldState, - const std::unordered_map& newState) { - for(const auto& [path, info] : newState) { - if(oldState.find(path) == oldState.end()) { - // Created - p_ptr->fileChanged(path); - } - } - - for(const auto& [path, info] : oldState) { - if(newState.find(path) == newState.end()) { - // Deleted - p_ptr->fileChanged(path); - } - } - - for(const auto& [path, info] : newState) { - auto it = oldState.find(path); - if(it != oldState.end()) { - if(info.lastWriteTime != it->second.lastWriteTime || info.size != it->second.size) { - // Modified - p_ptr->fileChanged(path); - } - } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } -#endif public: - std::vector paths; + StringList paths; std::unordered_map lastWriteTime; - std::atomic running; - std::atomic pollingMode; std::thread watcherThread; std::mutex mutex; - std::condition_variable cv; - FileSystemWatcher *p_ptr = nullptr; + bool running; -#ifdef _WIN32 - std::vector handles; - std::vector overlapped; -#elif defined(__linux__) - int inotifyFD = -1; - std::unordered_map watchDescriptors; -#endif + FileSystemWatcher *p_ptr = nullptr; }; FileSystemWatcher::FileSystemWatcher() : @@ -484,7 +160,7 @@ bool FileSystemWatcher::addPath(const TString &path) { bool FileSystemWatcher::addPaths(const StringList &paths) { bool allSuccess = true; - for(const auto& path : paths) { + for(const auto &path : paths) { if(!addPath(path)) { allSuccess = false; } @@ -496,6 +172,40 @@ bool FileSystemWatcher::removePath(const TString &path) { return m_ptr->removePath(path); } +bool FileSystemWatcher::removePaths(const StringList &paths) { + bool allSuccess = true; + for(const auto &path : paths) { + if(!removePath(path)) { + allSuccess = false; + } + } + return allSuccess; +} + +StringList FileSystemWatcher::directories() const { + StringList result; + + for(auto &it : m_ptr->paths) { + if(File::isDir(it)) { + result.push_back(it); + } + } + + return result; +} + +StringList FileSystemWatcher::files() const { + StringList result; + + for(auto &it : m_ptr->paths) { + if(File::isFile(it)) { + result.push_back(it); + } + } + + return result; +} + void FileSystemWatcher::fileChanged(const TString &path) { emitSignal(_SIGNAL(fileChanged(TString)), path); } @@ -503,3 +213,18 @@ void FileSystemWatcher::fileChanged(const TString &path) { void FileSystemWatcher::directoryChanged(const TString &path) { emitSignal(_SIGNAL(directoryChanged(TString)), path); } + +bool FileSystemWatcher::event(Event *event) { + FileSystemWatcherEvent *ev = dynamic_cast(event); + if(ev) { + TString path = ev->m_path; + if(File::isDir(path)) { // Directory modified + directoryChanged(path); + } else { // File modified + fileChanged(path); + } + return true; + } + + return false; +}