#pragma once #include // For std::string #include #include // For file watching #include // For file watching #include // For file watching //UpdateListener* global_ul = nullptr; // Global pointer to UpdateListener bool STOP = false; class FileQueue; // Forward declaration of FileQueue class bool endsWith(const std::string& str, const std::string& suffix); efsw::WatchID handleWatchID( efsw::WatchID watchid ) { switch ( watchid ) { case efsw::Errors::FileNotFound: case efsw::Errors::FileRepeated: case efsw::Errors::FileOutOfScope: case efsw::Errors::FileRemote: case efsw::Errors::WatcherFailed: case efsw::Errors::Unspecified: { std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl; break; } default: { std::cout << "Added WatchID: " << watchid << std::endl; } } return watchid; } /// A class that manages a queue of files added/modified or deleted fromthe filesystem /// with both FIFO order and fast lookups class FileQueue { public: // Default constructor FileQueue() = default; // Move constructor FileQueue(FileQueue&& other) noexcept { std::lock_guard lock(other.mutex); pathVector = std::move(other.pathVector); pathMap = std::move(other.pathMap); } // Move assignment operator FileQueue& operator=(FileQueue&& other) noexcept { if (this != &other) { std::lock_guard lock1(mutex); std::lock_guard lock2(other.mutex); pathVector = std::move(other.pathVector); pathMap = std::move(other.pathMap); } return *this; } // Delete copy operations since mutexes can't be copied FileQueue(const FileQueue&) = delete; FileQueue& operator=(const FileQueue&) = delete; // Add a file to the queue if it's not already there bool push(const std::string& path) { std::lock_guard lock(mutex); if (pathMap.find(path) == pathMap.end()) { pathVector.push_back(path); pathMap[path] = true; return true; } return false; } // Get the next file to process (FIFO order) bool pop(std::string& outPath) { std::lock_guard lock(mutex); if (!pathVector.empty()) { outPath = pathVector.front(); pathVector.erase(pathVector.begin()); pathMap.erase(outPath); return true; } return false; } // Get the next file to process (FIFO order) bool probe(std::string& outPath) { std::lock_guard lock(mutex); if (!pathVector.empty()) { outPath = pathVector.front(); return true; } return false; } // Remove a specific file by name bool remove(const std::string& path) { std::lock_guard lock(mutex); if (pathMap.find(path) != pathMap.end()) { // Remove from vector using erase-remove idiom pathVector.erase( std::remove(pathVector.begin(), pathVector.end(), path), pathVector.end() ); // Remove from map pathMap.erase(path); return true; } return false; } // Check if a file exists in the queue bool contains(const std::string& path) const { std::lock_guard lock(mutex); return pathMap.find(path) != pathMap.end(); } // Get the number of files in the queue size_t size() const { std::lock_guard lock(mutex); return pathVector.size(); } // Check if the queue is empty bool empty() const { std::lock_guard lock(mutex); return pathVector.empty(); } // Clear all files from the queue void clear() { std::lock_guard lock(mutex); pathVector.clear(); pathMap.clear(); } private: std::vector pathVector; // Maintains FIFO order std::map pathMap; // Enables fast lookups mutable std::mutex mutex; // Thread safety }; /// Processes a file action libefsw class UpdateListener : public efsw::FileWatchListener { public: // Modified constructor to take references to all FileQueue instances and mutexes UpdateListener(FileQueue& fileQueue, FileQueue& unfileQueue, FileQueue& processQueue, std::mutex& fileQueueMutex, std::mutex& unfileQueueMutex, std::mutex& processQueueMutex) : fileQueue_(fileQueue), unfileQueue_(unfileQueue), processQueue_(processQueue), fileQueueMutex_(fileQueueMutex), unfileQueueMutex_(unfileQueueMutex), processQueueMutex_(processQueueMutex), should_stop_(false) {} void stop() { should_stop_ = true; } std::string getActionName( efsw::Action action ) { switch ( action ) { case efsw::Actions::Add: return "Add"; case efsw::Actions::Modified: return "Modified"; case efsw::Actions::Delete: return "Delete"; case efsw::Actions::Moved: return "Moved"; default: return "Bad Action"; } } void handleFileAction( efsw::WatchID watchid, const std::string& dir, const std::string& filename, efsw::Action action, std::string oldFilename = "" ) override { if (should_stop_) return; // Early exit if we're stopping std::string actionName = getActionName( action ); if (actionName == "Delete") { // always push to unfile queue, cpp will handle the rest std::string belPath = dir + filename; if (endsWith(belPath, ".bsz")) { std::cout << "\n==" << "DELETE: " << dir + filename << "\n==" << std::endl; { std::lock_guard lock(unfileQueueMutex_); if (!unfileQueue_.contains(belPath)) { unfileQueue_.push(belPath); std::cout << "\n==" << "STOP PROCESSING: " << belPath << "\n==" << std::endl; } } } } if (actionName == "Add" || actionName == "Modified") { std::string belPath = dir + filename; std::string parentPath = dir; //std::cout << "parentPath: " << parentPath << std::endl; if (should_stop_) return; // Check again before starting render if (endsWith(belPath, ".vmax") || endsWith(belPath, ".bsz") || endsWith(belPath, ".zip") && !endsWith(parentPath, "download/")) { { std::lock_guard lock(fileQueueMutex_); if (!fileQueue_.contains(belPath)) { fileQueue_.push(belPath); #ifdef _DEBUG std::cout << "\n==" << "QUEUED: " << belPath << "\n==" << std::endl; #endif } } } } } private: // Store references to all queues and mutexes FileQueue& fileQueue_; FileQueue& unfileQueue_; FileQueue& processQueue_; std::mutex& fileQueueMutex_; std::mutex& unfileQueueMutex_; std::mutex& processQueueMutex_; std::atomic should_stop_; // ctrl-c was not working, so we use this to stop the thread }; // Utility function to check if a string ends with a specific suffix inline bool endsWith(const std::string& str, const std::string& suffix) { if (str.length() < suffix.length()) { return false; } return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; }