From 41d9bd0c3b19d839b72fdd20e613cb6ab3b1b1f4 Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Sat, 14 Nov 2015 11:51:28 +0000 Subject: [PATCH] Implement directory events * lisp/filenotify.el (file-notify-handle-event) (file-notify-callback): Remove traces. * src/kqueue.c: Include . (kqueue_generate_event, kqueue_compare_dir_list): New functions. (kqueue_callback): Use them. Call kevent() with a zero timeout. (Fkqueue_add_watch): Adapt docstring. Support directory events. Compute initial directory listing. Close file descriptor in case of errors. (syms_of_kqueue): Declare Qcreate. --- lisp/filenotify.el | 4 +- src/kqueue.c | 149 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 121 insertions(+), 32 deletions(-) diff --git a/lisp/filenotify.el b/lisp/filenotify.el index 029fcf82da8..f7c97569825 100644 --- a/lisp/filenotify.el +++ b/lisp/filenotify.el @@ -88,7 +88,7 @@ If EVENT is a filewatch event, call its callback. It has the format Otherwise, signal a `file-notify-error'." (interactive "e") - (message "file-notify-handle-event %S" event) + ;;(message "file-notify-handle-event %S" event) (if (and (eq (car event) 'file-notify) (>= (length event) 3)) (funcall (nth 2 event) (nth 1 event)) @@ -236,7 +236,7 @@ EVENT is the cadr of the event in `file-notify-handle-event' (setq pending-event nil)) ;; Check for stopped. - (message "file-notify-callback %S %S" file registered) + ;;(message "file-notify-callback %S %S" file registered) (setq stopped (or diff --git a/src/kqueue.c b/src/kqueue.c index d2f3d37e19c..0425a142a98 100644 --- a/src/kqueue.c +++ b/src/kqueue.c @@ -1,5 +1,5 @@ -/* Filesystem notifications support with glib API. - Copyright (C) 2013-2015 Free Software Foundation, Inc. +/* Filesystem notifications support with kqueue API. + Copyright (C) 2015 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -22,6 +22,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include "lisp.h" #include "keyboard.h" @@ -31,9 +32,97 @@ along with GNU Emacs. If not, see . */ /* File handle for kqueue. */ static int kqueuefd = -1; -/* This is a list, elements are triples (DESCRIPTOR FILE FLAGS CALLBACK) */ +/* This is a list, elements are (DESCRIPTOR FILE FLAGS CALLBACK [DIRLIST]) */ static Lisp_Object watch_list; +/* Generate a file notification event. */ +static void +kqueue_generate_event +(Lisp_Object ident, Lisp_Object actions, Lisp_Object file, Lisp_Object callback) +{ + struct input_event event; + EVENT_INIT (event); + event.kind = FILE_NOTIFY_EVENT; + event.frame_or_window = Qnil; + event.arg = list2 (Fcons (ident, Fcons (actions, Fcons (file, Qnil))), + callback); + + /* Store it into the input event queue. */ + kbd_buffer_store_event (&event); +} + +/* This compares two directory listings in case of a `write' event for + a directory. The old directory listing is stored in watch_object, + it will be replaced by a new directory listing at the end. */ +static void +kqueue_compare_dir_list (Lisp_Object watch_object) +{ + Lisp_Object dir, callback, old_dl, new_dl, dl, actions; + + dir = XCAR (XCDR (watch_object)); + callback = XCAR (XCDR (XCDR (XCDR (watch_object)))); + old_dl = XCAR (XCDR (XCDR (XCDR (XCDR (watch_object))))); + new_dl = directory_files_internal (dir, Qnil, Qnil, Qnil, 1, Qnil); + + for (dl = old_dl; ! NILP (dl); dl = XCDR (dl)) { + Lisp_Object old_entry, new_entry; + old_entry = XCAR (dl); + new_entry = Fassoc (XCAR (old_entry), new_dl); + + /* We ignore "." and "..". */ + if ((strcmp (".", SSDATA (XCAR (old_entry))) == 0) || + (strcmp ("..", SSDATA (XCAR (old_entry))) == 0)) + continue; + + /* A file has disappeared. */ + if (NILP (new_entry)) + kqueue_generate_event + (XCAR (watch_object), Fcons (Qdelete, Qnil), + XCAR (old_entry), callback); + + else { + /* A file has changed. We compare last modification time. */ + if (NILP + (Fequal + (XCAR (XCDR (XCDR (XCDR (XCDR (XCDR (XCDR (old_entry))))))), + XCAR (XCDR (XCDR (XCDR (XCDR (XCDR (XCDR (new_entry)))))))))) + kqueue_generate_event + (XCAR (watch_object), Fcons (Qwrite, Qnil), + XCAR (old_entry), callback); + + /* A file attribute has changed. We compare last status change time. */ + if (NILP + (Fequal + (XCAR (XCDR (XCDR (XCDR (XCDR (XCDR (XCDR (XCDR (old_entry)))))))), + XCAR (XCDR (XCDR (XCDR (XCDR (XCDR (XCDR (XCDR (new_entry))))))))))) + kqueue_generate_event + (XCAR (watch_object), Fcons (Qattrib, Qnil), + XCAR (old_entry), callback); + } + } + + for (dl = new_dl; ! NILP (dl); dl = XCDR (dl)) { + Lisp_Object old_entry, new_entry; + new_entry = XCAR (dl); + old_entry = Fassoc (XCAR (new_entry), old_dl); + + /* We ignore "." and "..". */ + if ((strcmp (".", SSDATA (XCAR (new_entry))) == 0) || + (strcmp ("..", SSDATA (XCAR (new_entry))) == 0)) + continue; + + /* A new file has appeared. */ + if (NILP (old_entry)) + kqueue_generate_event + (XCAR (watch_object), Fcons (Qcreate, Qnil), + XCAR (new_entry), callback); + } + + /* Replace directory listing with the new one. */ + XSETCDR (XCDR (XCDR (XCDR (watch_object))), Fcons (new_dl, Qnil)); + return; +} + /* This is the callback function for arriving input on kqueuefd. It shall create a Lisp event, and put it into Emacs input queue. */ static void @@ -41,11 +130,11 @@ kqueue_callback (int fd, void *data) { for (;;) { struct kevent kev; - struct input_event event; - Lisp_Object monitor_object, watch_object, file, callback, actions; + static const struct timespec nullts = { 0, 0 }; + Lisp_Object monitor_object, watch_object, file, callback, dirp, actions; /* Read one event. */ - int ret = kevent (kqueuefd, NULL, 0, &kev, 1, NULL); + int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts); if (ret < 1) { /* All events read. */ return; @@ -58,6 +147,7 @@ kqueue_callback (int fd, void *data) if (CONSP (watch_object)) { file = XCAR (XCDR (watch_object)); callback = XCAR (XCDR (XCDR (XCDR (watch_object)))); + dirp = XCDR (XCDR (XCDR (XCDR (watch_object)))); } else continue; @@ -66,8 +156,12 @@ kqueue_callback (int fd, void *data) actions = Qnil; if (kev.fflags & NOTE_DELETE) actions = Fcons (Qdelete, actions); - if (kev.fflags & NOTE_WRITE) - actions = Fcons (Qwrite, actions); + if (kev.fflags & NOTE_WRITE) { + if (NILP (dirp)) + actions = Fcons (Qwrite, actions); + else + kqueue_compare_dir_list (watch_object); + } if (kev.fflags & NOTE_EXTEND) actions = Fcons (Qextend, actions); if (kev.fflags & NOTE_ATTRIB) @@ -77,18 +171,9 @@ kqueue_callback (int fd, void *data) if (kev.fflags & NOTE_RENAME) actions = Fcons (Qrename, actions); - if (! NILP (actions)) { - /* Construct an event. */ - EVENT_INIT (event); - event.kind = FILE_NOTIFY_EVENT; - event.frame_or_window = Qnil; - event.arg = list2 (Fcons (monitor_object, - Fcons (actions, Fcons (file, Qnil))), - callback); - - /* Store it into the input event queue. */ - kbd_buffer_store_event (&event); - } + /* Construct an event. */ + if (! NILP (actions)) + kqueue_generate_event (monitor_object, actions, file, callback); /* Cancel monitor if file or directory is deleted. */ if (kev.fflags & (NOTE_DELETE | NOTE_RENAME)) @@ -109,6 +194,7 @@ watched for some reason, this function signals a `file-notify-error' error. FLAGS is a list of events to be watched for. It can include the following symbols: + `create' -- FILE was created `delete' -- FILE was deleted `write' -- FILE has changed `extend' -- FILE was extended @@ -128,7 +214,7 @@ FILE is the name of the file whose event is being reported. FILE1 will be reported only in case of the `rename' event. */) (Lisp_Object file, Lisp_Object flags, Lisp_Object callback) { - Lisp_Object watch_object; + Lisp_Object watch_object, dir_list; int fd; u_short fflags = 0; struct kevent ev; @@ -139,10 +225,6 @@ will be reported only in case of the `rename' event. */) if (NILP (Ffile_exists_p (file))) report_file_error ("File does not exist", file); - /* TODO: Directories shall be supported as well. */ - if (! NILP (Ffile_directory_p (file))) - report_file_error ("Directory watching is not supported (yet)", file); - CHECK_LIST (flags); if (! FUNCTIONP (callback)) @@ -156,14 +238,14 @@ will be reported only in case of the `rename' event. */) report_file_notify_error ("File watching is not available", Qnil); /* Start monitoring for possible I/O. */ - add_read_fd (kqueuefd, kqueue_callback, NULL); //data); + add_read_fd (kqueuefd, kqueue_callback, NULL); watch_list = Qnil; } /* Open file. */ file = ENCODE_FILE (file); - fd = emacs_open (SSDATA (file), O_NONBLOCK | O_RDONLY, 0); + fd = emacs_open (SSDATA (file), O_RDONLY, 0); if (fd == -1) report_file_error ("File cannot be opened", file); @@ -179,12 +261,19 @@ will be reported only in case of the `rename' event. */) EV_SET (&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, fflags, 0, NULL); - if (kevent (kqueuefd, &ev, 1, NULL, 0, NULL) < 0) + if (kevent (kqueuefd, &ev, 1, NULL, 0, NULL) < 0) { + emacs_close (fd); report_file_error ("Cannot watch file", file); + } /* Store watch object in watch list. */ Lisp_Object watch_descriptor = make_number (fd); - watch_object = list4 (watch_descriptor, file, flags, callback); + if (NILP (Ffile_directory_p (file))) + watch_object = list4 (watch_descriptor, file, flags, callback); + else { + dir_list = directory_files_internal (file, Qnil, Qnil, Qnil, 1, Qnil); + watch_object = list5 (watch_descriptor, file, flags, callback, dir_list); + } watch_list = Fcons (watch_object, watch_list); return watch_descriptor; @@ -248,6 +337,7 @@ syms_of_kqueue (void) defsubr (&Skqueue_valid_p); /* Event types. */ + DEFSYM (Qcreate, "create"); DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */ DEFSYM (Qwrite, "write"); /* NOTE_WRITE */ DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */ @@ -263,7 +353,6 @@ syms_of_kqueue (void) #endif /* HAVE_KQUEUE */ /* TODO - * Implement watching directories. * Add FILE1 in case of `rename'. */ /* PROBLEMS -- 2.39.2