abraunegg-onedrive/src/monitor.d

223 lines
5.9 KiB
D
Raw Normal View History

2015-09-10 23:05:15 +02:00
import core.sys.linux.sys.inotify;
import core.stdc.errno;
import core.sys.posix.poll, core.sys.posix.unistd;
import std.exception, std.file, std.path, std.regex, std.stdio, std.string;
import config;
import selective;
import util;
static import log;
2015-09-10 23:05:15 +02:00
// relevant inotify events
private immutable uint32_t mask = IN_CLOSE_WRITE | IN_CREATE | 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
}
}
final class Monitor
2015-09-10 23:05:15 +02:00
{
2015-09-17 00:16:23 +02:00
bool verbose;
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;
// skip symbolic links
bool skip_symlinks;
private SelectiveSync selectiveSync;
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
this(SelectiveSync selectiveSync)
{
assert(selectiveSync);
this.selectiveSync = selectiveSync;
}
2015-09-10 23:05:15 +02:00
void init(Config cfg, bool verbose, bool skip_symlinks)
2015-09-10 23:05:15 +02:00
{
2015-09-17 00:16:23 +02:00
this.verbose = verbose;
this.skip_symlinks = skip_symlinks;
assert(onDirCreated && onFileChanged && onDelete && onMove);
2015-09-10 23:05:15 +02:00
fd = inotify_init();
if (fd < 0) 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
{
2017-03-14 17:20:18 +01:00
// skip filtered items
if (dirname != ".") {
if (selectiveSync.isNameExcluded(baseName(dirname))) {
2017-03-14 17:20:18 +01:00
return;
}
if (selectiveSync.isPathExcluded(buildNormalizedPath(dirname))) {
2017-03-14 17:20:18 +01:00
return;
}
}
// skip symlinks if configured
if (isSymlink(dirname)) {
// if config says so we skip all symlinked items
if (skip_symlinks) {
// dont add a watch for this directory
return;
}
}
2017-03-14 17:20:18 +01:00
add(dirname);
foreach(DirEntry entry; dirEntries(dirname, SpanMode.shallow, false)) {
if (entry.isDir) {
addRecursive(entry.name);
}
2015-09-17 00:16:23 +02:00
}
2015-09-10 23:05:15 +02:00
}
private void add(string pathname)
2015-09-10 23:05:15 +02:00
{
int wd = inotify_add_watch(fd, toStringz(pathname), mask);
if (wd < 0) {
if (errno() == ENOSPC) {
log.log("The user limit on the total number of inotify watches has been reached.");
log.log("To see the current max number of watches run:");
log.log("sysctl fs.inotify.max_user_watches");
log.log("To change the current max number of watches to 524288 run:");
log.log("sudo sysctl fs.inotify.max_user_watches=524288");
}
throw new MonitorException("inotify_add_watch failed");
}
wdToDirName[wd] = buildNormalizedPath(pathname) ~ "/";
log.vlog("Monitor directory: ", pathname);
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 < 0) throw new MonitorException("inotify_rm_watch failed");
log.vlog("Monitored directory removed: ", wdToDirName[wd]);
2015-09-17 00:16:23 +02:00
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 < 0) throw new MonitorException("inotify_rm_watch failed");
2015-09-19 14:25:39 +02:00
wdToDirName.remove(wd);
log.vlog("Monitored directory removed: ", dirname);
2015-09-19 14:25:39 +02:00
}
}
}
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(bool useCallbacks = true)
2015-09-10 23:05:15 +02:00
{
pollfd fds = {
fd: fd,
events: POLLIN
};
2015-09-10 23:05:15 +02:00
2015-09-17 00:16:23 +02:00
while (true) {
int ret = poll(&fds, 1, 0);
2015-09-17 00:16:23 +02:00
if (ret == -1) throw new MonitorException("poll failed");
else if (ret == 0) break; // no events available
size_t length = read(fd, buffer.ptr, buffer.length);
2015-09-17 00:16:23 +02:00
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);
if (selectiveSync.isNameExcluded(baseName(path))) {
2017-03-14 17:20:18 +01:00
goto skip;
}
if (selectiveSync.isPathExcluded(path)) {
2017-03-14 17:20:18 +01:00
goto skip;
2015-09-18 22:56:09 +02:00
}
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);
if (useCallbacks) 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) {
if (useCallbacks) onDirCreated(path);
2015-09-17 00:16:23 +02:00
} else {
if (useCallbacks) onFileChanged(path);
2015-09-17 00:16:23 +02:00
}
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);
if (useCallbacks) onDirCreated(path);
2015-09-10 23:05:15 +02:00
}
2015-09-17 00:16:23 +02:00
} else if (event.mask & IN_DELETE) {
if (useCallbacks) onDelete(path);
} else if ((event.mask & IN_CLOSE_WRITE) && !(event.mask & IN_ISDIR)) {
if (useCallbacks) onFileChanged(path);
2015-09-17 00:16:23 +02:00
} else {
assert(0);
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 have been deleted
2015-09-17 00:16:23 +02:00
foreach (cookie, path; cookieToPath) {
if (useCallbacks) 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
}
}
}
}