From dbdc459a48091f5953faf14bcaaa7e6d37fbf024 Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Sun, 6 Sep 2015 14:21:56 +0200 Subject: [PATCH] File notifications: Support renaming over directory boundaries * lisp/filenotify.el (file-notify-handle-event): (file-notify--pending-event): Adapt docstring. (file-notify--descriptor, file-notify-callback): Reimplement in order to support renaming over directory boundaries. (file-notify-add-watch): Adapt `file-notify--descriptor' call. * doc/lispref/os.texi (File Notifications): Remove limitation of file renaming to the same directory. --- doc/lispref/os.texi | 22 +-------- lisp/filenotify.el | 114 +++++++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 80 deletions(-) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index 8d7177dc1d5..f7d4117673a 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -2675,32 +2675,14 @@ being reported. For example: @end example Whether the action @code{renamed} is returned, depends on the used -watch library. It can be expected, when a directory is watched, and -both @var{file} and @var{file1} belong to this directory. Otherwise, -the actions @code{deleted} and @code{created} could be returned in a -random order. +watch library. Otherwise, the actions @code{deleted} and +@code{created} could be returned in a random order. @example @group (rename-file "/tmp/foo" "/tmp/bla") @result{} Event (35025468 renamed "/tmp/foo" "/tmp/bla") @end group - -@group -(file-notify-add-watch - "/var/tmp" '(change attribute-change) 'my-notify-callback) - @result{} 35025504 -@end group - -@group -(rename-file "/tmp/bla" "/var/tmp/bla") - @result{} ;; gfilenotify - Event (35025468 renamed "/tmp/bla" "/var/tmp/bla") - - @result{} ;; inotify - Event (35025504 created "/var/tmp/bla") - Event (35025468 deleted "/tmp/bla") -@end group @end example @end defun diff --git a/lisp/filenotify.el b/lisp/filenotify.el index c94f631dde8..5822cf0cc7e 100644 --- a/lisp/filenotify.el +++ b/lisp/filenotify.el @@ -54,7 +54,7 @@ different files from the same directory are watched.") "Handle file system monitoring event. If EVENT is a filewatch event, call its callback. It has the format - \(file-notify (DESCRIPTOR ACTIONS FILE COOKIE) CALLBACK) + \(file-notify (DESCRIPTOR ACTIONS FILE [FILE1-OR-COOKIE]) CALLBACK) Otherwise, signal a `file-notify-error'." (interactive "e") @@ -64,10 +64,10 @@ Otherwise, signal a `file-notify-error'." (signal 'file-notify-error (cons "Not a valid file-notify event" event)))) -(defvar file-notify--pending-events nil - "List of pending file notification events for a future `renamed' action. -The entries are a list (DESCRIPTOR ACTION FILE COOKIE). ACTION -is either `moved-from' or `renamed-from'.") +;; Needed for `inotify' and `w32notify'. In the latter case, COOKIE is nil. +(defvar file-notify--pending-event nil + "A pending file notification events for a future `renamed' action. +It is a form ((DESCRIPTOR ACTION FILE [FILE1-OR-COOKIE]) CALLBACK).") (defun file-notify--event-file-name (event) "Return file name of file notification event, or nil." @@ -92,26 +92,26 @@ This is available in case a file has been moved." ;; `inotify' returns the same descriptor when the file (directory) ;; uses the same inode. We want to distinguish, and apply a virtual ;; descriptor which make the difference. -(defun file-notify--descriptor (descriptor file) +(defun file-notify--descriptor (descriptor) "Return the descriptor to be used in `file-notify-*-watch'. For `gfilenotify' and `w32notify' it is the same descriptor as used in the low-level file notification package." (if (and (natnump descriptor) (eq file-notify--library 'inotify)) - (cons descriptor file) + (cons descriptor + (car (cadr (gethash descriptor file-notify-descriptors)))) descriptor)) ;; The callback function used to map between specific flags of the ;; respective file notifications, and the ones we return. (defun file-notify-callback (event) "Handle an EVENT returned from file notification. -EVENT is the cdr of the event in `file-notify-handle-event' -\(DESCRIPTOR ACTIONS FILE COOKIE)." +EVENT is the cadr of the event in `file-notify-handle-event' +\(DESCRIPTOR ACTIONS FILE [FILE1-OR-COOKIE])." (let* ((desc (car event)) (registered (gethash desc file-notify-descriptors)) - (pending-event (assoc desc file-notify--pending-events)) (actions (nth 1 event)) (file (file-notify--event-file-name event)) - file1 callback) + file1 callback pending-event) ;; Make actions a list. (unless (consp actions) (setq actions (cons actions nil))) @@ -129,22 +129,23 @@ EVENT is the cdr of the event in `file-notify-handle-event' (dolist (action actions) ;; Send pending event, if it doesn't match. - (when (and pending-event + (when (and file-notify--pending-event ;; The cookie doesn't match. - (not (eq (file-notify--event-cookie pending-event) + (not (eq (file-notify--event-cookie + (car file-notify--pending-event)) (file-notify--event-cookie event))) (or ;; inotify. - (and (eq (nth 1 pending-event) 'moved-from) + (and (eq (nth 1 (car file-notify--pending-event)) + 'moved-from) (not (eq action 'moved-to))) ;; w32notify. - (and (eq (nth 1 pending-event) 'renamed-from) + (and (eq (nth 1 (car file-notify--pending-event)) + 'renamed-from) (not (eq action 'renamed-to))))) - (funcall callback - (list desc 'deleted - (file-notify--event-file-name pending-event))) - (setq file-notify--pending-events - (delete pending-event file-notify--pending-events))) + (setq pending-event file-notify--pending-event + file-notify--pending-event nil) + (setcar (cdar pending-event) 'deleted)) ;; Map action. We ignore all events which cannot be mapped. (setq action @@ -156,46 +157,42 @@ EVENT is the cdr of the event in `file-notify-handle-event' (setq file1 (file-notify--event-file1-name event)) 'renamed) - ;; inotify. + ;; inotify, w32notify. ((eq action 'attrib) 'attribute-changed) - ((eq action 'create) 'created) - ((eq action 'modify) 'changed) - ((memq action '(delete 'delete-self move-self)) 'deleted) + ((memq action '(create added)) 'created) + ((memq action '(modify modified)) 'changed) + ((memq action '(delete 'delete-self move-self removed)) 'deleted) ;; Make the event pending. - ((eq action 'moved-from) - (add-to-list 'file-notify--pending-events - (list desc action file - (file-notify--event-cookie event))) + ((memq action '(moved-from renamed-from)) + (setq file-notify--pending-event + `((,desc ,action ,file ,(file-notify--event-cookie event)) + ,callback)) nil) ;; Look for pending event. - ((eq action 'moved-to) - (if (null pending-event) + ((memq action '(moved-to renamed-to)) + (if (null file-notify--pending-event) 'created (setq file1 file - file (file-notify--event-file-name pending-event) - file-notify--pending-events - (delete pending-event file-notify--pending-events)) - 'renamed)) - - ;; w32notify. - ((eq action 'added) 'created) - ((eq action 'modified) 'changed) - ((eq action 'removed) 'deleted) - ;; Make the event pending. - ((eq action 'renamed-from) - (add-to-list 'file-notify--pending-events - (list desc action file - (file-notify--event-cookie event))) - nil) - ;; Look for pending event. - ((eq action 'renamed-to) - (if (null pending-event) - 'created - (setq file1 file - file (file-notify--event-file-name pending-event) - file-notify--pending-events - (delete pending-event file-notify--pending-events)) - 'renamed)))) + file (file-notify--event-file-name + (car file-notify--pending-event))) + ;; If the source is handled by another watch, we + ;; must fire the rename event there as well. + (when (not (eq (file-notify--descriptor desc) + (file-notify--descriptor + (caar file-notify--pending-event)))) + (setq pending-event + `((,(caar file-notify--pending-event) + renamed ,file ,file1) + ,(cadr file-notify--pending-event)))) + (setq file-notify--pending-event nil) + 'renamed)))) + + ;; Apply pending callback. + (when pending-event + (setcar + (car pending-event) (file-notify--descriptor (caar pending-event))) + (funcall (cadr pending-event) (car pending-event)) + (setq pending-event nil)) ;; Apply callback. (when (and action @@ -213,12 +210,10 @@ EVENT is the cdr of the event in `file-notify-handle-event' (if file1 (funcall callback - `(,(file-notify--descriptor desc (nth 0 entry)) - ,action ,file ,file1)) + `(,(file-notify--descriptor desc) ,action ,file ,file1)) (funcall callback - `(,(file-notify--descriptor desc (nth 0 entry)) - ,action ,file)))))))) + `(,(file-notify--descriptor desc) ,action ,file)))))))) ;; `gfilenotify' and `w32notify' return a unique descriptor for every ;; `file-notify-add-watch', while `inotify' returns a unique @@ -325,8 +320,7 @@ FILE is the name of the file whose event is being reported." file-notify-descriptors) ;; Return descriptor. - (file-notify--descriptor - desc (unless (file-directory-p file) (file-name-nondirectory file))))) + (file-notify--descriptor desc))) (defun file-notify-rm-watch (descriptor) "Remove an existing watch specified by its DESCRIPTOR. -- 2.39.2