2015-09-10 23:05:15 +02:00
|
|
|
import core.sys.linux.sys.inotify;
|
|
|
|
import core.sys.posix.poll;
|
|
|
|
import core.sys.posix.unistd;
|
2015-09-18 22:56:09 +02:00
|
|
|
import std.exception, std.file, std.path, std.regex, std.stdio, std.string;
|
2015-09-19 09:45:45 +02:00
|
|
|
import config, util;
|
2015-09-10 23:05:15 +02:00
|
|
|
|
|
|
|
// relevant inotify events
|
|
|
|
private immutable uint32_t mask = IN_ATTRIB | IN_CLOSE_WRITE | IN_CREATE |
|
2015-09-17 00:16:23 +02:00
|
|
|
IN_DELETE | IN_MOVE | IN_IGNORED | IN_Q_OVERFLOW;
|
2015-09-10 23:05:15 +02:00
|
|
|
|
2015-09-17 00:16:23 +02:00
|
|
|
class MonitorException: ErrnoException
|
2015-09-10 23:05:15 +02:00
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
@safe this(string msg, string file = __FILE__, size_t line = __LINE__)
|
2015-09-10 23:05:15 +02:00
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
super(msg, file, line);
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Monitor
|
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
bool verbose;
|
2015-09-17 17:34:58 +02:00
|
|
|
// regexes that match files/dirs to skip
|
|
|
|
private Regex!char skipDir, skipFile;
|
2015-09-10 23:05:15 +02:00
|
|
|
// inotify file descriptor
|
|
|
|
private int fd;
|
2015-09-17 00:16:23 +02:00
|
|
|
// map every inotify watch descriptor to its directory
|
|
|
|
private string[int] wdToDirName;
|
2015-09-10 23:05:15 +02:00
|
|
|
// map the inotify cookies of move_from events to their path
|
2015-09-11 11:02:07 +02:00
|
|
|
private string[int] cookieToPath;
|
2015-09-10 23:05:15 +02:00
|
|
|
// buffer to receive the inotify events
|
|
|
|
private void[] buffer;
|
|
|
|
|
2015-09-11 18:33:22 +02:00
|
|
|
void delegate(string path) onDirCreated;
|
|
|
|
void delegate(string path) onFileChanged;
|
|
|
|
void delegate(string path) onDelete;
|
|
|
|
void delegate(string from, string to) onMove;
|
2015-09-10 23:05:15 +02:00
|
|
|
|
|
|
|
@disable this(this);
|
|
|
|
|
2015-09-17 17:34:58 +02:00
|
|
|
void init(Config cfg, bool verbose)
|
2015-09-10 23:05:15 +02:00
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
this.verbose = verbose;
|
2015-09-19 09:45:45 +02:00
|
|
|
skipDir = regex(wild2regex(cfg.get("skip_dir", "")));
|
|
|
|
skipFile = regex(wild2regex(cfg.get("skip_file", "")));
|
2015-09-10 23:05:15 +02:00
|
|
|
fd = inotify_init();
|
|
|
|
if (fd == -1) throw new MonitorException("inotify_init failed");
|
2015-09-17 00:16:23 +02:00
|
|
|
if (!buffer) buffer = new void[4096];
|
|
|
|
addRecursive(".");
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void shutdown()
|
|
|
|
{
|
|
|
|
if (fd > 0) close(fd);
|
2015-09-17 00:16:23 +02:00
|
|
|
wdToDirName = null;
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
|
2015-09-17 00:16:23 +02:00
|
|
|
private void addRecursive(string dirname)
|
2015-09-10 23:05:15 +02:00
|
|
|
{
|
2015-09-19 14:25:39 +02:00
|
|
|
if (matchFirst(dirname, skipDir).empty) {
|
2015-09-18 22:56:09 +02:00
|
|
|
add(dirname);
|
|
|
|
foreach(DirEntry entry; dirEntries(dirname, SpanMode.shallow, false)) {
|
|
|
|
if (entry.isDir) {
|
|
|
|
addRecursive(entry.name);
|
|
|
|
}
|
2015-09-17 17:34:58 +02:00
|
|
|
}
|
2015-09-17 00:16:23 +02:00
|
|
|
}
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
|
2015-09-17 00:16:23 +02:00
|
|
|
private void add(string dirname)
|
2015-09-10 23:05:15 +02:00
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
int wd = inotify_add_watch(fd, toStringz(dirname), mask);
|
|
|
|
if (wd == -1) throw new MonitorException("inotify_add_watch failed");
|
|
|
|
wdToDirName[wd] = chompPrefix(dirname ~ "/", "./");
|
|
|
|
if (verbose) writeln("Monitor directory: ", dirname);
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// remove a watch descriptor
|
|
|
|
private void remove(int wd)
|
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
assert(wd in wdToDirName);
|
2015-09-10 23:05:15 +02:00
|
|
|
int ret = inotify_rm_watch(fd, wd);
|
|
|
|
if (ret == -1) throw new MonitorException("inotify_rm_watch failed");
|
2015-09-17 00:16:23 +02:00
|
|
|
if (verbose) writeln("Monitored directory removed: ", wdToDirName[wd]);
|
|
|
|
wdToDirName.remove(wd);
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
|
2015-09-19 14:25:39 +02:00
|
|
|
// remove the watch descriptors associated to the given path
|
|
|
|
private void remove(const(char)[] path)
|
|
|
|
{
|
|
|
|
path ~= "/";
|
|
|
|
foreach (wd, dirname; wdToDirName) {
|
|
|
|
if (dirname.startsWith(path)) {
|
|
|
|
int ret = inotify_rm_watch(fd, wd);
|
|
|
|
if (ret == -1) throw new MonitorException("inotify_rm_watch failed");
|
|
|
|
wdToDirName.remove(wd);
|
|
|
|
if (verbose) writeln("Monitored directory removed: ", dirname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-10 23:05:15 +02:00
|
|
|
// return the file path from an inotify event
|
|
|
|
private string getPath(const(inotify_event)* event)
|
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
string path = wdToDirName[event.wd];
|
2015-09-10 23:05:15 +02:00
|
|
|
if (event.len > 0) path ~= fromStringz(event.name.ptr);
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
void update()
|
|
|
|
{
|
2015-09-17 00:16:23 +02:00
|
|
|
assert(onDirCreated && onFileChanged && onDelete && onMove);
|
|
|
|
pollfd[1] fds = void;
|
2015-09-10 23:05:15 +02:00
|
|
|
fds[0].fd = fd;
|
|
|
|
fds[0].events = POLLIN;
|
|
|
|
|
2015-09-17 00:16:23 +02:00
|
|
|
while (true) {
|
|
|
|
int ret = poll(fds.ptr, 1, 0);
|
|
|
|
if (ret == -1) throw new MonitorException("poll failed");
|
|
|
|
else if (ret == 0) break; // no events available
|
|
|
|
|
|
|
|
assert(fds[0].revents & POLLIN);
|
|
|
|
size_t length = read(fds[0].fd, buffer.ptr, buffer.length);
|
|
|
|
if (length == -1) throw new MonitorException("read failed");
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
while (i < length) {
|
|
|
|
inotify_event *event = cast(inotify_event*) &buffer[i];
|
2015-09-19 14:25:39 +02:00
|
|
|
string path;
|
|
|
|
|
2015-09-17 00:16:23 +02:00
|
|
|
if (event.mask & IN_IGNORED) {
|
|
|
|
// forget the directory associated to the watch descriptor
|
|
|
|
wdToDirName.remove(event.wd);
|
2015-09-18 22:56:09 +02:00
|
|
|
goto skip;
|
2015-09-17 00:16:23 +02:00
|
|
|
} else if (event.mask & IN_Q_OVERFLOW) {
|
|
|
|
throw new MonitorException("Inotify overflow, events missing");
|
2015-09-18 22:56:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// skip filtered items
|
2015-09-19 14:25:39 +02:00
|
|
|
path = getPath(event);
|
2015-09-18 22:56:09 +02:00
|
|
|
if (event.mask & IN_ISDIR) {
|
2015-09-19 14:25:39 +02:00
|
|
|
if (!matchFirst(path, skipDir).empty) {
|
2015-09-18 22:56:09 +02:00
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
} else {
|
2015-09-19 14:25:39 +02:00
|
|
|
if (!matchFirst(path, skipFile).empty) {
|
2015-09-18 22:56:09 +02:00
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.mask & IN_MOVED_FROM) {
|
2015-09-17 00:16:23 +02:00
|
|
|
cookieToPath[event.cookie] = path;
|
|
|
|
} else if (event.mask & IN_MOVED_TO) {
|
|
|
|
if (event.mask & IN_ISDIR) addRecursive(path);
|
|
|
|
auto from = event.cookie in cookieToPath;
|
|
|
|
if (from) {
|
|
|
|
cookieToPath.remove(event.cookie);
|
|
|
|
onMove(*from, path);
|
2015-09-11 11:02:07 +02:00
|
|
|
} else {
|
2015-09-17 00:16:23 +02:00
|
|
|
// item moved from the outside
|
|
|
|
if (event.mask & IN_ISDIR) {
|
|
|
|
onDirCreated(path);
|
|
|
|
} else {
|
|
|
|
onFileChanged(path);
|
|
|
|
}
|
2015-09-11 11:02:07 +02:00
|
|
|
}
|
2015-09-17 00:16:23 +02:00
|
|
|
} else if (event.mask & IN_CREATE) {
|
|
|
|
if (event.mask & IN_ISDIR) {
|
2015-09-11 11:02:07 +02:00
|
|
|
addRecursive(path);
|
|
|
|
onDirCreated(path);
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
2015-09-17 00:16:23 +02:00
|
|
|
} else if (event.mask & IN_DELETE) {
|
|
|
|
onDelete(path);
|
|
|
|
} else if (event.mask & IN_ATTRIB || event.mask & IN_CLOSE_WRITE) {
|
|
|
|
if (!(event.mask & IN_ISDIR)) {
|
2015-09-11 11:02:07 +02:00
|
|
|
onFileChanged(path);
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
2015-09-17 00:16:23 +02:00
|
|
|
} else {
|
|
|
|
writeln("Unknow inotify event: ", format("%#x", event.mask));
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
2015-09-18 22:56:09 +02:00
|
|
|
|
|
|
|
skip:
|
2015-09-17 00:16:23 +02:00
|
|
|
i += inotify_event.sizeof + event.len;
|
|
|
|
}
|
|
|
|
// assume that the items moved outside the watched directory has been deleted
|
|
|
|
foreach (cookie, path; cookieToPath) {
|
|
|
|
onDelete(path);
|
2015-09-19 14:25:39 +02:00
|
|
|
remove(path);
|
2015-09-17 00:16:23 +02:00
|
|
|
cookieToPath.remove(cookie);
|
2015-09-10 23:05:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|