emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH updated] Support for filesystem watching (inotify)


From: Stefan Monnier
Subject: Re: [PATCH updated] Support for filesystem watching (inotify)
Date: Wed, 06 Jul 2011 09:36:26 -0400
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.0.50 (gnu/linux)

Do you guys think this patch is good enough to make it into Emacs-24.1?

My preference would be to wait for 24.2 since we're in feature freeze,
but it's the kind of feature which benefits from being available early
so other packages can start using it.  WDYT?


        Stefan


>> > The problem is: How to implement the file-unwatch then? I need a way
>> > to identify each separate file-watch request.  What would be the best
>> > way to do that?
>> 
>> How about letting file-watch return a "file-watcher" which you then need
>> to pass to file-unwatch?  This "file-watcher" could be any kind of Elisp
>> data you find convenient for this.  You may decide to provide no other
>> operation than file-unwatch, but you could also decide to provided
>> additional operations such as changing the callback (I'm not saying
>> that would necessarily be a good idea, tho, but maybe other operations
>> would be handy).

> I updated the patch to return a "file-watcher". file-watch can now be called
> several times for the same file with different flags and callbacks. This
> however required large changes to the patch. So it would be great if someone
> could take a look at it again. I added an automated test. But it doesn't test 
> the
> actually file-watching yet. This is a bit tricky to get right in a unit test.
> But I'll add it. The code requires some heavy testing.

> Regards,
> Rüdiger

> From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= <address@hidden>
> Date: Fri, 3 Jun 2011 23:58:12 +0200
> Subject: [PATCH] Added basic file system watching support.

> It currently only works with inotify/Linux. It adds file-watch and 
> file-unwatch functions.
> ---
>  configure.in                      |   14 +
>  lisp/filewatch.el                 |   54 ++++
>  src/Makefile.in                   |    2 +-
>  src/emacs.c                       |    4 +
>  src/filewatch.c                   |  485 
> +++++++++++++++++++++++++++++++++++++
>  src/keyboard.c                    |   28 +++
>  src/lisp.h                        |    5 +
>  src/termhooks.h                   |    5 +
>  test/automated/filewatch-tests.el |   50 ++++
>  9 files changed, 646 insertions(+), 1 deletions(-)
>  create mode 100644 lisp/filewatch.el
>  create mode 100644 src/filewatch.c
>  create mode 100644 test/automated/filewatch-tests.el

