inotifyx-0.2.0/0000755000175000017500000000000011606136726012760 5ustar forestforestinotifyx-0.2.0/release0000644000175000017500000000000611606136726014317 0ustar forestforest0.2.0 inotifyx-0.2.0/README0000644000175000017500000000102011606136726013631 0ustar forestforestinotifyx is a simple Python binding to the Linux inotify file system event monitoring API. Documentation is provided in the module. To get help, start an interactive Python session and type: >>> import inotifyx >>> help(inotifyx) You can also test out inotifyx easily. The following command will print events for /tmp: python -m inotifyx /tmp Tests can be run via setup.py: ./setup.py test Note that the module must be built and installed for tests to run correctly. In the future, this requirement will be lifted. inotifyx-0.2.0/tests/0000755000175000017500000000000011606136726014122 5ustar forestforestinotifyx-0.2.0/tests/__init__.py0000644000175000017500000000333111606136726016233 0ustar forestforest# Copyright (c) 2011 Forest Bond # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import glob, os def get_test_modules(): for name in glob.glob(os.path.join(os.path.dirname(__file__), '*.py')): if name == '__init__': continue module = os.path.basename(name)[:-3] yield module def load(): for module in get_test_modules(): __import__('tests', {}, {}, [module]) def run(**kwargs): from tests.manager import manager manager.run(**kwargs) def main(**kwargs): from tests.manager import manager manager.main(**kwargs) def print_names(test_names = None): from tests.manager import manager print '\n'.join(manager.get_names(test_names)) inotifyx-0.2.0/tests/manager.py0000644000175000017500000001601311606136726016107 0ustar forestforest# Copyright (c) 2011 Forest Bond # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import os, sys, glob, inspect from doctest import DocTestCase, DocTestFinder, DocTestParser from unittest import TestSuite, TextTestRunner, TestLoader class ListTestLoader(TestLoader): suiteClass = list class TestManager(object): tests = None loader = None def __init__(self): self.tests = [] self.loader = ListTestLoader() def get_test_modules(self): for name in glob.glob(os.path.join(os.path.dirname(__file__), '*.py')): if name == '__init__': continue module = os.path.basename(name)[:-3] yield module def load(self): for module in self.get_test_modules(): __import__('tests', {}, {}, [module]) def find_modules(self, package): modules = [package] for name in dir(package): value = getattr(package, name) if (inspect.ismodule(value)) and ( value.__name__.rpartition('.')[0] == package.__name__): modules.extend(self.find_modules(value)) return modules def main(self, test_names = None, print_only = False, coverage = False): result = self.run( test_names = test_names, print_only = print_only, coverage = coverage, ) if (not print_only) and (not result.wasSuccessful()): sys.exit(1) sys.exit(0) def run(self, test_names = None, print_only = False, coverage = False): if 'inotifyx' in sys.modules: raise AssertionError( 'inotifyx already imported; ' 'this interferes with coverage analysis' ) if coverage: import coverage as _coverage if int(_coverage.__version__.split('.')[0]) < 3: print >>sys.stderr, ( 'warning: coverage versions < 3 ' 'are known to produce imperfect results' ) _coverage.use_cache(False) _coverage.start() try: self.load() suite = TestSuite() runner = TextTestRunner(verbosity = 2) for test in self.tests: if self.should_run_test(test, test_names): if print_only: print test.id() else: suite.addTest(test) if not print_only: return runner.run(suite) finally: if coverage: _coverage.stop() import inotifyx _coverage.report(self.find_modules(inotifyx)) def should_run_test(self, test, test_names): if test_names is None: return True for test_name in test_names: test_name_parts = test_name.split('.') relevant_id_parts = test.id().split('.')[:len(test_name_parts)] if test_name_parts == relevant_id_parts: return True return False def add_test_suite(self, test_suite): self.tests.extend(self.flatten_test_suite(test_suite)) def flatten_test_suite(self, test_suite): tests = [] if isinstance(test_suite, TestSuite): for test in list(test_suite): tests.extend(self.flatten_test_suite(test)) else: tests.append(test_suite) return tests def add_test_case_class(self, test_case_class): self.tests.extend( self.loader.loadTestsFromTestCase(test_case_class)) def make_doc_test_case(self, test): def __init__(self, *args, **kwargs): DocTestCase.__init__(self, test) return type( '%s_TestCase' % test.name.split('.')[-1], (DocTestCase,), {'__init__': __init__}, ) def get_doc_test_cases_from_string( self, string, name = '', filename = '', globs = None, ): if globs is None: globs = {} # Make sure __name__ == '__main__' checks fail: globs = dict(globs) globs['__name__'] = None parser = DocTestParser() test = parser.get_doctest( string, globs = globs, name = name, filename = filename, lineno = 0, ) test_case = self.make_doc_test_case(test) return [test_case] def add_doc_test_cases_from_string(self, *args, **kwargs): for test_case in self.get_doc_test_cases_from_string(*args, **kwargs): self.add_test_case_class(test_case) def import_dotted_name(self, name): mod = __import__(name) components = name.split('.') for component in components[1:]: try: mod = getattr(mod, component) except AttributeError: raise ImportError('%r has no attribute %s' % (mod, component)) return mod def get_doc_test_cases_from_module(self, name): mod = self.import_dotted_name(name) finder = DocTestFinder() tests = finder.find(mod) doc_test_cases = [] for test in tests: doc_test_cases.append(self.make_doc_test_case(test)) return doc_test_cases def add_doc_test_cases_from_module(self, dst_name, src_name = None): if src_name is None: src_name = dst_name for test_case in self.get_doc_test_cases_from_module(src_name): test_case.__module__ = dst_name self.add_test_case_class(test_case) def get_doc_test_cases_from_text_file(self, filename, *args, **kwargs): f = open(filename, 'r') try: data = f.read() finally: f.close() return self.get_doc_test_cases_from_string(data, *args, **kwargs) def add_doc_test_cases_from_text_file(self, *args, **kwargs): for test_case in self.get_doc_test_cases_from_text_file( *args, **kwargs ): self.add_test_case_class(test_case) manager = TestManager() inotifyx-0.2.0/tests/inotifyx_.py0000644000175000017500000000562511606136726016514 0ustar forestforest# Copyright (c) 2009-2011 Forest Bond # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import os, sys, unittest from unittest import TestCase import inotifyx from tests.manager import manager class TestInotifyx(TestCase): fd = None workdir = None testdir = None def setUp(self): self.workdir = os.path.abspath(os.getcwd()) self.testdir = os.path.abspath( os.path.join(os.path.dirname(__file__), 'test') ) os.mkdir(self.testdir) os.chdir(self.testdir) self.fd = inotifyx.init() def tearDown(self): os.close(self.fd) del self.fd os.rmdir(self.testdir) del self.testdir os.chdir(self.workdir) del self.workdir def _create_file(self, path, content = ''): f = open(path, 'w') try: f.write(content) finally: f.close() def test_file_create(self): inotifyx.add_watch(self.fd, self.testdir, inotifyx.IN_CREATE) self._create_file('foo') try: events = inotifyx.get_events(self.fd) self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, inotifyx.IN_CREATE) self.assertEqual(events[0].name, 'foo') finally: os.unlink('foo') def test_file_remove(self): self._create_file('foo') try: inotifyx.add_watch(self.fd, self.testdir, inotifyx.IN_DELETE) except: os.unlink('foo') raise os.unlink('foo') events = inotifyx.get_events(self.fd) self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, inotifyx.IN_DELETE) self.assertEqual(events[0].name, 'foo') def test_version_attribute(self): from inotifyx import distinfo self.assertEqual(inotifyx.__version__, distinfo.version) manager.add_test_case_class(TestInotifyx) inotifyx-0.2.0/NEWS0000644000175000017500000000246211606136726013463 0ustar forestforest====================== inotifyx Release Notes ====================== .. contents:: inotifyx 0.2.0 2011-07-09 ========================= * The distutils option "download_url" is now specified. This should fix problems using pip, zc.buildout, and other tools that rely on PyPI for package downloads to install inotifyx. Thanks to Dariusz Suchojad for the report. (Forest Bond) * inotifyx is now distributed as a Python package instead of a few bare modules. The "_inotifyx" C extension is now shipped as "inotifyx.binding". This change should be backwards compatible with respect to imports since the C extension did not directly implement public interfaces. (Forest Bond) * A __version__ attribute is now provided. This specifies the version of the inotifyx library. Thanks to Jérôme Laheurte for the suggestion. (Forest Bond) inotifyx 0.1.2 2011-03-31 ========================= * Threads are now allowed during I/O operations. Thanks to Jean-Baptiste Denis for the report and patch. (Forest Bond) inotifyx 0.1.1 2009-10-18 ========================= * Fix integer truncation of timeout argument in get_events. Thanks to Eric Firing for the report and patch. (Forest Bond) inotifyx 0.1.0 2009-04-26 ========================= * Initial release. (Forest Bond) inotifyx-0.2.0/inotifyx/0000755000175000017500000000000011606136726014631 5ustar forestforestinotifyx-0.2.0/inotifyx/__init__.py0000644000175000017500000001067411606136726016752 0ustar forestforest# Copyright (c) 2005 Manuel Amador # Copyright (c) 2009-2011 Forest Bond # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. ''' inotifyx is a simple Python binding to the Linux inotify file system event monitoring API. Generally, usage is as follows: >>> fd = init() >>> try: ... wd = add_watch(fd, '/path', IN_ALL_EVENTS) ... events = get_events(fd) ... rm_watch(fd, wd) ... finally: ... os.close(fd) ''' import os, select from inotifyx import binding from inotifyx.distinfo import version as __version__ constants = {} for name in dir(binding): if name.startswith('IN_'): globals()[name] = constants[name] = getattr(binding, name) init = binding.init rm_watch = binding.rm_watch add_watch = binding.add_watch class InotifyEvent(object): ''' InotifyEvent(wd, mask, cookie, name) A representation of the inotify_event structure. See the inotify documentation for a description of these fields. ''' wd = None mask = None cookie = None name = None def __init__(self, wd, mask, cookie, name): self.wd = wd self.mask = mask self.cookie = cookie self.name = name def __str__(self): return '%s: %s' % (self.wd, self.get_mask_description()) def __repr__(self): return '%s(%s, %s, %s, %s)' % ( self.__class__.__name__, repr(self.wd), repr(self.mask), repr(self.cookie), repr(self.name), ) def get_mask_description(self): ''' Return an ASCII string describing the mask field in terms of bitwise-or'd IN_* constants, or 0. The result is valid Python code that could be eval'd to get the value of the mask field. In other words, for a given event: >>> from inotifyx import * >>> assert (event.mask == eval(event.get_mask_description())) ''' parts = [] for name, value in constants.items(): if self.mask & value: parts.append(name) if parts: return '|'.join(parts) return '0' def get_events(fd, *args): ''' get_events(fd[, timeout]) Return a list of InotifyEvent instances representing events read from inotify. If timeout is None, this will block forever until at least one event can be read. Otherwise, timeout should be an integer or float specifying a timeout in seconds. If get_events times out waiting for events, an empty list will be returned. If timeout is zero, get_events will not block. ''' return [ InotifyEvent(wd, mask, cookie, name) for wd, mask, cookie, name in binding.get_events(fd, *args) ] if __name__ == '__main__': import sys if len(sys.argv) == 1: print >>sys.stderr, 'usage: inotify path [path ...]' sys.exit(1) paths = sys.argv[1:] fd = init() wd_to_path = {} try: for path in paths: wd = add_watch(fd, path) wd_to_path[wd] = path try: while True: events = get_events(fd) for event in events: path = wd_to_path[event.wd] parts = [event.get_mask_description()] if event.name: parts.append(event.name) print '%s: %s' % (path, ' '.join(parts)) except KeyboardInterrupt: pass finally: os.close(fd) inotifyx-0.2.0/inotifyx/distinfo.py0000644000175000017500000000002411606136726017016 0ustar forestforestversion = 'unknown' inotifyx-0.2.0/inotifyx/binding.c0000644000175000017500000002107411606136726016413 0ustar forestforest/* * Copyright (c) 2004 Novell, Inc. * Copyright (c) 2005 Manuel Amador * Copyright (c) 2009-2011 Forest Bond * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_PENDING_PAUSE_COUNT 5 #define PENDING_PAUSE_MICROSECONDS 2000 #define PENDING_THRESHOLD(qsize) ((qsize) >> 1) #define EVENT_SIZE (sizeof (struct inotify_event)) #define BUF_LEN (1024 * (EVENT_SIZE + 16)) static PyObject * inotifyx_init(PyObject *self, PyObject *args) { int fd; Py_BEGIN_ALLOW_THREADS; fd = inotify_init(); Py_END_ALLOW_THREADS; if(fd < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } return Py_BuildValue("i", fd); } static PyObject * inotifyx_add_watch(PyObject *self, PyObject *args) { int fd; char *path; int watch_descriptor; uint32_t mask; mask = IN_ALL_EVENTS; if(! PyArg_ParseTuple(args, "is|i", &fd, &path, &mask)) { return NULL; } Py_BEGIN_ALLOW_THREADS; watch_descriptor = inotify_add_watch(fd, (const char *)path, mask); Py_END_ALLOW_THREADS; if(watch_descriptor < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } return Py_BuildValue("i", watch_descriptor); } static PyObject * inotifyx_rm_watch(PyObject *self, PyObject *args) { int fd; int watch_descriptor; int retvalue; if(! PyArg_ParseTuple(args, "ii", &fd, &watch_descriptor)) { return NULL; } Py_BEGIN_ALLOW_THREADS; retvalue = inotify_rm_watch(fd, watch_descriptor); Py_END_ALLOW_THREADS; if(retvalue < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } return Py_BuildValue("i", retvalue); } static PyObject * inotifyx_get_events(PyObject *self, PyObject *args) { int fd; static char buf[BUF_LEN]; int i; int len; float timeout_arg; struct timeval timeout; void *ptimeout; struct inotify_event *event; fd_set read_fds; int select_retval; timeout_arg = -1.0; if(! PyArg_ParseTuple(args, "i|f", &fd, &timeout_arg)) { return NULL; } if(timeout_arg < 0.0) { ptimeout = NULL; } else { timeout.tv_sec = (int)timeout_arg; timeout.tv_usec = (int)(1000000.0 * (timeout_arg - (float)((int) timeout_arg))); ptimeout = &timeout; } FD_ZERO(&read_fds); FD_SET(fd, &read_fds); Py_BEGIN_ALLOW_THREADS; select_retval = select(fd + 1, &read_fds, NULL, NULL, ptimeout); Py_END_ALLOW_THREADS; if(select_retval == 0) { // Timed out. return PyList_New(0); } else if(select_retval < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } PyObject* retvalue = PyList_New(0); timeout.tv_sec = 0; timeout.tv_usec = 0; ptimeout = &timeout; while (1) { Py_BEGIN_ALLOW_THREADS; len = read(fd, buf, BUF_LEN); Py_END_ALLOW_THREADS; if(len < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } else if(len == 0) { PyErr_SetString(PyExc_IOError, "event buffer too small"); return NULL; } i = 0; while(i < len) { event = (struct inotify_event *)(& buf[i]); PyObject* value = NULL; if(event->name[0] != '\0') { value = Py_BuildValue( "iiis", event->wd, event->mask, event->cookie, event->name ); } else { value = Py_BuildValue( "iiiO", event->wd, event->mask, event->cookie, Py_None ); } if(PyList_Append(retvalue, value) == -1) { return NULL; } i += EVENT_SIZE + event->len; } FD_ZERO(&read_fds); FD_SET(fd, &read_fds); Py_BEGIN_ALLOW_THREADS; select_retval = select(fd + 1, &read_fds, NULL, NULL, ptimeout); Py_END_ALLOW_THREADS; if (select_retval <= 0) break; } return retvalue; } static PyMethodDef InotifyMethods[] = { { "init", inotifyx_init, METH_VARARGS, ( "init()\n\n" "Initialize an inotify instance and return the associated file\n" "descriptor. The file descriptor should be closed via os.close\n" "after it is no longer needed." ) }, { "add_watch", inotifyx_add_watch, METH_VARARGS, ( "add_watch(fd, path[, mask])\n\n" "Add a watch for path and return the watch descriptor.\n" "fd should be the file descriptor returned by init.\n" "If left unspecified, mask defaults to IN_ALL_EVENTS.\n" "See the inotify documentation for details." ) }, { "rm_watch", inotifyx_rm_watch, METH_VARARGS, ( "rm_watch(fd, wd)\n\n" "Remove the watch associated with watch descriptor wd.\n" "fd should be the file descriptor returned by init.\n" ) }, { "get_events", inotifyx_get_events, METH_VARARGS, "get_events(fd[, timeout])\n\n" "Read events from inotify and return a list of tuples " "(wd, mask, cookie, name).\n" "The name field is None if no name is associated with the inotify event.\n" "Timeout specifies a timeout in seconds (as an integer or float).\n" "If left unspecified, there is no timeout and get_events will block\n" "indefinitely. If timeout is zero, get_events will not block." }, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initbinding(void) { PyObject* module = Py_InitModule3( "inotifyx.binding", InotifyMethods, ( "Low-level interface to inotify. Do not use this module directly.\n" "Instead, use the inotifyx module." ) ); if (module == NULL) return; PyModule_AddIntConstant(module, "IN_ACCESS", IN_ACCESS); PyModule_AddIntConstant(module, "IN_MODIFY", IN_MODIFY); PyModule_AddIntConstant(module, "IN_ATTRIB", IN_ATTRIB); PyModule_AddIntConstant(module, "IN_CLOSE_WRITE", IN_CLOSE_WRITE); PyModule_AddIntConstant(module, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE); PyModule_AddIntConstant(module, "IN_CLOSE", IN_CLOSE); PyModule_AddIntConstant(module, "IN_OPEN", IN_OPEN); PyModule_AddIntConstant(module, "IN_MOVED_FROM", IN_MOVED_FROM); PyModule_AddIntConstant(module, "IN_MOVED_TO", IN_MOVED_TO); PyModule_AddIntConstant(module, "IN_MOVE", IN_MOVE); PyModule_AddIntConstant(module, "IN_CREATE", IN_CREATE); PyModule_AddIntConstant(module, "IN_DELETE", IN_DELETE); PyModule_AddIntConstant(module, "IN_DELETE_SELF", IN_DELETE_SELF); PyModule_AddIntConstant(module, "IN_MOVE_SELF", IN_MOVE_SELF); PyModule_AddIntConstant(module, "IN_UNMOUNT", IN_UNMOUNT); PyModule_AddIntConstant(module, "IN_Q_OVERFLOW", IN_Q_OVERFLOW); PyModule_AddIntConstant(module, "IN_IGNORED", IN_IGNORED); PyModule_AddIntConstant(module, "IN_ONLYDIR", IN_ONLYDIR); PyModule_AddIntConstant(module, "IN_DONT_FOLLOW", IN_DONT_FOLLOW); PyModule_AddIntConstant(module, "IN_MASK_ADD", IN_MASK_ADD); PyModule_AddIntConstant(module, "IN_ISDIR", IN_ISDIR); PyModule_AddIntConstant(module, "IN_ONESHOT", IN_ONESHOT); PyModule_AddIntConstant(module, "IN_ALL_EVENTS", IN_ALL_EVENTS); } inotifyx-0.2.0/setup.py0000755000175000017500000000242211606136726014475 0ustar forestforest#!/usr/bin/env python # Author: Forest Bond # This file is in the public domain. import os, sys, commands from distutils.core import Extension sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'modules')) sys.path.insert(0, os.path.join(os.path.dirname(__file__))) from setuplib import setup def get_version(release_file): try: f = open(release_file, 'r') try: return f.read().strip() finally: f.close() except (IOError, OSError): status, output = commands.getstatusoutput('bzr revno') if status == 0: return 'bzr%s' % output.strip() return 'unknown' version = get_version('release') setup( name = 'inotifyx', distinfo_module = 'inotifyx.distinfo', version = version, description = 'Simple Linux inotify bindings', author = 'Forest Bond', author_email = 'forest@alittletooquiet.net', url = 'http://www.alittletooquiet.net/software/inotifyx/', download_url = ( 'http://launchpad.net/inotifyx/dev/v%(version)s/+download/inotifyx-%(version)s.tar.gz' % {'version': version} ), packages = ['inotifyx'], ext_modules = [ Extension( 'inotifyx.binding', sources = [os.path.join('inotifyx', 'binding.c')], ), ], ) inotifyx-0.2.0/COPYING0000644000175000017500000000222111606136726014010 0ustar forestforestCopyright (c) 2004 Novell, Inc. Copyright (c) 2005 Manuel Amador Copyright (c) 2009 Forest Bond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. inotifyx-0.2.0/AUTHORS0000644000175000017500000000033511606136726014031 0ustar forestforestCurrent authors: Forest Bond inotifyx is a derived work, based on the following: python-inotify by Manuel Amador inotify-glue.c from Beagle inotifyx-0.2.0/setuplib.py0000644000175000017500000004455311606136726015174 0ustar forestforest# Author: Forest Bond # This file is in the public domain. from __future__ import with_statement import os, sys, shutil from tempfile import mkdtemp from modulefinder import Module from distutils.command.build import build from distutils.command.clean import clean as _clean from distutils.command.install import install from distutils.command.install_lib import install_lib as _install_lib from distutils.command.install_data import install_data as _install_data from distutils.core import ( setup as _setup, Command, ) from distutils.spawn import spawn from distutils import log from distutils.dir_util import remove_tree from distutils.dist import Distribution try: from py2exe.build_exe import py2exe as _py2exe except ImportError: _py2exe = None else: from py2exe.build_exe import byte_compile ### class test(Command): description = 'run tests' user_options = [ ('tests=', None, 'names of tests to run'), ('print-only', None, "don't run tests, just print their names"), ('coverage', None, "print coverage analysis (requires coverage.py)"), ] def initialize_options(self): self.tests = None self.print_only = False self.coverage = False def finalize_options(self): if self.tests is not None: self.tests = self.tests.split(',') def run(self): build_obj = self.get_finalized_command('build') build_obj.run() sys.path.insert(0, build_obj.build_lib) try: mod = __import__(self.distribution.test_module) main = getattr(mod, 'main') main( test_names = self.tests, print_only = self.print_only, coverage = self.coverage, ) finally: sys.path.remove(build_obj.build_lib) Distribution.test_module = 'tests' ### class clean(_clean): user_options = _clean.user_options + [ ('build-man=', None, 'manpage build directory'), ] def initialize_options(self): _clean.initialize_options(self) self.build_man = None def finalize_options(self): _clean.finalize_options(self) self.set_undefined_options('build_man', ('build_dir', 'build_man')) def run(self): if self.all: if os.path.exists(self.build_man): remove_tree(self.build_man, dry_run = self.dry_run) else: log.debug( "'%s' does not exist -- can't clean it", self.build_man, ) _clean.run(self) ### class build_man(Command): user_options = _install_data.user_options + [ ('build-dir=', 'b', 'manpage build directory'), ] description = 'Build manual pages from docbook XML.' xsltproc = ['xsltproc', '--nonet', '--novalid', '--xinclude'] def initialize_options(self): self.build_base = None self.build_dir = None def finalize_options(self): self.set_undefined_options('build', ('build_base', 'build_base')) if self.build_dir is None: self.build_dir = os.path.join(self.build_base, 'man') self.docbook_files = [] if self.distribution.manpage_sources is not None: unclaimed_files = list(self.distribution.manpage_sources) for index, filename in enumerate(list(unclaimed_files)): if filename.endswith('.xml'): self.docbook_files.append(os.path.abspath(filename)) del unclaimed_files[index] if unclaimed_files: log.error( 'unknown manpage source file types: %s', ', '.join(unclaimed_files), ) raise SystemExit(1) def _find_docbook_manpage_stylesheet(self): from libxml2 import catalogResolveURI return catalogResolveURI( 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl' ) def build_manpage_from_docbook(self, stylesheet, docbook_file): if stylesheet is None: raise AssertionError('stylesheet is None') command = self.xsltproc + [stylesheet, docbook_file] orig_wd = os.getcwd() os.chdir(self.build_dir) try: spawn(command, dry_run = self.dry_run) finally: os.chdir(orig_wd) def run(self): if self.docbook_files: stylesheet = self._find_docbook_manpage_stylesheet() if stylesheet is None: log.warn( 'Warning: missing docbook XSL stylesheets; ' 'manpages will not be built.\n' 'Please install the docbook XSL stylesheets from ' 'http://docbook.org/.' ) else: if not self.dry_run: if not os.path.exists(self.build_dir): os.mkdir(self.build_dir) for docbook_file in self.docbook_files: log.info('building manpage from docbook: %s', docbook_file) if not self.dry_run: self.build_manpage_from_docbook( stylesheet, docbook_file, ) Distribution.manpage_sources = None build.sub_commands.append(( 'build_man', (lambda self: bool(self.distribution.manpage_sources)), )) class install_man(_install_data): def initialize_options(self): _install_data.initialize_options(self) self.build_dir = None def finalize_options(self): _install_data.finalize_options(self) self.set_undefined_options('build_man', ('build_dir', 'build_dir')) self.data_files = [] if os.path.exists(self.build_dir): for entry in os.listdir(self.build_dir): path = os.path.join(self.build_dir, entry) if os.path.isfile(path): base, ext = os.path.splitext(entry) section = int(ext[1:]) self.data_files.append( ('share/man/man%u' % section, [path]) ) install.sub_commands.append(( 'install_man', (lambda self: bool(self.distribution.manpage_sources)), )) ### class _DistinfoMixin: def _split_distinfo_module(self): parts = self.distribution.distinfo_module.split('.') return parts[:-1], parts[-1] def _prepare_distinfo_string(self, value): if isinstance(value, str): value = unicode(value) return unicode(repr(value)).encode('utf-8') def _write_distinfo_module(self, outfile, distinfo = (), imports = ()): distinfo = list(distinfo) imports = list(imports) distinfo.insert( 0, ( 'version', self._prepare_distinfo_string(self.distribution.metadata.version), ), ) log.info("creating distinfo file %r:", outfile) for k, v in distinfo: log.info(' %s = %s', k, v) if not self.dry_run: with open(outfile, 'wb') as f: f.write('# coding: utf-8\n') f.write('\n') for modname in imports: f.write('import %s\n' % modname) if imports: f.write('\n') for k, v in distinfo: f.write('%s = %s\n' % (k, v)) ### class install_data(_install_data): user_options = _install_data.user_options + [ ( 'install-dir-arch=', None, 'base directory for installing architecture-dependent data files', ), ( 'install-dir-arch-pkg=', None, 'package-specific directory for installing architecture-dependent data files', ), ( 'install-dir-indep=', None, 'base directory for installing architecture-independent data files', ), ( 'install-dir-indep-pkg=', None, 'package-specific directory for installing architecture-independent data files', ), ] def initialize_options(self): _install_data.initialize_options(self) self.install_dir_arch = None self.install_dir_arch_pkg = None self.install_dir_indep = None self.install_dir_indep_pkg = None self.data_files_arch = self.distribution.data_files_arch self.data_files_arch_pkg = self.distribution.data_files_arch_pkg self.data_files_indep = self.distribution.data_files_indep self.data_files_indep_pkg = self.distribution.data_files_indep_pkg def _get_relative_install_dir(self, p): return p[len(self.install_dir) + len(os.sep):] def finalize_options(self): _install_data.finalize_options(self) py2exe_obj = self.distribution.get_command_obj('py2exe', False) if py2exe_obj is not None: py2exe_obj.ensure_finalized() if (py2exe_obj is not None) and ( self.install_dir == py2exe_obj.dist_dir ): self.install_dir_arch = self.install_dir self.install_dir_arch_pkg = self.install_dir self.install_dir_indep = self.install_dir self.install_dir_indep_pkg = self.install_dir else: if self.install_dir_arch is None: if sys.platform == 'win32': self.install_dir_arch = self.install_dir else: self.install_dir_arch = os.path.join(self.install_dir, 'lib') if self.install_dir_arch_pkg is None: self.install_dir_arch_pkg = os.path.join( self.install_dir_arch, self.distribution.metadata.name, ) if self.install_dir_indep is None: if sys.platform == 'win32': self.install_dir_indep = self.install_dir else: self.install_dir_indep = os.path.join(self.install_dir, 'share') if self.install_dir_indep_pkg is None: self.install_dir_indep_pkg = os.path.join( self.install_dir_indep, self.distribution.metadata.name, ) if self.data_files is None: self.data_files = [] if self.data_files_arch: self.data_files.extend(self._gen_data_files( self._get_relative_install_dir(self.install_dir_arch), self.data_files_arch, )) if self.data_files_arch_pkg: self.data_files.extend(self._gen_data_files( self._get_relative_install_dir(self.install_dir_arch_pkg), self.data_files_arch_pkg, )) if self.data_files_indep: self.data_files.extend(self._gen_data_files( self._get_relative_install_dir(self.install_dir_indep), self.data_files_indep, )) if self.data_files_indep_pkg: self.data_files.extend(self._gen_data_files( self._get_relative_install_dir(self.install_dir_indep_pkg), self.data_files_indep_pkg, )) def _gen_data_files(self, base_dir, data_files): for arg in data_files: print arg if isinstance(arg, basestring): yield (base_dir, [arg]) else: subdir, filenames = arg yield (os.path.join(base_dir, subdir), filenames) Distribution.data_files_arch = None Distribution.data_files_arch_pkg = None Distribution.data_files_indep = None Distribution.data_files_indep_pkg = None orig_has_data_files = Distribution.has_data_files def has_data_files(self): if orig_has_data_files(self): return True return any([ self.data_files_arch, self.data_files_arch_pkg, self.data_files_indep, self.data_files_indep_pkg, ]) Distribution.has_data_files = has_data_files ### class install_lib(_DistinfoMixin, _install_lib): def initialize_options(self): _install_lib.initialize_options(self) self.distinfo_package = None self.distinfo_module = None def finalize_options(self): _install_lib.finalize_options(self) if self.distribution.distinfo_module is not None: self.distinfo_package, self.distinfo_module = \ self._split_distinfo_module() def install(self): retval = _install_lib.install(self) if retval is None: return retval py2exe_obj = self.distribution.get_command_obj('py2exe', False) if (py2exe_obj is None) and (self.distinfo_package is not None): parts = [self.install_dir] parts.extend(self.distinfo_package) parts.append('%s.py' % self.distinfo_module) installed_module_path = os.path.join(*parts) install_obj = self.get_finalized_command('install') install_data_obj = self.get_finalized_command('install_data') distinfo = [ ( 'install_base', self._prepare_distinfo_string(os.path.abspath( install_obj.install_base, )), ), ( 'install_platbase', self._prepare_distinfo_string(os.path.abspath( install_obj.install_platbase, )), ), ( 'install_purelib', self._prepare_distinfo_string(os.path.abspath( install_obj.install_purelib, )), ), ( 'install_platlib', self._prepare_distinfo_string(os.path.abspath( install_obj.install_platlib, )), ), ( 'install_lib', self._prepare_distinfo_string(os.path.abspath( install_obj.install_lib, )), ), ( 'install_headers', self._prepare_distinfo_string(os.path.abspath( install_obj.install_headers, )), ), ( 'install_scripts', self._prepare_distinfo_string(os.path.abspath( install_obj.install_scripts, )), ), ( 'install_data', self._prepare_distinfo_string(os.path.abspath( install_data_obj.install_dir, )), ), ( 'install_data_arch', self._prepare_distinfo_string(os.path.abspath( install_data_obj.install_dir_arch, )), ), ( 'install_data_arch_pkg', self._prepare_distinfo_string(os.path.abspath( install_data_obj.install_dir_arch_pkg, )), ), ( 'install_data_indep', self._prepare_distinfo_string(os.path.abspath( install_data_obj.install_dir_indep, )), ), ( 'install_data_indep_pkg', self._prepare_distinfo_string(os.path.abspath( install_data_obj.install_dir_indep_pkg, )), ), ] self._write_distinfo_module(installed_module_path, distinfo) retval.append(installed_module_path) return retval if _py2exe is None: py2exe = None else: class py2exe(_DistinfoMixin, _py2exe): def make_lib_archive(self, *args, **kwargs): if self.distribution.distinfo_module is not None: imports = ['os', 'sys'] distinfo = [ ( 'install_data', 'os.path.dirname(sys.executable)', ), ( 'install_data_arch', 'os.path.dirname(sys.executable)', ), ( 'install_data_arch_pkg', 'os.path.dirname(sys.executable)', ), ( 'install_data_indep', 'os.path.dirname(sys.executable)', ), ( 'install_data_indep_pkg', 'os.path.dirname(sys.executable)', ), ] tmp_dir_path = mkdtemp() try: distinfo_package, distinfo_module = self._split_distinfo_module() tmp_file_parent_path = os.path.join( tmp_dir_path, *distinfo_package ) os.makedirs(tmp_file_parent_path) tmp_file_path = os.path.join( tmp_file_parent_path, ('%s.py' % distinfo_module), ) self._write_distinfo_module(tmp_file_path, distinfo, imports) sys.path.insert(0, tmp_dir_path) try: self._distinfo_compiled_files = byte_compile( [Module( name = self.distribution.distinfo_module, file = tmp_file_path, )], target_dir = self.collect_dir, optimize = self.optimize, force = 0, verbose = self.verbose, dry_run = self.dry_run, ) finally: del sys.path[0] finally: shutil.rmtree(tmp_dir_path) self.compiled_files.extend(self._distinfo_compiled_files) return _py2exe.make_lib_archive(self, *args, **kwargs) Distribution.distinfo_module = None ### def setup(*args, **kwargs): cmdclass = kwargs.setdefault('cmdclass', {}) kwargs['cmdclass'].setdefault('test', test) kwargs['cmdclass'].setdefault('install_data', install_data) kwargs['cmdclass'].setdefault('install_lib', install_lib) kwargs['cmdclass'].setdefault('build_man', build_man) kwargs['cmdclass'].setdefault('install_man', install_man) kwargs['cmdclass'].setdefault('clean', clean) if py2exe is not None: kwargs['cmdclass'].setdefault('py2exe', py2exe) return _setup(*args, **kwargs)