diff options
Diffstat (limited to 'hgext/inotify/linux')
-rw-r--r-- | hgext/inotify/linux/__init__.py | 44 | ||||
-rw-r--r-- | hgext/inotify/linux/_inotify.c | 649 | ||||
-rw-r--r-- | hgext/inotify/linux/watcher.py | 335 |
3 files changed, 1028 insertions, 0 deletions
diff --git a/hgext/inotify/linux/__init__.py b/hgext/inotify/linux/__init__.py new file mode 100644 index 0000000..8a1bafd --- /dev/null +++ b/hgext/inotify/linux/__init__.py @@ -0,0 +1,44 @@ +# __init__.py - low-level interfaces to the Linux inotify subsystem + +# Copyright 2006 Bryan O'Sullivan <bos@serpentine.com> + +# This library is free software; you can redistribute it and/or modify +# it under the terms of version 2.1 of the GNU Lesser General Public +# License, or any later version. + +'''Low-level interface to the Linux inotify subsystem. + +The inotify subsystem provides an efficient mechanism for file status +monitoring and change notification. + +This package provides the low-level inotify system call interface and +associated constants and helper functions. + +For a higher-level interface that remains highly efficient, use the +inotify.watcher package.''' + +__author__ = "Bryan O'Sullivan <bos@serpentine.com>" + +from _inotify import * + +procfs_path = '/proc/sys/fs/inotify' + +def _read_procfs_value(name): + def read_value(): + try: + fp = open(procfs_path + '/' + name) + r = int(fp.read()) + fp.close() + return r + except OSError: + return None + + read_value.__doc__ = '''Return the value of the %s setting from /proc. + + If inotify is not enabled on this system, return None.''' % name + + return read_value + +max_queued_events = _read_procfs_value('max_queued_events') +max_user_instances = _read_procfs_value('max_user_instances') +max_user_watches = _read_procfs_value('max_user_watches') diff --git a/hgext/inotify/linux/_inotify.c b/hgext/inotify/linux/_inotify.c new file mode 100644 index 0000000..5e31b85 --- /dev/null +++ b/hgext/inotify/linux/_inotify.c @@ -0,0 +1,649 @@ +/* + * _inotify.c - Python extension interfacing to the Linux inotify subsystem + * + * Copyright 2006 Bryan O'Sullivan <bos@serpentine.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General + * Public License or any later version. + */ + +#include <Python.h> +#include <alloca.h> +#include <sys/inotify.h> +#include <stdint.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <util.h> + +/* Variables used in the event string representation */ +static PyObject *join; +static PyObject *er_wm; +static PyObject *er_wmc; +static PyObject *er_wmn; +static PyObject *er_wmcn; + +static PyObject *init(PyObject *self, PyObject *args) +{ + PyObject *ret = NULL; + int fd = -1; + + if (!PyArg_ParseTuple(args, ":init")) + goto bail; + + Py_BEGIN_ALLOW_THREADS; + fd = inotify_init(); + Py_END_ALLOW_THREADS; + + if (fd == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto bail; + } + + ret = PyInt_FromLong(fd); + if (ret == NULL) + goto bail; + + goto done; + +bail: + if (fd != -1) + close(fd); + + Py_CLEAR(ret); + +done: + return ret; +} + +PyDoc_STRVAR( + init_doc, + "init() -> fd\n" + "\n" + "Initialise an inotify instance.\n" + "Return a file descriptor associated with a new inotify event queue."); + +static PyObject *add_watch(PyObject *self, PyObject *args) +{ + PyObject *ret = NULL; + uint32_t mask; + int wd = -1; + char *path; + int fd; + + if (!PyArg_ParseTuple(args, "isI:add_watch", &fd, &path, &mask)) + goto bail; + + Py_BEGIN_ALLOW_THREADS; + wd = inotify_add_watch(fd, path, mask); + Py_END_ALLOW_THREADS; + + if (wd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + goto bail; + } + + ret = PyInt_FromLong(wd); + if (ret == NULL) + goto bail; + + goto done; + +bail: + if (wd != -1) + inotify_rm_watch(fd, wd); + + Py_CLEAR(ret); + +done: + return ret; +} + +PyDoc_STRVAR( + add_watch_doc, + "add_watch(fd, path, mask) -> wd\n" + "\n" + "Add a watch to an inotify instance, or modify an existing watch.\n" + "\n" + " fd: file descriptor returned by init()\n" + " path: path to watch\n" + " mask: mask of events to watch for\n" + "\n" + "Return a unique numeric watch descriptor for the inotify instance\n" + "mapped by the file descriptor."); + +static PyObject *remove_watch(PyObject *self, PyObject *args) +{ + uint32_t wd; + int fd; + int r; + + if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd)) + return NULL; + + Py_BEGIN_ALLOW_THREADS; + r = inotify_rm_watch(fd, wd); + Py_END_ALLOW_THREADS; + + if (r == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR( + remove_watch_doc, + "remove_watch(fd, wd)\n" + "\n" + " fd: file descriptor returned by init()\n" + " wd: watch descriptor returned by add_watch()\n" + "\n" + "Remove a watch associated with the watch descriptor wd from the\n" + "inotify instance associated with the file descriptor fd.\n" + "\n" + "Removing a watch causes an IN_IGNORED event to be generated for this\n" + "watch descriptor."); + +#define bit_name(x) {x, #x} + +static struct { + int bit; + const char *name; + PyObject *pyname; +} bit_names[] = { + bit_name(IN_ACCESS), + bit_name(IN_MODIFY), + bit_name(IN_ATTRIB), + bit_name(IN_CLOSE_WRITE), + bit_name(IN_CLOSE_NOWRITE), + bit_name(IN_OPEN), + bit_name(IN_MOVED_FROM), + bit_name(IN_MOVED_TO), + bit_name(IN_CREATE), + bit_name(IN_DELETE), + bit_name(IN_DELETE_SELF), + bit_name(IN_MOVE_SELF), + bit_name(IN_UNMOUNT), + bit_name(IN_Q_OVERFLOW), + bit_name(IN_IGNORED), + bit_name(IN_ONLYDIR), + bit_name(IN_DONT_FOLLOW), + bit_name(IN_MASK_ADD), + bit_name(IN_ISDIR), + bit_name(IN_ONESHOT), + {0} +}; + +static PyObject *decode_mask(int mask) +{ + PyObject *ret = PyList_New(0); + int i; + + if (ret == NULL) + goto bail; + + for (i = 0; bit_names[i].bit; i++) { + if (mask & bit_names[i].bit) { + if (bit_names[i].pyname == NULL) { + bit_names[i].pyname = PyString_FromString(bit_names[i].name); + if (bit_names[i].pyname == NULL) + goto bail; + } + Py_INCREF(bit_names[i].pyname); + if (PyList_Append(ret, bit_names[i].pyname) == -1) + goto bail; + } + } + + goto done; + +bail: + Py_CLEAR(ret); + +done: + return ret; +} + +static PyObject *pydecode_mask(PyObject *self, PyObject *args) +{ + int mask; + + if (!PyArg_ParseTuple(args, "i:decode_mask", &mask)) + return NULL; + + return decode_mask(mask); +} + +PyDoc_STRVAR( + decode_mask_doc, + "decode_mask(mask) -> list_of_strings\n" + "\n" + "Decode an inotify mask value into a list of strings that give the\n" + "name of each bit set in the mask."); + +static char doc[] = "Low-level inotify interface wrappers."; + +static void define_const(PyObject *dict, const char *name, uint32_t val) +{ + PyObject *pyval = PyInt_FromLong(val); + PyObject *pyname = PyString_FromString(name); + + if (!pyname || !pyval) + goto bail; + + PyDict_SetItem(dict, pyname, pyval); + +bail: + Py_XDECREF(pyname); + Py_XDECREF(pyval); +} + +static void define_consts(PyObject *dict) +{ + define_const(dict, "IN_ACCESS", IN_ACCESS); + define_const(dict, "IN_MODIFY", IN_MODIFY); + define_const(dict, "IN_ATTRIB", IN_ATTRIB); + define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE); + define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE); + define_const(dict, "IN_OPEN", IN_OPEN); + define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM); + define_const(dict, "IN_MOVED_TO", IN_MOVED_TO); + + define_const(dict, "IN_CLOSE", IN_CLOSE); + define_const(dict, "IN_MOVE", IN_MOVE); + + define_const(dict, "IN_CREATE", IN_CREATE); + define_const(dict, "IN_DELETE", IN_DELETE); + define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF); + define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF); + define_const(dict, "IN_UNMOUNT", IN_UNMOUNT); + define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW); + define_const(dict, "IN_IGNORED", IN_IGNORED); + + define_const(dict, "IN_ONLYDIR", IN_ONLYDIR); + define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW); + define_const(dict, "IN_MASK_ADD", IN_MASK_ADD); + define_const(dict, "IN_ISDIR", IN_ISDIR); + define_const(dict, "IN_ONESHOT", IN_ONESHOT); + define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS); +} + +struct event { + PyObject_HEAD + PyObject *wd; + PyObject *mask; + PyObject *cookie; + PyObject *name; +}; + +static PyObject *event_wd(PyObject *self, void *x) +{ + struct event *evt = (struct event *)self; + Py_INCREF(evt->wd); + return evt->wd; +} + +static PyObject *event_mask(PyObject *self, void *x) +{ + struct event *evt = (struct event *)self; + Py_INCREF(evt->mask); + return evt->mask; +} + +static PyObject *event_cookie(PyObject *self, void *x) +{ + struct event *evt = (struct event *)self; + Py_INCREF(evt->cookie); + return evt->cookie; +} + +static PyObject *event_name(PyObject *self, void *x) +{ + struct event *evt = (struct event *)self; + Py_INCREF(evt->name); + return evt->name; +} + +static struct PyGetSetDef event_getsets[] = { + {"wd", event_wd, NULL, + "watch descriptor"}, + {"mask", event_mask, NULL, + "event mask"}, + {"cookie", event_cookie, NULL, + "rename cookie, if rename-related event"}, + {"name", event_name, NULL, + "file name"}, + {NULL} +}; + +PyDoc_STRVAR( + event_doc, + "event: Structure describing an inotify event."); + +static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k) +{ + return (*t->tp_alloc)(t, 0); +} + +static void event_dealloc(struct event *evt) +{ + Py_XDECREF(evt->wd); + Py_XDECREF(evt->mask); + Py_XDECREF(evt->cookie); + Py_XDECREF(evt->name); + + Py_TYPE(evt)->tp_free(evt); +} + +static PyObject *event_repr(struct event *evt) +{ + int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie); + PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL; + PyObject *tuple = NULL, *formatstr = NULL; + + pymasks = decode_mask(PyInt_AsLong(evt->mask)); + if (pymasks == NULL) + goto bail; + + pymask = _PyString_Join(join, pymasks); + if (pymask == NULL) + goto bail; + + if (evt->name != Py_None) { + if (cookie == -1) { + formatstr = er_wmn; + tuple = PyTuple_Pack(3, evt->wd, pymask, evt->name); + } + else { + formatstr = er_wmcn; + tuple = PyTuple_Pack(4, evt->wd, pymask, + evt->cookie, evt->name); + } + } else { + if (cookie == -1) { + formatstr = er_wm; + tuple = PyTuple_Pack(2, evt->wd, pymask); + } + else { + formatstr = er_wmc; + tuple = PyTuple_Pack(3, evt->wd, pymask, evt->cookie); + } + } + + if (tuple == NULL) + goto bail; + + ret = PyNumber_Remainder(formatstr, tuple); + + if (ret == NULL) + goto bail; + + goto done; +bail: + Py_CLEAR(ret); + +done: + Py_XDECREF(pymask); + Py_XDECREF(pymasks); + Py_XDECREF(tuple); + + return ret; +} + +static PyTypeObject event_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_inotify.event", /*tp_name*/ + sizeof(struct event), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)event_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)event_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + event_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + event_getsets, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + event_new, /* tp_new */ +}; + +PyObject *read_events(PyObject *self, PyObject *args) +{ + PyObject *ctor_args = NULL; + PyObject *pybufsize = NULL; + PyObject *ret = NULL; + int bufsize = 65536; + char *buf = NULL; + int nread, pos; + int fd; + + if (!PyArg_ParseTuple(args, "i|O:read", &fd, &pybufsize)) + goto bail; + + if (pybufsize && pybufsize != Py_None) + bufsize = PyInt_AsLong(pybufsize); + + ret = PyList_New(0); + if (ret == NULL) + goto bail; + + if (bufsize <= 0) { + int r; + + Py_BEGIN_ALLOW_THREADS; + r = ioctl(fd, FIONREAD, &bufsize); + Py_END_ALLOW_THREADS; + + if (r == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto bail; + } + if (bufsize == 0) + goto done; + } + else { + static long name_max; + static long name_fd = -1; + long min; + + if (name_fd != fd) { + name_fd = fd; + Py_BEGIN_ALLOW_THREADS; + name_max = fpathconf(fd, _PC_NAME_MAX); + Py_END_ALLOW_THREADS; + } + + min = sizeof(struct inotify_event) + name_max + 1; + + if (bufsize < min) { + PyErr_Format(PyExc_ValueError, + "bufsize must be at least %d", (int)min); + goto bail; + } + } + + buf = alloca(bufsize); + + Py_BEGIN_ALLOW_THREADS; + nread = read(fd, buf, bufsize); + Py_END_ALLOW_THREADS; + + if (nread == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto bail; + } + + ctor_args = PyTuple_New(0); + + if (ctor_args == NULL) + goto bail; + + pos = 0; + + while (pos < nread) { + struct inotify_event *in = (struct inotify_event *)(buf + pos); + struct event *evt; + PyObject *obj; + + obj = PyObject_CallObject((PyObject *)&event_type, ctor_args); + + if (obj == NULL) + goto bail; + + evt = (struct event *)obj; + + evt->wd = PyInt_FromLong(in->wd); + evt->mask = PyInt_FromLong(in->mask); + if (in->mask & IN_MOVE) + evt->cookie = PyInt_FromLong(in->cookie); + else { + Py_INCREF(Py_None); + evt->cookie = Py_None; + } + if (in->len) + evt->name = PyString_FromString(in->name); + else { + Py_INCREF(Py_None); + evt->name = Py_None; + } + + if (!evt->wd || !evt->mask || !evt->cookie || !evt->name) + goto mybail; + + if (PyList_Append(ret, obj) == -1) + goto mybail; + + pos += sizeof(struct inotify_event) + in->len; + continue; + + mybail: + Py_CLEAR(evt->wd); + Py_CLEAR(evt->mask); + Py_CLEAR(evt->cookie); + Py_CLEAR(evt->name); + Py_DECREF(obj); + + goto bail; + } + + goto done; + +bail: + Py_CLEAR(ret); + +done: + Py_XDECREF(ctor_args); + + return ret; +} + +static int init_globals(void) +{ + join = PyString_FromString("|"); + er_wm = PyString_FromString("event(wd=%d, mask=%s)"); + er_wmn = PyString_FromString("event(wd=%d, mask=%s, name=%s)"); + er_wmc = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x)"); + er_wmcn = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x, name=%s)"); + + return join && er_wm && er_wmn && er_wmc && er_wmcn; +} + +PyDoc_STRVAR( + read_doc, + "read(fd, bufsize[=65536]) -> list_of_events\n" + "\n" + "\nRead inotify events from a file descriptor.\n" + "\n" + " fd: file descriptor returned by init()\n" + " bufsize: size of buffer to read into, in bytes\n" + "\n" + "Return a list of event objects.\n" + "\n" + "If bufsize is > 0, block until events are available to be read.\n" + "Otherwise, immediately return all events that can be read without\n" + "blocking."); + +static PyMethodDef methods[] = { + {"init", init, METH_VARARGS, init_doc}, + {"add_watch", add_watch, METH_VARARGS, add_watch_doc}, + {"remove_watch", remove_watch, METH_VARARGS, remove_watch_doc}, + {"read", read_events, METH_VARARGS, read_doc}, + {"decode_mask", pydecode_mask, METH_VARARGS, decode_mask_doc}, + {NULL}, +}; + +#ifdef IS_PY3K +static struct PyModuleDef _inotify_module = { + PyModuleDef_HEAD_INIT, + "_inotify", + doc, + -1, + methods +}; + +PyMODINIT_FUNC PyInit__inotify(void) +{ + PyObject *mod, *dict; + + mod = PyModule_Create(&_inotify_module); + + if (mod == NULL) + return NULL; + + if (!init_globals()) + return; + + dict = PyModule_GetDict(mod); + + if (dict) + define_consts(dict); + + return mod; +} +#else +void init_inotify(void) +{ + PyObject *mod, *dict; + + if (PyType_Ready(&event_type) == -1) + return; + + if (!init_globals()) + return; + + mod = Py_InitModule3("_inotify", methods, doc); + + dict = PyModule_GetDict(mod); + + if (dict) + define_consts(dict); +} +#endif diff --git a/hgext/inotify/linux/watcher.py b/hgext/inotify/linux/watcher.py new file mode 100644 index 0000000..cd62006 --- /dev/null +++ b/hgext/inotify/linux/watcher.py @@ -0,0 +1,335 @@ +# watcher.py - high-level interfaces to the Linux inotify subsystem + +# Copyright 2006 Bryan O'Sullivan <bos@serpentine.com> + +# This library is free software; you can redistribute it and/or modify +# it under the terms of version 2.1 of the GNU Lesser General Public +# License, or any later version. + +'''High-level interfaces to the Linux inotify subsystem. + +The inotify subsystem provides an efficient mechanism for file status +monitoring and change notification. + +The watcher class hides the low-level details of the inotify +interface, and provides a Pythonic wrapper around it. It generates +events that provide somewhat more information than raw inotify makes +available. + +The autowatcher class is more useful, as it automatically watches +newly-created directories on your behalf.''' + +__author__ = "Bryan O'Sullivan <bos@serpentine.com>" + +import _inotify as inotify +import array +import errno +import fcntl +import os +import termios + + +class event(object): + '''Derived inotify event class. + + The following fields are available: + + mask: event mask, indicating what kind of event this is + + cookie: rename cookie, if a rename-related event + + path: path of the directory in which the event occurred + + name: name of the directory entry to which the event occurred + (may be None if the event happened to a watched directory) + + fullpath: complete path at which the event occurred + + wd: watch descriptor that triggered this event''' + + __slots__ = ( + 'cookie', + 'fullpath', + 'mask', + 'name', + 'path', + 'raw', + 'wd', + ) + + def __init__(self, raw, path): + self.path = path + self.raw = raw + if raw.name: + self.fullpath = path + '/' + raw.name + else: + self.fullpath = path + + self.wd = raw.wd + self.mask = raw.mask + self.cookie = raw.cookie + self.name = raw.name + + def __repr__(self): + r = repr(self.raw) + return 'event(path=' + repr(self.path) + ', ' + r[r.find('(')+1:] + + +_event_props = { + 'access': 'File was accessed', + 'modify': 'File was modified', + 'attrib': 'Attribute of a directory entry was changed', + 'close_write': 'File was closed after being written to', + 'close_nowrite': 'File was closed without being written to', + 'open': 'File was opened', + 'moved_from': 'Directory entry was renamed from this name', + 'moved_to': 'Directory entry was renamed to this name', + 'create': 'Directory entry was created', + 'delete': 'Directory entry was deleted', + 'delete_self': 'The watched directory entry was deleted', + 'move_self': 'The watched directory entry was renamed', + 'unmount': 'Directory was unmounted, and can no longer be watched', + 'q_overflow': 'Kernel dropped events due to queue overflow', + 'ignored': 'Directory entry is no longer being watched', + 'isdir': 'Event occurred on a directory', + } + +for k, v in _event_props.iteritems(): + mask = getattr(inotify, 'IN_' + k.upper()) + def getter(self): + return self.mask & mask + getter.__name__ = k + getter.__doc__ = v + setattr(event, k, property(getter, doc=v)) + +del _event_props + + +class watcher(object): + '''Provide a Pythonic interface to the low-level inotify API. + + Also adds derived information to each event that is not available + through the normal inotify API, such as directory name.''' + + __slots__ = ( + 'fd', + '_paths', + '_wds', + ) + + def __init__(self): + '''Create a new inotify instance.''' + + self.fd = inotify.init() + self._paths = {} + self._wds = {} + + def fileno(self): + '''Return the file descriptor this watcher uses. + + Useful for passing to select and poll.''' + + return self.fd + + def add(self, path, mask): + '''Add or modify a watch. + + Return the watch descriptor added or modified.''' + + path = os.path.normpath(path) + wd = inotify.add_watch(self.fd, path, mask) + self._paths[path] = wd, mask + self._wds[wd] = path, mask + return wd + + def remove(self, wd): + '''Remove the given watch.''' + + inotify.remove_watch(self.fd, wd) + self._remove(wd) + + def _remove(self, wd): + path_mask = self._wds.pop(wd, None) + if path_mask is not None: + self._paths.pop(path_mask[0]) + + def path(self, path): + '''Return a (watch descriptor, event mask) pair for the given path. + + If the path is not being watched, return None.''' + + return self._paths.get(path) + + def wd(self, wd): + '''Return a (path, event mask) pair for the given watch descriptor. + + If the watch descriptor is not valid or not associated with + this watcher, return None.''' + + return self._wds.get(wd) + + def read(self, bufsize=None): + '''Read a list of queued inotify events. + + If bufsize is zero, only return those events that can be read + immediately without blocking. Otherwise, block until events are + available.''' + + events = [] + for evt in inotify.read(self.fd, bufsize): + events.append(event(evt, self._wds[evt.wd][0])) + if evt.mask & inotify.IN_IGNORED: + self._remove(evt.wd) + elif evt.mask & inotify.IN_UNMOUNT: + self.close() + return events + + def close(self): + '''Shut down this watcher. + + All subsequent method calls are likely to raise exceptions.''' + + os.close(self.fd) + self.fd = None + self._paths = None + self._wds = None + + def __len__(self): + '''Return the number of active watches.''' + + return len(self._paths) + + def __iter__(self): + '''Yield a (path, watch descriptor, event mask) tuple for each + entry being watched.''' + + for path, (wd, mask) in self._paths.iteritems(): + yield path, wd, mask + + def __del__(self): + if self.fd is not None: + os.close(self.fd) + + ignored_errors = [errno.ENOENT, errno.EPERM, errno.ENOTDIR] + + def add_iter(self, path, mask, onerror=None): + '''Add or modify watches over path and its subdirectories. + + Yield each added or modified watch descriptor. + + To ensure that this method runs to completion, you must + iterate over all of its results, even if you do not care what + they are. For example: + + for wd in w.add_iter(path, mask): + pass + + By default, errors are ignored. If optional arg "onerror" is + specified, it should be a function; it will be called with one + argument, an OSError instance. It can report the error to + continue with the walk, or raise the exception to abort the + walk.''' + + # Add the IN_ONLYDIR flag to the event mask, to avoid a possible + # race when adding a subdirectory. In the time between the + # event being queued by the kernel and us processing it, the + # directory may have been deleted, or replaced with a different + # kind of entry with the same name. + + submask = mask | inotify.IN_ONLYDIR + + try: + yield self.add(path, mask) + except OSError, err: + if onerror and err.errno not in self.ignored_errors: + onerror(err) + for root, dirs, names in os.walk(path, topdown=False, onerror=onerror): + for d in dirs: + try: + yield self.add(root + '/' + d, submask) + except OSError, err: + if onerror and err.errno not in self.ignored_errors: + onerror(err) + + def add_all(self, path, mask, onerror=None): + '''Add or modify watches over path and its subdirectories. + + Return a list of added or modified watch descriptors. + + By default, errors are ignored. If optional arg "onerror" is + specified, it should be a function; it will be called with one + argument, an OSError instance. It can report the error to + continue with the walk, or raise the exception to abort the + walk.''' + + return [w for w in self.add_iter(path, mask, onerror)] + + +class autowatcher(watcher): + '''watcher class that automatically watches newly created directories.''' + + __slots__ = ( + 'addfilter', + ) + + def __init__(self, addfilter=None): + '''Create a new inotify instance. + + This instance will automatically watch newly created + directories. + + If the optional addfilter parameter is not None, it must be a + callable that takes one parameter. It will be called each time + a directory is about to be automatically watched. If it returns + True, the directory will be watched if it still exists, + otherwise, it will beb skipped.''' + + super(autowatcher, self).__init__() + self.addfilter = addfilter + + _dir_create_mask = inotify.IN_ISDIR | inotify.IN_CREATE + + def read(self, bufsize=None): + events = super(autowatcher, self).read(bufsize) + for evt in events: + if evt.mask & self._dir_create_mask == self._dir_create_mask: + if self.addfilter is None or self.addfilter(evt): + parentmask = self._wds[evt.wd][1] + # See note about race avoidance via IN_ONLYDIR above. + mask = parentmask | inotify.IN_ONLYDIR + try: + self.add_all(evt.fullpath, mask) + except OSError, err: + if err.errno not in self.ignored_errors: + raise + return events + + +class threshold(object): + '''Class that indicates whether a file descriptor has reached a + threshold of readable bytes available. + + This class is not thread-safe.''' + + __slots__ = ( + 'fd', + 'threshold', + '_iocbuf', + ) + + def __init__(self, fd, threshold=1024): + self.fd = fd + self.threshold = threshold + self._iocbuf = array.array('i', [0]) + + def readable(self): + '''Return the number of bytes readable on this file descriptor.''' + + fcntl.ioctl(self.fd, termios.FIONREAD, self._iocbuf, True) + return self._iocbuf[0] + + def __call__(self): + '''Indicate whether the number of readable bytes has met or + exceeded the threshold.''' + + return self.readable() >= self.threshold |