--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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