Anonymous avatar Anonymous committed 376d4c3

Checkpoint simple code that works.

Comments (0)

Files changed (2)

inotify/_inotify.c

 #include <Python.h>
+#include <alloca.h>
 #include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
 
 static PyObject *init(PyObject *self, PyObject *args)
 {
      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;
     if (fd != -1)
 	close(fd);
 
-    Py_XDECREF(ret);
+    Py_CLEAR(ret);
     
 done:
     return ret;
     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;
     if (wd != -1)
 	inotify_rm_watch(fd, wd);
     
-    Py_XDECREF(ret);
+    Py_CLEAR(ret);
 
 done:
     return ret;
     if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd))
 	goto bail;
 
+    Py_BEGIN_ALLOW_THREADS
     r = inotify_rm_watch(fd, wd);
+    Py_END_ALLOW_THREADS
+
     if (r == -1) {
 	PyErr_SetFromErrno(PyExc_OSError);
 	goto bail;
     goto done;
     
 bail:
-    Py_XDECREF(ret);
+    Py_CLEAR(ret);
     
 done:
     return ret;
     "Removing a watch causes an IN_IGNORED event to be generated for this\n"
     "watch descriptor.");
 
-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},
-    {NULL},
+#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)
-	PyDict_SetItem(dict, pyname, pyval);
+    if (!pyname || !pyval)
+	goto bail;
+    
+    PyDict_SetItem(dict, pyname, pyval);
 
+bail:
     Py_XDECREF(pyname);
     Py_XDECREF(pyval);
 }
 
 static void define_consts(PyObject *dict)
 {
-#ifdef IN_ACCESS
     define_const(dict, "IN_ACCESS", IN_ACCESS);
-#endif
-#ifdef IN_MODIFY
     define_const(dict, "IN_MODIFY", IN_MODIFY);
-#endif
-#ifdef IN_ATTRIB
     define_const(dict, "IN_ATTRIB", IN_ATTRIB);
-#endif
-#ifdef IN_CLOSE_WRITE
     define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
-#endif
-#ifdef IN_CLOSE_NOWRITE
     define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
-#endif
-#ifdef IN_CLOSE
+    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);
-#endif
-#ifdef IN_OPEN
-    define_const(dict, "IN_OPEN", IN_OPEN);
-#endif
-#ifdef IN_MOVED_FROM
-    define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM);
-#endif
-#ifdef IN_MOVED_TO
-    define_const(dict, "IN_MOVED_TO", IN_MOVED_TO);
-#endif
-#ifdef IN_MOVE
     define_const(dict, "IN_MOVE", IN_MOVE);
-#endif
-#ifdef IN_CREATE
+
     define_const(dict, "IN_CREATE", IN_CREATE);
-#endif
-#ifdef IN_DELETE
     define_const(dict, "IN_DELETE", IN_DELETE);
-#endif
-#ifdef IN_DELETE_SELF
     define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF);
-#endif
-#ifdef IN_MOVE_SELF
     define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF);
-#endif
+    define_const(dict, "IN_UNMOUNT", IN_UNMOUNT);
+    define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW);
+    define_const(dict, "IN_IGNORED", IN_IGNORED);
 
-#ifdef IN_UNMOUNT
-    define_const(dict, "IN_UNMOUNT", IN_UNMOUNT);
-#endif
-#ifdef IN_Q_OVERFLOW
-    define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW);
-#endif
-#ifdef IN_IGNORED
-    define_const(dict, "IN_IGNORED", IN_IGNORED);
-#endif
+    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);
+}
 
-#ifdef IN_CLOSE
-    define_const(dict, "IN_CLOSE", IN_CLOSE);
-#endif
-#ifdef IN_MOVE
-    define_const(dict, "IN_MOVE", IN_MOVE);
-#endif
+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;
+}
 
-#ifdef IN_ONLYDIR
-    define_const(dict, "IN_ONLYDIR", IN_ONLYDIR);
-#endif
+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}
+};
 
-#ifdef IN_DONT_FOLLOW
-    define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW);
-#endif
-#ifdef IN_MASK_ADD
-    define_const(dict, "IN_MASK_ADD", IN_MASK_ADD);
-#endif
+PyDoc_STRVAR(
+    event_doc,
+    "event: Structure describing an inotify event.");
 
-#ifdef IN_ISDIR
-    define_const(dict, "IN_ISDIR", IN_ISDIR);
-#endif
-#ifdef IN_ONESHOT
-    define_const(dict, "IN_ONESHOT", IN_ONESHOT);
-#endif
+static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k)
+{
+    return (*t->tp_alloc)(t, 0);
+}
 
-#ifdef IN_ALL_EVENTS
-    define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS);
-#endif
+static void event_dealloc(struct event *evt)
+{
+    Py_XDECREF(evt->wd);
+    Py_XDECREF(evt->mask);
+    Py_XDECREF(evt->cookie);
+    Py_XDECREF(evt->name);
+    
+    (*evt->ob_type->tp_free)(evt);
 }
 
