Several values for a given DIR happen only for `inotify', when
different files from the same directory are watched.")
+(defun file-notify--rm-descriptor (descriptor)
+ "Remove DESCRIPTOR from `file-notify-descriptors'.
+DESCRIPTOR should be an object returned by `file-notify-add-watch'.
+If it is registered in `file-notify-descriptors', a stopped event is sent."
+ (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
+ (file (if (consp descriptor) (cdr descriptor)))
+ (registered (gethash desc file-notify-descriptors))
+ (dir (car registered)))
+
+ (when (consp registered)
+ ;; Send `stopped' event.
+ (dolist (entry (cdr registered))
+ (funcall (cdr entry)
+ `(,(file-notify--descriptor desc) stopped
+ ,(or (and (stringp (car entry))
+ (expand-file-name (car entry) dir))
+ dir))))
+
+ ;; Modify `file-notify-descriptors'.
+ (if (not file)
+ (remhash desc file-notify-descriptors)
+ (setcdr registered
+ (delete (assoc file (cdr registered)) (cdr registered)))
+ (if (null (cdr registered))
+ (remhash desc file-notify-descriptors)
+ (puthash desc registered file-notify-descriptors))))))
+
;; This function is used by `gfilenotify', `inotify' and `w32notify' events.
;;;###autoload
(defun file-notify-handle-event (event)
(registered (gethash desc file-notify-descriptors))
(actions (nth 1 event))
(file (file-notify--event-file-name event))
- file1 callback pending-event)
+ file1 callback pending-event stopped)
;; Make actions a list.
(unless (consp actions) (setq actions (cons actions nil)))
'renamed)
;; inotify, w32notify.
+ ((eq action 'ignored)
+ (setq stopped t actions nil))
((eq action 'attrib) 'attribute-changed)
((memq action '(create added)) 'created)
((memq action '(modify modified)) 'changed)
(funcall (cadr pending-event) (car pending-event))
(setq pending-event nil))
+ ;; Check for stopped.
+ (setq
+ stopped
+ (or
+ stopped
+ (and
+ (memq action '(deleted renamed))
+ (= (length (cdr registered)) 1)
+ (string-equal
+ (or (file-name-nondirectory file) "") (car (cadr registered))))))
+
;; Apply callback.
(when (and action
(or
`(,(file-notify--descriptor desc) ,action ,file ,file1))
(funcall
callback
- `(,(file-notify--descriptor desc) ,action ,file))))))))
+ `(,(file-notify--descriptor desc) ,action ,file)))))
+
+ ;; Modify `file-notify-descriptors'.
+ (when stopped
+ (file-notify--rm-descriptor (file-notify--descriptor desc))))))
;; `gfilenotify' and `w32notify' return a unique descriptor for every
;; `file-notify-add-watch', while `inotify' returns a unique
`changed' -- FILE has changed
`renamed' -- FILE has been renamed to FILE1
`attribute-changed' -- a FILE attribute was changed
+ `stopped' -- watching FILE has been stopped
FILE is the name of the file whose event is being reported."
;; Check arguments.
(unless (stringp file)
- (signal 'wrong-type-argument (list file)))
+ (signal 'wrong-type-argument `(,file)))
(setq file (expand-file-name file))
(unless (and (consp flags)
(null (delq 'change (delq 'attribute-change (copy-tree flags)))))
- (signal 'wrong-type-argument (list flags)))
+ (signal 'wrong-type-argument `(,flags)))
(unless (functionp callback)
- (signal 'wrong-type-argument (list callback)))
+ (signal 'wrong-type-argument `(,callback)))
(let* ((handler (find-file-name-handler file 'file-notify-add-watch))
(dir (directory-file-name
(file-name-directory file))))
desc func l-flags registered)
+ (unless (file-directory-p dir)
+ (signal 'file-notify-error `("Directory does not exist" ,dir)))
+
(if handler
;; A file name handler could exist even if there is no local
;; file notification support.
DESCRIPTOR should be an object returned by `file-notify-add-watch'."
(let* ((desc (if (consp descriptor) (car descriptor) descriptor))
(file (if (consp descriptor) (cdr descriptor)))
- (dir (car (gethash desc file-notify-descriptors)))
+ (registered (gethash desc file-notify-descriptors))
+ (dir (car registered))
(handler (and (stringp dir)
- (find-file-name-handler dir 'file-notify-rm-watch)))
- (registered (gethash desc file-notify-descriptors)))
+ (find-file-name-handler dir 'file-notify-rm-watch))))
(when (stringp dir)
;; Call low-level function.
(file-notify-error nil)))
;; Modify `file-notify-descriptors'.
- (if (not file)
- (remhash desc file-notify-descriptors)
-
- (setcdr registered
- (delete (assoc file (cdr registered)) (cdr registered)))
- (if (null (cdr registered))
- (remhash desc file-notify-descriptors)
- (puthash desc registered file-notify-descriptors))))))
+ (file-notify--rm-descriptor descriptor))))
(defun file-notify-valid-p (descriptor)
"Check a watch specified by its DESCRIPTOR.
(tramp-cleanup-connection
(tramp-dissect-file-name temporary-file-directory) nil 'keep-password))
- (setq file-notify--test-tmpfile nil)
- (setq file-notify--test-tmpfile1 nil)
- (setq file-notify--test-desc nil)
- (setq file-notify--test-results nil)
- (setq file-notify--test-events nil)
+ (setq file-notify--test-tmpfile nil
+ file-notify--test-tmpfile1 nil
+ file-notify--test-desc nil
+ file-notify--test-results nil
+ file-notify--test-events nil)
(when file-notify--test-event
(error "file-notify--test-event should not be set but bound dynamically")))
(ert-deftest file-notify-test01-add-watch ()
"Check `file-notify-add-watch'."
(skip-unless (file-notify--test-local-enabled))
+
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-tmpfile1
+ (format "%s/%s" file-notify--test-tmpfile (md5 (current-time-string))))
+
;; Check, that different valid parameters are accepted.
(should
(setq file-notify--test-desc
(file-notify-add-watch
temporary-file-directory '(change attribute-change) 'ignore)))
(file-notify-rm-watch file-notify--test-desc)
+ ;; The file does not need to exist, just the upper directory.
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile '(change attribute-change) 'ignore)))
+ (file-notify-rm-watch file-notify--test-desc)
;; Check error handling.
(should-error (file-notify-add-watch 1 2 3 4)
(equal (should-error
(file-notify-add-watch temporary-file-directory '(change) 3))
'(wrong-type-argument 3)))
+ ;; The upper directory of a file must exist.
+ (should
+ (equal (should-error
+ (file-notify-add-watch
+ file-notify--test-tmpfile1 '(change attribute-change) 'ignore))
+ `(file-notify-error
+ "Directory does not exist" ,file-notify--test-tmpfile)))
;; Cleanup.
(file-notify--test-cleanup))
(result
(ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
(setq file-notify--test-events
- (append file-notify--test-events `(,file-notify--test-event)))
- (setq file-notify--test-results
+ (append file-notify--test-events `(,file-notify--test-event))
+ file-notify--test-results
(append file-notify--test-results `(,result)))))
(defun file-notify--test-make-temp-name ()
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler))
(file-notify--test-with-events
- (file-notify--test-timeout) '(created changed deleted)
+ (file-notify--test-timeout) '(created changed deleted stopped)
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(delete-file file-notify--test-tmpfile))
;; w32notify does not distinguish between `changed' and
;; `attribute-changed'.
(if (eq file-notify--library 'w32notify)
- '(created changed changed deleted)
- '(created changed deleted))
+ '(created changed changed deleted stopped)
+ '(created changed deleted stopped))
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
'(change) 'file-notify--test-event-handler))
(should file-notify--test-desc)
(file-notify--test-with-events
- (file-notify--test-timeout) '(created changed renamed)
+ (file-notify--test-timeout) '(created changed renamed stopped)
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
;; Otherwise, not all events arrive us in the remote case.
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
- (sleep-for 0.1)
+ (read-event nil nil 0.1)
(set-file-modes file-notify--test-tmpfile 000)
- (sleep-for 0.1)
+ (read-event nil nil 0.1)
(set-file-times file-notify--test-tmpfile '(0 0))
- (sleep-for 0.1)
+ (read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile))
(file-notify-rm-watch file-notify--test-desc))
(should (equal
(mapcar #'cadr file-notify--test-events)
(if (eq file-notify--library 'w32notify)
- '(created changed deleted
- created changed changed deleted
- created changed renamed)
+ '(created changed deleted stopped
+ created changed changed deleted stopped
+ created changed renamed stopped)
(if (file-remote-p temporary-file-directory)
- '(created changed deleted
- created changed deleted
- created changed renamed
- attribute-changed attribute-changed attribute-changed)
- '(created changed deleted
- created changed deleted
- created changed renamed
- attribute-changed attribute-changed)))))
+ '(created changed deleted stopped
+ created changed deleted stopped
+ created changed renamed stopped
+ attribute-changed attribute-changed
+ attribute-changed stopped)
+ '(created changed deleted stopped
+ created changed deleted stopped
+ created changed renamed stopped
+ attribute-changed attribute-changed stopped)))))
(should file-notify--test-results)
(dolist (result file-notify--test-results)
;;(message "%s" (ert-test-result-messages result))
(unwind-protect
(progn
- (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
- (setq file-notify--test-desc
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler))
;; After removing the watch, the descriptor must not be valid
;; anymore.
(file-notify-rm-watch file-notify--test-desc)
+ (file-notify--wait-for-events
+ (file-notify--test-timeout)
+ (not (file-notify-valid-p file-notify--test-desc)))
+ (should-not (file-notify-valid-p file-notify--test-desc)))
+
+ ;; Cleanup.
+ (file-notify--test-cleanup))
+
+ (unwind-protect
+ (progn
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) #'file-notify--test-event-handler))
+ (file-notify--test-with-events
+ (file-notify--test-timeout) '(created changed)
+ (should (file-notify-valid-p file-notify--test-desc))
+ (write-region
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (should (file-notify-valid-p file-notify--test-desc)))
+ ;; After deleting the file, the descriptor must not be valid
+ ;; anymore.
+ (delete-file file-notify--test-tmpfile)
+ (file-notify--wait-for-events
+ (file-notify--test-timeout)
+ (not (file-notify-valid-p file-notify--test-desc)))
(should-not (file-notify-valid-p file-notify--test-desc)))
;; Cleanup.
(unless (and noninteractive (eq file-notify--library 'w32notify))
(let ((temporary-file-directory (make-temp-file
"file-notify-test-parent" t)))
- (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
- (setq file-notify--test-desc
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler))
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(should (file-notify-valid-p file-notify--test-desc)))
- ;; After deleting the parent, the descriptor must not be valid
- ;; anymore.
+ ;; After deleting the parent, the descriptor must not be
+ ;; valid anymore.
(delete-directory temporary-file-directory t)
(file-notify--wait-for-events
(file-notify--test-timeout)