1 module ws.inotify;
2 
3 
4 import
5     core.sys.linux.sys.inotify,
6     core.sys.posix.sys.time,
7     core.sys.posix.unistd,
8     core.sys.posix.sys.select,
9     core.stdc.errno,
10     std.string,
11     std.stdio,
12     std.algorithm,
13     std.conv,
14     std.file,
15     ws.event;
16 
17 
18 version(Posix):
19 
20 __gshared:
21 
22 
23 class Inotify {
24 
25     enum EVENT_BUFFER_LENGTH = (inotify_event.sizeof + 16) * 1024;
26 
27     static int inotify = -1;
28     static timeval timeOut;
29     static fd_set descriptorSet;
30 
31     static Watcher[int] staticWatchers;
32 
33     enum {
34         Add,
35         Remove,
36         Modify
37     }
38 
39 
40     shared static this(){
41         if(inotify >= 0)
42             return;
43         inotify = inotify_init;
44         if(inotify < 0)
45             throw new Exception("Failed to initialize inotify: %s".format(errno));
46         timeOut.tv_sec = 0;
47         timeOut.tv_usec = 0;
48         FD_ZERO(&descriptorSet);
49     }
50 
51 
52     static class Watcher {
53         this(){
54             event = new Event!(string, string, int);
55         }
56         string directory;
57         Event!(string, string, int) event;
58     }
59 
60 
61     static void watch(string path, void delegate(string, string, int) event){
62         assert(inotify >= 0);
63         if(!path.isDir)
64             throw new Exception("\"" ~ path ~ "\" is not a directory. Please don't watch single files, they need to be \"re-watched\" in almost all cases on change");
65         foreach(wd, watcher; staticWatchers){
66             if(watcher.directory == path){
67                 watcher.event ~= event;
68                 return;
69             }
70         }
71         if(staticWatchers.values.find!(a => a.directory == path).length){
72             return;
73         }
74         int wd = inotify_add_watch(inotify, path.toStringz,
75                 IN_CLOSE_WRITE
76                 | IN_MOVED_FROM
77                 | IN_MOVED_TO
78                 | IN_CREATE
79                 | IN_DELETE
80                 | IN_MASK_ADD);
81         if(wd < 0)
82             throw new Exception("inotify error in %s: %s".format(path, errno));
83         auto watcher = new Watcher;
84         watcher.directory = path;
85         watcher.event ~= event;
86         staticWatchers[wd] = watcher;
87     }
88 
89     static void update(){
90         FD_SET(inotify, &descriptorSet);
91         int ret = select(inotify + 1, &descriptorSet, null, null, &timeOut);
92         if(ret < 0){
93             perror("select");
94         }else if(FD_ISSET(inotify, &descriptorSet)){
95             ssize_t len, i = 0;
96             byte[EVENT_BUFFER_LENGTH] buff = 0;
97             len = read(inotify, buff.ptr, buff.length);
98             while(i < len){
99                 auto pevent = cast(inotify_event*)&buff[i];
100                 auto watcher = staticWatchers[pevent.wd];
101                 watcher.event(
102                     watcher.directory,
103                     (cast(char*)&pevent.name).to!string,
104                     pevent.mask & IN_CLOSE_WRITE ? Modify
105                         : pevent.mask & (IN_MOVED_TO | IN_CREATE) ? Add
106                         : pevent.mask & (IN_MOVED_FROM | IN_DELETE) ? Remove
107                         : -1);
108                 i += inotify_event.sizeof + pevent.len;
109             }
110         }
111 
112     }
113 
114 }
115