oom/oom_file.h

552 lines
22 KiB
C++

#pragma once
#include <iostream>
#include <fstream>
#include <thread>
#include <vector>
#include <chrono>
#include <filesystem>
#include <string>
#include <atomic>
#include <mutex> // Add this line for std::mutex and std::lock_guard
#include <map> // Add this line for std::map
#include "../efsw/src/efsw/FileSystem.hpp" // For file watching
#include "../efsw/src/efsw/System.hpp" // For file watching
#include "../efsw/include/efsw/efsw.hpp" // For file watching
//==============================================================================
// OOM NAMESPACE - Contains core file watching functionality
//==============================================================================
namespace oom {
namespace file {
//--------------------------------------------------------------------------
// RenderQueue Class
//--------------------------------------------------------------------------
/**
* A thread-safe FIFO queue that tracks render files
*
* This class implements a specialized queue that maintains both:
* 1. FIFO (First-In-First-Out) ordering via a vector
* 2. Fast lookup capability via a map
*
* It's designed to be safely used across multiple threads
*/
class Queue {
public:
// Default constructor - Creates an empty queue
Queue() = default;
// Move constructor - Transfers ownership of resources from another queue
Queue(Queue&& other) noexcept {
// Lock the other queue to prevent concurrent modification during the move
std::lock_guard<std::mutex> lock(other.mutex);
// Transfer ownership of the data structures using std::move
pathVector = std::move(other.pathVector);
pathMap = std::move(other.pathMap);
}
// Move assignment operator - Similar to move constructor but for assignment operations
Queue& operator=(Queue&& other) noexcept {
// Avoid self-assignment (moving to itself)
if (this != &other) {
// Lock both queues to prevent concurrent modification
std::lock_guard<std::mutex> lock1(mutex);
std::lock_guard<std::mutex> lock2(other.mutex);
// Transfer ownership of the data structures
pathVector = std::move(other.pathVector);
pathMap = std::move(other.pathMap);
}
return *this;
}
// Delete copy operations since mutexes can't be copied
Queue(const Queue&) = delete;
Queue& operator=(const Queue&) = delete;
// Add a file to the queue if it's not already there
bool push(const std::filesystem::path& path) {
// Lock the queue during modification to ensure thread safety
std::lock_guard<std::mutex> lock(mutex);
// Check if the path already exists in our map (fast lookup)
if (pathMap.find(path) == pathMap.end()) {
// If not found, add to the vector (maintains order)
pathVector.push_back(path);
// Mark as present in the map for fast future lookups
pathMap[path] = true;
return true;
}
return false;
}
// Get the next file to render (FIFO order) and remove it from the queue
bool pop(std::filesystem::path& outPath) {
// Lock the queue during access to ensure thread safety
std::lock_guard<std::mutex> lock(mutex);
if (!pathVector.empty()) {
// Get the first item (oldest) from the vector
outPath = pathVector.front();
// Remove it from the vector
pathVector.erase(pathVector.begin());
// Remove it from the map as well
pathMap.erase(outPath);
return true;
}
return false;
}
// Remove a specific file by name
bool remove(const std::filesystem::path& path) {
// Lock the queue during modification to ensure thread safety
std::lock_guard<std::mutex> 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::filesystem::path& path) const {
// Lock the queue during access to ensure thread safety
std::lock_guard<std::mutex> lock(mutex);
// Use the map for fast lookup
return pathMap.find(path) != pathMap.end();
}
// Get the number of files in the queue
size_t size() const {
// Lock the queue during access to ensure thread safety
std::lock_guard<std::mutex> lock(mutex);
return pathVector.size();
}
// Check if the queue is empty
bool empty() const {
// Lock the queue during access to ensure thread safety
std::lock_guard<std::mutex> lock(mutex);
return pathVector.empty();
}
// Clear all files from the queue
void clear() {
// Lock the queue during modification to ensure thread safety
std::lock_guard<std::mutex> lock(mutex);
// Clear both data structures
pathVector.clear();
pathMap.clear();
}
private:
std::vector<std::filesystem::path> pathVector; // Stores paths in order (FIFO queue)
std::map<std::filesystem::path, bool> pathMap; // Maps paths to bool for fast existence checks
mutable std::mutex mutex; // Makes operations thread-safe; mutable allows locking in const methods
};
// Forward declaration needed for UpdateListener
class Watcher;
//--------------------------------------------------------------------------
// UpdateListener Class
//--------------------------------------------------------------------------
/**
* Processes file system events from efsw and filters them
*
* This class receives notifications from the file system watcher (efsw)
* when files are created, modified or deleted, and then filters these
* events based on file extensions and directories.
*/
class UpdateListener : public efsw::FileWatchListener {
public:
// Constructor takes a pointer to its parent Watcher
UpdateListener(Watcher* parent);
// Signals the listener to stop processing events
void stop();
// Converts efsw action codes to human-readable strings
std::string getActionName(efsw::Action action);
// Check if a file extension is in our watch list
bool isWatchedExtension(const std::string& extension);
// Check if a directory should be ignored
bool isIgnoredDirectory(const std::string& directory);
// Main callback that efsw calls when file system events occur
void handleFileAction(efsw::WatchID watchid,
const std::string& dir,
const std::string& filename,
efsw::Action action,
std::string oldFilename = "") override;
private:
std::atomic<bool> should_stop_{false}; // Flag to signal stopping
Watcher* parentWatcher; // Reference to parent Watcher
};
//--------------------------------------------------------------------------
// Watcher Class
//--------------------------------------------------------------------------
/**
* Manages file watching and file queues
*
* This class coordinates the file system watching functionality
* and maintains queues of files to be rendered or deleted.
*/
class Watcher {
public:
// Constructor with configurable extensions and ignored directories
Watcher(const std::vector<std::string>& extensions = {".bsz", ".zip"},
const std::vector<std::string>& ignoreDirs = {"download"});
// Destructor to clean up resources
~Watcher();
// Prevent copying or moving to simplify handling
Watcher(const Watcher&) = delete;
Watcher& operator=(const Watcher&) = delete;
Watcher(Watcher&&) = delete;
Watcher& operator=(Watcher&&) = delete;
// Start watching a directory
bool startWatching(const std::string& directory);
// Stop watching
void stopWatching();
// Get the next file to render
bool getNextFileToRender(std::filesystem::path& outPath);
// Get the next file to delete
bool getNextFileToDelete(std::filesystem::path& outPath);
// Check if there are files to render
bool hasFilesToRender() const;
// Check if there are files to delete
bool hasFilesToDelete() const;
// Add or remove extensions dynamically
void addExtension(const std::string& extension);
void removeExtension(const std::string& extension);
// Add or remove ignored directories
void addIgnoreDirectory(const std::string& directory);
void removeIgnoreDirectory(const std::string& directory);
// Get extensions and ignored directories (for UpdateListener)
const std::vector<std::string>& getWatchExtensions() const { return watchExtensions; }
const std::vector<std::string>& getIgnoreDirectories() const { return ignoreDirectories; }
// Access to queues (for UpdateListener)
void addToRenderQueue(const std::filesystem::path& path);
void addToDeleteQueue(const std::filesystem::path& path);
private:
// File extensions to monitor (without leading dot)
std::vector<std::string> watchExtensions;
// Directory names to ignore
std::vector<std::string> ignoreDirectories;
// Queues for the different file actions
Queue incomingRenderQueue;
Queue incomingDeleteQueue;
// Mutexes for thread safety
mutable std::mutex renderQueueMutex;
mutable std::mutex deleteQueueMutex;
mutable std::mutex extensionsMutex;
mutable std::mutex directoriesMutex;
// File watcher components
std::unique_ptr<efsw::FileWatcher> fileWatcher;
std::unique_ptr<UpdateListener> updateListener;
// Watch state
bool isWatching;
std::atomic<bool> stopRequested;
std::thread watcherThread;
// Path being watched
std::string watchPath;
// Internal function to run the watcher in a separate thread
void watcherThreadFunc();
};
//--------------------------------------------------------------------------
// Global variable within oom namespace
//--------------------------------------------------------------------------
std::atomic<bool> active_render(false);
//==========================================================================
// IMPLEMENTATION SECTION - Method implementations for the above classes
//==========================================================================
//--------------------------------------------------------------------------
// UpdateListener implementation
//--------------------------------------------------------------------------
UpdateListener::UpdateListener(Watcher* parent)
: should_stop_(false), parentWatcher(parent) {}
void UpdateListener::stop() {
should_stop_ = true;
}
std::string UpdateListener::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";
}
}
bool UpdateListener::isWatchedExtension(const std::string& extension) {
// Get the extensions list from parent
const auto& extensions = parentWatcher->getWatchExtensions();
// Check if the extension is in our watch list
return std::find(extensions.begin(), extensions.end(), extension) != extensions.end();
}
bool UpdateListener::isIgnoredDirectory(const std::string& directory) {
// Get the ignored directories list from parent
const auto& ignoreDirs = parentWatcher->getIgnoreDirectories();
// Check if the directory is in our ignore list
return std::find(ignoreDirs.begin(), ignoreDirs.end(), directory) != ignoreDirs.end();
}
void UpdateListener::handleFileAction(efsw::WatchID watchid,
const std::string& dir,
const std::string& filename,
efsw::Action action,
std::string oldFilename) {
if (should_stop_) return; // Early exit if we're stopping
std::string actionName = getActionName(action);
std::filesystem::path filePath = dir + filename;
std::filesystem::path parentPath = dir;
// Get the filename extension without the dot
std::string extension = filePath.extension().string();
if (!extension.empty() && extension[0] == '.') {
extension = extension.substr(1);
}
// Get the parent directory name
std::string parentDir = parentPath.filename().string();
// Check if we should ignore this directory
if (isIgnoredDirectory(parentDir)) {
return;
}
// Process based on the action type
if (actionName == "Delete") {
if (isWatchedExtension(extension)) {
parentWatcher->addToDeleteQueue(filePath);
std::cout << "\n==" << "STOP RENDER: " << filePath.string() << "\n==" << std::endl;
}
} else if (actionName == "Add" || actionName == "Modified") {
if (should_stop_) return; // Check again before starting render
if (isWatchedExtension(extension)) {
parentWatcher->addToRenderQueue(filePath);
std::cout << "\n==" << "RENDER QUEUED: " << filePath.string() << "\n==" << std::endl;
}
}
}
//--------------------------------------------------------------------------
// Watcher implementation
//--------------------------------------------------------------------------
Watcher::Watcher(const std::vector<std::string>& extensions,
const std::vector<std::string>& ignoreDirs)
: isWatching(false), stopRequested(false) {
// Process extensions to ensure they don't have leading dots
for (const auto& ext : extensions) {
std::string processedExt = ext;
if (!ext.empty() && ext[0] == '.') {
processedExt = ext.substr(1);
}
watchExtensions.push_back(processedExt);
}
// Store ignored directories
ignoreDirectories = ignoreDirs;
}
Watcher::~Watcher() {
stopWatching();
}
bool Watcher::startWatching(const std::string& directory) {
if (isWatching) {
stopWatching();
}
watchPath = directory;
stopRequested = false;
// Start the watcher thread
watcherThread = std::thread(&Watcher::watcherThreadFunc, this);
isWatching = true;
return true;
}
void Watcher::stopWatching() {
if (!isWatching) return;
stopRequested = true;
if (updateListener) {
updateListener->stop();
}
if (watcherThread.joinable()) {
watcherThread.join();
}
isWatching = false;
}
bool Watcher::getNextFileToRender(std::filesystem::path& outPath) {
std::lock_guard<std::mutex> lock(renderQueueMutex);
return incomingRenderQueue.pop(outPath);
}
bool Watcher::getNextFileToDelete(std::filesystem::path& outPath) {
std::lock_guard<std::mutex> lock(deleteQueueMutex);
return incomingDeleteQueue.pop(outPath);
}
bool Watcher::hasFilesToRender() const {
std::lock_guard<std::mutex> lock(renderQueueMutex);
return !incomingRenderQueue.empty();
}
bool Watcher::hasFilesToDelete() const {
std::lock_guard<std::mutex> lock(deleteQueueMutex);
return !incomingDeleteQueue.empty();
}
void Watcher::addExtension(const std::string& extension) {
std::lock_guard<std::mutex> lock(extensionsMutex);
std::string processedExt = extension;
if (!extension.empty() && extension[0] == '.') {
processedExt = extension.substr(1);
}
// Check if it already exists
if (std::find(watchExtensions.begin(), watchExtensions.end(), processedExt) == watchExtensions.end()) {
watchExtensions.push_back(processedExt);
}
}
void Watcher::removeExtension(const std::string& extension) {
std::lock_guard<std::mutex> lock(extensionsMutex);
std::string processedExt = extension;
if (!extension.empty() && extension[0] == '.') {
processedExt = extension.substr(1);
}
auto it = std::find(watchExtensions.begin(), watchExtensions.end(), processedExt);
if (it != watchExtensions.end()) {
watchExtensions.erase(it);
}
}
void Watcher::addIgnoreDirectory(const std::string& directory) {
std::lock_guard<std::mutex> lock(directoriesMutex);
// Check if it already exists
if (std::find(ignoreDirectories.begin(), ignoreDirectories.end(), directory) == ignoreDirectories.end()) {
ignoreDirectories.push_back(directory);
}
}
void Watcher::removeIgnoreDirectory(const std::string& directory) {
std::lock_guard<std::mutex> lock(directoriesMutex);
auto it = std::find(ignoreDirectories.begin(), ignoreDirectories.end(), directory);
if (it != ignoreDirectories.end()) {
ignoreDirectories.erase(it);
}
}
void Watcher::addToRenderQueue(const std::filesystem::path& path) {
std::lock_guard<std::mutex> lock(renderQueueMutex);
if (!incomingRenderQueue.contains(path)) {
incomingRenderQueue.push(path);
}
}
void Watcher::addToDeleteQueue(const std::filesystem::path& path) {
std::lock_guard<std::mutex> lock(deleteQueueMutex);
if (!incomingDeleteQueue.contains(path)) {
incomingDeleteQueue.push(path);
}
}
void Watcher::watcherThreadFunc() {
// Create a new FileWatcher
fileWatcher = std::make_unique<efsw::FileWatcher>(false);
// Create a new UpdateListener
updateListener = std::make_unique<UpdateListener>(this);
// Configure the watcher
fileWatcher->followSymlinks(false);
fileWatcher->allowOutOfScopeLinks(false);
// Check if the directory exists
if (!watchPath.empty() && std::filesystem::exists(watchPath)) {
// Add the directory to watch
efsw::WatchID watchID = fileWatcher->addWatch(watchPath, updateListener.get(), true);
if (watchID > 0) {
// Start watching
fileWatcher->watch();
std::cout << "Watching directory: " << watchPath << std::endl;
// Run until stopped
while (!stopRequested) {
efsw::System::sleep(500);
}
} else {
std::cout << "Error trying to watch directory: " << watchPath << std::endl;
std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl;
}
} else {
std::cout << "Error: Directory does not exist: " << watchPath << std::endl;
}
}
} // namespace file
} // namespace oom