A little toolkit for single-quad fragment shader demos
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

429 lines
11 KiB

/**
* @file Inotify.h
* @author Erik Zenker
* @date 02.11.2012
* @copyright Gnu Public License
**/
#pragma once
#include <sys/inotify.h>
#include <string>
#include <queue>
#include <map>
#include <vector>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <poll.h>
#include <errno.h>
#include <string>
#include <exception>
#include <sstream>
#include <experimental/filesystem>
#include <experimental/optional>
#include <FileSystemEvent.h>
#define MAX_EVENTS 4096
#define EVENT_SIZE (sizeof (inotify_event))
#define EVENT_BUF_LEN (MAX_EVENTS * (EVENT_SIZE + 16))
namespace fs = std::experimental::filesystem;
/**
* @brief C++ wrapper for linux inotify interface
* @class Inotify
* Inotify.h
* "include/Inotify.h"
*
* folders will be watched by watchFolderRecursively or
* files by watchFile. If there are changes inside this
* folder or files events will be raised. This events
* can be get by getNextEvent.
*
* @eventMask
*
* IN_ACCESS File was accessed (read) (*).
* IN_ATTRIB Metadata changed—for example, permissions,
* timestamps, extended attributes, link count
* (since Linux 2.6.25), UID, or GID. (*).
* IN_CLOSE_WRITE File opened for writing was closed (*).
* IN_CLOSE_NOWRITE File not opened for writing was closed (*).
* IN_CREATE File/directory created in watched directory(*).
* IN_DELETE File/directory deleted from watched directory(*).
* IN_DELETE_SELF Watched file/directory was itself deleted.
* IN_MODIFY File was modified (*).
* IN_MOVE_SELF Watched file/directory was itself moved.
* IN_MOVED_FROM Generated for the directory containing the old
* filename when a file is renamed (*).
* IN_MOVED_TO Generated for the directory containing the new
* filename when a file is renamed (*).
* IN_OPEN File was opened (*).
* IN_ALL_EVENTS macro is defined as a bit mask of all of the above
* events
* IN_MOVE IN_MOVED_FROM|IN_MOVED_TO
* IN_CLOSE IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
*
* See inotify manpage for more event details
*
*/
class Inotify {
public:
Inotify();
Inotify(uint32_t eventMask);
Inotify(std::vector< std::string> ignoredDirectories, unsigned eventTimeout, uint32_t eventMask);
~Inotify();
void watchDirectoryRecursively(fs::path path);
void watchFile(fs::path file, std::optional<fs::path> recurseRoot = std::nullopt);
void ignoreFileOnce(fs::path file);
FileSystemEvent getNextEvent();
std::optional<FileSystemEvent> nb_getNextEvent();
int getLastErrno();
private:
fs::path wdToPath(int wd);
bool isIgnored(std::string file);
bool onTimeout(time_t eventTime);
void removeWatch(int wd); // TODO
void init();
// Member
int mError;
time_t mEventTimeout;
time_t mLastEventTime;
uint32_t mEventMask;
std::vector<std::string> mIgnoredDirectories;
std::vector<std::string> mOnceIgnoredDirectories;
std::queue<FileSystemEvent> mEventQueue;
std::map<int, fs::path> mDirectorieMap;
std::map<int, fs::path> mRecurseMap;
int mInotifyFd;
};
inline Inotify::Inotify() :
mError(0),
mEventTimeout(0),
mLastEventTime(0),
mEventMask(IN_ALL_EVENTS),
mIgnoredDirectories(std::vector<std::string>()),
mInotifyFd(0){
// Initialize inotify
init();
}
inline Inotify::Inotify(uint32_t eventMask) :
mError(0),
mEventTimeout(0),
mLastEventTime(0),
mEventMask(eventMask),
mIgnoredDirectories(std::vector<std::string>()),
mInotifyFd(0){
// Initialize inotify
init();
}
inline Inotify::Inotify(std::vector<std::string> ignoredDirectories, unsigned eventTimeout, uint32_t eventMask) :
mError(0),
mEventTimeout(eventTimeout),
mLastEventTime(0),
mEventMask(eventMask),
mIgnoredDirectories(ignoredDirectories),
mInotifyFd(0){
// Initialize inotify
init();
}
inline Inotify::~Inotify(){
if(!close(mInotifyFd)){
mError = errno;
}
}
inline void Inotify::init(){
mInotifyFd = inotify_init();
if(mInotifyFd == -1){
mError = errno;
std::stringstream errorStream;
errorStream << "Can't initialize inotify ! " << strerror(mError) << ".";
throw std::runtime_error(errorStream.str());
}
}
/**
* @brief Adds the given path and all files and subdirectories
* to the set of watched files/directories.
* Symlinks will be followed!
*
* @param path that will be watched recursively
*
*/
inline void Inotify::watchDirectoryRecursively(fs::path path){
if(fs::exists(path)){
if(fs::is_directory(path)){
fs::recursive_directory_iterator it(path, fs::directory_options::follow_directory_symlink);
fs::recursive_directory_iterator end;
while(it != end){
fs::path currentPath = *it;
if(fs::is_directory(currentPath)){
watchFile(currentPath, path);
}
if(fs::is_symlink(currentPath)){
watchFile(currentPath, path);
}
++it;
}
}
watchFile(path);
}
else {
throw std::invalid_argument("Can´t watch Path! Path does not exist. Path: " + path.string());
}
}
/**
* @brief Adds a single file/directorie to the list of
* watches. Path and corresponding watchdescriptor
* will be stored in the directorieMap. This is done
* because events on watches just return this
* watchdescriptor.
*
* @param path that will be watched
*
*/
inline void Inotify::watchFile(fs::path filePath, std::optional<fs::path> recurseRoot) {
if(fs::exists(filePath)){
mError = 0;
int wd = 0;
if(!isIgnored(filePath.string())){
wd = inotify_add_watch(mInotifyFd, filePath.string().c_str(), mEventMask);
}
if(wd == -1){
mError = errno;
std::stringstream errorStream;
if(mError == 28){
errorStream << "Failed to watch! " << strerror(mError) << ". Please increase number of watches in \"/proc/sys/fs/inotify/max_user_watches\".";
throw std::runtime_error(errorStream.str());
}
errorStream << "Failed to watch! " << strerror(mError) << ". Path: " << filePath.string();
throw std::runtime_error(errorStream.str());
}
mDirectorieMap[wd] = filePath;
if (recurseRoot)
mRecurseMap[wd] = *recurseRoot;
}
}
inline void Inotify::ignoreFileOnce(fs::path file){
mOnceIgnoredDirectories.push_back(file.string());
}
/**
* @brief Removes watch from set of watches. This
* is not done recursively!
*
* @param wd watchdescriptor
*
*/
inline void Inotify::removeWatch(int wd){
int result = inotify_rm_watch(mInotifyFd, wd);
if(result == -1){
mError = errno;
std::stringstream errorStream;
errorStream << "Failed to remove watch! " << strerror(mError) << ".";
throw std::runtime_error(errorStream.str());
}
mDirectorieMap.erase(wd);
}
inline fs::path Inotify::wdToPath(int wd){
return mDirectorieMap[wd];
}
/**
* @brief Blocking wait on new events of watched files/directories
* specified on the eventmask. FileSystemEvents
* will be returned one by one. Thus this
* function can be called in some while(true)
* loop.
*
* @return A new FileSystemEvent
*
*/
inline FileSystemEvent Inotify::getNextEvent(){
int length = 0;
char buffer[EVENT_BUF_LEN];
time_t currentEventTime = time(NULL);
std::vector<FileSystemEvent> events;
// Read Events from fd into buffer
while(mEventQueue.empty()){
length = 0;
memset(&buffer, 0, EVENT_BUF_LEN);
while(length <= 0 ){
length = read(mInotifyFd, buffer, EVENT_BUF_LEN);
currentEventTime = time(NULL);
if(length == -1){
mError = errno;
if(mError != EINTR){
continue;
}
}
}
// Read events from buffer into queue
currentEventTime = time(NULL);
int i = 0;
while(i < length){
inotify_event *event = ((struct inotify_event*) &buffer[i]);
fs::path path(wdToPath(event->wd) / std::string(event->name));
if(fs::is_directory(path)){
event->mask |= IN_ISDIR;
}
FileSystemEvent fsEvent(event->wd, event->mask, path);
if(!fsEvent.path.empty()){
events.push_back(fsEvent);
}
else{
// Event is not complete --> ignore
}
i += EVENT_SIZE + event->len;
}
// Filter events
for(auto eventIt = events.begin(); eventIt < events.end(); ++eventIt){
FileSystemEvent currentEvent = *eventIt;
if(onTimeout(currentEventTime)){
events.erase(eventIt);
}
else if(isIgnored(currentEvent.path.string())){
events.erase(eventIt);
}
else{
mLastEventTime = currentEventTime;
mEventQueue.push(currentEvent);
}
}
}
// Return next event
FileSystemEvent event = mEventQueue.front();
mEventQueue.pop();
return event;
}
std::optional<FileSystemEvent> Inotify::nb_getNextEvent() {
char buffer[EVENT_BUF_LEN]
__attribute__ ((aligned(__alignof__(struct inotify_event))));
std::vector<FileSystemEvent> events;
struct pollfd fd = {.fd = mInotifyFd, .events = POLLIN, 0};
int pn = poll(&fd, 1, 0); // nonblocking poll(2)
if (pn == -1 && errno != EINTR) {
perror("poll()");
exit(EXIT_FAILURE);
}
if (pn > 0) {
ssize_t len = read(mInotifyFd, buffer, sizeof buffer);
if (len <= 0)
return std::nullopt;
struct inotify_event *event;
for (char *ptr = buffer; ptr < buffer + len;
ptr += sizeof(struct inotify_event) + event->len) {
event = (struct inotify_event*) ptr;
fs::path path(wdToPath(event->wd) / std::string(event->name));
if (fs::is_directory(path))
event->mask |= IN_ISDIR;
FileSystemEvent fsEvent(event->wd, event->mask, path);
if (mRecurseMap.find(event->wd) != mRecurseMap.end()) {
fsEvent.recursive_root_path = mRecurseMap[event->wd];
}
if (!fsEvent.path.empty())
events.push_back(fsEvent);
}
for (auto eventIt = events.begin(); eventIt < events.end(); ++eventIt){
FileSystemEvent currentEvent = *eventIt;
if (isIgnored(currentEvent.path.string())) {
events.erase(eventIt);
} else {
mEventQueue.push(currentEvent);
}
}
}
if (mEventQueue.empty())
return std::nullopt;
auto event = mEventQueue.front(); mEventQueue.pop();
return event;
}
inline int Inotify::getLastErrno(){
return mError;
}
inline bool Inotify::isIgnored(std::string file){
if(mIgnoredDirectories.empty() and mOnceIgnoredDirectories.empty()){
return false;
}
for(unsigned i = 0; i < mOnceIgnoredDirectories.size(); ++i){
size_t pos = file.find(mOnceIgnoredDirectories[i]);
if(pos!= std::string::npos){
mOnceIgnoredDirectories.erase(mOnceIgnoredDirectories.begin() + i);
return true;
}
}
for(unsigned i = 0; i < mIgnoredDirectories.size(); ++i){
size_t pos = file.find(mIgnoredDirectories[i]);
if(pos!= std::string::npos){
return true;
}
}
return false;
}
inline bool Inotify::onTimeout(time_t eventTime){
return (mLastEventTime + mEventTimeout) > eventTime;
}