Add a class for monitoring changes in files
authorMikko Rasa <tdb@tdb.fi>
Sat, 21 Sep 2019 15:25:59 +0000 (18:25 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sat, 21 Sep 2019 15:25:59 +0000 (18:25 +0300)
source/fs/filemonitor.cpp [new file with mode: 0644]
source/fs/filemonitor.h [new file with mode: 0644]
source/fs/unix/filemonitor.cpp [new file with mode: 0644]
source/fs/unix/filemonitor_platform.h [new file with mode: 0644]

diff --git a/source/fs/filemonitor.cpp b/source/fs/filemonitor.cpp
new file mode 100644 (file)
index 0000000..5cc990d
--- /dev/null
@@ -0,0 +1,51 @@
+#include "filemonitor.h"
+#include "filemonitor_platform.h"
+
+using namespace std;
+
+namespace Msp {
+namespace FS {
+
+FileMonitor::FileMonitor():
+       priv(new Private(*this)),
+       event_disp(0)
+{ }
+
+FileMonitor::~FileMonitor()
+{
+       delete priv;
+}
+
+void FileMonitor::use_event_dispatcher(IO::EventDispatcher &ed)
+{
+       if(event_disp)
+               throw logic_error("event_disp!=0");
+
+       event_disp = &ed;
+       platform_use_event_dispatcher();
+}
+
+void FileMonitor::add_file(const FS::Path &path)
+{
+       MonitoredFile file;
+       file.path = path;
+       file.modified = false;
+       prepare_file(file);
+       files.push_back(file);
+}
+
+void FileMonitor::remove_file(const FS::Path &path)
+{
+       for(vector<MonitoredFile>::iterator i=files.begin(); i!=files.end(); ++i)
+               if(i->path==path)
+               {
+                       cleanup_file(*i);
+                       if(&*i!=&files.back())
+                               *i = files.back();
+                       files.pop_back();
+                       break;
+               }
+}
+
+} // namespace FS
+} // namespace Msp
diff --git a/source/fs/filemonitor.h b/source/fs/filemonitor.h
new file mode 100644 (file)
index 0000000..e96aff6
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef FILEMONITOR_H_
+#define FILEMONITOR_H_
+
+#include <msp/core/noncopyable.h>
+#include <msp/fs/path.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/io/eventobject.h>
+
+namespace Msp {
+namespace FS {
+
+class FileMonitor: NonCopyable
+{
+private:
+       struct Private;
+
+       struct MonitoredFile
+       {
+               FS::Path path;
+               bool modified;
+               int tag;
+       };
+
+public:
+       sigc::signal<void, const FS::Path &> signal_file_modified;
+
+private:
+       Private *priv;
+       IO::EventDispatcher *event_disp;
+       std::vector<MonitoredFile> files;
+
+public:
+       FileMonitor();
+       ~FileMonitor();
+
+       void add_file(const FS::Path &);
+       void remove_file(const FS::Path &);
+private:
+       void prepare_file(MonitoredFile &);
+       void cleanup_file(MonitoredFile &);
+
+public:
+       void use_event_dispatcher(IO::EventDispatcher &);
+private:
+       void platform_use_event_dispatcher();
+
+public:
+       void tick();
+       void tick(const Msp::Time::TimeDelta &);
+};
+
+} // namespace FS
+} // namespace Msp
+
+#endif
diff --git a/source/fs/unix/filemonitor.cpp b/source/fs/unix/filemonitor.cpp
new file mode 100644 (file)
index 0000000..70cfc4e
--- /dev/null
@@ -0,0 +1,131 @@
+#include <sys/inotify.h>
+#include <msp/core/systemerror.h>
+#include <msp/io/handle_private.h>
+#include "filemonitor.h"
+#include "filemonitor_platform.h"
+
+using namespace std;
+
+namespace Msp {
+namespace FS {
+
+INotify::INotify()
+{
+       *fd = inotify_init();
+       mode = IO::M_READ;
+       set_events(IO::P_INPUT);
+}
+
+INotify::~INotify()
+{
+       IO::sys_close(fd);
+}
+
+int INotify::add_watch(const FS::Path &path, int ev)
+{
+       int ret = inotify_add_watch(*fd, path.c_str(), ev);
+       if(ret==-1)
+               throw system_error("inotify_add_watch");
+       return ret;
+}
+
+void INotify::remove_watch(int wd)
+{
+       int ret = inotify_rm_watch(*fd, wd);
+       if(ret==-1)
+               throw system_error("inotify_rm_watch");
+}
+
+unsigned INotify::do_write(const char *, unsigned)
+{
+       check_access(IO::M_WRITE);
+       return 0;
+}
+
+unsigned INotify::do_read(char *buf, unsigned size)
+{
+       return IO::sys_read(fd, buf, size);
+}
+
+
+void FileMonitor::platform_use_event_dispatcher()
+{
+       event_disp->add(priv->inotify);
+}
+
+void FileMonitor::prepare_file(MonitoredFile &file)
+{
+       file.tag = priv->inotify.add_watch(file.path, IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF);
+}
+
+void FileMonitor::cleanup_file(MonitoredFile &file)
+{
+       if(file.tag!=-1)
+               priv->inotify.remove_watch(file.tag);
+}
+
+void FileMonitor::tick()
+{
+       bool first = true;
+       while(1)
+       {
+               if(!first && !IO::poll(priv->inotify, IO::P_INPUT, Time::zero))
+                       break;
+
+               first = false;
+               priv->events_available();
+       }
+}
+
+void FileMonitor::tick(const Time::TimeDelta &timeout)
+{
+       if(IO::poll(priv->inotify, IO::P_INPUT, timeout))
+               tick();
+}
+
+
+FileMonitor::Private::Private(FileMonitor &m):
+       monitor(m)
+{
+       inotify.signal_data_available.connect(sigc::mem_fun(this, &Private::events_available));
+}
+
+void FileMonitor::Private::events_available()
+{
+       vector<FS::Path> changed_files;
+       char event_buf[sizeof(struct inotify_event)+NAME_MAX+1];
+       unsigned len = inotify.read(event_buf, sizeof(event_buf));
+       for(unsigned i=0; i<len; )
+       {
+               struct inotify_event *event = reinterpret_cast<struct inotify_event *>(event_buf+i);
+               for(vector<MonitoredFile>::iterator j=monitor.files.begin(); j!=monitor.files.end(); ++j)
+                       if(j->tag==event->wd)
+                       {
+                               if(event->mask&IN_MODIFY)
+                                       j->modified = true;
+                               if(((event->mask&IN_CLOSE_WRITE) && j->modified) || (event->mask&IN_DELETE_SELF))
+                               {
+                                       j->modified = false;
+                                       changed_files.push_back(j->path);
+                               }
+                               if(event->mask&IN_IGNORED)
+                                       j->tag = -1;
+                               break;
+                       }
+               i += sizeof(struct inotify_event)+event->len;
+       }
+
+       for(vector<FS::Path>::const_iterator i=changed_files.begin(); i!=changed_files.end(); ++i)
+               monitor.signal_file_modified.emit(*i);
+
+       for(vector<MonitoredFile>::iterator j=monitor.files.begin(); j!=monitor.files.end(); )
+       {
+               if(j->tag==-1)
+                       monitor.files.erase(j++);
+               else
+                       ++j;
+       }
+}
+
+} // namespace FS
+} // namespace Msp
diff --git a/source/fs/unix/filemonitor_platform.h b/source/fs/unix/filemonitor_platform.h
new file mode 100644 (file)
index 0000000..31b728f
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef MSP_FS_FILEMONITOR_PLATFORM_H_
+#define MSP_FS_FILEMONITOR_PLATFORM_H_
+
+namespace Msp {
+namespace FS {
+
+class INotify: public IO::EventObject
+{
+private:
+       IO::Handle fd;
+
+public:
+       INotify();
+       ~INotify();
+
+       int add_watch(const FS::Path &, int);
+       void remove_watch(int);
+
+       virtual void set_block(bool) { }
+       virtual void set_inherit(bool) { }
+
+protected:
+       virtual unsigned do_write(const char *, unsigned);
+       virtual unsigned do_read(char *, unsigned);
+
+public:
+       virtual const IO::Handle &get_handle(IO::Mode) { return fd; }
+       virtual const IO::Handle &get_event_handle() { return fd; }
+};
+
+
+struct FileMonitor::Private
+{
+       FileMonitor &monitor;
+       INotify inotify;
+
+       Private(FileMonitor &);
+
+       void events_available();
+};
+
+} // namespace FS
+} // namespace Msp
+
+#endif