+static PyObject *event_repr(struct event *evt)
+{
+    int wd = PyInt_AsLong(evt->wd);
+    int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie);
+    PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL;
+    PyObject *join = NULL;
+    char *maskstr;
+
+    join = PyString_FromString("|");
+    if (join == NULL)
+	goto bail;
+
+    pymasks = decode_mask(PyInt_AsLong(evt->mask));
+    if (pymasks == NULL)
+	goto bail;
+    
+    pymask = _PyString_Join(join, pymasks);
+    if (pymask == NULL)
+	goto bail;
+    
+    maskstr = PyString_AsString(pymask);
+    
+    if (evt->name != Py_None) {
+	PyObject *pyname = PyString_Repr(evt->name, 1);
+	char *name = pyname ? PyString_AsString(pyname) : "???";
+	
+	if (cookie == -1)
+	    ret = PyString_FromFormat("event(wd=%d, mask=%s, name=%s)",
+				      wd, maskstr, name);
+	else
+	    ret = PyString_FromFormat("event(wd=%d, mask=%s, "
+				      "cookie=0x%x, name=%s)",
+				      wd, maskstr, cookie, name);
+
+	Py_XDECREF(pyname);
+    } else {
+	if (cookie == -1)
+	    ret = PyString_FromFormat("event(wd=%d, mask=%s)",
+				      wd, maskstr);
+	else {
+	    ret = PyString_FromFormat("event(wd=%d, mask=%s, cookie=0x%x)",
+				      wd, maskstr, cookie);
+	}
+    }
+
+    goto done;
+bail:
+    Py_CLEAR(ret);
+    
+done:
+    Py_XDECREF(pymask);
+    Py_XDECREF(pymasks);
+    Py_XDECREF(join);
+
+    return ret;
+}
+
+static PyTypeObject event_type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                         /*ob_size*/
+    "_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;
+}
+
+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},
+};
+
 void init_inotify(void)
 {
     PyObject *mod, *dict;
 
+    if (PyType_Ready(&event_type) == -1)
+        return;
+
     mod = Py_InitModule3("_inotify", methods, doc);
 
     dict = PyModule_GetDict(mod);

inotify/watcher.py

+import _inotify as inotify
+import errno
+import fcntl
+import os
+import termios
+
+def _join(a, b):
+    if a:
+        if a[-1] == '/':
+            return a + b
+        return a + '/' + b
+    return b
+
+class Event(object):
+    __slots__ = (
+        'path',
+        'raw',
+        )
+
+    def __init__(self, raw, path):
+        self.raw = raw
+        self.path = path
+
+    def fullpath(self):
+        if self.raw.name:
+            return _join(self.path, self.raw.name)
+        return self.path
+
+    def __getattr__(self, key):
+        return getattr(self.raw, key)
+    
+    def __repr__(self):
+        r = repr(self.raw)
+        return 'Event(path=' + repr(self.path) + ', ' + r[r.find('(')+1:]
+
+class BasicWatcher(object):
+    __slots__ = (
+        'fd',
+        '_paths',
+        '_wds',
+        )
+
+    def __init__(self):
+        self.fd = inotify.init()
+        self._paths = {}
+        self._wds = {}
+
+    def fileno(self):
+        return self.fd
+
+    def add(self, path, mask):
+        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):
+        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 self._paths.get(path)
+
+    def wd(self, wd):
+        return self._wds.get(wd)
+        
+    def read(self, bufsize=None):
+        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):
+        os.close(self.fd)
+        self.fd = None
+        self._paths.clear()
+        self._wds.clear()
+
+    def __len__(self):
+        return len(self._paths)
+
+    def __iter__(self):
+        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)
+
+
+class AutoWatcher(BasicWatcher):
+    __slots__ = (
+        'auto_add',
+        )
+
+    def __init__(self):
+        super(AutoWatcher, self).__init__()
+
+    _auto_add_ignored_errors = errno.ENOENT, errno.EPERM, errno.ENOTDIR
+    _dir_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_mask == self._dir_mask:
+                try:
+                    parentmask = self._wds[evt.wd][1]
+                    self.add(evt.fullpath(), parentmask | inotify.IN_ONLYDIR)
+                except OSError, err:
+                    if err.errno not in self._auto_add_ignored_errors:
+                        raise
+        return events
+
+
+class Threshold(object):
+    __slots__ = (
+        'fd',
+        'threshold',
+        '_iocbuf',
+        )
+
+    def __init__(self, fd, threshold=1024):
+        self.fd = fd
+        self.threshold = threshold
+        self._iocbuf = array.array(1)
+
+    def __call__(self):
+        readable = fcntl.ioctl(self.fd, termios.FIONREAD, self._iocbuf, True)
+        return readable >= threshold
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.