> diff --git a/configure.in b/configure.in
> index 0135b9f..4a816ec 100644
> --- a/configure.in
> +++ b/configure.in
> @@ -174,6 +174,7 @@ OPTION_DEFAULT_ON([dbus],[don't compile with D-Bus 
> support])
>  OPTION_DEFAULT_ON([gconf],[don't compile with GConf support])
>  OPTION_DEFAULT_ON([selinux],[don't compile with SELinux support])
>  OPTION_DEFAULT_ON([gnutls],[don't use -lgnutls for SSL/TLS support])
> +OPTION_DEFAULT_ON([inotify],[don't compile with inotify (file-watch) 
> support])
 
>  ## For the times when you want to build Emacs but don't have
>  ## a suitable makeinfo, and can live without the manuals.
> @@ -1999,6 +2000,19 @@ fi
>  AC_SUBST(LIBGNUTLS_LIBS)
>  AC_SUBST(LIBGNUTLS_CFLAGS)
 
> +dnl inotify is only available on GNU/Linux.
> +HAVE_INOTIFY=no
> +if test "${with_inotify}" = "yes"; then
> +   AC_CHECK_HEADERS(sys/inotify.h)
> +   if test "$ac_cv_header_sys_inotify_h" = yes ; then
> +     AC_CHECK_FUNCS(inotify_init1 inotify_add_watch inotify_rm_watch, 
> HAVE_INOTIFY=yes)
> +   fi
> +fi
> +if test "${HAVE_INOTIFY}" = "yes"; then
> +   AC_DEFINE(HAVE_INOTIFY, [1], [Define to 1 to use inotify])
> +   AC_DEFINE(HAVE_FILEWATCH, [1], [Define to 1 to support filewatch])
> +fi
> +
>  dnl Do not put whitespace before the #include statements below.
>  dnl Older compilers (eg sunos4 cc) choke on it.
>  HAVE_XAW3D=no
> diff --git a/lisp/filewatch.el b/lisp/filewatch.el
> new file mode 100644
> index 0000000..2c97589
> --- /dev/null
> +++ b/lisp/filewatch.el
> @@ -0,0 +1,54 @@
> +;;; filewatch.el --- Elisp support for watching filesystem events.
> +
> +;; Copyright (C) 2011 Free Software Foundation, Inc.
> +
> +;; Author: Rüdiger Sonderfeld <address@hidden>
> +;; Keywords: files
> +
> +;; This file is part of GNU Emacs.
> +
> +;; GNU Emacs is free software: you can redistribute it and/or modify
> +;; it under the terms of the GNU General Public License as published by
> +;; the Free Software Foundation, either version 3 of the License, or
> +;; (at your option) any later version.
> +
> +;; GNU Emacs is distributed in the hope that it will be useful,
> +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
> +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +;; GNU General Public License for more details.
> +
> +;; You should have received a copy of the GNU General Public License
> +;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
> +
> +;;; Commentary:
> +
> +;; This file contains the elisp part of the filewatch API.
> +
> +;;; Code:
> +
> +(eval-when-compile
> +  (require 'cl))
> +
> +(defun filewatch-event-p (event)
> +  "Check if EVENT is a filewatch event."
> +  (and (listp event)
> +       (eq (car event) 'filewatch-event)))
> +
> +;;;###autoload
> +(defun filewatch-handle-event (event)
> +  "Handle file system monitoring event.
> +If EVENT is a filewatch event then the callback is called.  If EVENT is
> +not a filewatch event then a `filewatch-error' is signaled."
> +  (interactive "e")
> +
> +  (unless (filewatch-event-p event)
> +    (signal 'filewatch-error (cons "Not a valid filewatch event" event)))
> +
> +  (loop for ev in (cdr event)
> +     unless (and (listp ev) (>= (length ev) 3))
> +     do (signal 'filewatch-error (cons "Not a valid filewatch event" event))
> +     do (funcall (cadr ev) (car ev) (caddr ev))))
> +
> +(provide 'filewatch)
> +
> +;;; filewatch.el ends here
> diff --git a/src/Makefile.in b/src/Makefile.in
> index c4250b9..9135e7d 100644
> --- a/src/Makefile.in
> +++ b/src/Makefile.in
> @@ -334,7 +334,7 @@ base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o 
> $(XMENU_OBJ) window.o \
>       syntax.o $(UNEXEC_OBJ) bytecode.o \
>       process.o gnutls.o callproc.o \
>       region-cache.o sound.o atimer.o \
> -     doprnt.o intervals.o textprop.o composite.o xml.o \
> +     doprnt.o intervals.o textprop.o composite.o xml.o filewatch.o \
>       $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ)
>  obj = $(base_obj) $(NS_OBJC_OBJ)
 
> diff --git a/src/emacs.c b/src/emacs.c
> index d14acd6..db897d5 100644
> --- a/src/emacs.c
> +++ b/src/emacs.c
> @@ -1563,6 +1563,10 @@ Using an Emacs configured with --with-x-toolkit=lucid 
> does not have this problem
>        syms_of_gnutls ();
>  #endif
 
> +#ifdef HAVE_FILEWATCH
> +      syms_of_filewatch ();
> +#endif /* HAVE_FILEWATCH */
> +
>  #ifdef HAVE_DBUS
>        syms_of_dbusbind ();
>  #endif /* HAVE_DBUS */
> diff --git a/src/filewatch.c b/src/filewatch.c
> new file mode 100644
> index 0000000..2833544
> --- /dev/null
> +++ b/src/filewatch.c
> @@ -0,0 +1,485 @@
> +/* Watching file system changes.
> +
> +Copyright (C) 2011
> +  Free Software Foundation, Inc.
> +
> +This file is part of GNU Emacs.
> +
> +GNU Emacs is free software: you can redistribute it and/or modify
> +it under the terms of the GNU General Public License as published by
> +the Free Software Foundation, either version 3 of the License, or
> +(at your option) any later version.
> +
> +GNU Emacs is distributed in the hope that it will be useful,
> +but WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +GNU General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include <config.h>
> +
> +#ifdef HAVE_FILEWATCH
> +#include <setjmp.h> /* Required for lisp.h.  */
> +#include "lisp.h"
> +#include "coding.h"
> +#include "process.h"
> +#include "keyboard.h"
> +#include "character.h"
> +#include "frame.h" /* Required for termhooks.h.  */
> +#include "termhooks.h"
> +
> +static Lisp_Object Qmodify, Qmove, Qattrib, Qdelete, Qfrom, Qto, Qall_modify;
> +static Lisp_Object Qunknown_aspect, Qfile_watch;
> +static Lisp_Object Qaccess, Qclose_write, Qclose_nowrite, Qopen, Qall;
> +
> +#ifdef HAVE_INOTIFY
> +#include <sys/inotify.h>
> +#include <sys/ioctl.h>
> +
> +enum { uninitialized = -100 };
> +/* File handle for inotify.  */
> +static int inotifyfd = uninitialized;
> +
> +/* Assoc list of files being watched.
> + Format:
> + (id . (filename ((subid callback flags) (subid callbacks flags) ...))) */
> +static Lisp_Object watch_list;
> +
> +#define CADDR(x) Fcar (Fcdr (Fcdr (x)))
> +#define CADR(x) Fcar (Fcdr (x))
> +
> +static Lisp_Object
> +append2 (Lisp_Object s1, Lisp_Object s2)
> +{
> +  Lisp_Object args[2];
> +  args[0] = s1;
> +  args[1] = s2;
> +  return Fappend (2, args);
> +}
> +
> +/* Returns a list with all the elements from EVENTS the `watch_list' CELL
> +   is interested in.  */
> +static Lisp_Object
> +match_aspects (Lisp_Object cell, Lisp_Object events)
> +{
> +  Lisp_Object ret = Qnil;
> +  Lisp_Object aspects = CADDR (cell);
> +  Lisp_Object i;
> +  if (EQ (aspects, Qt) || EQ (aspects, Qall))
> +    return events;
> +  else if (EQ (aspects, Qall_modify))
> +    aspects = list4 (Qmodify, Qmove, Qattrib, Qdelete);
> +
> +  i = Fcar (aspects);
> +  while (!NILP (i))
> +    {
> +      Lisp_Object e = Fcar (events);
> +      while (!NILP (e))
> +        {
> +          if (EQ (Fcar (e), i))
> +            ret = Fcons (e, ret);
> +          events = Fcdr (events);
> +          e = Fcar (events);
> +        }
> +      aspects = Fcdr (aspects);
> +      i = Fcar (aspects);
> +    }
> +  return ret;
> +}
> +
> +static Lisp_Object
> +inotifyevent_to_aspects (Lisp_Object name, struct inotify_event *ev)
> +{
> +  Lisp_Object events = Qnil;
> +  if (ev->mask & (IN_MODIFY|IN_CREATE) )
> +    events = Fcons (Fcons (Qmodify, name), events);
> +  if (ev->mask & IN_MOVE_SELF)
> +    events = Fcons (Fcons (Qmove, name), events);
> +  if (ev->mask & IN_MOVED_FROM)
> +    events = Fcons (Fcons (Qmove,
> +                           Fcons (Qfrom,
> +                                  Fcons (name,
> +                                         make_number (ev->cookie)))),
> +                    events);
> +  if (ev->mask & IN_MOVED_TO)
> +    events = Fcons (Fcons (Qmove,
> +                           Fcons (Qto,
> +                                  Fcons (name,
> +                                         make_number (ev->cookie)))),
> +                    events);
> +  if (ev->mask & IN_ATTRIB)
> +    events = Fcons (Fcons (Qattrib, name), events);
> +  if (ev->mask & (IN_DELETE|IN_DELETE_SELF) )
> +    events = Fcons (Fcons (Qdelete, name), events);
> +  if (ev->mask & IN_ACCESS)
> +    events = Fcons (Fcons (Qaccess, name), events);
> +  if (ev->mask & IN_CLOSE_WRITE)
> +    events = Fcons (Fcons (Qclose_write, name), events);
> +  if (ev->mask & IN_CLOSE_NOWRITE)
> +    events = Fcons (Fcons (Qclose_nowrite, name), events);
> +  if (ev->mask & IN_OPEN)
> +    events = Fcons (Fcons (Qopen, name), events);
> +  return events;
> +}
> +
> +/* This callback is called when the FD is available FOR_READ.  The inotify
> +   events are read from FD and converted into input_events.  */
> +static void
> +inotify_callback (int fd, void *_, int for_read)
> +{
> +  struct input_event event;
> +  int to_read;
> +  char *buffer;
> +  ssize_t n;
> +  size_t i;
> +
> +  if (!for_read)
> +    return;
> +
> +  to_read = 0;
> +  if (ioctl (fd, FIONREAD,  &to_read) == -1)
> +    report_file_error ("Error while trying to retrieve file system events",
> +                       Qnil);
> +  buffer = xmalloc (to_read);
> +  n = read (fd, buffer, to_read);
> +  if (n < 0)
> +    {
> +      xfree (buffer);
> +      report_file_error ("Error while trying to read file system events",
> +                         Qnil);
> +    }
> +
> +  EVENT_INIT (event);
> +  event.kind = FILEWATCH_EVENT;
> +  event.arg = Qnil;
> +
> +  i = 0;
> +  while (i < (size_t)n)
> +    {
> +      struct inotify_event *ev = (struct inotify_event*)&buffer[i];
> +
> +      Lisp_Object watch_data = Fassoc (make_number (ev->wd), watch_list);
> +      if (!NILP (watch_data))
> +        {
> +          Lisp_Object name, events;
> +
> +          /* If a directory is watched name contains the name
> +             of the file that was changed.  */
> +          if (ev->len > 0)
> +            {
> +              size_t const len = strlen (ev->name);
> +              name = make_unibyte_string (ev->name, min (len, ev->len));
> +              name = DECODE_FILE (name);
> +            }
> +          else
> +            {
> +              name = CADR (watch_data);
> +            }
> +
> +          events = inotifyevent_to_aspects (name, ev);
> +
> +          if (!NILP (events))
> +            {
> +              Lisp_Object x = CADDR (watch_data);
> +              while (!NILP (x))
> +                {
> +                  Lisp_Object cell = Fcar (x);
> +                  Lisp_Object aspects = match_aspects (cell, events);
> +                  if (!NILP (aspects))
> +                    {
> +                      Lisp_Object id = list3 (Qfile_watch, make_number 
> (ev->wd),
> +                                              Fcar (cell));
> +                      Lisp_Object event_info = list1 (list3 (id, CADR (cell),
> +                                                             aspects));
> +
> +                      if (NILP (event.arg))
> +                        event.arg = event_info;
> +                      else
> +                        event.arg = append2 (event.arg, event_info);
> +                    }
> +                  x = Fcdr (x);
> +                }
> +            }
> +
> +          if (ev->mask & IN_IGNORED)
> +            {
> +              /* Event was removed automatically: Drop it from data list.  */
> +              add_to_log ("File-watch: \"%s\" will be ignored", name, Qnil);
> +              watch_list = Fdelete (watch_data, watch_list);
> +            }
> +          if (ev->mask & IN_Q_OVERFLOW)
> +            add_to_log ("File watch: Inotify Queue Overflow!", Qnil, Qnil);
> +        }
> +
> +      i += sizeof (*ev) + ev->len;
> +    }
> +
> +  if (!NILP (event.arg))
> +    kbd_buffer_store_event (&event);
> +
> +  xfree (buffer);
> +}
> +
> +static uint32_t
> +symbol_to_inotifymask (Lisp_Object symb, int in_list)
> +{
> +  if (EQ (symb, Qmodify))
> +    return IN_MODIFY | IN_CREATE;
> +  else if (EQ (symb, Qmove))
> +    return IN_MOVE_SELF | IN_MOVE;
> +  else if (EQ (symb, Qattrib))
> +    return IN_ATTRIB;
> +  else if (EQ (symb, Qdelete))
> +    return IN_DELETE_SELF | IN_DELETE;
> +  else if (!in_list && EQ (symb, Qall_modify))
> +    return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB
> +      | IN_DELETE_SELF | IN_DELETE;
> +  else if (EQ (symb, Qaccess))
> +    return IN_ACCESS;
> +  else if (EQ (symb, Qclose_write))
> +    return IN_CLOSE_WRITE;
> +  else if (EQ (symb, Qclose_nowrite))
> +    return IN_CLOSE_NOWRITE;
> +  else if (EQ (symb, Qopen))
> +    return IN_OPEN;
> +  else if (!in_list && (EQ (symb, Qt) || EQ (symb, Qall)))
> +    return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB
> +      | IN_DELETE_SELF | IN_DELETE | IN_ACCESS | IN_CLOSE_WRITE
> +      | IN_CLOSE_NOWRITE | IN_OPEN;
> +  else
> +    Fsignal (Qunknown_aspect, Fcons (symb, Qnil));
> +}
> +
> +static uint32_t
> +aspect_to_inotifymask (Lisp_Object aspect)
> +{
> +  if (CONSP (aspect))
> +    {
> +      Lisp_Object x = aspect;
> +      uint32_t mask = 0;
> +      while (!NILP (x))
> +        {
> +          mask |= symbol_to_inotifymask (Fcar (x), 1);
> +          x = Fcdr (x);
> +        }
> +      return mask;
> +    }
> +  else
> +    return symbol_to_inotifymask (aspect, 0);
> +}
> +
> +static Lisp_Object
> +get_watch_data_by_file_name (Lisp_Object file_name)
> +{
> +  Lisp_Object cell, file_name_data;
> +  Lisp_Object x = watch_list;
> +  CHECK_STRING (file_name);
> +
> +  while (!NILP (x))
> +    {
> +      cell = Fcar (x);
> +      x = Fcdr (x);
> +      file_name_data = Fcdr (cell);
> +      if (!NILP (file_name_data) && STRINGP (Fcar (file_name_data))
> +          && !NILP (Fstring_equal (Fcar (file_name_data), file_name))
> +          && NUMBERP (Fcar (cell)))
> +        return cell;
> +    }
> +  return Qnil;
> +}
> +
> +DEFUN ("file-watch", Ffile_watch, Sfile_watch, 3, 3, 0,
> +       doc: /* Arrange to call FUNC if ASPECT of FILENAME changes.
> +
> +ASPECT might be one of the following symbols or a list of those symbols 
> (except
> +for all-modify and t):
> +
> +modify -- notify when a file is modified or created.
> +
> +move -- notify when a file/directory is moved.
> +
> +attrib -- notify when attributes change.
> +
> +delete -- notify when a file/directory is deleted.
> +
> +all-modify -- notify for all of the above (all modifying changes).
> +
> +access -- notify when file was accessed/read.
> +
> +close-write -- notify when a file opened for writing was closed.
> +
> +close-nowrite -- notify when a file not opened for writing was closed.
> +
> +open -- notify when a file was opened.
> +
> +t -- notify for all of the above.
> +
> +Watching a directory is not recursive.  CALLBACK receives the events as a 
> list
> +with each list element being a list containing information about an event.  
> The
> +first element is a flag symbol.  If a directory is watched the second 
> element is
> +the name of the file that changed.  If a file is moved from or to the 
> directory
> +the second element is either 'from or 'to and the third element is the file
> +name.  A fourth element contains a numeric identifier (cookie) that can be 
> used
> +to identify matching move operations if a file is moved inside the directory.
> +
> +Example:
> +(file-watch "foo" t #'(lambda (id event) (message "%s %s" id event)))
> +
> +Use `file-unwatch' to stop watching.
> +
> +usage: (file-watch FILENAME ASPECT CALLBACK) */)
> +  (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback)
> +{
> +  static int intern_counter = 0; /* assign local ids */
> +  uint32_t mask;
> +  int watchdesc;
> +  Lisp_Object decoded_file_name;
> +  Lisp_Object data;
> +  Lisp_Object info;
> +
> +  CHECK_STRING (file_name);
> +
> +  if (inotifyfd == uninitialized)
> +    {
> +      inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
> +      if (inotifyfd == -1)
> +        {
> +          inotifyfd = uninitialized;
> +          report_file_error ("File watching feature (inotify) is not 
> available",
> +                             Qnil);
> +        }
> +      watch_list = Qnil;
> +      add_read_fd (inotifyfd, &inotify_callback, &watch_list);
> +    }
> +
> +  mask = aspect_to_inotifymask (aspect);
> +  data = get_watch_data_by_file_name (file_name);
> +  if (!NILP (data))
> +    mask |= IN_MASK_ADD;
> +
> +  decoded_file_name = ENCODE_FILE (file_name);
> +  watchdesc = inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), 
> mask);
> +  if (watchdesc == -1)
> +    report_file_error ("Could not watch file", Fcons (file_name, Qnil));
> +
> +  info = list3 (make_number (intern_counter), callback, aspect);
> +  ++intern_counter;
> +
> +  if (!NILP (data))
> +    Fsetcdr (Fcdr (data), Fcons (append2 (CADDR (data), Fcons (info, Qnil)),
> +                                 Qnil));
> +  else
> +    watch_list = Fcons (list3 (make_number (watchdesc),
> +                               file_name, Fcons (info, Qnil)),
> +                        watch_list);
> +
> +  return list3 (Qfile_watch, make_number (watchdesc), Fcar (info));
> +}
> +
> +static int
> +file_watch_objectp (Lisp_Object obj)
> +{
> +  return CONSP (obj) && XINT (Flength (obj)) == 3
> +    && EQ (Fcar (obj), Qfile_watch);
> +}
> +
> +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0,
> +       doc: /* Stop watching a file or directory.  */)
> +  (Lisp_Object watch_object)
> +{
> +  Lisp_Object watch_data;
> +  Lisp_Object info;
> +
> +  if (!file_watch_objectp (watch_object))
> +    wrong_type_argument (Qfile_watch, watch_object);
> +
> +  if (inotifyfd == uninitialized)
> +    return Qnil;
> +
> +  watch_data = Fassoc ( CADR (watch_object), watch_list );
> +  if (NILP (watch_data))
> +    return Qnil;
> +
> +  info = Fassoc (CADDR (watch_object), CADDR (watch_data));
> +  if (NILP (info))
> +    return Qnil;
> +
> +  Fsetcdr (Fcdr (watch_data), Fcons (Fdelete (info, CADDR (watch_data)), 
> Qnil));
> +
> +  if (NILP (CADDR (watch_data)))
> +    {
> +      int const magicno = XINT (CADR (watch_object));
> +      watch_list = Fdelete (watch_data, watch_list);
> +
> +      if (inotify_rm_watch (inotifyfd, magicno) == -1)
> +        report_file_error ("Could not unwatch file",
> +                           Fcons (Fcar (watch_data), Qnil));
> +
> +      /* Cleanup if watch_list is empty.  */
> +      if (NILP (watch_list))
> +        {
> +          close (inotifyfd);
> +          delete_read_fd (inotifyfd);
> +          inotifyfd = uninitialized;
> +        }
> +    }
> +  else
> +    {
> +      Lisp_Object decoded_file_name = ENCODE_FILE (CADR (watch_data));
> +      Lisp_Object x = CADDR (watch_data);
> +      uint32_t mask = 0;
> +      while (!NILP (x))
> +        {
> +          Lisp_Object cell = Fcar (x);
> +          mask |= aspect_to_inotifymask (CADDR (cell));
> +          x = Fcdr (x);
> +        }
> +      /* Reset watch mask */
> +      if (inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask) == 
> -1)
> +        report_file_error ("Could not unwatch file",
> +                           Fcons (Fcar (watch_data), Qnil));
> +    }
> +
> +  return Qt;
> +}
> +
> +#else /* HAVE_INOTIFY */
> +#error "Filewatch defined but no watch mechanism (inotify) available"
> +#endif /* HAVE_INOTIFY */
> +
> +void
> +syms_of_filewatch (void)
> +{
> +  DEFSYM (Qmodify, "modify");
> +  DEFSYM (Qmove, "move");
> +  DEFSYM (Qattrib, "attrib");
> +  DEFSYM (Qdelete, "delete");
> +  DEFSYM (Qfrom, "from");
> +  DEFSYM (Qto, "to");
> +  DEFSYM (Qall_modify, "all-modify");
> +
> +  DEFSYM (Qaccess, "access");
> +  DEFSYM (Qclose_write, "close-write");
> +  DEFSYM (Qclose_nowrite, "close-nowrite");
> +  DEFSYM (Qopen, "open");
> +  DEFSYM (Qall, "all");
> +
> +  DEFSYM (Qunknown_aspect, "unknown-aspect");
> +  DEFSYM (Qfile_watch, "file-watch");
> +
> +  Fput (Qunknown_aspect, Qerror_conditions,
> +        list2 (Qunknown_aspect, Qerror));
> +  Fput (Qunknown_aspect, Qerror_message,
> +        make_pure_c_string ("Unknown Aspect"));
> +
> +  defsubr (&Sfile_watch);
> +  defsubr (&Sfile_unwatch);
> +
> +  staticpro (&watch_list);
> +
> +  Fprovide (intern_c_string ("filewatch"), Qnil);
> +}
> +
> +#endif /* HAVE_FILEWATCH */
> diff --git a/src/keyboard.c b/src/keyboard.c
> index e7a0598..673cb6e 100644
> --- a/src/keyboard.c
> +++ b/src/keyboard.c
> @@ -329,6 +329,9 @@ static Lisp_Object Qsave_session;
>  #ifdef HAVE_DBUS
>  static Lisp_Object Qdbus_event;
>  #endif
> +#ifdef HAVE_FILEWATCH
> +static Lisp_Object Qfilewatch_event;
> +#endif /* HAVE_FILEWATCH */
>  static Lisp_Object Qconfig_changed_event;
 
>  /* Lisp_Object Qmouse_movement; - also an event header */
> @@ -4017,6 +4020,13 @@ kbd_buffer_get_event (KBOARD **kbp,
>         kbd_fetch_ptr = event + 1;
>       }
>  #endif
> +#ifdef HAVE_FILEWATCH
> +      else if (event->kind == FILEWATCH_EVENT)
> +        {
> +          obj = make_lispy_event (event);
> +          kbd_fetch_ptr = event + 1;
> +        }
> +#endif
>        else if (event->kind == CONFIG_CHANGED_EVENT)
>       {
>         obj = make_lispy_event (event);
> @@ -5913,6 +5923,13 @@ make_lispy_event (struct input_event *event)
>        }
>  #endif /* HAVE_DBUS */
 
> +#ifdef HAVE_FILEWATCH
> +    case FILEWATCH_EVENT:
> +      {
> +        return Fcons (Qfilewatch_event, event->arg);
> +      }
> +#endif /* HAVE_FILEWATCH */
> +
>      case CONFIG_CHANGED_EVENT:
>       return Fcons (Qconfig_changed_event,
>                        Fcons (event->arg,
> @@ -11524,6 +11541,10 @@ syms_of_keyboard (void)
>    DEFSYM (Qdbus_event, "dbus-event");
>  #endif
 
> +#ifdef HAVE_FILEWATCH
> +  DEFSYM (Qfilewatch_event, "filewatch-event");
> +#endif /* HAVE_FILEWATCH */
> +
>    DEFSYM (QCenable, ":enable");
>    DEFSYM (QCvisible, ":visible");
>    DEFSYM (QChelp, ":help");
> @@ -12274,6 +12295,13 @@ keys_of_keyboard (void)
>                           "dbus-handle-event");
>  #endif
 
> +#ifdef HAVE_FILEWATCH
> +  /* Define a special event which is raised for filewatch callback
> +     functions.  */
> +  initial_define_lispy_key (Vspecial_event_map, "filewatch-event",
> +                            "filewatch-handle-event");
> +#endif /* HAVE_FILEWATCH */
> +
>    initial_define_lispy_key (Vspecial_event_map, "config-changed-event",
>                           "ignore");
>  }
> diff --git a/src/lisp.h b/src/lisp.h
> index 1e30363..a8419d1 100644
> --- a/src/lisp.h
> +++ b/src/lisp.h
> @@ -3494,6 +3494,11 @@ EXFUN (Fxw_display_color_p, 1);
>  EXFUN (Fx_focus_frame, 1);
>  #endif
 
> +/* Defined in filewatch.c */
> +#ifdef HAVE_FILEWATCH
> +extern void syms_of_filewatch (void);
> +#endif
> +
>  /* Defined in xfaces.c */
>  extern Lisp_Object Qdefault, Qtool_bar, Qfringe;
>  extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor;
> diff --git a/src/termhooks.h b/src/termhooks.h
> index 6a58517..9db2019 100644
> --- a/src/termhooks.h
> +++ b/src/termhooks.h
> @@ -206,6 +206,11 @@ enum event_kind
>    , NS_NONKEY_EVENT
>  #endif
 
> +#ifdef HAVE_FILEWATCH
> +  /* File or directory was changed.  */
> +  , FILEWATCH_EVENT
> +#endif
> +
>  };
 
>  /* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT
> diff --git a/test/automated/filewatch-tests.el 
> b/test/automated/filewatch-tests.el
> new file mode 100644
> index 0000000..494d704
> --- /dev/null
> +++ b/test/automated/filewatch-tests.el
> @@ -0,0 +1,50 @@
> +;;; filewatch-tests.el --- Test suite for filewatch.
> +
> +;; Copyright (C) 2011 Free Software Foundation, Inc.
> +
> +;; Author: Rüdiger Sonderfeld <address@hidden>
> +;; Keywords:       internal
> +;; Human-Keywords: internal
> +
> +;; This file is part of GNU Emacs.
> +
> +;; GNU Emacs is free software: you can redistribute it and/or modify
> +;; it under the terms of the GNU General Public License as published by
> +;; the Free Software Foundation, either version 3 of the License, or
> +;; (at your option) any later version.
> +
> +;; GNU Emacs is distributed in the hope that it will be useful,
> +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
> +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +;; GNU General Public License for more details.
> +
> +;; You should have received a copy of the GNU General Public License
> +;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
> +
> +;;; Code:
> +
> +(require 'ert)
> +
> +(when (featurep 'filewatch)
> +
> +  (ert-deftest filewatch-file-unwatch-type-check ()
> +    "Test whether `file-unwatch' does proper type checking."
> +    (should-error (file-unwatch "path") :type 'wrong-type-argument)
> +    (should-error (file-unwatch '(file 1 2)) :type 'wrong-type-argument))
> +
> +  (ert-deftest filewatch-file-watch-aspects-check ()
> +    "Test whether `file-watch' properly checks the aspects."
> +    (let ((temp-file (make-temp-file "filewatch-aspects")))
> +      (should (stringp temp-file))
> +      (should-error (file-watch temp-file 'wrong nil)
> +                    :type 'unknown-aspect)
> +      (should-error (file-watch temp-file '(modify t) nil)
> +                    :type 'unknown-aspect)
> +      (should-error (file-watch temp-file '(modify Qall-modify) nil)
> +                    :type 'unknown-aspect)
> +      (should-error (file-watch temp-file '(access wrong modify) nil)
> +                    :type 'unknown-aspect)))
> +)
> +
> +(provide 'filewatch-tests)
> +;;; filewatch-tests.el ends here.
> -- 
> 1.7.5.4



reply via email to

[Prev in Thread] Current Thread [Next in Thread]