filenotify.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. ;;; filenotify.el --- watch files for changes on disk
  2. ;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
  3. ;; Author: Michael Albinus <michael.albinus@gmx.de>
  4. ;; This file is part of GNU Emacs.
  5. ;; GNU Emacs is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; GNU Emacs is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary
  16. ;; This package is an abstraction layer from the different low-level
  17. ;; file notification packages `gfilenotify', `inotify' and
  18. ;; `w32notify'.
  19. ;;; Code:
  20. (defconst file-notify--library
  21. (cond
  22. ((featurep 'gfilenotify) 'gfilenotify)
  23. ((featurep 'inotify) 'inotify)
  24. ((featurep 'w32notify) 'w32notify))
  25. "Non-nil when Emacs has been compiled with file notification support.
  26. The value is the name of the low-level file notification package
  27. to be used for local file systems. Remote file notifications
  28. could use another implementation.")
  29. (defvar file-notify-descriptors (make-hash-table :test 'equal)
  30. "Hash table for registered file notification descriptors.
  31. A key in this hash table is the descriptor as returned from
  32. `gfilenotify', `inotify', `w32notify' or a file name handler.
  33. The value in the hash table is a list
  34. \(DIR (FILE . CALLBACK) (FILE . CALLBACK) ...)
  35. Several values for a given DIR happen only for `inotify', when
  36. different files from the same directory are watched.")
  37. ;; This function is used by `gfilenotify', `inotify' and `w32notify' events.
  38. ;;;###autoload
  39. (defun file-notify-handle-event (event)
  40. "Handle file system monitoring event.
  41. If EVENT is a filewatch event, call its callback. It has the format
  42. \(file-notify (DESCRIPTOR ACTIONS FILE COOKIE) CALLBACK)
  43. Otherwise, signal a `file-notify-error'."
  44. (interactive "e")
  45. (if (and (eq (car event) 'file-notify)
  46. (>= (length event) 3))
  47. (funcall (nth 2 event) (nth 1 event))
  48. (signal 'file-notify-error
  49. (cons "Not a valid file-notify event" event))))
  50. (defvar file-notify--pending-events nil
  51. "List of pending file notification events for a future `renamed' action.
  52. The entries are a list (DESCRIPTOR ACTION FILE COOKIE). ACTION
  53. is either `moved-from' or `renamed-from'.")
  54. (defun file-notify--event-file-name (event)
  55. "Return file name of file notification event, or nil."
  56. (expand-file-name
  57. (or (and (stringp (nth 2 event)) (nth 2 event)) "")
  58. (car (gethash (car event) file-notify-descriptors))))
  59. ;; Only `gfilenotify' could return two file names.
  60. (defun file-notify--event-file1-name (event)
  61. "Return second file name of file notification event, or nil.
  62. This is available in case a file has been moved."
  63. (and (stringp (nth 3 event))
  64. (expand-file-name
  65. (nth 3 event) (car (gethash (car event) file-notify-descriptors)))))
  66. ;; Cookies are offered by `inotify' only.
  67. (defun file-notify--event-cookie (event)
  68. "Return cookie of file notification event, or nil.
  69. This is available in case a file has been moved."
  70. (nth 3 event))
  71. ;; `inotify' returns the same descriptor when the file (directory)
  72. ;; uses the same inode. We want to distinguish, and apply a virtual
  73. ;; descriptor which make the difference.
  74. (defun file-notify--descriptor (descriptor file)
  75. "Return the descriptor to be used in `file-notify-*-watch'.
  76. For `gfilenotify' and `w32notify' it is the same descriptor as
  77. used in the low-level file notification package."
  78. (if (and (natnump descriptor) (eq file-notify--library 'inotify))
  79. (cons descriptor file)
  80. descriptor))
  81. ;; The callback function used to map between specific flags of the
  82. ;; respective file notifications, and the ones we return.
  83. (defun file-notify-callback (event)
  84. "Handle an EVENT returned from file notification.
  85. EVENT is the cdr of the event in `file-notify-handle-event'
  86. \(DESCRIPTOR ACTIONS FILE COOKIE)."
  87. (let* ((desc (car event))
  88. (registered (gethash desc file-notify-descriptors))
  89. (pending-event (assoc desc file-notify--pending-events))
  90. (actions (nth 1 event))
  91. (file (file-notify--event-file-name event))
  92. file1 callback)
  93. ;; Make actions a list.
  94. (unless (consp actions) (setq actions (cons actions nil)))
  95. ;; Loop over registered entries. In fact, more than one entry
  96. ;; happens only for `inotify'.
  97. (dolist (entry (cdr registered))
  98. ;; Check, that event is meant for us.
  99. (unless (setq callback (cdr entry))
  100. (setq actions nil))
  101. ;; Loop over actions. In fact, more than one action happens only
  102. ;; for `inotify'.
  103. (dolist (action actions)
  104. ;; Send pending event, if it doesn't match.
  105. (when (and pending-event
  106. ;; The cookie doesn't match.
  107. (not (eq (file-notify--event-cookie pending-event)
  108. (file-notify--event-cookie event)))
  109. (or
  110. ;; inotify.
  111. (and (eq (nth 1 pending-event) 'moved-from)
  112. (not (eq action 'moved-to)))
  113. ;; w32notify.
  114. (and (eq (nth 1 pending-event) 'renamed-from)
  115. (not (eq action 'renamed-to)))))
  116. (funcall callback
  117. (list desc 'deleted
  118. (file-notify--event-file-name pending-event)))
  119. (setq file-notify--pending-events
  120. (delete pending-event file-notify--pending-events)))
  121. ;; Map action. We ignore all events which cannot be mapped.
  122. (setq action
  123. (cond
  124. ;; gfilenotify.
  125. ((memq action '(attribute-changed changed created deleted))
  126. action)
  127. ((eq action 'moved)
  128. (setq file1 (file-notify--event-file1-name event))
  129. 'renamed)
  130. ;; inotify.
  131. ((eq action 'attrib) 'attribute-changed)
  132. ((eq action 'create) 'created)
  133. ((eq action 'modify) 'changed)
  134. ((memq action '(delete 'delete-self move-self)) 'deleted)
  135. ;; Make the event pending.
  136. ((eq action 'moved-from)
  137. (add-to-list 'file-notify--pending-events
  138. (list desc action file
  139. (file-notify--event-cookie event)))
  140. nil)
  141. ;; Look for pending event.
  142. ((eq action 'moved-to)
  143. (if (null pending-event)
  144. 'created
  145. (setq file1 file
  146. file (file-notify--event-file-name pending-event)
  147. file-notify--pending-events
  148. (delete pending-event file-notify--pending-events))
  149. 'renamed))
  150. ;; w32notify.
  151. ((eq action 'added) 'created)
  152. ((eq action 'modified) 'changed)
  153. ((eq action 'removed) 'deleted)
  154. ;; Make the event pending.
  155. ((eq action 'renamed-from)
  156. (add-to-list 'file-notify--pending-events
  157. (list desc action file
  158. (file-notify--event-cookie event)))
  159. nil)
  160. ;; Look for pending event.
  161. ((eq action 'renamed-to)
  162. (if (null pending-event)
  163. 'created
  164. (setq file1 file
  165. file (file-notify--event-file-name pending-event)
  166. file-notify--pending-events
  167. (delete pending-event file-notify--pending-events))
  168. 'renamed))))
  169. ;; Apply callback.
  170. (when (and action
  171. (or
  172. ;; If there is no relative file name for that watch,
  173. ;; we watch the whole directory.
  174. (null (nth 0 entry))
  175. ;; File matches.
  176. (string-equal
  177. (nth 0 entry) (file-name-nondirectory file))
  178. ;; File1 matches.
  179. (and (stringp file1)
  180. (string-equal
  181. (nth 0 entry) (file-name-nondirectory file1)))))
  182. (if file1
  183. (funcall
  184. callback
  185. `(,(file-notify--descriptor desc (nth 0 entry))
  186. ,action ,file ,file1))
  187. (funcall
  188. callback
  189. `(,(file-notify--descriptor desc (nth 0 entry))
  190. ,action ,file))))))))
  191. ;; `gfilenotify' and `w32notify' return a unique descriptor for every
  192. ;; `file-notify-add-watch', while `inotify' returns a unique
  193. ;; descriptor per inode only.
  194. (defun file-notify-add-watch (file flags callback)
  195. "Add a watch for filesystem events pertaining to FILE.
  196. This arranges for filesystem events pertaining to FILE to be reported
  197. to Emacs. Use `file-notify-rm-watch' to cancel the watch.
  198. The returned value is a descriptor for the added watch. If the
  199. file cannot be watched for some reason, this function signals a
  200. `file-notify-error' error.
  201. FLAGS is a list of conditions to set what will be watched for. It can
  202. include the following symbols:
  203. `change' -- watch for file changes
  204. `attribute-change' -- watch for file attributes changes, like
  205. permissions or modification time
  206. If FILE is a directory, `change' watches for file creation or
  207. deletion in that directory. This does not work recursively.
  208. When any event happens, Emacs will call the CALLBACK function passing
  209. it a single argument EVENT, which is of the form
  210. (DESCRIPTOR ACTION FILE [FILE1])
  211. DESCRIPTOR is the same object as the one returned by this function.
  212. ACTION is the description of the event. It could be any one of the
  213. following:
  214. `created' -- FILE was created
  215. `deleted' -- FILE was deleted
  216. `changed' -- FILE has changed
  217. `renamed' -- FILE has been renamed to FILE1
  218. `attribute-changed' -- a FILE attribute was changed
  219. FILE is the name of the file whose event is being reported."
  220. ;; Check arguments.
  221. (unless (stringp file)
  222. (signal 'wrong-type-argument (list file)))
  223. (setq file (expand-file-name file))
  224. (unless (and (consp flags)
  225. (null (delq 'change (delq 'attribute-change (copy-tree flags)))))
  226. (signal 'wrong-type-argument (list flags)))
  227. (unless (functionp callback)
  228. (signal 'wrong-type-argument (list callback)))
  229. (let* ((handler (find-file-name-handler file 'file-notify-add-watch))
  230. (dir (directory-file-name
  231. (if (file-directory-p file)
  232. file
  233. (file-name-directory file))))
  234. desc func l-flags registered)
  235. (if handler
  236. ;; A file name handler could exist even if there is no local
  237. ;; file notification support.
  238. (setq desc (funcall
  239. handler 'file-notify-add-watch dir flags callback))
  240. ;; Check, whether Emacs has been compiled with file
  241. ;; notification support.
  242. (unless file-notify--library
  243. (signal 'file-notify-error
  244. '("No file notification package available")))
  245. ;; Determine low-level function to be called.
  246. (setq func
  247. (cond
  248. ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
  249. ((eq file-notify--library 'inotify) 'inotify-add-watch)
  250. ((eq file-notify--library 'w32notify) 'w32notify-add-watch)))
  251. ;; Determine respective flags.
  252. (if (eq file-notify--library 'gfilenotify)
  253. (setq l-flags '(watch-mounts send-moved))
  254. (when (memq 'change flags)
  255. (setq
  256. l-flags
  257. (cond
  258. ((eq file-notify--library 'inotify) '(create modify move delete))
  259. ((eq file-notify--library 'w32notify)
  260. '(file-name directory-name size last-write-time)))))
  261. (when (memq 'attribute-change flags)
  262. (add-to-list
  263. 'l-flags
  264. (cond
  265. ((eq file-notify--library 'inotify) 'attrib)
  266. ((eq file-notify--library 'w32notify) 'attributes)))))
  267. ;; Call low-level function.
  268. (setq desc (funcall func dir l-flags 'file-notify-callback)))
  269. ;; Modify `file-notify-descriptors'.
  270. (setq registered (gethash desc file-notify-descriptors))
  271. (puthash
  272. desc
  273. `(,dir
  274. (,(unless (file-directory-p file) (file-name-nondirectory file))
  275. . ,callback)
  276. . ,(cdr registered))
  277. file-notify-descriptors)
  278. ;; Return descriptor.
  279. (file-notify--descriptor
  280. desc (unless (file-directory-p file) (file-name-nondirectory file)))))
  281. (defun file-notify-rm-watch (descriptor)
  282. "Remove an existing watch specified by its DESCRIPTOR.
  283. DESCRIPTOR should be an object returned by `file-notify-add-watch'."
  284. (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
  285. (file (if (consp descriptor) (cdr descriptor)))
  286. (dir (car (gethash desc file-notify-descriptors)))
  287. handler registered)
  288. (when (stringp dir)
  289. (setq handler (find-file-name-handler dir 'file-notify-rm-watch))
  290. ;; Modify `file-notify-descriptors'.
  291. (if (not file)
  292. (remhash desc file-notify-descriptors)
  293. (setq registered (gethash desc file-notify-descriptors))
  294. (setcdr registered
  295. (delete (assoc file (cdr registered)) (cdr registered)))
  296. (if (null (cdr registered))
  297. (remhash desc file-notify-descriptors)
  298. (puthash desc registered file-notify-descriptors)))
  299. ;; Call low-level function.
  300. (when (null (cdr registered))
  301. (if handler
  302. ;; A file name handler could exist even if there is no local
  303. ;; file notification support.
  304. (funcall handler 'file-notify-rm-watch desc)
  305. (funcall
  306. (cond
  307. ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
  308. ((eq file-notify--library 'inotify) 'inotify-rm-watch)
  309. ((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
  310. desc))))))
  311. ;; The end:
  312. (provide 'filenotify)
  313. ;;; filenotify.el ends here