From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: =?ISO-8859-1?Q?R=FCdiger?= Sonderfeld Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] Added basic file system watching support. Date: Fri, 28 Sep 2012 15:06:24 +0200 Message-ID: <6218185.ViukoKRdFp@descartes> References: NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Trace: ger.gmane.org 1348837621 2192 80.91.229.3 (28 Sep 2012 13:07:01 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Fri, 28 Sep 2012 13:07:01 +0000 (UTC) Cc: Stefan Monnier , Leo To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Fri Sep 28 15:07:05 2012 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1THaHV-0002im-Og for ged-emacs-devel@m.gmane.org; Fri, 28 Sep 2012 15:07:02 +0200 Original-Received: from localhost ([::1]:51754 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1THaHQ-0002gj-EM for ged-emacs-devel@m.gmane.org; Fri, 28 Sep 2012 09:06:56 -0400 Original-Received: from eggs.gnu.org ([208.118.235.92]:51310) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1THaHG-0002gI-QD for emacs-devel@gnu.org; Fri, 28 Sep 2012 09:06:54 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1THaH8-00010m-Ev for emacs-devel@gnu.org; Fri, 28 Sep 2012 09:06:46 -0400 Original-Received: from ptmx.org ([178.63.28.110]:45650) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1THaH7-0000zy-Qv for emacs-devel@gnu.org; Fri, 28 Sep 2012 09:06:38 -0400 Original-Received: from localhost (localhost [127.0.0.1]) by ptmx.org (Postfix) with ESMTP id 9986C21DD5; Fri, 28 Sep 2012 15:06:33 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at ptmx.org Original-Received: from ptmx.org ([127.0.0.1]) by localhost (ptmx.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id u1pZi13HZAX1; Fri, 28 Sep 2012 15:06:30 +0200 (CEST) Original-Received: from descartes.localnet (chello080108246092.7.14.vie.surfer.at [80.108.246.92]) by ptmx.org (Postfix) with ESMTPSA id D3EA0200BF; Fri, 28 Sep 2012 15:06:29 +0200 (CEST) User-Agent: KMail/4.8.5 (Linux/3.2.0-31-generic; KDE/4.8.5; x86_64; ; ) In-Reply-To: X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 178.63.28.110 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:153676 Archived-At: Hello, this is an updated version of my "file system watching" patch (full thread: http://thread.gmane.org/gmane.emacs.devel/140158 ) I don't think the patch will be ready for 24.3. It is still not complet= e and needs some heavy testing. I addressed some of the issues mentioned by Stefan Monnier here http://article.gmane.org/gmane.emacs.devel/141741 > It seems that each event is either of form (SYMBOL . FILENAME) where > FILENAME is the watched object, or of the form (SYMBOL from NAME . CO= OKIE) > where NAME is the file added/removed from the watched object (a > directory, presumably), but this object's FILENAME is not present. > Any particular reason for this inconsistency? FILENAME is not always the watched object. If a directory is watched t= hen it is the file name of the file inside the directory. This should real= ly not be used as identification for the watched object! That's why CALLBACK also gets the ID. > IIUC, these cookies are part of the patch to which Paul objected. > And IIUC their content has no importance, they're only checked for > equality, right? > > So they don't have to be represented as numbers, but could be > represented by some other special object, as long as (eq cookie1 > cookie2) returns the proper boolean value when comparing those > special objects. > > I guess that several `ev' objects can have the same `ev->cookie' > value, right? Yes exactly. The value is unimportant as long as it is unique to an ev= ent and can be compared with eq. What would you propose as an alternative object here? > Wouldn't it be a bug for events to be nil here (that would mean you > receive inotify events for files you do not know you're watching, or > something like that)? The problem is that even after removal of a watch descriptor there can still be issues in the queue. Therefore I think it is best to ignore a= ny events for files we do not watch. There could also be event types we d= o not know about when inotify gets expanded. That's why I think unknown event types should be ignored instead of reporting an error. > I don't think you need ev->wd in your file-watch objects. You could = use > Fcons (Qfile_watch, cell) instead, IIUC. It would be good, because i= t > would let you use a SAVE_VALUE object for ev->wd (since it wouldn't > escape to Lisp any more), which would solve the problem of losing a f= ew > bits of data in make_number. I don't need to make the watchdescriptor (wd) available to elisp. But = I need a way to get the descriptor from the watch handle. How could this be implemented with SAVE_VALUE? > Shouldn't this signal an error instead? This could be the result of calling file-unwatch too often. Currently the behaviour is to return nil in that case. But this could also be seen as an error of course. Regards, R=C3=BCdiger -- >8 -- The current patch only works with inotify (Linux). It adds two elisp functions file-watch and file-unwatch. Example (let ((fh (file-watch "foo" t #'(lambda (id event) (message "%s %s" id event))))) (delete-file "foo/bar") (file-unwatch fh)) (Expects a directory foo containing a file bar) This watches for any kind of changes in a directory foo and then deletes a file foo/bar and finally removes the watch. Signed-off-by: R=C3=BCdiger Sonderfeld --- configure.ac | 14 ++ lisp/subr.el | 23 ++ src/Makefile.in | 2 +- src/emacs.c | 4 + src/filewatch.c | 457 ++++++++++++++++++++++++++++++= ++++++++ src/keyboard.c | 28 +++ src/lisp.h | 5 + src/termhooks.h | 5 + test/automated/filewatch-tests.el | 50 +++++ 9 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 src/filewatch.c create mode 100644 test/automated/filewatch-tests.el diff --git a/configure.ac b/configure.ac index 5a3aea7..8b8054b 100644 --- a/configure.ac +++ b/configure.ac @@ -184,6 +184,7 @@ OPTION_DEFAULT_ON([gconf],[don't compile with GConf= support]) OPTION_DEFAULT_ON([gsettings],[don't compile with GSettings 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) s= upport]) =20 ## For the times when you want to build Emacs but don't have ## a suitable makeinfo, and can live without the manuals. @@ -2101,6 +2102,19 @@ fi AC_SUBST(LIBGNUTLS_LIBS) AC_SUBST(LIBGNUTLS_CFLAGS) =20 +dnl inotify is only available on GNU/Linux. +HAVE_INOTIFY=3Dno +if test "${with_inotify}" =3D "yes"; then + AC_CHECK_HEADERS(sys/inotify.h) + if test "$ac_cv_header_sys_inotify_h" =3D yes ; then + AC_CHECK_FUNCS(inotify_init1 inotify_add_watch inotify_rm_watch, = HAVE_INOTIFY=3Dyes) + fi +fi +if test "${HAVE_INOTIFY}" =3D "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=3Dno diff --git a/lisp/subr.el b/lisp/subr.el index 8dfe78d..f47d4fb 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -4159,6 +4159,29 @@ convenience wrapper around `make-progress-report= er' and friends. nil ,@(cdr (cdr spec))))) =20 =0C +;;;; Support for watching filesystem events. + +(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 i= s +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= ))) + + (dolist (ev (cdr event)) + (unless (and (listp ev) (>=3D (length ev) 3)) + (signal 'filewatch-error (cons "Not a valid filewatch event" eve= nt))) + (funcall (car (cdr ev)) (car ev) (car (cdr (cdr ev)))))) + +=0C ;;;; Comparing version strings. =20 (defconst version-separator "." diff --git a/src/Makefile.in b/src/Makefile.in index e43f83e..1f17830 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -338,7 +338,7 @@ base_obj =3D dispnew.o frame.o scroll.o xdisp.o men= u.o $(XMENU_OBJ) window.o \ =09syntax.o $(UNEXEC_OBJ) bytecode.o \ =09process.o gnutls.o callproc.o \ =09region-cache.o sound.o atimer.o \ -=09doprnt.o intervals.o textprop.o composite.o xml.o \ +=09doprnt.o intervals.o textprop.o composite.o xml.o filewatch.o \ =09profiler.o \ =09$(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \ =09$(WINDOW_SYSTEM_OBJ) diff --git a/src/emacs.c b/src/emacs.c index 05affee..7c306ee 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1411,6 +1411,10 @@ Using an Emacs configured with --with-x-toolkit=3D= lucid does not have this problem syms_of_gnutls (); #endif =20 +#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..c29e8b1 --- /dev/null +++ b/src/filewatch.c @@ -0,0 +1,457 @@ +/* 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 . */= + +#include + +#ifdef HAVE_FILEWATCH +#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 Qfile_watch; +static Lisp_Object Qaccess, Qclose_write, Qclose_nowrite, Qopen, Qall;= + +#ifdef HAVE_INOTIFY +#include +#include + +enum { uninitialized =3D -100 }; +/* File handle for inotify. */ +static int inotifyfd =3D uninitialized; + +/* Assoc list of files being watched. + Format: + (id . (filename ((subid callback flags) (subid callbacks flags) ...))= ) */ +static Lisp_Object watch_list; + +static Lisp_Object +get_watch_data_by_watchdesc (int watchdesc) +{ + return Fassq (make_number (watchdesc), watch_list); +} + +#define CADDR(x) XCAR (XCDR (XCDR (x))) +#define CADR(x) XCAR (XCDR (x)) + +static Lisp_Object +append2 (Lisp_Object s1, Lisp_Object s2) +{ + Lisp_Object args[2]; + args[0] =3D s1; + args[1] =3D s2; + return Fappend (2, args); +} + +/* Returns a list with all the elements from EVENTS the `watch_list' C= ELL + is interested in. */ +static Lisp_Object +match_aspects (Lisp_Object cell, Lisp_Object events) +{ + Lisp_Object e; + Lisp_Object ret =3D Qnil; + Lisp_Object aspects =3D CADDR (cell); + if (EQ (aspects, Qt) || EQ (aspects, Qall)) + return events; + else if (EQ (aspects, Qall_modify)) + aspects =3D list4 (Qmodify, Qmove, Qattrib, Qdelete); + else if (! CONSP (aspects)) + aspects =3D list1 (aspects); + + while (CONSP (events)) + { + e =3D XCAR (events); + if (! NILP (Fmemq (XCAR (e), aspects))) + ret =3D Fcons (e, ret); + events =3D XCDR (events); + } + return ret; +} + +static Lisp_Object +inotifyevent_to_events (Lisp_Object name, struct inotify_event *ev) +{ + Lisp_Object events =3D Qnil; + if (ev->mask & (IN_MODIFY|IN_CREATE) ) + events =3D Fcons (Fcons (Qmodify, name), events); + if (ev->mask & IN_MOVE_SELF) + events =3D Fcons (Fcons (Qmove, name), events); + if (ev->mask & IN_MOVED_FROM) + events =3D Fcons (Fcons (Qmove, + Fcons (Qfrom, + Fcons (name, + make_number (ev->cookie)))), + events); + if (ev->mask & IN_MOVED_TO) + events =3D Fcons (Fcons (Qmove, + Fcons (Qto, + Fcons (name, + make_number (ev->cookie)))), + events); + if (ev->mask & IN_ATTRIB) + events =3D Fcons (Fcons (Qattrib, name), events); + if (ev->mask & (IN_DELETE|IN_DELETE_SELF) ) + events =3D Fcons (Fcons (Qdelete, name), events); + if (ev->mask & IN_ACCESS) + events =3D Fcons (Fcons (Qaccess, name), events); + if (ev->mask & IN_CLOSE_WRITE) + events =3D Fcons (Fcons (Qclose_write, name), events); + if (ev->mask & IN_CLOSE_NOWRITE) + events =3D Fcons (Fcons (Qclose_nowrite, name), events); + if (ev->mask & IN_OPEN) + events =3D Fcons (Fcons (Qopen, name), events); + return events; +} + +/* This callback is called when the FD is available for read. The ino= tify + events are read from FD and converted into input_events. */ +static void +inotify_callback (int fd, void *_) +{ + struct input_event event; + int to_read; + char *buffer; + ssize_t n; + size_t i; + + to_read =3D 0; + if (ioctl (fd, FIONREAD, &to_read) =3D=3D -1) + report_file_error ("Error while trying to retrieve file system eve= nts", + Qnil); + buffer =3D xmalloc (to_read); + n =3D read (fd, buffer, to_read); + if (n < 0) + { + xfree (buffer); + report_file_error ("Error while trying to read file system event= s", + Qnil); + } + + EVENT_INIT (event); + event.kind =3D FILEWATCH_EVENT; + event.arg =3D Qnil; + + i =3D 0; + while (i < (size_t)n) + { + struct inotify_event *ev =3D (struct inotify_event*)&buffer[i]; + + Lisp_Object watch_data =3D get_watch_data_by_watchdesc (ev->wd);= + 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 =3D strlen (ev->name); + name =3D make_unibyte_string (ev->name, min (len, ev->le= n)); + name =3D DECODE_FILE (name); + } + else + { + name =3D CADR (watch_data); + } + + events =3D inotifyevent_to_events (name, ev); + + if (!NILP (events)) + { + Lisp_Object x =3D CADDR (watch_data); + while (CONSP (x)) + { + Lisp_Object cell =3D XCAR (x); + Lisp_Object aspects =3D match_aspects (cell, events)= ; + if (!NILP (aspects)) + { + Lisp_Object id =3D list3 (Qfile_watch, make_numb= er (ev->wd), + XCAR (cell)); + Lisp_Object event_info =3D list1 (list3 (id, CAD= R (cell), + aspects))= ; + + if (NILP (event.arg)) + event.arg =3D event_info; + else + event.arg =3D append2 (event.arg, event_info);= + } + x =3D XCDR (x); + } + } + + if (ev->mask & IN_IGNORED) + { + /* Event was removed automatically: Drop it from data li= st. */ + add_to_log ("File-watch: \"%s\" will be ignored", name, = Qnil); + watch_list =3D Fdelete (watch_data, watch_list); + } + if (ev->mask & IN_Q_OVERFLOW) + add_to_log ("File watch: Inotify Queue Overflow!", Qnil, Q= nil); + } + + i +=3D 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 + signal_error ("Unknown aspect", symb); +} + +static uint32_t +aspect_to_inotifymask (Lisp_Object aspect) +{ + if (CONSP (aspect)) + { + Lisp_Object x =3D aspect; + uint32_t mask =3D 0; + while (CONSP (x)) + { + mask |=3D symbol_to_inotifymask (XCAR (x), 1); + x =3D XCDR (x); + } + return mask; + } + else + return symbol_to_inotifymask (aspect, 0); +} + +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 symbol= s (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 gets passed two +arguments ID and EVENT. The ID argument is the id returned by +file-watch. The EVENT argument is a list of events taking the form +(OP . FILENAME) or (OP DIRECTION NAME COOKIE). OP is the operation +that caused the event and FILENAME is the name of the file the +operation applied to. If the even belongs to a move operation inside +a directory then the (OP DIRECTION NAME COOKIE) form is used and +DIRECTION is either 'from or 'to indicating the direction of the move +operation. NAME is the corresponding file name. COOKIE is an +unspecified object that can be compared using `eq' to identify the +matching from and to move operation. + +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 =3D 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 =3D=3D uninitialized) + { + inotifyfd =3D inotify_init1 (IN_NONBLOCK|IN_CLOEXEC); + if (inotifyfd =3D=3D -1) + { + inotifyfd =3D uninitialized; + report_file_error ("File watching feature (inotify) is not a= vailable", + Qnil); + } + watch_list =3D Qnil; + add_read_fd (inotifyfd, &inotify_callback, NULL); + } + + mask =3D aspect_to_inotifymask (aspect) | IN_MASK_ADD; + decoded_file_name =3D ENCODE_FILE (file_name); + watchdesc =3D inotify_add_watch (inotifyfd, SSDATA (decoded_file_nam= e), mask); + if (watchdesc =3D=3D -1) + report_file_error ("Could not watch file", Fcons (file_name, Qnil)= ); + + info =3D list3 (make_number (intern_counter), callback, aspect); + ++intern_counter; + + data =3D get_watch_data_by_watchdesc (watchdesc); + if (CONSP (data)) + XSETCDR (XCDR (data), Fcons (append2 (CADDR (data), Fcons (info, Q= nil)), + Qnil)); + else + watch_list =3D Fcons (list3 (make_number (watchdesc), + file_name, Fcons (info, Qnil)), + watch_list); + + return list3 (Qfile_watch, make_number (watchdesc), XCAR (info)); +} + +static int +file_watch_objectp (Lisp_Object obj) +{ + return CONSP (obj) && XINT (Flength (obj)) =3D=3D 3 + && EQ (XCAR (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 =3D=3D uninitialized) + return Qnil; + + watch_data =3D Fassoc ( CADR (watch_object), watch_list ); + if (NILP (watch_data)) + return Qnil; + + info =3D Fassoc (CADDR (watch_object), CADDR (watch_data)); + if (NILP (info)) + return Qnil; + + XSETCDR (XCDR (watch_data), Fcons (Fdelete (info, CADDR (watch_data)= ), Qnil)); + + if (NILP (CADDR (watch_data))) + { + int const magicno =3D XINT (CADR (watch_object)); + watch_list =3D Fdelete (watch_data, watch_list); + + if (inotify_rm_watch (inotifyfd, magicno) =3D=3D -1) + report_file_error ("Could not unwatch file", + Fcons (XCAR (watch_data), Qnil)); + + /* Cleanup if watch_list is empty. */ + if (NILP (watch_list)) + { + close (inotifyfd); + delete_read_fd (inotifyfd); + inotifyfd =3D uninitialized; + } + } + else + { + Lisp_Object decoded_file_name =3D ENCODE_FILE (CADR (watch_data)= ); + Lisp_Object x =3D CADDR (watch_data); + uint32_t mask =3D 0; + while (CONSP (x)) + { + Lisp_Object cell =3D XCAR (x); + mask |=3D aspect_to_inotifymask (CADDR (cell)); + x =3D XCDR (x); + } + /* Reset watch mask */ + if (inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), ma= sk) =3D=3D -1) + report_file_error ("Could not unwatch file", + Fcons (XCAR (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 (Qfile_watch, "file-watch"); + + 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 f3d7df5..f694b13 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -321,6 +321,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; =20 /* Lisp_Object Qmouse_movement; - also an event header */ @@ -4021,6 +4024,13 @@ kbd_buffer_get_event (KBOARD **kbp, =09 kbd_fetch_ptr =3D event + 1; =09} #endif +#ifdef HAVE_FILEWATCH + else if (event->kind =3D=3D FILEWATCH_EVENT) + { + obj =3D make_lispy_event (event); + kbd_fetch_ptr =3D event + 1; + } +#endif else if (event->kind =3D=3D CONFIG_CHANGED_EVENT) =09{ =09 obj =3D make_lispy_event (event); @@ -5938,6 +5948,13 @@ make_lispy_event (struct input_event *event) } #endif /* HAVE_DBUS */ =20 +#ifdef HAVE_FILEWATCH + case FILEWATCH_EVENT: + { + return Fcons (Qfilewatch_event, event->arg); + } +#endif /* HAVE_FILEWATCH */ + case CONFIG_CHANGED_EVENT: =09return Fcons (Qconfig_changed_event, Fcons (event->arg, @@ -11408,6 +11425,10 @@ syms_of_keyboard (void) DEFSYM (Qdbus_event, "dbus-event"); #endif =20 +#ifdef HAVE_FILEWATCH + DEFSYM (Qfilewatch_event, "filewatch-event"); +#endif /* HAVE_FILEWATCH */ + DEFSYM (QCenable, ":enable"); DEFSYM (QCvisible, ":visible"); DEFSYM (QChelp, ":help"); @@ -12168,6 +12189,13 @@ keys_of_keyboard (void) =09=09=09 "dbus-handle-event"); #endif =20 +#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"= , =09=09=09 "ignore"); #if defined (WINDOWSNT) diff --git a/src/lisp.h b/src/lisp.h index 21ac55c..2f3e8b7 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -3497,6 +3497,11 @@ extern void syms_of_fontset (void); extern Lisp_Object Qfont_param; #endif =20 +/* 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 f35bd92..ccb5887 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -211,6 +211,11 @@ enum event_kind , NS_NONKEY_EVENT #endif =20 +#ifdef HAVE_FILEWATCH + /* File or directory was changed. */ + , FILEWATCH_EVENT +#endif + }; =20 /* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT= diff --git a/test/automated/filewatch-tests.el b/test/automated/filewat= ch-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=C3=BCdiger Sonderfeld +;; 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 b= y +;; 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 . + +;;; 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-argumen= t)) + + (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. --=20 1.7.11.3