]> code.delx.au - gnu-emacs/blob - lisp/filenotify.el
Minor fix 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-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 `(,(file-notify--descriptor desc) 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 (descriptor)
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 descriptor) (eq file-notify--library 'inotify))
131 (cons descriptor
132 (car (cadr (gethash descriptor file-notify-descriptors))))
133 descriptor))
134
135 ;; The callback function used to map between specific flags of the
136 ;; respective file notifications, and the ones we return.
137 (defun file-notify-callback (event)
138 "Handle an EVENT returned from file notification.
139 EVENT is the cadr of the event in `file-notify-handle-event'
140 \(DESCRIPTOR ACTIONS FILE [FILE1-OR-COOKIE])."
141 (let* ((desc (car event))
142 (registered (gethash desc file-notify-descriptors))
143 (actions (nth 1 event))
144 (file (file-notify--event-file-name event))
145 file1 callback pending-event stopped)
146
147 ;; Make actions a list.
148 (unless (consp actions) (setq actions (cons actions nil)))
149
150 ;; Loop over registered entries. In fact, more than one entry
151 ;; happens only for `inotify'.
152 (dolist (entry (cdr registered))
153
154 ;; Check, that event is meant for us.
155 (unless (setq callback (cdr entry))
156 (setq actions nil))
157
158 ;; Loop over actions. In fact, more than one action happens only
159 ;; for `inotify'.
160 (dolist (action actions)
161
162 ;; Send pending event, if it doesn't match.
163 (when (and file-notify--pending-event
164 ;; The cookie doesn't match.
165 (not (eq (file-notify--event-cookie
166 (car file-notify--pending-event))
167 (file-notify--event-cookie event)))
168 (or
169 ;; inotify.
170 (and (eq (nth 1 (car file-notify--pending-event))
171 'moved-from)
172 (not (eq action 'moved-to)))
173 ;; w32notify.
174 (and (eq (nth 1 (car file-notify--pending-event))
175 'renamed-from)
176 (not (eq action 'renamed-to)))))
177 (setq pending-event file-notify--pending-event
178 file-notify--pending-event nil)
179 (setcar (cdar pending-event) 'deleted))
180
181 ;; Map action. We ignore all events which cannot be mapped.
182 (setq action
183 (cond
184 ;; gfilenotify.
185 ((memq action '(attribute-changed changed created deleted))
186 action)
187 ((eq action 'moved)
188 (setq file1 (file-notify--event-file1-name event))
189 'renamed)
190
191 ;; inotify, w32notify.
192 ((eq action 'ignored)
193 (setq stopped t actions nil))
194 ((eq action 'attrib) 'attribute-changed)
195 ((memq action '(create added)) 'created)
196 ((memq action '(modify modified)) 'changed)
197 ((memq action '(delete delete-self move-self removed)) 'deleted)
198 ;; Make the event pending.
199 ((memq action '(moved-from renamed-from))
200 (setq file-notify--pending-event
201 `((,desc ,action ,file ,(file-notify--event-cookie event))
202 ,callback))
203 nil)
204 ;; Look for pending event.
205 ((memq action '(moved-to renamed-to))
206 (if (null file-notify--pending-event)
207 'created
208 (setq file1 file
209 file (file-notify--event-file-name
210 (car file-notify--pending-event)))
211 ;; If the source is handled by another watch, we
212 ;; must fire the rename event there as well.
213 (when (not (equal (file-notify--descriptor desc)
214 (file-notify--descriptor
215 (caar file-notify--pending-event))))
216 (setq pending-event
217 `((,(caar file-notify--pending-event)
218 renamed ,file ,file1)
219 ,(cadr file-notify--pending-event))))
220 (setq file-notify--pending-event nil)
221 'renamed))))
222
223 ;; Apply pending callback.
224 (when pending-event
225 (setcar
226 (car pending-event) (file-notify--descriptor (caar pending-event)))
227 (funcall (cadr pending-event) (car pending-event))
228 (setq pending-event nil))
229
230 ;; Check for stopped.
231 ;;(message "file-notify-callback %S %S" file registered)
232 (setq
233 stopped
234 (or
235 stopped
236 (and
237 (memq action '(deleted renamed))
238 (= (length (cdr registered)) 1)
239 (string-equal
240 (file-name-nondirectory file)
241 (or (file-name-nondirectory (car registered))
242 (car (cadr registered)))))))
243
244 ;; Apply callback.
245 (when (and action
246 (or
247 ;; If there is no relative file name for that watch,
248 ;; we watch the whole directory.
249 (null (nth 0 entry))
250 ;; File matches.
251 (string-equal
252 (nth 0 entry) (file-name-nondirectory file))
253 ;; File1 matches.
254 (and (stringp file1)
255 (string-equal
256 (nth 0 entry) (file-name-nondirectory file1)))))
257 (if file1
258 (funcall
259 callback
260 `(,(file-notify--descriptor desc) ,action ,file ,file1))
261 (funcall
262 callback
263 `(,(file-notify--descriptor desc) ,action ,file)))))
264
265 ;; Modify `file-notify-descriptors'.
266 (when stopped
267 (file-notify--rm-descriptor (file-notify--descriptor desc) file)))))
268
269 ;; `gfilenotify' and `w32notify' return a unique descriptor for every
270 ;; `file-notify-add-watch', while `inotify' returns a unique
271 ;; descriptor per inode only.
272 (defun file-notify-add-watch (file flags callback)
273 "Add a watch for filesystem events pertaining to FILE.
274 This arranges for filesystem events pertaining to FILE to be reported
275 to Emacs. Use `file-notify-rm-watch' to cancel the watch.
276
277 The returned value is a descriptor for the added watch. If the
278 file cannot be watched for some reason, this function signals a
279 `file-notify-error' error.
280
281 FLAGS is a list of conditions to set what will be watched for. It can
282 include the following symbols:
283
284 `change' -- watch for file changes
285 `attribute-change' -- watch for file attributes changes, like
286 permissions or modification time
287
288 If FILE is a directory, `change' watches for file creation or
289 deletion in that directory. This does not work recursively.
290
291 When any event happens, Emacs will call the CALLBACK function passing
292 it a single argument EVENT, which is of the form
293
294 (DESCRIPTOR ACTION FILE [FILE1])
295
296 DESCRIPTOR is the same object as the one returned by this function.
297 ACTION is the description of the event. It could be any one of the
298 following:
299
300 `created' -- FILE was created
301 `deleted' -- FILE was deleted
302 `changed' -- FILE has changed
303 `renamed' -- FILE has been renamed to FILE1
304 `attribute-changed' -- a FILE attribute was changed
305 `stopped' -- watching FILE has been stopped
306
307 FILE is the name of the file whose event is being reported."
308 ;; Check arguments.
309 (unless (stringp file)
310 (signal 'wrong-type-argument `(,file)))
311 (setq file (expand-file-name file))
312 (unless (and (consp flags)
313 (null (delq 'change (delq 'attribute-change (copy-tree flags)))))
314 (signal 'wrong-type-argument `(,flags)))
315 (unless (functionp callback)
316 (signal 'wrong-type-argument `(,callback)))
317
318 (let* ((handler (find-file-name-handler file 'file-notify-add-watch))
319 (dir (directory-file-name
320 (if (file-directory-p file)
321 file
322 (file-name-directory file))))
323 desc func l-flags registered)
324
325 (unless (file-directory-p dir)
326 (signal 'file-notify-error `("Directory does not exist" ,dir)))
327
328 (if handler
329 ;; A file name handler could exist even if there is no local
330 ;; file notification support.
331 (setq desc (funcall
332 handler 'file-notify-add-watch dir flags callback))
333
334 ;; Check, whether Emacs has been compiled with file notification
335 ;; support.
336 (unless file-notify--library
337 (signal 'file-notify-error
338 '("No file notification package available")))
339
340 ;; Determine low-level function to be called.
341 (setq func
342 (cond
343 ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
344 ((eq file-notify--library 'inotify) 'inotify-add-watch)
345 ((eq file-notify--library 'w32notify) 'w32notify-add-watch)))
346
347 ;; Determine respective flags.
348 (if (eq file-notify--library 'gfilenotify)
349 (setq l-flags (append '(watch-mounts send-moved) flags))
350 (when (memq 'change flags)
351 (setq
352 l-flags
353 (cond
354 ((eq file-notify--library 'inotify)
355 '(create delete delete-self modify move-self move))
356 ((eq file-notify--library 'w32notify)
357 '(file-name directory-name size last-write-time)))))
358 (when (memq 'attribute-change flags)
359 (push (cond
360 ((eq file-notify--library 'inotify) 'attrib)
361 ((eq file-notify--library 'w32notify) 'attributes))
362 l-flags)))
363
364 ;; Call low-level function.
365 (setq desc (funcall func dir l-flags 'file-notify-callback)))
366
367 ;; Modify `file-notify-descriptors'.
368 (setq registered (gethash desc file-notify-descriptors))
369 (puthash
370 desc
371 `(,dir
372 (,(unless (file-directory-p file) (file-name-nondirectory file))
373 . ,callback)
374 . ,(cdr registered))
375 file-notify-descriptors)
376
377 ;; Return descriptor.
378 (file-notify--descriptor desc)))
379
380 (defun file-notify-rm-watch (descriptor)
381 "Remove an existing watch specified by its DESCRIPTOR.
382 DESCRIPTOR should be an object returned by `file-notify-add-watch'."
383 (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
384 (file (if (consp descriptor) (cdr descriptor)))
385 (registered (gethash desc file-notify-descriptors))
386 (dir (car registered))
387 (handler (and (stringp dir)
388 (find-file-name-handler dir 'file-notify-rm-watch))))
389
390 (when (stringp dir)
391 ;; Call low-level function.
392 (when (or (not file)
393 (and (= (length (cdr registered)) 1)
394 (assoc file (cdr registered))))
395 (condition-case nil
396 (if handler
397 ;; A file name handler could exist even if there is no local
398 ;; file notification support.
399 (funcall handler 'file-notify-rm-watch desc)
400
401 (funcall
402 (cond
403 ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
404 ((eq file-notify--library 'inotify) 'inotify-rm-watch)
405 ((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
406 desc))
407 (file-notify-error nil)))
408
409 ;; Modify `file-notify-descriptors'.
410 (file-notify--rm-descriptor descriptor))))
411
412 (defun file-notify-valid-p (descriptor)
413 "Check a watch specified by its DESCRIPTOR.
414 DESCRIPTOR should be an object returned by `file-notify-add-watch'."
415 (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
416 (file (if (consp descriptor) (cdr descriptor)))
417 (registered (gethash desc file-notify-descriptors))
418 (dir (car registered))
419 handler)
420
421 (when (stringp dir)
422 (setq handler (find-file-name-handler dir 'file-notify-valid-p))
423
424 (and (or ;; It is a directory.
425 (not file)
426 ;; The file is registered.
427 (assoc file (cdr registered)))
428 (if handler
429 ;; A file name handler could exist even if there is no
430 ;; local file notification support.
431 (funcall handler 'file-notify-valid-p descriptor)
432 (funcall
433 (cond
434 ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
435 ((eq file-notify--library 'inotify) 'inotify-valid-p)
436 ((eq file-notify--library 'w32notify) 'w32notify-valid-p))
437 desc))
438 t))))
439
440 ;; The end:
441 (provide 'filenotify)
442
443 ;;; filenotify.el ends here