impl/linux/file_watch: Support watching directories

This commit is contained in:
Perttu Ahola 2014-09-24 11:23:52 +03:00
parent 3110d21c3c
commit 552c1ec684
3 changed files with 118 additions and 59 deletions

View File

@ -12,6 +12,21 @@
namespace interface {
bool Filesystem::check_file_extension(const char *path, const char *ext)
{
return c55fs::checkFileExtension(path, ext);
}
ss_ Filesystem::strip_file_extension(const ss_ &path)
{
return c55fs::stripFileExtension(path);
}
ss_ Filesystem::strip_file_name(const ss_ &path)
{
return c55fs::stripFilename(path);
}
struct CFilesystem : public Filesystem
{
sv_<Node> list_directory(const ss_ &path)

View File

@ -5,8 +5,12 @@
#include <cstring>
#include <sys/inotify.h>
#include <unistd.h>
#include <linux/limits.h> // PATH_MAX
#define MODULE "__filewatch"
#define INOTIFY_BUFSIZE (sizeof(struct inotify_event) + NAME_MAX + 1)
#define INOTIFY_STRUCTSIZE (sizeof(struct inotify_event))
namespace interface {
struct CFileWatch: FileWatch
@ -51,41 +55,50 @@ struct CFileWatch: FileWatch
{
if(fd != m_fd)
return;
struct inotify_event in_event;
char buf[INOTIFY_BUFSIZE];
for(;;){
int r = read(fd, &in_event, sizeof(struct inotify_event));
if(r != sizeof(struct inotify_event))
int r = read(fd, buf, INOTIFY_BUFSIZE);
if(r == -1){
if(errno == EAGAIN)
break;
log_w(MODULE, "CFileWatch::report_fd(): read() failed "
"on fd=%i: %s", fd, strerror(errno));
break;
sv_<char> name_buf(in_event.len);
read(fd, &name_buf[0], in_event.len);
ss_ name(name_buf.begin(), name_buf.end());
}
if(r < (int)INOTIFY_STRUCTSIZE){
throw Exception("CFileWatch::report_fd(): read() -> "+itos(r));
}
struct inotify_event *in_event = (struct inotify_event*)buf;
ss_ name;
if(r >= (int)INOTIFY_STRUCTSIZE)
name = &buf[INOTIFY_STRUCTSIZE]; // Null-terminated string
ss_ mask_s;
if(in_event.mask & IN_ACCESS) mask_s += "IN_ACCESS | ";
if(in_event.mask & IN_ATTRIB) mask_s += "IN_ATTRIB | ";
if(in_event.mask & IN_CLOSE_WRITE) mask_s += "IN_CLOSE_WRITE | ";
if(in_event.mask & IN_CLOSE_NOWRITE) mask_s += "IN_CLOSE_NOWRITE | ";
if(in_event.mask & IN_CREATE) mask_s += "IN_CREATE | ";
if(in_event.mask & IN_DELETE) mask_s += "IN_DELETE | ";
if(in_event.mask & IN_DELETE_SELF) mask_s += "IN_DELETE_SELF | ";
if(in_event.mask & IN_MODIFY) mask_s += "IN_MODIFY | ";
if(in_event.mask & IN_MOVE_SELF) mask_s += "IN_MOVE_SELF | ";
if(in_event.mask & IN_MOVED_FROM) mask_s += "IN_MOVED_FROM | ";
if(in_event.mask & IN_MOVED_TO) mask_s += "IN_MOVED_TO | ";
if(in_event.mask & IN_OPEN) mask_s += "IN_OPEN | ";
if(in_event.mask & IN_IGNORED) mask_s += "IN_IGNORED | ";
if(in_event.mask & IN_ISDIR) mask_s += "IN_ISDIR | ";
if(in_event.mask & IN_Q_OVERFLOW) mask_s += "IN_Q_OVERFLOW | ";
if(in_event.mask & IN_UNMOUNT) mask_s += "IN_UNMOUNT | ";
if(in_event->mask & IN_ACCESS) mask_s += "IN_ACCESS | ";
if(in_event->mask & IN_ATTRIB) mask_s += "IN_ATTRIB | ";
if(in_event->mask & IN_CLOSE_WRITE) mask_s += "IN_CLOSE_WRITE | ";
if(in_event->mask & IN_CLOSE_NOWRITE) mask_s += "IN_CLOSE_NOWRITE | ";
if(in_event->mask & IN_CREATE) mask_s += "IN_CREATE | ";
if(in_event->mask & IN_DELETE) mask_s += "IN_DELETE | ";
if(in_event->mask & IN_DELETE_SELF) mask_s += "IN_DELETE_SELF | ";
if(in_event->mask & IN_MODIFY) mask_s += "IN_MODIFY | ";
if(in_event->mask & IN_MOVE_SELF) mask_s += "IN_MOVE_SELF | ";
if(in_event->mask & IN_MOVED_FROM) mask_s += "IN_MOVED_FROM | ";
if(in_event->mask & IN_MOVED_TO) mask_s += "IN_MOVED_TO | ";
if(in_event->mask & IN_OPEN) mask_s += "IN_OPEN | ";
if(in_event->mask & IN_IGNORED) mask_s += "IN_IGNORED | ";
if(in_event->mask & IN_ISDIR) mask_s += "IN_ISDIR | ";
if(in_event->mask & IN_Q_OVERFLOW) mask_s += "IN_Q_OVERFLOW | ";
if(in_event->mask & IN_UNMOUNT) mask_s += "IN_UNMOUNT | ";
mask_s = mask_s.substr(0, mask_s.size()-3);
log_d(MODULE, "in_event.wd=%i, mask=%s, name=%s",
in_event.wd, cs(mask_s), cs(name));
log_d(MODULE, "in_event->wd=%i, mask=%s, name=%s",
in_event->wd, cs(mask_s), cs(name));
if(in_event.mask & IN_IGNORED){
if(in_event->mask & IN_IGNORED){
// Inotify removed path from watch
ss_ path = m_watch_paths[in_event.wd];
m_watch_paths.erase(in_event.wd);
ss_ path = m_watch_paths[in_event->wd];
m_watch_paths.erase(in_event->wd);
int r = inotify_add_watch(m_fd, path.c_str(), IN_CLOSE_WRITE |
IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE |
IN_MODIFY | IN_ATTRIB);
@ -118,9 +131,10 @@ struct CMultiFileWatch: MultiFileWatch
{
struct WatchThing {
ss_ path;
std::function<void(const ss_ &path)> cb;
WatchThing(const ss_ &path, std::function<void(const ss_ &path)> cb):
path(path), cb(cb){}
sv_<std::function<void(const ss_ &path)>> cbs;
WatchThing(const ss_ &path,
const sv_<std::function<void(const ss_ &path)>> &cbs):
path(path), cbs(cbs){}
};
int m_fd = -1;
@ -129,6 +143,7 @@ struct CMultiFileWatch: MultiFileWatch
CMultiFileWatch()
{
m_fd = inotify_init1(IN_NONBLOCK);
log_d(MODULE, "CMultiFileWatch(): m_fd=%i", m_fd);
if(m_fd == -1){
throw Exception(ss_()+"inotify_init() failed: "+strerror(errno));
}
@ -141,6 +156,15 @@ struct CMultiFileWatch: MultiFileWatch
void add(const ss_ &path, std::function<void(const ss_ &path)> cb)
{
for(auto &pair : m_watch){
sp_<WatchThing> &watch = pair.second;
if(watch->path == path){
log_v(MODULE, "Adding callback to path \"%s\" (inotify fd=%i)",
cs(path), pair.first);
watch->cbs.push_back(cb);
return;
}
}
int r = inotify_add_watch(m_fd, path.c_str(), IN_CLOSE_WRITE |
IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE |
IN_MODIFY | IN_ATTRIB);
@ -149,7 +173,7 @@ struct CMultiFileWatch: MultiFileWatch
strerror(errno)+" (path="+path+")");
}
log_v(MODULE, "Watching path \"%s\" (inotify fd=%i)", cs(path), m_fd);
m_watch[r] = sp_<WatchThing>(new WatchThing(path, cb));
m_watch[r] = sp_<WatchThing>(new WatchThing(path, {cb}));
}
// Used on Linux; no-op on Windows
@ -163,47 +187,61 @@ struct CMultiFileWatch: MultiFileWatch
{
if(fd != m_fd)
return;
struct inotify_event in_event;
char buf[INOTIFY_BUFSIZE];
for(;;){
int r = read(fd, &in_event, sizeof(struct inotify_event));
if(r != sizeof(struct inotify_event))
int r = read(fd, buf, INOTIFY_BUFSIZE);
if(r == -1){
if(errno == EAGAIN)
break;
log_w(MODULE, "CMultiFileWatch::report_fd(): read() failed "
"on fd=%i: %s", fd, strerror(errno));
break;
sv_<char> name_buf(in_event.len);
read(fd, &name_buf[0], in_event.len);
ss_ name(name_buf.begin(), name_buf.end());
}
if(r < (int)INOTIFY_STRUCTSIZE){
throw Exception("CMultiFileWatch::report_fd(): read() -> "+itos(r));
}
struct inotify_event *in_event = (struct inotify_event*)buf;
ss_ name;
if(r >= (int)INOTIFY_STRUCTSIZE)
name = &buf[INOTIFY_STRUCTSIZE]; // Null-terminated string
ss_ mask_s;
if(in_event.mask & IN_ACCESS) mask_s += "IN_ACCESS | ";
if(in_event.mask & IN_ATTRIB) mask_s += "IN_ATTRIB | ";
if(in_event.mask & IN_CLOSE_WRITE) mask_s += "IN_CLOSE_WRITE | ";
if(in_event.mask & IN_CLOSE_NOWRITE) mask_s += "IN_CLOSE_NOWRITE | ";
if(in_event.mask & IN_CREATE) mask_s += "IN_CREATE | ";
if(in_event.mask & IN_DELETE) mask_s += "IN_DELETE | ";
if(in_event.mask & IN_DELETE_SELF) mask_s += "IN_DELETE_SELF | ";
if(in_event.mask & IN_MODIFY) mask_s += "IN_MODIFY | ";
if(in_event.mask & IN_MOVE_SELF) mask_s += "IN_MOVE_SELF | ";
if(in_event.mask & IN_MOVED_FROM) mask_s += "IN_MOVED_FROM | ";
if(in_event.mask & IN_MOVED_TO) mask_s += "IN_MOVED_TO | ";
if(in_event.mask & IN_OPEN) mask_s += "IN_OPEN | ";
if(in_event.mask & IN_IGNORED) mask_s += "IN_IGNORED | ";
if(in_event.mask & IN_ISDIR) mask_s += "IN_ISDIR | ";
if(in_event.mask & IN_Q_OVERFLOW) mask_s += "IN_Q_OVERFLOW | ";
if(in_event.mask & IN_UNMOUNT) mask_s += "IN_UNMOUNT | ";
if(in_event->mask & IN_ACCESS) mask_s += "IN_ACCESS | ";
if(in_event->mask & IN_ATTRIB) mask_s += "IN_ATTRIB | ";
if(in_event->mask & IN_CLOSE_WRITE) mask_s += "IN_CLOSE_WRITE | ";
if(in_event->mask & IN_CLOSE_NOWRITE) mask_s += "IN_CLOSE_NOWRITE | ";
if(in_event->mask & IN_CREATE) mask_s += "IN_CREATE | ";
if(in_event->mask & IN_DELETE) mask_s += "IN_DELETE | ";
if(in_event->mask & IN_DELETE_SELF) mask_s += "IN_DELETE_SELF | ";
if(in_event->mask & IN_MODIFY) mask_s += "IN_MODIFY | ";
if(in_event->mask & IN_MOVE_SELF) mask_s += "IN_MOVE_SELF | ";
if(in_event->mask & IN_MOVED_FROM) mask_s += "IN_MOVED_FROM | ";
if(in_event->mask & IN_MOVED_TO) mask_s += "IN_MOVED_TO | ";
if(in_event->mask & IN_OPEN) mask_s += "IN_OPEN | ";
if(in_event->mask & IN_IGNORED) mask_s += "IN_IGNORED | ";
if(in_event->mask & IN_ISDIR) mask_s += "IN_ISDIR | ";
if(in_event->mask & IN_Q_OVERFLOW) mask_s += "IN_Q_OVERFLOW | ";
if(in_event->mask & IN_UNMOUNT) mask_s += "IN_UNMOUNT | ";
mask_s = mask_s.substr(0, mask_s.size()-3);
log_d(MODULE, "in_event.wd=%i, mask=%s, name=%s",
in_event.wd, cs(mask_s), cs(name));
log_d(MODULE, "in_event->wd=%i, mask=%s, name=%s",
in_event->wd, cs(mask_s), cs(name));
auto it = m_watch.find(in_event.wd);
auto it = m_watch.find(in_event->wd);
if(it == m_watch.end())
throw Exception("inotify returned unknown wd");
sp_<WatchThing> thing = it->second;
thing->cb(thing->path);
for(auto &cb : thing->cbs){
if(!name.empty())
cb(thing->path+"/"+name);
else
cb(thing->path);
};
if(in_event.mask & IN_IGNORED){
if(in_event->mask & IN_IGNORED){
// Inotify removed path from watch
const ss_ &path = thing->path;
m_watch.erase(in_event.wd);
m_watch.erase(in_event->wd);
int r = inotify_add_watch(m_fd, path.c_str(), IN_CLOSE_WRITE |
IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE |
IN_MODIFY | IN_ATTRIB);

View File

@ -5,6 +5,7 @@
namespace interface
{
// TODO: Get some sanity into this; what's up with the virtual methods?
struct Filesystem
{
virtual ~Filesystem(){}
@ -21,6 +22,11 @@ namespace interface
virtual ss_ get_cwd() = 0;
virtual ss_ get_absolute_path(const ss_ &path) = 0;
// "image.png", "png" -> true
static bool check_file_extension(const char *path, const char *ext);
static ss_ strip_file_extension(const ss_ &path);
static ss_ strip_file_name(const ss_ &path);
};
Filesystem* getGlobalFilesystem();