├── gevent_inotifyx
├── vendor
│ ├── __init__.py
│ └── inotifyx
│ │ ├── distinfo.py
│ │ ├── __init__.py
│ │ └── binding.c
└── __init__.py
├── requirements.txt
├── .gitignore
├── MANIFEST.in
├── Makefile
├── .travis.yml
├── CHANGELOG.md
├── example.py
├── LICENSE
├── setup.py
├── test_gevent_inotifyx.py
├── README.md
└── README.rst
/gevent_inotifyx/vendor/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | -e .
2 |
3 | pytest
4 |
--------------------------------------------------------------------------------
/gevent_inotifyx/vendor/inotifyx/distinfo.py:
--------------------------------------------------------------------------------
1 | version = 'unknown'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info
2 | *.pyc
3 | *.so
4 | .pytest_cache
5 | /build
6 | /dist
7 | __pycache__
8 | MANIFEST
9 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include README.rst
3 | include CHANGELOG.md
4 | include LICENSE
5 | include example.py
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 |
3 | test:
4 | pytest
5 |
6 | README.rst: README.md
7 | pandoc --from=markdown --to=rst $< > $@
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | - "3.6"
5 | - "3.7"
6 | - "3.8"
7 | - "3.9"
8 | install: pip install -r requirements.txt
9 | script: make test
10 | notifications:
11 | email: false
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.2 (2018-03-01)
4 |
5 | - Vendor a copy of inotifyx, with patches for Python 3 compatibility from the
6 | upstream bug tracker applied.
7 | - Support for Python 3
8 |
9 | ## 0.1.1 (2013-04-12)
10 |
11 | - Packaging fixes
12 |
13 | ## 0.1.0 (2012-12-07)
14 |
15 | - Initial release
16 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import print_function
3 |
4 | import os
5 | import gevent
6 | import gevent_inotifyx as inotify
7 |
8 | def create_file_events():
9 | """Open and close a file to generate inotify events."""
10 | while True:
11 | with open('/tmp/test.txt', 'a'):
12 | pass
13 | gevent.sleep(1)
14 |
15 |
16 | def watch_for_events():
17 | """Wait for events and print them to stdout."""
18 | fd = inotify.init()
19 | try:
20 | wd = inotify.add_watch(fd, '/tmp', inotify.IN_CLOSE_WRITE)
21 | while True:
22 | for event in inotify.get_events(fd):
23 | print("event:", event.name, event.get_mask_description())
24 | finally:
25 | os.close(fd)
26 |
27 | if __name__ == '__main__':
28 | tasks = [
29 | gevent.spawn(watch_for_events),
30 | gevent.spawn(create_file_events),
31 | ]
32 | gevent.joinall(tasks)
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Stanis Trendelenburg
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included
11 | in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
17 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19 | OTHER DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import re
3 | from setuptools import setup, find_packages, Extension
4 |
5 | with open('gevent_inotifyx/__init__.py') as f:
6 | version = re.search(r"^__version__ = '(.*)'", f.read(), re.M).group(1)
7 |
8 | with open('README.rst') as f:
9 | README = f.read()
10 |
11 | setup(
12 | name='gevent_inotifyx',
13 | version=version,
14 | author='Stanis Trendelenburg',
15 | author_email='stanis.trendelenburg@gmail.com',
16 | url='https://github.com/trendels/gevent_inotifyx',
17 | license='MIT',
18 | packages=find_packages(),
19 | ext_modules=[
20 | Extension(
21 | 'gevent_inotifyx.vendor.inotifyx.binding',
22 | sources=['gevent_inotifyx/vendor/inotifyx/binding.c'],
23 | )
24 | ],
25 | install_requires=['gevent'],
26 | description='gevent compatibility for inotifyx',
27 | long_description=README,
28 | classifiers=[
29 | 'License :: OSI Approved :: MIT License',
30 | 'Programming Language :: Python',
31 | 'Programming Language :: Python :: 2',
32 | 'Programming Language :: Python :: 3',
33 | ],
34 | )
35 |
--------------------------------------------------------------------------------
/test_gevent_inotifyx.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | import gevent
4 |
5 | #import inotifyx as inotify
6 | import gevent_inotifyx as inotify
7 |
8 | class BasicTests(unittest.TestCase):
9 | def setUp(self):
10 | self.workdir = os.path.abspath(os.getcwd())
11 | self.testdir = os.path.join(self.workdir, 'test')
12 | os.mkdir(self.testdir)
13 | os.chdir(self.testdir)
14 | self.fd = inotify.init()
15 |
16 | def tearDown(self):
17 | os.close(self.fd)
18 | os.rmdir(self.testdir)
19 | os.chdir(self.workdir)
20 |
21 | def _create_file(self, path, content = ''):
22 | f = open(path, 'w')
23 | try:
24 | f.write(content)
25 | finally:
26 | f.close()
27 |
28 | def test_get_events(self):
29 | wd = inotify.add_watch(self.fd, self.testdir, inotify.IN_CREATE)
30 | self._create_file('foo')
31 | try:
32 | events = inotify.get_events(self.fd)
33 | self.assertEqual(len(events), 1)
34 | self.assertEqual(events[0].mask, inotify.IN_CREATE)
35 | self.assertEqual(events[0].name, 'foo')
36 | finally:
37 | os.unlink('foo')
38 |
39 | def test_get_events_async(self):
40 | wd = inotify.add_watch(self.fd, self.testdir, inotify.IN_CREATE)
41 | task1 = gevent.spawn(inotify.get_events, self.fd, 10)
42 | task2 = gevent.spawn(self._create_file, 'bar')
43 | try:
44 | # in the synchronous case we would block here for 10s and the
45 | # return value in task1.value would be empty
46 | task1.join()
47 | task2.join()
48 | events = task1.value
49 | self.assertEqual(len(events), 1)
50 | self.assertEqual(events[0].mask, inotify.IN_CREATE)
51 | self.assertEqual(events[0].name, 'bar')
52 | finally:
53 | os.unlink('bar')
54 |
55 | if __name__ == "__main__":
56 | unittest.main()
57 |
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gevent_inotifyx
2 |
3 | [![build-status-img]][build-status-url]
4 | [](https://pypi.python.org/pypi/gevent_inotifyx)
5 |
6 | Gevent-compatible low-level inotify bindings based on [inotifyx].
7 |
8 | - Python 2 and 3 compatible
9 | - Exposes a low-level [inotify(7)][inotify] API
10 | - Allows to wait for events in a non-blocking way when using [gevent].
11 |
12 | [inotify]: http://man7.org/linux/man-pages/man7/inotify.7.html
13 | [inotifyx]: https://launchpad.net/inotifyx/
14 | [gevent]: http://www.gevent.org/
15 | [build-status-url]: https://travis-ci.org/trendels/gevent_inotifyx
16 | [build-status-img]: https://travis-ci.org/trendels/gevent_inotifyx.svg
17 |
18 | ## Installation
19 |
20 | $ pip install gevent_inotifyx
21 |
22 | From source:
23 |
24 | $ python setup.py install
25 |
26 | To run the tests:
27 |
28 | $ make test
29 |
30 | ## Examples
31 |
32 | Watch a directory while creating new files. This prints
33 |
34 | event: test.txt IN_CLOSE|IN_CLOSE_WRITE|IN_ALL_EVENTS
35 |
36 | every second:
37 |
38 | ```python
39 | #!/usr/bin/env python
40 | from __future__ import print_function
41 | import os
42 | import gevent
43 | import gevent_inotifyx as inotify
44 |
45 | def create_file_events():
46 | """Open and close a file to generate inotify events."""
47 | while True:
48 | with open('/tmp/test.txt', 'a'):
49 | pass
50 | gevent.sleep(1)
51 |
52 | def watch_for_events():
53 | """Wait for events and print them to stdout."""
54 | fd = inotify.init()
55 | try:
56 | wd = inotify.add_watch(fd, '/tmp', inotify.IN_CLOSE_WRITE)
57 | while True:
58 | for event in inotify.get_events(fd):
59 | print("event:", event.name, event.get_mask_description())
60 | finally:
61 | os.close(fd)
62 |
63 | if __name__ == '__main__':
64 | tasks = [
65 | gevent.spawn(watch_for_events),
66 | gevent.spawn(create_file_events),
67 | ]
68 | gevent.joinall(tasks)
69 | ```
70 |
71 | ## License
72 |
73 | gevent_inotifyx is licensed under the MIT License. See the included file `LICENSE` for details.
74 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | gevent_inotifyx
2 | ===============
3 |
4 | |build-status-img|
5 |
6 | Gevent-compatible low-level inotify bindings based on
7 | `inotifyx `__.
8 |
9 | - Python 2 and 3 compatible
10 | - Exposes a low-level
11 | `inotify(7) `__
12 | API
13 | - Allows to wait for events in a non-blocking way when using
14 | `gevent `__.
15 |
16 | Installation
17 | ------------
18 |
19 | ::
20 |
21 | $ pip install gevent_inotifyx
22 |
23 | From source:
24 |
25 | ::
26 |
27 | $ python setup.py install
28 |
29 | To run the tests:
30 |
31 | ::
32 |
33 | $ make test
34 |
35 | Examples
36 | --------
37 |
38 | Watch a directory while creating new files. This prints
39 |
40 | ::
41 |
42 | event: test.txt IN_CLOSE|IN_CLOSE_WRITE|IN_ALL_EVENTS
43 |
44 | every second:
45 |
46 | .. code:: python
47 |
48 | #!/usr/bin/env python
49 | from __future__ import print_function
50 | import os
51 | import gevent
52 | import gevent_inotifyx as inotify
53 |
54 | def create_file_events():
55 | """Open and close a file to generate inotify events."""
56 | while True:
57 | with open('/tmp/test.txt', 'a'):
58 | pass
59 | gevent.sleep(1)
60 |
61 | def watch_for_events():
62 | """Wait for events and print them to stdout."""
63 | fd = inotify.init()
64 | try:
65 | wd = inotify.add_watch(fd, '/tmp', inotify.IN_CLOSE_WRITE)
66 | while True:
67 | for event in inotify.get_events(fd):
68 | print("event:", event.name, event.get_mask_description())
69 | finally:
70 | os.close(fd)
71 |
72 | if __name__ == '__main__':
73 | tasks = [
74 | gevent.spawn(watch_for_events),
75 | gevent.spawn(create_file_events),
76 | ]
77 | gevent.joinall(tasks)
78 |
79 | License
80 | -------
81 |
82 | gevent_inotifyx is licensed under the MIT License. See the included file
83 | ``LICENSE`` for details.
84 |
85 | .. |build-status-img| image:: https://travis-ci.org/trendels/gevent_inotifyx.svg
86 | :target: https://travis-ci.org/trendels/gevent_inotifyx
87 |
--------------------------------------------------------------------------------
/gevent_inotifyx/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | gevent compatibility for inotifyx
3 |
4 | General usage:
5 | >>> import os
6 | >>> import gevent_inotifyx as inotify
7 | >>> fd = inotify.init()
8 | >>> try:
9 | ... wd = inotify.add_watch(fd, '/path', inotify.IN_CREATE)
10 | ... events = inotify.get_events(fd)
11 | ... for event in events:
12 | ... print("File created: ", os.path.join('/path', event.name))
13 | ... inotify.rm_watch(fd, wd)
14 | ... finally:
15 | ... os.close(fd)
16 |
17 |
18 | This module is designed as a drop-in replacement for inotifyx.
19 | Calling gevent_inotifyx.get_events() will only block the current greenlet
20 | instead of the current thread.
21 | """
22 |
23 | __version__ = '0.2'
24 |
25 | import os
26 | import struct
27 | import sys
28 |
29 | from .vendor import inotifyx
30 | from .vendor.inotifyx import *
31 | from gevent.select import select
32 |
33 | _EVENT_FMT = 'iIII'
34 | _EVENT_SIZE = struct.calcsize(_EVENT_FMT)
35 | _BUF_LEN = inotifyx.BUF_LEN
36 |
37 | __all__ = ['InotifyEvent', 'add_watch', 'get_events', 'init', 'rm_watch']
38 | __all__.extend([name for name in dir(inotifyx) if name.startswith('IN_')])
39 |
40 | ENCODING = sys.getfilesystemencoding()
41 |
42 | # Replacement for the inotifyx_get_events() C function using gevent.select
43 | def get_events(fd, timeout=None):
44 | """get_events(fd[, timeout])
45 |
46 | Return a list of InotifyEvent instances representing events read from
47 | inotify. If timeout is None, this will block forever until at least one
48 | event can be read. Otherwise, timeout should be an integer or float
49 | specifying a timeout in seconds. If get_events times out waiting for
50 | events, an empty list will be returned. If timeout is zero, get_events
51 | will not block.
52 | This version of get_events() will only block the current greenlet.
53 | """
54 | (rlist, _, _) = select([fd], [], [], timeout)
55 | if not rlist:
56 | return []
57 | events = []
58 |
59 | while True:
60 | buf = os.read(fd, _BUF_LEN)
61 | i = 0
62 | while i < len(buf):
63 | (wd, mask, cookie, len_) = struct.unpack_from(_EVENT_FMT, buf, i)
64 | name = None
65 | if len_ > 0:
66 | start = i + _EVENT_SIZE
67 | end = start + len_
68 | # remove \0 terminator and padding
69 | name = buf[start:end].rstrip(b'\0').decode(ENCODING)
70 |
71 | events.append(InotifyEvent(wd, mask, cookie, name))
72 | i += _EVENT_SIZE + len_
73 |
74 | (rlist, _, _) = select([fd], [], [], 0)
75 | if not rlist:
76 | break
77 |
78 | return events
79 |
80 | # Reproduced here from inotifyx/__init__.py to enable command line use:
81 | # $ python -m gevent_inofityx /path
82 | if __name__ == "__main__":
83 | import sys
84 |
85 | if len(sys.argv) == 1:
86 | sys.stderr.write('usage: gevent_inotifyx path [path ...]\n')
87 | sys.exit(1)
88 |
89 | paths = sys.argv[1:]
90 | fd = init()
91 | wd_to_path = {}
92 | try:
93 | for path in paths:
94 | wd = add_watch(fd, path)
95 | wd_to_path[wd] = path
96 | try:
97 | while True:
98 | events = get_events(fd)
99 | for event in events:
100 | path = wd_to_path[event.wd]
101 | parts = [event.get_mask_description()]
102 | if event.name:
103 | parts.append(event.name)
104 | print('%s: %s' % (path, ' '.join(parts)))
105 | except KeyboardInterrupt:
106 | pass
107 |
108 | finally:
109 | os.close(fd)
110 |
111 |
--------------------------------------------------------------------------------
/gevent_inotifyx/vendor/inotifyx/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2005 Manuel Amador
2 | # Copyright (c) 2009-2011 Forest Bond
3 | #
4 | # Permission is hereby granted, free of charge, to any person obtaining a
5 | # copy of this software and associated documentation files (the "Software"),
6 | # to deal in the Software without restriction, including without limitation
7 | # the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | # and/or sell copies of the Software, and to permit persons to whom the
9 | # Software is furnished to do so, subject to the following conditions:
10 | #
11 | # The above copyright notice and this permission notice shall be included in
12 | # all copies or substantial portions of the Software.
13 | #
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 | # DEALINGS IN THE SOFTWARE.
21 |
22 | '''
23 | inotifyx is a simple Python binding to the Linux inotify file system event
24 | monitoring API.
25 |
26 | Generally, usage is as follows:
27 |
28 | >>> fd = init()
29 | >>> try:
30 | ... wd = add_watch(fd, '/path', IN_ALL_EVENTS)
31 | ... events = get_events(fd)
32 | ... rm_watch(fd, wd)
33 | ... finally:
34 | ... os.close(fd)
35 | '''
36 | from __future__ import print_function
37 |
38 | import os, select
39 |
40 | from . import binding
41 | from .distinfo import version as __version__
42 |
43 |
44 | constants = {}
45 |
46 | for name in dir(binding):
47 | if name.startswith('IN_') or name == 'BUF_LEN':
48 | globals()[name] = constants[name] = getattr(binding, name)
49 |
50 |
51 | init = binding.init
52 | rm_watch = binding.rm_watch
53 | add_watch = binding.add_watch
54 |
55 |
56 | class InotifyEvent(object):
57 | '''
58 | InotifyEvent(wd, mask, cookie, name)
59 |
60 | A representation of the inotify_event structure. See the inotify
61 | documentation for a description of these fields.
62 | '''
63 |
64 | wd = None
65 | mask = None
66 | cookie = None
67 | name = None
68 |
69 | def __init__(self, wd, mask, cookie, name):
70 | self.wd = wd
71 | self.mask = mask
72 | self.cookie = cookie
73 | self.name = name
74 |
75 | def __str__(self):
76 | return '%s: %s' % (self.wd, self.get_mask_description())
77 |
78 | def __repr__(self):
79 | return '%s(%s, %s, %s, %s)' % (
80 | self.__class__.__name__,
81 | repr(self.wd),
82 | repr(self.mask),
83 | repr(self.cookie),
84 | repr(self.name),
85 | )
86 |
87 | def get_mask_description(self):
88 | '''
89 | Return an ASCII string describing the mask field in terms of
90 | bitwise-or'd IN_* constants, or 0. The result is valid Python code
91 | that could be eval'd to get the value of the mask field. In other
92 | words, for a given event:
93 |
94 | >>> from inotifyx import *
95 | >>> assert (event.mask == eval(event.get_mask_description()))
96 | '''
97 |
98 | parts = []
99 | for name, value in list(constants.items()):
100 | if self.mask & value:
101 | parts.append(name)
102 | if parts:
103 | return '|'.join(parts)
104 | return '0'
105 |
106 |
107 | def get_events(fd, *args):
108 | '''
109 | get_events(fd[, timeout])
110 |
111 | Return a list of InotifyEvent instances representing events read from
112 | inotify. If timeout is None, this will block forever until at least one
113 | event can be read. Otherwise, timeout should be an integer or float
114 | specifying a timeout in seconds. If get_events times out waiting for
115 | events, an empty list will be returned. If timeout is zero, get_events
116 | will not block.
117 | '''
118 | return [
119 | InotifyEvent(wd, mask, cookie, name)
120 | for wd, mask, cookie, name in binding.get_events(fd, *args)
121 | ]
122 |
123 |
124 | if __name__ == '__main__':
125 | import sys
126 |
127 | if len(sys.argv) == 1:
128 | sys.stderr.write('usage: inotify path [path ...]')
129 | sys.exit(1)
130 |
131 | paths = sys.argv[1:]
132 |
133 | fd = init()
134 |
135 | wd_to_path = {}
136 |
137 | try:
138 | for path in paths:
139 | wd = add_watch(fd, path)
140 | wd_to_path[wd] = path
141 |
142 | try:
143 | while True:
144 | events = get_events(fd)
145 | for event in events:
146 | path = wd_to_path[event.wd]
147 | parts = [event.get_mask_description()]
148 | if event.name:
149 | parts.append(event.name)
150 | print('%s: %s' % (path, ' '.join(parts)))
151 | except KeyboardInterrupt:
152 | pass
153 |
154 | finally:
155 | os.close(fd)
156 |
--------------------------------------------------------------------------------
/gevent_inotifyx/vendor/inotifyx/binding.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2004 Novell, Inc.
3 | * Copyright (c) 2005 Manuel Amador
4 | * Copyright (c) 2009-2014 Forest Bond
5 | * Copyright (c) 2014 Henry Stern
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a
8 | * copy of this software and associated documentation files (the "Software"),
9 | * to deal in the Software without restriction, including without limitation
10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 | * and/or sell copies of the Software, and to permit persons to whom the
12 | * Software is furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | * DEALINGS IN THE SOFTWARE.
24 | */
25 |
26 | #include
27 |
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 |
40 |
41 | #define MAX_PENDING_PAUSE_COUNT 5
42 | #define PENDING_PAUSE_MICROSECONDS 2000
43 | #define PENDING_THRESHOLD(qsize) ((qsize) >> 1)
44 |
45 | #define EVENT_SIZE (sizeof (struct inotify_event))
46 | /* From inotify(7): Specifying a buffer of size
47 | *
48 | * sizeof(struct inotify_event) + NAME_MAX + 1
49 | *
50 | * will be sufficient to read at least one event.
51 | */
52 | #define BUF_LEN_MIN (EVENT_SIZE + NAME_MAX + 1)
53 | #define BUF_LEN_MAX 32768
54 | #define BUF_LEN ((BUF_LEN_MIN) > (BUF_LEN_MAX) ? (BUF_LEN_MIN) : (BUF_LEN_MAX))
55 |
56 | #if PY_MAJOR_VERSION >= 3
57 | #define IS_PY3
58 | #endif
59 |
60 | static PyObject * inotifyx_init(PyObject *self, PyObject *args) {
61 | int fd;
62 |
63 | Py_BEGIN_ALLOW_THREADS;
64 | fd = inotify_init();
65 | Py_END_ALLOW_THREADS;
66 |
67 | if(fd < 0) {
68 | PyErr_SetFromErrno(PyExc_IOError);
69 | return NULL;
70 | }
71 | return Py_BuildValue("i", fd);
72 | }
73 |
74 |
75 | static PyObject * inotifyx_add_watch(PyObject *self, PyObject *args) {
76 | int fd;
77 | char *path;
78 | int watch_descriptor;
79 | uint32_t mask;
80 |
81 | mask = IN_ALL_EVENTS;
82 |
83 | if(! PyArg_ParseTuple(args, "is|i", &fd, &path, &mask)) {
84 | return NULL;
85 | }
86 |
87 | Py_BEGIN_ALLOW_THREADS;
88 | watch_descriptor = inotify_add_watch(fd, (const char *)path, mask);
89 | Py_END_ALLOW_THREADS;
90 |
91 | if(watch_descriptor < 0) {
92 | PyErr_SetFromErrno(PyExc_IOError);
93 | return NULL;
94 | }
95 | return Py_BuildValue("i", watch_descriptor);
96 | }
97 |
98 |
99 | static PyObject * inotifyx_rm_watch(PyObject *self, PyObject *args) {
100 | int fd;
101 | int watch_descriptor;
102 | int retvalue;
103 |
104 | if(! PyArg_ParseTuple(args, "ii", &fd, &watch_descriptor)) {
105 | return NULL;
106 | }
107 |
108 | Py_BEGIN_ALLOW_THREADS;
109 | retvalue = inotify_rm_watch(fd, watch_descriptor);
110 | Py_END_ALLOW_THREADS;
111 |
112 | if(retvalue < 0) {
113 | PyErr_SetFromErrno(PyExc_IOError);
114 | return NULL;
115 | }
116 | return Py_BuildValue("i", retvalue);
117 | }
118 |
119 |
120 | static PyObject * inotifyx_get_events(PyObject *self, PyObject *args) {
121 | int fd;
122 |
123 | static char buf[BUF_LEN];
124 | int i;
125 | int len;
126 |
127 | float timeout_arg;
128 |
129 | struct timeval timeout;
130 | void *ptimeout;
131 |
132 | struct inotify_event *event;
133 |
134 | fd_set read_fds;
135 | int select_retval;
136 |
137 | timeout_arg = -1.0;
138 | if(! PyArg_ParseTuple(args, "i|f", &fd, &timeout_arg)) {
139 | return NULL;
140 | }
141 |
142 | if(timeout_arg < 0.0) {
143 | ptimeout = NULL;
144 | } else {
145 | timeout.tv_sec = (int)timeout_arg;
146 | timeout.tv_usec = (int)(1000000.0 * (timeout_arg - (float)((int) timeout_arg)));
147 | ptimeout = &timeout;
148 | }
149 |
150 | FD_ZERO(&read_fds);
151 | FD_SET(fd, &read_fds);
152 |
153 | Py_BEGIN_ALLOW_THREADS;
154 | select_retval = select(fd + 1, &read_fds, NULL, NULL, ptimeout);
155 | Py_END_ALLOW_THREADS;
156 |
157 | if(select_retval == 0) {
158 | // Timed out.
159 | return PyList_New(0);
160 | } else if(select_retval < 0) {
161 | PyErr_SetFromErrno(PyExc_IOError);
162 | return NULL;
163 | }
164 |
165 | PyObject* retvalue = PyList_New(0);
166 | if (retvalue == NULL)
167 | return NULL;
168 |
169 | timeout.tv_sec = 0;
170 | timeout.tv_usec = 0;
171 | ptimeout = &timeout;
172 |
173 | while (1) {
174 | Py_BEGIN_ALLOW_THREADS;
175 | len = read(fd, buf, BUF_LEN);
176 | Py_END_ALLOW_THREADS;
177 |
178 | if(len < 0) {
179 | PyErr_SetFromErrno(PyExc_IOError);
180 | Py_DECREF(retvalue);
181 | return NULL;
182 | } else if(len == 0) {
183 | PyErr_SetString(PyExc_IOError, "event buffer too small");
184 | Py_DECREF(retvalue);
185 | return NULL;
186 | }
187 |
188 | i = 0;
189 | while(i < len) {
190 | event = (struct inotify_event *)(& buf[i]);
191 |
192 | PyObject* value = NULL;
193 | if(event->len > 0 && event->name[0] != '\0') {
194 | value = Py_BuildValue(
195 | "iiis",
196 | event->wd,
197 | event->mask,
198 | event->cookie,
199 | event->name
200 | );
201 | } else {
202 | value = Py_BuildValue(
203 | "iiiO",
204 | event->wd,
205 | event->mask,
206 | event->cookie,
207 | Py_None
208 | );
209 | }
210 | if(PyList_Append(retvalue, value) == -1) {
211 | Py_DECREF(retvalue);
212 | Py_DECREF(value);
213 | return NULL;
214 | }
215 | Py_DECREF(value);
216 |
217 | i += EVENT_SIZE + event->len;
218 | }
219 |
220 | FD_ZERO(&read_fds);
221 | FD_SET(fd, &read_fds);
222 |
223 | Py_BEGIN_ALLOW_THREADS;
224 | select_retval = select(fd + 1, &read_fds, NULL, NULL, ptimeout);
225 | Py_END_ALLOW_THREADS;
226 |
227 | if (select_retval <= 0)
228 | break;
229 | }
230 |
231 | return retvalue;
232 | }
233 |
234 |
235 | static PyMethodDef InotifyMethods[] = {
236 | {
237 | "init",
238 | inotifyx_init,
239 | METH_VARARGS,
240 | (
241 | "init()\n\n"
242 | "Initialize an inotify instance and return the associated file\n"
243 | "descriptor. The file descriptor should be closed via os.close\n"
244 | "after it is no longer needed."
245 | )
246 | },
247 | {
248 | "add_watch",
249 | inotifyx_add_watch,
250 | METH_VARARGS,
251 | (
252 | "add_watch(fd, path[, mask])\n\n"
253 | "Add a watch for path and return the watch descriptor.\n"
254 | "fd should be the file descriptor returned by init.\n"
255 | "If left unspecified, mask defaults to IN_ALL_EVENTS.\n"
256 | "See the inotify documentation for details."
257 | )
258 | },
259 | {
260 | "rm_watch",
261 | inotifyx_rm_watch,
262 | METH_VARARGS,
263 | (
264 | "rm_watch(fd, wd)\n\n"
265 | "Remove the watch associated with watch descriptor wd.\n"
266 | "fd should be the file descriptor returned by init.\n"
267 | )
268 | },
269 | {
270 | "get_events",
271 | inotifyx_get_events,
272 | METH_VARARGS,
273 | "get_events(fd[, timeout])\n\n"
274 | "Read events from inotify and return a list of tuples "
275 | "(wd, mask, cookie, name).\n"
276 | "The name field is None if no name is associated with the inotify event.\n"
277 | "Timeout specifies a timeout in seconds (as an integer or float).\n"
278 | "If left unspecified, there is no timeout and get_events will block\n"
279 | "indefinitely. If timeout is zero, get_events will not block."
280 | },
281 | {NULL, NULL, 0, NULL}
282 | };
283 |
284 | #ifdef IS_PY3
285 | static struct PyModuleDef inotifybinding =
286 | {
287 | PyModuleDef_HEAD_INIT,
288 | "gevent_inotifyx.vendor.inotifyx.binding",
289 | (
290 | "Low-level interface to inotify. Do not use this module directly.\n"
291 | "Instead, use the inotifyx module."
292 | ),
293 | -1,
294 | InotifyMethods
295 | };
296 |
297 | PyMODINIT_FUNC PyInit_binding(void) {
298 | PyObject *module = PyModule_Create(&inotifybinding);
299 |
300 | if (module == NULL)
301 | return NULL;
302 | #else
303 | PyMODINIT_FUNC initbinding(void) {
304 | PyObject* module = Py_InitModule3(
305 | "gevent_inotifyx.vendor.inotifyx.binding",
306 | InotifyMethods,
307 | (
308 | "Low-level interface to inotify. Do not use this module directly.\n"
309 | "Instead, use the inotifyx module."
310 | )
311 | );
312 |
313 | if (module == NULL)
314 | return;
315 | #endif
316 |
317 | PyModule_AddIntConstant(module, "IN_ACCESS", IN_ACCESS);
318 | PyModule_AddIntConstant(module, "IN_MODIFY", IN_MODIFY);
319 | PyModule_AddIntConstant(module, "IN_ATTRIB", IN_ATTRIB);
320 | PyModule_AddIntConstant(module, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
321 | PyModule_AddIntConstant(module, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
322 | PyModule_AddIntConstant(module, "IN_CLOSE", IN_CLOSE);
323 | PyModule_AddIntConstant(module, "IN_OPEN", IN_OPEN);
324 | PyModule_AddIntConstant(module, "IN_MOVED_FROM", IN_MOVED_FROM);
325 | PyModule_AddIntConstant(module, "IN_MOVED_TO", IN_MOVED_TO);
326 | PyModule_AddIntConstant(module, "IN_MOVE", IN_MOVE);
327 | PyModule_AddIntConstant(module, "IN_CREATE", IN_CREATE);
328 | PyModule_AddIntConstant(module, "IN_DELETE", IN_DELETE);
329 | PyModule_AddIntConstant(module, "IN_DELETE_SELF", IN_DELETE_SELF);
330 | PyModule_AddIntConstant(module, "IN_MOVE_SELF", IN_MOVE_SELF);
331 | PyModule_AddIntConstant(module, "IN_UNMOUNT", IN_UNMOUNT);
332 | PyModule_AddIntConstant(module, "IN_Q_OVERFLOW", IN_Q_OVERFLOW);
333 | PyModule_AddIntConstant(module, "IN_IGNORED", IN_IGNORED);
334 | PyModule_AddIntConstant(module, "IN_ONLYDIR", IN_ONLYDIR);
335 | PyModule_AddIntConstant(module, "IN_DONT_FOLLOW", IN_DONT_FOLLOW);
336 | PyModule_AddIntConstant(module, "IN_MASK_ADD", IN_MASK_ADD);
337 | PyModule_AddIntConstant(module, "IN_ISDIR", IN_ISDIR);
338 | PyModule_AddIntConstant(module, "IN_ONESHOT", IN_ONESHOT);
339 | PyModule_AddIntConstant(module, "IN_ALL_EVENTS", IN_ALL_EVENTS);
340 | PyModule_AddIntConstant(module, "BUF_LEN", BUF_LEN);
341 | #ifdef IS_PY3
342 | return module;
343 | #endif
344 | }
345 |
--------------------------------------------------------------------------------