aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Albinus2013-07-04 11:39:36 +0200
committerMichael Albinus2013-07-04 11:39:36 +0200
commit864c58ca5f32d564d79707b862cfba0b9cf7107e (patch)
tree257ade009531572963b7c987a12f4b05212b924c
parent86dfb7a8155ba4705f6bdc8e9be3a38388ad207e (diff)
downloademacs-864c58ca5f32d564d79707b862cfba0b9cf7107e.tar.gz
emacs-864c58ca5f32d564d79707b862cfba0b9cf7107e.zip
* filenotify.el: New package.
* autorevert.el (top): Require filenotify.el. (auto-revert-notify-enabled): Remove. Use `file-notify-support' instead. (auto-revert-notify-rm-watch, auto-revert-notify-add-watch) (auto-revert-notify-handler): Use `file-notify-*' functions. * subr.el (file-notify-handle-event): Move function to filenotify.el. * net/tramp.el (tramp-file-name-for-operation): Handle `file-notify-add-watch' and `file-notify-rm-watch'. * net/tramp-sh.el (tramp-sh-file-name-handler-alist): Add handler for `file-notify-add-watch' and `file-notify-rm-watch'. (tramp-process-sentinel): Improve trace. (tramp-sh-handle-file-notify-add-watch) (tramp-sh-file-notify-process-filter) (tramp-sh-handle-file-notify-rm-watch) (tramp-get-remote-inotifywait): New defuns.
-rw-r--r--lisp/ChangeLog27
-rw-r--r--lisp/autorevert.el158
-rw-r--r--lisp/filenotify.el324
-rw-r--r--lisp/net/tramp-sh.el68
-rw-r--r--lisp/net/tramp.el6
-rw-r--r--lisp/subr.el14
6 files changed, 474 insertions, 123 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 8a54c5ac370..7921f77ca05 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,26 @@
12013-07-04 Michael Albinus <michael.albinus@gmx.de>
2
3 * filenotify.el: New package.
4
5 * autorevert.el (top): Require filenotify.el.
6 (auto-revert-notify-enabled): Remove. Use `file-notify-support'
7 instead.
8 (auto-revert-notify-rm-watch, auto-revert-notify-add-watch)
9 (auto-revert-notify-handler): Use `file-notify-*' functions.
10
11 * subr.el (file-notify-handle-event): Move function to filenotify.el.
12
13 * net/tramp.el (tramp-file-name-for-operation): Handle
14 `file-notify-add-watch' and `file-notify-rm-watch'.
15
16 * net/tramp-sh.el (tramp-sh-file-name-handler-alist): Add handler
17 for `file-notify-add-watch' and `file-notify-rm-watch'.
18 (tramp-process-sentinel): Improve trace.
19 (tramp-sh-handle-file-notify-add-watch)
20 (tramp-sh-file-notify-process-filter)
21 (tramp-sh-handle-file-notify-rm-watch)
22 (tramp-get-remote-inotifywait): New defuns.
23
12013-07-03 Juri Linkov <juri@jurta.org> 242013-07-03 Juri Linkov <juri@jurta.org>
2 25
3 * buff-menu.el (Buffer-menu-multi-occur): Add args and move the 26 * buff-menu.el (Buffer-menu-multi-occur): Add args and move the
@@ -299,12 +322,12 @@
299 322
3002013-06-25 Rüdiger Sonderfeld <ruediger@c-plusplus.de> 3232013-06-25 Rüdiger Sonderfeld <ruediger@c-plusplus.de>
301 324
302 * lisp/textmodes/bibtex.el (bibtex-generate-url-list): Add support 325 * textmodes/bibtex.el (bibtex-generate-url-list): Add support
303 for DOI URLs. 326 for DOI URLs.
304 327
3052013-06-25 Rüdiger Sonderfeld <ruediger@c-plusplus.de> 3282013-06-25 Rüdiger Sonderfeld <ruediger@c-plusplus.de>
306 329
307 * lisp/textmodes/bibtex.el (bibtex-mode, bibtex-set-dialect): 330 * textmodes/bibtex.el (bibtex-mode, bibtex-set-dialect):
308 Update imenu-support when dialect changes. 331 Update imenu-support when dialect changes.
309 332
3102013-06-25 Leo Liu <sdl.web@gmail.com> 3332013-06-25 Leo Liu <sdl.web@gmail.com>
diff --git a/lisp/autorevert.el b/lisp/autorevert.el
index 4a6d4cb4cc0..00e88fc4a3d 100644
--- a/lisp/autorevert.el
+++ b/lisp/autorevert.el
@@ -103,6 +103,7 @@
103 103
104(eval-when-compile (require 'cl-lib)) 104(eval-when-compile (require 'cl-lib))
105(require 'timer) 105(require 'timer)
106(require 'filenotify)
106 107
107;; Custom Group: 108;; Custom Group:
108;; 109;;
@@ -270,21 +271,17 @@ This variable becomes buffer local when set in any fashion.")
270 :type 'boolean 271 :type 'boolean
271 :version "24.4") 272 :version "24.4")
272 273
273(defconst auto-revert-notify-enabled 274(defcustom auto-revert-use-notify (and file-notify-support t)
274 (or (featurep 'gfilenotify) (featurep 'inotify) (featurep 'w32notify))
275 "Non-nil when Emacs has been compiled with file notification support.")
276
277(defcustom auto-revert-use-notify auto-revert-notify-enabled
278 "If non-nil Auto Revert Mode uses file notification functions. 275 "If non-nil Auto Revert Mode uses file notification functions.
279This requires Emacs being compiled with file notification 276This requires Emacs being compiled with file notification
280support (see `auto-revert-notify-enabled'). You should set this 277support (see `file-notify-support'). You should set this variable
281variable through Custom." 278through Custom."
282 :group 'auto-revert 279 :group 'auto-revert
283 :type 'boolean 280 :type 'boolean
284 :set (lambda (variable value) 281 :set (lambda (variable value)
285 (set-default variable (and auto-revert-notify-enabled value)) 282 (set-default variable (and file-notify-support value))
286 (unless (symbol-value variable) 283 (unless (symbol-value variable)
287 (when auto-revert-notify-enabled 284 (when file-notify-support
288 (dolist (buf (buffer-list)) 285 (dolist (buf (buffer-list))
289 (with-current-buffer buf 286 (with-current-buffer buf
290 (when (symbol-value 'auto-revert-notify-watch-descriptor) 287 (when (symbol-value 'auto-revert-notify-watch-descriptor)
@@ -502,12 +499,7 @@ will use an up-to-date value of `auto-revert-interval'"
502 (puthash key value auto-revert-notify-watch-descriptor-hash-list) 499 (puthash key value auto-revert-notify-watch-descriptor-hash-list)
503 (remhash key auto-revert-notify-watch-descriptor-hash-list) 500 (remhash key auto-revert-notify-watch-descriptor-hash-list)
504 (ignore-errors 501 (ignore-errors
505 (funcall 502 (file-notify-rm-watch auto-revert-notify-watch-descriptor)))))
506 (cond
507 ((fboundp 'gfile-rm-watch) 'gfile-rm-watch)
508 ((fboundp 'inotify-rm-watch) 'inotify-rm-watch)
509 ((fboundp 'w32notify-rm-watch) 'w32notify-rm-watch))
510 auto-revert-notify-watch-descriptor)))))
511 auto-revert-notify-watch-descriptor-hash-list) 503 auto-revert-notify-watch-descriptor-hash-list)
512 (remove-hook 'kill-buffer-hook 'auto-revert-notify-rm-watch)) 504 (remove-hook 'kill-buffer-hook 'auto-revert-notify-rm-watch))
513 (setq auto-revert-notify-watch-descriptor nil 505 (setq auto-revert-notify-watch-descriptor nil
@@ -522,100 +514,58 @@ will use an up-to-date value of `auto-revert-interval'"
522 514
523 (when (and buffer-file-name auto-revert-use-notify 515 (when (and buffer-file-name auto-revert-use-notify
524 (not auto-revert-notify-watch-descriptor)) 516 (not auto-revert-notify-watch-descriptor))
525 (let ((func 517 (setq auto-revert-notify-watch-descriptor
526 (cond 518 (ignore-errors
527 ((fboundp 'gfile-add-watch) 'gfile-add-watch) 519 (file-notify-add-watch
528 ((fboundp 'inotify-add-watch) 'inotify-add-watch) 520 (expand-file-name buffer-file-name default-directory)
529 ((fboundp 'w32notify-add-watch) 'w32notify-add-watch))) 521 '(change attribute-change) 'auto-revert-notify-handler)))
530 (aspect 522 (if auto-revert-notify-watch-descriptor
531 (cond 523 (progn
532 ((fboundp 'gfile-add-watch) '(watch-mounts)) 524 (puthash
533 ;; `attrib' is needed for file modification time. 525 auto-revert-notify-watch-descriptor
534 ((fboundp 'inotify-add-watch) '(attrib create modify moved-to)) 526 (cons (current-buffer)
535 ((fboundp 'w32notify-add-watch) '(size last-write-time)))) 527 (gethash auto-revert-notify-watch-descriptor
536 (file (if (or (fboundp 'gfile-add-watch) (fboundp 'inotify-add-watch)) 528 auto-revert-notify-watch-descriptor-hash-list))
537 (directory-file-name (expand-file-name default-directory)) 529 auto-revert-notify-watch-descriptor-hash-list)
538 (buffer-file-name)))) 530 (add-hook (make-local-variable 'kill-buffer-hook)
539 (setq auto-revert-notify-watch-descriptor 531 'auto-revert-notify-rm-watch))
540 (ignore-errors 532 ;; Fallback to file checks.
541 (funcall func file aspect 'auto-revert-notify-handler))) 533 (set (make-local-variable 'auto-revert-use-notify) nil))))
542 (if auto-revert-notify-watch-descriptor
543 (progn
544 (puthash
545 auto-revert-notify-watch-descriptor
546 (cons (current-buffer)
547 (gethash auto-revert-notify-watch-descriptor
548 auto-revert-notify-watch-descriptor-hash-list))
549 auto-revert-notify-watch-descriptor-hash-list)
550 (add-hook (make-local-variable 'kill-buffer-hook)
551 'auto-revert-notify-rm-watch))
552 ;; Fallback to file checks.
553 (set (make-local-variable 'auto-revert-use-notify) nil)))))
554
555(defun auto-revert-notify-event-p (event)
556 "Check that event is a file notification event."
557 (and (listp event)
558 (cond ((featurep 'gfilenotify)
559 (and (>= (length event) 3) (stringp (nth 2 event))))
560 ((featurep 'inotify)
561 (= (length event) 4))
562 ((featurep 'w32notify)
563 (and (= (length event) 3) (stringp (nth 2 event)))))))
564
565(defun auto-revert-notify-event-descriptor (event)
566 "Return watch descriptor of file notification event, or nil."
567 (and (auto-revert-notify-event-p event) (car event)))
568
569(defun auto-revert-notify-event-action (event)
570 "Return action of file notification event, or nil."
571 (and (auto-revert-notify-event-p event) (nth 1 event)))
572
573(defun auto-revert-notify-event-file-name (event)
574 "Return file name of file notification event, or nil."
575 (and (auto-revert-notify-event-p event)
576 (cond ((featurep 'gfilenotify) (nth 2 event))
577 ((featurep 'inotify) (nth 3 event))
578 ((featurep 'w32notify) (nth 2 event)))))
579 534
580(defun auto-revert-notify-handler (event) 535(defun auto-revert-notify-handler (event)
581 "Handle an EVENT returned from file notification." 536 "Handle an EVENT returned from file notification."
582 (when (auto-revert-notify-event-p event) 537 (ignore-errors
583 (let* ((descriptor (auto-revert-notify-event-descriptor event)) 538 (let* ((descriptor (car event))
584 (action (auto-revert-notify-event-action event)) 539 (action (nth 1 event))
585 (file (auto-revert-notify-event-file-name event)) 540 (file (nth 2 event))
541 (file1 (nth 3 event)) ;; Target of `renamed'.
586 (buffers (gethash descriptor 542 (buffers (gethash descriptor
587 auto-revert-notify-watch-descriptor-hash-list))) 543 auto-revert-notify-watch-descriptor-hash-list)))
588 (ignore-errors 544 ;; Check, that event is meant for us.
589 ;; Check, that event is meant for us. 545 (cl-assert descriptor)
590 ;; TODO: Filter events which stop watching, like `move' or `removed'. 546 ;; We do not handle `deleted', because nothing has to be refreshed.
591 (cl-assert descriptor) 547 (cl-assert (memq action '(attribute-changed changed created renamed)) t)
592 (cond 548 ;; Since we watch a directory, a file name must be returned.
593 ((featurep 'gfilenotify) 549 (cl-assert (stringp file))
594 (cl-assert (memq action '(attribute-changed changed created deleted 550 (when (eq action 'renamed) (cl-assert (stringp file1)))
595 ;; FIXME: I keep getting this action, so I 551 ;; Loop over all buffers, in order to find the intended one.
596 ;; added it here, but I have no idea what 552 (dolist (buffer buffers)
597 ;; I'm doing. --Stef 553 (when (buffer-live-p buffer)
598 changes-done-hint)) 554 (with-current-buffer buffer
599 t)) 555 (when (and (stringp buffer-file-name)
600 ((featurep 'inotify) 556 (or
601 (cl-assert (or (memq 'attrib action) 557 (and (memq action '(attribute-changed changed created))
602 (memq 'create action) 558 (string-equal
603 (memq 'modify action) 559 (file-name-nondirectory file)
604 (memq 'moved-to action)))) 560 (file-name-nondirectory buffer-file-name)))
605 ((featurep 'w32notify) (cl-assert (eq 'modified action)))) 561 (and (eq action 'renamed)
606 ;; Since we watch a directory, a file name must be returned. 562 (string-equal
607 (cl-assert (stringp file)) 563 (file-name-nondirectory file1)
608 (dolist (buffer buffers) 564 (file-name-nondirectory buffer-file-name)))))
609 (when (buffer-live-p buffer) 565 ;; Mark buffer modified.
610 (with-current-buffer buffer 566 (setq auto-revert-notify-modified-p t)
611 (when (and (stringp buffer-file-name) 567 ;; No need to check other buffers.
612 (string-equal 568 (cl-return))))))))
613 (file-name-nondirectory file)
614 (file-name-nondirectory buffer-file-name)))
615 ;; Mark buffer modified.
616 (setq auto-revert-notify-modified-p t)
617 ;; No need to check other buffers.
618 (cl-return)))))))))
619 569
620(defun auto-revert-active-p () 570(defun auto-revert-active-p ()
621 "Check if auto-revert is active (in current buffer or globally)." 571 "Check if auto-revert is active (in current buffer or globally)."
diff --git a/lisp/filenotify.el b/lisp/filenotify.el
new file mode 100644
index 00000000000..e170db2dd5f
--- /dev/null
+++ b/lisp/filenotify.el
@@ -0,0 +1,324 @@
1;;; filenotify.el --- watch files for changes on disk
2
3;; Copyright (C) 2013 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;;;###autoload
31(defconst file-notify-support
32 (cond
33 ((featurep 'gfilenotify) 'gfilenotify)
34 ((featurep 'inotify) 'inotify)
35 ((featurep 'w32notify) 'w32notify))
36 "Non-nil when Emacs has been compiled with file notification support.
37The value is the name of the low-level file notification package
38to be used for local file systems. Remote file notifications
39could use another implementation.")
40
41(defvar file-notify-descriptors (make-hash-table :test 'equal)
42 "Hash table for registered file notification descriptors.
43A key in this hash table is the descriptor as returned from
44`gfilenotify', `inotify', `w32notify' or a file name handler.
45The value in the hash table is the cons cell (DIR FILE CALLBACK).")
46
47;; This function is used by `gfilenotify', `inotify' and `w32notify' events.
48;;;###autoload
49(defun file-notify-handle-event (event)
50 "Handle file system monitoring event.
51If EVENT is a filewatch event, call its callback.
52Otherwise, signal a `file-notify-error'."
53 (interactive "e")
54 (if (and (eq (car event) 'file-notify)
55 (>= (length event) 3))
56 (funcall (nth 2 event) (nth 1 event))
57 (signal 'file-notify-error
58 (cons "Not a valid file-notify event" event))))
59
60(defvar file-notify--pending-events nil
61 "List of pending file notification events for a future `renamed' action.
62The entries are a list (DESCRIPTOR ACTION FILE COOKIE). ACTION
63is either `moved-from' or `renamed-from'.")
64
65(defun file-notify--event-file-name (event)
66 "Return file name of file notification event, or nil."
67 (expand-file-name
68 (or (and (stringp (nth 2 event)) (nth 2 event)) "")
69 (car (gethash (car event) file-notify-descriptors))))
70
71;; Only `gfilenotify' could return two file names.
72(defun file-notify--event-file1-name (event)
73 "Return second file name of file notification event, or nil.
74This is available in case a file has been moved."
75 (and (stringp (nth 3 event))
76 (expand-file-name
77 (nth 3 event) (car (gethash (car event) file-notify-descriptors)))))
78
79;; Cookies are offered by `inotify' only.
80(defun file-notify--event-cookie (event)
81 "Return cookie of file notification event, or nil.
82This is available in case a file has been moved."
83 (nth 3 event))
84
85;; The callback function used to map between specific flags of the
86;; respective file notifications, and the ones we return.
87(defun file-notify-callback (event)
88 "Handle an EVENT returned from file notification.
89EVENT is the same one as in `file-notify-handle-event' except the
90car of that event, which is the symbol `file-notify'."
91 (let* ((desc (car event))
92 (registered (gethash desc file-notify-descriptors))
93 (pending-event (assoc desc file-notify--pending-events))
94 (actions (nth 1 event))
95 (file (file-notify--event-file-name event))
96 file1 cookie callback)
97
98 ;; Make actions a list.
99 (unless (consp actions) (setq actions (cons actions nil)))
100
101 ;; Check, that event is meant for us.
102 (unless (setq callback (nth 2 registered))
103 (setq actions nil))
104
105 ;; Loop over actions. In fact, more than one action happens only
106 ;; for `inotify'.
107 (dolist (action actions)
108
109 ;; Send pending event, if it doesn't match.
110 (when (and pending-event
111 ;; The cookie doesn't match.
112 (not (eq (file-notify--event-cookie pending-event)
113 (file-notify--event-cookie event)))
114 (or
115 ;; inotify.
116 (and (eq (nth 1 pending-event) 'moved-from)
117 (not (eq action 'moved-to)))
118 ;; w32notify.
119 (and (eq (nth 1 pending-event) 'renamed-from)
120 (not (eq action 'renamed-to)))))
121 (funcall callback
122 (list desc 'deleted
123 (file-notify--event-file-name pending-event)))
124 (setq file-notify--pending-events
125 (delete pending-event file-notify--pending-events)))
126
127 ;; Map action. We ignore all events which cannot be mapped.
128 (setq action
129 (cond
130 ;; gfilenotify.
131 ((memq action '(attribute-changed changed created deleted)) action)
132 ((eq action 'moved)
133 (setq file1 (file-notify--event-file1-name event))
134 'renamed)
135
136 ;; inotify.
137 ((eq action 'attrib) 'attribute-changed)
138 ((eq action 'create) 'created)
139 ((eq action 'modify) 'changed)
140 ((memq action '(delete 'delete-self move-self)) 'deleted)
141 ;; Make the event pending.
142 ((eq action 'moved-from)
143 (add-to-list 'file-notify--pending-events
144 (list desc action file
145 (file-notify--event-cookie event)))
146 nil)
147 ;; Look for pending event.
148 ((eq action 'moved-to)
149 (if (null pending-event)
150 'created
151 (setq file1 file
152 file (file-notify--event-file-name pending-event)
153 file-notify--pending-events
154 (delete pending-event file-notify--pending-events))
155 'renamed))
156
157 ;; w32notify.
158 ((eq action 'added) 'created)
159 ((eq action 'modified) 'changed)
160 ((eq action 'removed) 'deleted)
161 ;; Make the event pending.
162 ((eq 'renamed-from action)
163 (add-to-list 'file-notify--pending-events
164 (list desc action file
165 (file-notify--event-cookie event)))
166 nil)
167 ;; Look for pending event.
168 ((eq 'renamed-to action)
169 (if (null pending-event)
170 'created
171 (setq file1 file
172 file (file-notify--event-file-name pending-event)
173 file-notify--pending-events
174 (delete pending-event file-notify--pending-events))
175 'renamed))))
176
177 ;; Apply callback.
178 (when (and action
179 (or
180 ;; If there is no relative file name for that watch,
181 ;; we watch the whole directory.
182 (null (nth 1 registered))
183 ;; File matches.
184 (string-equal
185 (nth 1 registered) (file-name-nondirectory file))
186 ;; File1 matches.
187 (and (stringp file1)
188 (string-equal
189 (nth 1 registered) (file-name-nondirectory file1)))))
190 (if file1
191 (funcall callback (list desc action file file1))
192 (funcall callback (list desc action file)))))))
193
194(defun file-notify-add-watch (file flags callback)
195 "Add a watch for filesystem events pertaining to FILE.
196This arranges for filesystem events pertaining to FILE to be reported
197to Emacs. Use `file-notify-rm-watch' to cancel the watch.
198
199The returned value is a descriptor for the added watch. If the
200file cannot be watched for some reason, this function signals a
201`file-notify-error' error.
202
203FLAGS is a list of conditions to set what will be watched for. It can
204include the following symbols:
205
206 `change' -- watch for file changes
207 `attribute-change' -- watch for file attributes changes, like
208 permissions or modification time
209
210If FILE is a directory, 'change' watches for file creation or
211deletion in that directory.
212
213When any event happens, Emacs will call the CALLBACK function passing
214it a single argument EVENT, which is of the form
215
216 (DESCRIPTOR ACTION FILE [FILE1])
217
218DESCRIPTOR is the same object as the one returned by this function.
219ACTION is the description of the event. It could be any one of the
220following:
221
222 `created' -- FILE was created
223 `deleted' -- FILE was deleted
224 `changed' -- FILE has changed
225 `renamed' -- FILE has been renamed to FILE1
226 `attribute-changed' -- a FILE attribute was changed
227
228FILE is the name of the file whose event is being reported."
229 ;; Check arguments.
230 (unless (stringp file)
231 (signal 'wrong-type-argument (list file)))
232 (setq file (expand-file-name file))
233 (unless (and (consp flags)
234 (null (delq 'change (delq 'attribute-change (copy-tree flags)))))
235 (signal 'wrong-type-argument (list flags)))
236 (unless (functionp callback)
237 (signal 'wrong-type-argument (list callback)))
238
239 (let* ((handler (find-file-name-handler file 'file-notify-add-watch))
240 (dir (directory-file-name
241 (if (or (and (not handler) (eq file-notify-support 'w32notify))
242 (file-directory-p file))
243 file
244 (file-name-directory file))))
245 desc func l-flags)
246
247 ;; Check, whether this has been registered already.
248; (maphash
249; (lambda (key value)
250; (when (equal (cons file callback) value) (setq desc key)))
251; file-notify-descriptors)
252
253 (unless desc
254 (if handler
255 ;; A file name handler could exist even if there is no local
256 ;; file notification support.
257 (setq desc (funcall
258 handler 'file-notify-add-watch dir flags callback))
259
260 ;; Check, whether Emacs has been compiled with file
261 ;; notification support.
262 (unless file-notify-support
263 (signal 'file-notify-error
264 '("No file notification package available")))
265
266 ;; Determine low-level function to be called.
267 (setq func (cond
268 ((eq file-notify-support 'gfilenotify) 'gfile-add-watch)
269 ((eq file-notify-support 'inotify) 'inotify-add-watch)
270 ((eq file-notify-support 'w32notify) 'w32notify-add-watch)))
271
272 ;; Determine respective flags.
273 (if (eq file-notify-support 'gfilenotify)
274 (setq l-flags '(watch-mounts send-moved))
275 (when (memq 'change flags)
276 (setq
277 l-flags
278 (cond
279 ((eq file-notify-support 'inotify) '(create modify move delete))
280 ((eq file-notify-support 'w32notify)
281 '(file-name directory-name size last-write-time)))))
282 (when (memq 'attribute-change flags)
283 (add-to-list
284 'l-flags
285 (cond
286 ((eq file-notify-support 'inotify) 'attrib)
287 ((eq file-notify-support 'w32notify) 'attributes)))))
288
289 ;; Call low-level function.
290 (setq desc (funcall func dir l-flags 'file-notify-callback))))
291
292 ;; Return descriptor.
293 (puthash desc
294 (list (directory-file-name
295 (if (file-directory-p dir) dir (file-name-directory dir)))
296 (unless (file-directory-p file)
297 (file-name-nondirectory file))
298 callback)
299 file-notify-descriptors)
300 desc))
301
302(defun file-notify-rm-watch (descriptor)
303 "Remove an existing watch specified by its DESCRIPTOR.
304DESCRIPTOR should be an object returned by `file-notify-add-watch'."
305 (let ((file (car (gethash descriptor file-notify-descriptors)))
306 handler)
307
308 (when (stringp file)
309 (setq handler (find-file-name-handler file 'file-notify-rm-watch))
310 (if handler
311 (funcall handler 'file-notify-rm-watch descriptor)
312 (funcall
313 (cond
314 ((eq file-notify-support 'gfilenotify) 'gfile-rm-watch)
315 ((eq file-notify-support 'inotify) 'inotify-rm-watch)
316 ((eq file-notify-support 'w32notify) 'w32notify-rm-watch))
317 descriptor)))
318
319 (remhash descriptor file-notify-descriptors)))
320
321;; The end:
322(provide 'filenotify)
323
324;;; filenotify.el ends here
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index 387084a807b..f402e2b2774 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -862,7 +862,9 @@ of command line.")
862 (set-file-selinux-context . tramp-sh-handle-set-file-selinux-context) 862 (set-file-selinux-context . tramp-sh-handle-set-file-selinux-context)
863 (file-acl . tramp-sh-handle-file-acl) 863 (file-acl . tramp-sh-handle-file-acl)
864 (set-file-acl . tramp-sh-handle-set-file-acl) 864 (set-file-acl . tramp-sh-handle-set-file-acl)
865 (vc-registered . tramp-sh-handle-vc-registered)) 865 (vc-registered . tramp-sh-handle-vc-registered)
866 (file-notify-add-watch . tramp-sh-handle-file-notify-add-watch)
867 (file-notify-rm-watch . tramp-sh-handle-file-notify-rm-watch))
866 "Alist of handler functions. 868 "Alist of handler functions.
867Operations not mentioned here will be handled by the normal Emacs functions.") 869Operations not mentioned here will be handled by the normal Emacs functions.")
868 870
@@ -2669,7 +2671,7 @@ the result will be a local, non-Tramp, filename."
2669 (unless (memq (process-status proc) '(run open)) 2671 (unless (memq (process-status proc) '(run open))
2670 (let ((vec (tramp-get-connection-property proc "vector" nil))) 2672 (let ((vec (tramp-get-connection-property proc "vector" nil)))
2671 (when vec 2673 (when vec
2672 (tramp-message vec 5 "Sentinel called: `%s' `%s'" proc event) 2674 (tramp-message vec 5 "Sentinel called: `%S' `%s'" proc event)
2673 (tramp-flush-connection-property proc) 2675 (tramp-flush-connection-property proc)
2674 (tramp-flush-directory-property vec ""))))) 2676 (tramp-flush-directory-property vec "")))))
2675 2677
@@ -3376,6 +3378,63 @@ Fall back to normal file name handler if no Tramp handler exists."
3376 ;; Default file name handlers, we don't care. 3378 ;; Default file name handlers, we don't care.
3377 (t (tramp-run-real-handler operation args))))))) 3379 (t (tramp-run-real-handler operation args)))))))
3378 3380
3381;; We use inotify for implementation. It is more likely to exist than glib.
3382(defun tramp-sh-handle-file-notify-add-watch (file-name flags callback)
3383 "Like `file-notify-add-watch' for Tramp files."
3384 (setq file-name (expand-file-name file-name))
3385 (with-parsed-tramp-file-name file-name nil
3386 (let* ((default-directory (file-name-directory file-name))
3387 (command (tramp-get-remote-inotifywait v))
3388 (events
3389 (cond
3390 ((and (memq 'change flags) (memq 'attribute-change flags))
3391 "create,modify,move,delete,attrib")
3392 ((memq 'change flags) "create,modify,move,delete")
3393 ((memq 'attribute-change flags) "attrib")))
3394 (p (and command
3395 (start-file-process
3396 "inotifywait" (generate-new-buffer " *inotifywait*")
3397 command "-mq" "-e" events localname))))
3398 ;; Return the process object as watch-descriptor.
3399 (if (not (processp p))
3400 (tramp-error
3401 v 'file-notify-error "`inotifywait' not found on remote host")
3402 (tramp-compat-set-process-query-on-exit-flag p nil)
3403 (set-process-filter p 'tramp-sh-file-notify-process-filter)
3404 p))))
3405
3406(defun tramp-sh-file-notify-process-filter (proc string)
3407 "Read output from \"inotifywait\" and add corresponding file-notify events."
3408 (tramp-message proc 6 (format "%S\n%s" proc string))
3409 (dolist (line (split-string string "[\n\r]+" 'omit-nulls))
3410 ;; Check, whether there is a problem.
3411 (unless
3412 (string-match
3413 "^[^[:blank:]]+[[:blank:]]+\\([^[:blank:]]+\\)+\\([[:blank:]]+\\([^[:blank:]]+\\)\\)?[[:blank:]]*$" line)
3414 (tramp-error proc 'file-notify-error "%s" line))
3415
3416 ;; Usually, we would add an Emacs event now. Unfortunately,
3417 ;; `unread-command-events' does not accept several events at once.
3418 ;; Therefore, we apply the callback directly.
3419 (let* ((object
3420 (list
3421 proc
3422 (mapcar
3423 (lambda (x)
3424 (intern-soft (replace-regexp-in-string "_" "-" (downcase x))))
3425 (split-string (match-string 1 line) "," 'omit-nulls))
3426 (match-string 3 line))))
3427 (tramp-compat-funcall 'file-notify-callback object))))
3428
3429(defvar file-notify-descriptors)
3430(defun tramp-sh-handle-file-notify-rm-watch (proc)
3431 "Like `file-notify-rm-watch' for Tramp files."
3432 ;; The descriptor must be a process object.
3433 (unless (and (processp proc) (gethash proc file-notify-descriptors))
3434 (tramp-error proc 'file-notify-error "Not a valid descriptor %S" proc))
3435 (tramp-message proc 6 (format "Kill %S" proc))
3436 (kill-process proc))
3437
3379;;; Internal Functions: 3438;;; Internal Functions:
3380 3439
3381(defun tramp-maybe-send-script (vec script name) 3440(defun tramp-maybe-send-script (vec script name)
@@ -4864,6 +4923,11 @@ Return ATTR."
4864 (tramp-message vec 5 "Finding a suitable `trash' command") 4923 (tramp-message vec 5 "Finding a suitable `trash' command")
4865 (tramp-find-executable vec "trash" (tramp-get-remote-path vec)))) 4924 (tramp-find-executable vec "trash" (tramp-get-remote-path vec))))
4866 4925
4926(defun tramp-get-remote-inotifywait (vec)
4927 (with-tramp-connection-property vec "inotifywait"
4928 (tramp-message vec 5 "Finding a suitable `inotifywait' command")
4929 (tramp-find-executable vec "inotifywait" (tramp-get-remote-path vec) t t)))
4930
4867(defun tramp-get-remote-id (vec) 4931(defun tramp-get-remote-id (vec)
4868 (with-tramp-connection-property vec "id" 4932 (with-tramp-connection-property vec "id"
4869 (tramp-message vec 5 "Finding POSIX `id' command") 4933 (tramp-message vec 5 "Finding POSIX `id' command")
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index 4ec3a4b7829..8b19a7ca5d3 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -1964,7 +1964,7 @@ ARGS are the arguments OPERATION has been called with."
1964 ;; Emacs 22+ only. 1964 ;; Emacs 22+ only.
1965 'set-file-times 1965 'set-file-times
1966 ;; Emacs 24+ only. 1966 ;; Emacs 24+ only.
1967 'file-acl 'file-selinux-context 1967 'file-acl 'file-notify-add-watch 'file-selinux-context
1968 'set-file-acl 'set-file-selinux-context 1968 'set-file-acl 'set-file-selinux-context
1969 ;; XEmacs only. 1969 ;; XEmacs only.
1970 'abbreviate-file-name 'create-file-buffer 1970 'abbreviate-file-name 'create-file-buffer
@@ -2018,6 +2018,10 @@ ARGS are the arguments OPERATION has been called with."
2018 ;; XEmacs only. 2018 ;; XEmacs only.
2019 'dired-print-file 'dired-shell-call-process)) 2019 'dired-print-file 'dired-shell-call-process))
2020 default-directory) 2020 default-directory)
2021 ;; PROC.
2022 ((eq operation 'file-notify-rm-watch)
2023 (with-current-buffer (process-buffer (nth 0 args))
2024 default-directory))
2021 ;; Unknown file primitive. 2025 ;; Unknown file primitive.
2022 (t (error "unknown file I/O primitive: %s" operation)))) 2026 (t (error "unknown file I/O primitive: %s" operation))))
2023 2027
diff --git a/lisp/subr.el b/lisp/subr.el
index 55cdcb45f50..f8262eb7f6d 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -4496,20 +4496,6 @@ convenience wrapper around `make-progress-reporter' and friends.
4496 nil ,@(cdr (cdr spec))))) 4496 nil ,@(cdr (cdr spec)))))
4497 4497
4498 4498
4499;;;; Support for watching filesystem events.
4500
4501(defun file-notify-handle-event (event)
4502 "Handle file system monitoring event.
4503If EVENT is a filewatch event, call its callback.
4504Otherwise, signal a `filewatch-error'."
4505 (interactive "e")
4506 (if (and (eq (car event) 'file-notify)
4507 (>= (length event) 3))
4508 (funcall (nth 2 event) (nth 1 event))
4509 (signal 'filewatch-error
4510 (cons "Not a valid file-notify event" event))))
4511
4512
4513;;;; Comparing version strings. 4499;;;; Comparing version strings.
4514 4500
4515(defconst version-separator "." 4501(defconst version-separator "."