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