]> code.delx.au - gnu-emacs/blob - lisp/filenotify.el
4c5d43fb44eaca7156e553c862460197e141e22c
[gnu-emacs] / lisp / filenotify.el
1 ;;; filenotify.el --- watch files for changes on disk -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
4
5 ;; Author: Michael Albinus <michael.albinus@gmx.de>
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22 ;;; Commentary
23
24 ;; This package is an abstraction layer from the different low-level
25 ;; file notification packages `gfilenotify', `inotify' and
26 ;; `w32notify'.
27
28 ;;; Code:
29
30 (defconst file-notify--library
31 (cond
32 ((featurep 'gfilenotify) 'gfilenotify)
33 ((featurep 'inotify) 'inotify)
34 ((featurep 'w32notify) 'w32notify))
35 "Non-nil when Emacs has been compiled with file notification support.
36 The value is the name of the low-level file notification package
37 to be used for local file systems. Remote file notifications
38 could use another implementation.")
39
40 (defvar file-notify-descriptors (make-hash-table :test 'equal)
41 "Hash table for registered file notification descriptors.
42 A key in this hash table is the descriptor as returned from
43 `gfilenotify', `inotify', `w32notify' or a file name handler.
44 The value in the hash table is a list
45
46 (DIR (FILE . CALLBACK) (FILE . CALLBACK) ...)
47
48 Several values for a given DIR happen only for `inotify', when
49 different files from the same directory are watched.")
50
51 (defun file-notify--rm-descriptor (descriptor &optional what)
52 "Remove DESCRIPTOR from `file-notify-descriptors'.
53 DESCRIPTOR should be an object returned by `file-notify-add-watch'.
54 If it is registered in `file-notify-descriptors', a stopped event is sent.
55 WHAT is a file or directory name to be removed, needed just for `inotify'."
56 (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
57 (file (if (consp descriptor) (cdr descriptor)))
58 (registered (gethash desc file-notify-descriptors))
59 (dir (car registered)))
60
61 (when (and (consp registered) (or (null what) (string-equal dir what)))
62 ;; Send `stopped' event.
63 (dolist (entry (cdr registered))
64 (funcall (cdr entry)
65 `(,descriptor stopped
66 ,(or (and (stringp (car entry))
67 (expand-file-name (car entry) dir))
68 dir))))
69
70 ;; Modify `file-notify-descriptors'.
71 (if (not file)
72 (remhash desc file-notify-descriptors)
73 (setcdr registered
74 (delete (assoc file (cdr registered)) (cdr registered)))
75 (if (null (cdr registered))
76 (remhash desc file-notify-descriptors)
77 (puthash desc registered file-notify-descriptors))))))
78
79 ;; This function is used by `gfilenotify', `inotify' and `w32notify' events.
80 ;;;###autoload
81 (defun file-notify-handle-event (event)
82 "Handle file system monitoring event.
83 If EVENT is a filewatch event, call its callback. It has the format
84
85 (file-notify (DESCRIPTOR ACTIONS FILE [FILE1-OR-COOKIE]) CALLBACK)
86
87 Otherwise, signal a `file-notify-error'."
88 (interactive "e")
89 ;;(message "file-notify-handle-event %S" event)
90 (if (and (eq (car event) 'file-notify)
91 (>= (length event) 3))
92 (funcall (nth 2 event) (nth 1 event))
93 (signal 'file-notify-error
94 (cons "Not a valid file-notify event" event))))
95
96 ;; Needed for `inotify' and `w32notify'. In the latter case, COOKIE is nil.
97 (defvar file-notify--pending-event nil
98 "A pending file notification events for a future `renamed' action.
99 It is a form ((DESCRIPTOR ACTION FILE [FILE1-OR-COOKIE]) CALLBACK).")
100
101 (defun file-notify--event-file-name (event)
102 "Return file name of file notification event, or nil."
103 (directory-file-name
104 (expand-file-name
105 (or (and (stringp (nth 2 event)) (nth 2 event)) "")
106 (car (gethash (car event) file-notify-descriptors)))))
107
108 ;; Only `gfilenotify' could return two file names.
109 (defun file-notify--event-file1-name (event)
110 "Return second file name of file notification event, or nil.
111 This is available in case a file has been moved."
112 (and (stringp (nth 3 event))
113 (directory-file-name
114 (expand-file-name
115 (nth 3 event) (car (gethash (car event) file-notify-descriptors))))))
116
117 ;; Cookies are offered by `inotify' only.
118 (defun file-notify--event-cookie (event)
119 "Return cookie of file notification event, or nil.
120 This is available in case a file has been moved."
121 (nth 3 event))
122
123 ;; `inotify' returns the same descriptor when the file (directory)
124 ;; uses the same inode. We want to distinguish, and apply a virtual
125 ;; descriptor which make the difference.
126 (defun file-notify--descriptor (desc file)
127 "Return the descriptor to be used in `file-notify-*-watch'.
128 For `gfilenotify' and `w32notify' it is the same descriptor as
129 used in the low-level file notification package."
130 (if (and (natnump desc) (eq file-notify--library 'inotify))
131 (cons desc
132 (and (stringp file)
133 (car (assoc
134 (file-name-nondirectory file)
135 (gethash desc file-notify-descriptors)))))
136 desc))
137
138 ;; The callback function used to map between specific flags of the
139 ;; respective file notifications, and the ones we return.
140 (defun file-notify-callback (event)
141 "Handle an EVENT returned from file notification.
142 EVENT is the cadr of the event in `file-notify-handle-event'
143 \(DESCRIPTOR ACTIONS FILE [FILE1-OR-COOKIE])."
144 (let* ((desc (car event))
145 (registered (gethash desc file-notify-descriptors))
146 (actions (nth 1 event))
147 (file (file-notify--event-file-name event))
148 file1 callback pending-event stopped)
149
150 ;; Make actions a list.
151 (unless (consp actions) (setq actions (cons actions nil)))
152
153 ;; Loop over registered entries. In fact, more than one entry
154 ;; happens only for `inotify'.
155 (dolist (entry (cdr registered))
156
157 ;; Check, that event is meant for us.
158 (unless (setq callback (cdr entry))
159 (setq actions nil))
160
161 ;; Loop over actions. In fact, more than one action happens only
162 ;; for `inotify'.
163 (dolist (action actions)
164
165 ;; Send pending event, if it doesn't match.
166 (when (and file-notify--pending-event
167 ;; The cookie doesn't match.
168 (not (eq (file-notify--event-cookie
169 (car file-notify--pending-event))
170 (file-notify--event-cookie event)))
171 (or
172 ;; inotify.
173 (and (eq (nth 1 (car file-notify--pending-event))
174 'moved-from)
175 (not (eq action 'moved-to)))
176 ;; w32notify.
177 (and (eq (nth 1 (car file-notify--pending-event))
178 'renamed-from)
179 (not (eq action 'renamed-to)))))
180 (setq pending-event file-notify--pending-event
181 file-notify--pending-event nil)
182 (setcar (cdar pending-event) 'deleted))
183
184 ;; Map action. We ignore all events which cannot be mapped.
185 (setq action
186 (cond
187 ;; gfilenotify.
188 ((memq action '(attribute-changed changed created deleted))
189 action)
190 ((eq action 'moved)
191 (setq file1 (file-notify--event-file1-name event))
192 'renamed)
193
194 ;; inotify, w32notify.
195 ((eq action 'ignored)
196 (setq stopped t actions nil))
197 ((eq action 'attrib) 'attribute-changed)
198 ((memq action '(create added)) 'created)
199 ((memq action '(modify modified)) 'changed)
200 ((memq action '(delete delete-self move-self removed)) 'deleted)
201 ;; Make the event pending.
202 ((memq action '(moved-from renamed-from))
203 (setq file-notify--pending-event
204 `((,desc ,action ,file ,(file-notify--event-cookie event))
205 ,callback))
206 nil)
207 ;; Look for pending event.
208 ((memq action '(moved-to renamed-to))
209 (if (null file-notify--pending-event)
210 'created
211 (setq file1 file
212 file (file-notify--event-file-name
213 (car file-notify--pending-event)))
214 ;; If the source is handled by another watch, we
215 ;; must fire the rename event there as well.
216 (when (not (equal (file-notify--descriptor desc file1)
217 (file-notify--descriptor
218 (caar file-notify--pending-event)
219 (file-notify--event-file-name
220 file-notify--pending-event))))
221 (setq pending-event
222 `((,(caar file-notify--pending-event)
223 renamed ,file ,file1)
224 ,(cadr file-notify--pending-event))))
225 (setq file-notify--pending-event nil)
226 'renamed))))
227
228 ;; Apply pending callback.
229 (when pending-event
230 (setcar
231 (car pending-event)
232 (file-notify--descriptor
233 (caar pending-event)
234 (file-notify--event-file-name file-notify--pending-event)))
235 (funcall (cadr pending-event) (car pending-event))
236 (setq pending-event nil))
237
238 ;; Check for stopped.
239 ;;(message "file-notify-callback %S %S" file registered)
240 (setq
241 stopped
242 (or
243 stopped
244 (and
245 (memq action '(deleted renamed))
246 (= (length (cdr registered)) 1)
247 (string-equal
248 (file-name-nondirectory file)
249 (or (file-name-nondirectory (car registered))
250 (car (cadr registered)))))))
251
252 ;; Apply callback.
253 (when (and action
254 (or
255 ;; If there is no relative file name for that watch,
256 ;; we watch the whole directory.
257 (null (nth 0 entry))
258 ;; File matches.
259 (string-equal
260 (nth 0 entry) (file-name-nondirectory file))
261 ;; File1 matches.
262 (and (stringp file1)
263 (string-equal
264 (nth 0 entry) (file-name-nondirectory file1)))))
265 (if file1
266 (funcall
267 callback
268 `(,(file-notify--descriptor desc file) ,action ,file ,file1))
269 (funcall
270 callback
271 `(,(file-notify--descriptor desc file) ,action ,file)))))
272
273 ;; Modify `file-notify-descriptors'.
274 (when stopped
275 (file-notify--rm-descriptor
276 (file-notify--descriptor desc file) file)))))
277
278 ;; `gfilenotify' and `w32notify' return a unique descriptor for every
279 ;; `file-notify-add-watch', while `inotify' returns a unique
280 ;; descriptor per inode only.
281 (defun file-notify-add-watch (file flags callback)
282 "Add a watch for filesystem events pertaining to FILE.
283 This arranges for filesystem events pertaining to FILE to be reported
284 to Emacs. Use `file-notify-rm-watch' to cancel the watch.
285
286 The returned value is a descriptor for the added watch. If the
287 file cannot be watched for some reason, this function signals a
288 `file-notify-error' error.
289
290 FLAGS is a list of conditions to set what will be watched for. It can
291 include the following symbols:
292
293 `change' -- watch for file changes
294 `attribute-change' -- watch for file attributes changes, like
295 permissions or modification time
296
297 If FILE is a directory, `change' watches for file creation or
298 deletion in that directory. This does not work recursively.
299
300 When any event happens, Emacs will call the CALLBACK function passing
301 it a single argument EVENT, which is of the form
302
303 (DESCRIPTOR ACTION FILE [FILE1])
304
305 DESCRIPTOR is the same object as the one returned by this function.
306 ACTION is the description of the event. It could be any one of the
307 following:
308
309 `created' -- FILE was created
310 `deleted' -- FILE was deleted
311 `changed' -- FILE has changed
312 `renamed' -- FILE has been renamed to FILE1
313 `attribute-changed' -- a FILE attribute was changed
314 `stopped' -- watching FILE has been stopped
315
316 FILE is the name of the file whose event is being reported."
317 ;; Check arguments.
318 (unless (stringp file)
319 (signal 'wrong-type-argument `(,file)))
320 (setq file (expand-file-name file))
321 (unless (and (consp flags)
322 (null (delq 'change (delq 'attribute-change (copy-tree flags)))))
323 (signal 'wrong-type-argument `(,flags)))
324 (unless (functionp callback)
325 (signal 'wrong-type-argument `(,callback)))
326
327 (let* ((handler (find-file-name-handler file 'file-notify-add-watch))
328 (dir (directory-file-name
329 (if (file-directory-p file)
330 file
331 (file-name-directory file))))
332 desc func l-flags registered)
333
334 (unless (file-directory-p dir)
335 (signal 'file-notify-error `("Directory does not exist" ,dir)))
336
337 (if handler
338 ;; A file name handler could exist even if there is no local
339 ;; file notification support.
340 (setq desc (funcall
341 handler 'file-notify-add-watch dir flags callback))
342
343 ;; Check, whether Emacs has been compiled with file notification
344 ;; support.
345 (unless file-notify--library
346 (signal 'file-notify-error
347 '("No file notification package available")))
348
349 ;; Determine low-level function to be called.
350 (setq func
351 (cond
352 ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
353 ((eq file-notify--library 'inotify) 'inotify-add-watch)
354 ((eq file-notify--library 'w32notify) 'w32notify-add-watch)))
355
356 ;; Determine respective flags.
357 (if (eq file-notify--library 'gfilenotify)
358 (setq l-flags (append '(watch-mounts send-moved) flags))
359 (when (memq 'change flags)
360 (setq
361 l-flags
362 (cond
363 ((eq file-notify--library 'inotify)
364 '(create delete delete-self modify move-self move))
365 ((eq file-notify--library 'w32notify)
366 '(file-name directory-name size last-write-time)))))
367 (when (memq 'attribute-change flags)
368 (push (cond
369 ((eq file-notify--library 'inotify) 'attrib)
370 ((eq file-notify--library 'w32notify) 'attributes))
371 l-flags)))
372
373 ;; Call low-level function.
374 (setq desc (funcall func dir l-flags 'file-notify-callback)))
375
376 ;; Modify `file-notify-descriptors'.
377 (setq registered (gethash desc file-notify-descriptors))
378 (puthash
379 desc
380 `(,dir
381 (,(unless (file-directory-p file) (file-name-nondirectory file))
382 . ,callback)
383 . ,(cdr registered))
384 file-notify-descriptors)
385
386 ;; Return descriptor.
387 (file-notify--descriptor
388 desc (unless (file-directory-p file) (file-name-nondirectory file)))))
389
390 (defun file-notify-rm-watch (descriptor)
391 "Remove an existing watch specified by its DESCRIPTOR.
392 DESCRIPTOR should be an object returned by `file-notify-add-watch'."
393 (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
394 (file (if (consp descriptor) (cdr descriptor)))
395 (registered (gethash desc file-notify-descriptors))
396 (dir (car registered))
397 (handler (and (stringp dir)
398 (find-file-name-handler dir 'file-notify-rm-watch))))
399
400 (when (stringp dir)
401 ;; Call low-level function.
402 (when (or (not file)
403 (and (= (length (cdr registered)) 1)
404 (assoc file (cdr registered))))
405 (condition-case nil
406 (if handler
407 ;; A file name handler could exist even if there is no local
408 ;; file notification support.
409 (funcall handler 'file-notify-rm-watch descriptor)
410
411 (funcall
412 (cond
413 ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
414 ((eq file-notify--library 'inotify) 'inotify-rm-watch)
415 ((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
416 desc))
417 (file-notify-error nil)))
418
419 ;; Modify `file-notify-descriptors'.
420 (file-notify--rm-descriptor descriptor))))
421
422 (defun file-notify-valid-p (descriptor)
423 "Check a watch specified by its DESCRIPTOR.
424 DESCRIPTOR should be an object returned by `file-notify-add-watch'."
425 (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
426 (file (if (consp descriptor) (cdr descriptor)))
427 (registered (gethash desc file-notify-descriptors))
428 (dir (car registered))
429 handler)
430
431 (when (stringp dir)
432 (setq handler (find-file-name-handler dir 'file-notify-valid-p))
433
434 (and (or ;; It is a directory.
435 (not file)
436 ;; The file is registered.
437 (assoc file (cdr registered)))
438 (if handler
439 ;; A file name handler could exist even if there is no
440 ;; local file notification support.
441 (funcall handler 'file-notify-valid-p descriptor)
442 (funcall
443 (cond
444 ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
445 ((eq file-notify--library 'inotify) 'inotify-valid-p)
446 ((eq file-notify--library 'w32notify) 'w32notify-valid-p))
447 desc))
448 t))))
449
450 ;; The end:
451 (provide 'filenotify)
452
453 ;;; filenotify.el ends here