diff options
author | Guido Guenther <agx@sigxcpu.org> | 2007-08-23 15:47:20 +0200 |
---|---|---|
committer | Guido Guenther <agx@bogon.sigxcpu.org> | 2007-08-23 15:47:20 +0200 |
commit | e5f5a2ab23ace19c6de39512f76f8fed5f5ad912 (patch) | |
tree | a3a2a1ba762515d37acaf85d9e5c27f81a3ab6c1 /lib/Dnotify.py |
Imported upstream version 0.6.21upstream/0.6.21debian/0.6.21-0.1upstream
Diffstat (limited to 'lib/Dnotify.py')
-rw-r--r-- | lib/Dnotify.py | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/lib/Dnotify.py b/lib/Dnotify.py new file mode 100644 index 0000000..122e03c --- /dev/null +++ b/lib/Dnotify.py @@ -0,0 +1,199 @@ +# Dnotify -*- mode: python; coding: utf-8 -*- + +# A simple FAM-like beast in Python + +# Copyright © 2002 Colin Walters <walters@gnu.org> + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, re, sys, string, stat, threading, Queue, time +import logging +from minidinstall import misc + +class DnotifyException(Exception): + def __init__(self, value): + self._value = value + def __str__(self): + return `self._value` + +class DirectoryNotifierFactory: + def create(self, dirs, use_dnotify=1, poll_time=30, logger=None, cancel_event=None): + if use_dnotify and os.access('/usr/bin/dnotify', os.X_OK): + if logger: + logger.debug("Using dnotify directory notifier") + return DnotifyDirectoryNotifier(dirs, logger) + else: + if logger: + logger.debug("Using mtime-polling directory notifier") + return MtimeDirectoryNotifier(dirs, poll_time, logger, cancel_event=cancel_event) + +class DnotifyNullLoggingFilter(logging.Filter): + def filter(self, record): + return 0 + +class DirectoryNotifier: + def __init__(self, dirs, logger, cancel_event=None): + self._cwd = os.getcwd() + self._dirs = dirs + if cancel_event is None: + self._cancel_event = threading.Event() + else: + self._cancel_event = cancel_event + if logger is None: + self._logger = logging.getLogger("Dnotify") + self._logger.addFilter(DnotifyNullLoggingFilter()) + else: + self._logger = logger + + def cancelled(self): + return self._cancel_event.isSet() + +class DirectoryNotifierAsyncWrapper(threading.Thread): + def __init__(self, dnotify, queue, logger=None, name=None): + if not name is None: + threading.Thread.__init__(self, name=name) + else: + threading.Thread.__init__(self) + self._eventqueue = queue + self._dnotify = dnotify + if logger is None: + self._logger = logging.getLogger("Dnotify") + self._logger.addFilter(DnotifyNullLoggingFilter()) + else: + self._logger = logger + + def cancel(self): + self._cancel_event.set() + + def run(self): + self._logger.info('Created new thread (%s) for async directory notification' % (self.getName())) + while not self._dnotify.cancelled(): + dir = self._dnotify.poll() + self._eventqueue.put(dir) + self._logger.info('Caught cancel event; async dnotify thread exiting') + +class MtimeDirectoryNotifier(DirectoryNotifier): + def __init__(self, dirs, poll_time, logger, cancel_event=None): + DirectoryNotifier.__init__(self, dirs, logger, cancel_event=cancel_event) + self._changed = [] + self._dirmap = {} + self._polltime = poll_time + for dir in dirs: + self._dirmap[dir] = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME] + + def poll(self, timeout=None): + timeout_time = None + if timeout: + timeout_time = time.time() + timeout + while self._changed == []: + if timeout_time and time.time() > timeout_time: + return None + self._logger.debug('Polling...') + for dir in self._dirmap.keys(): + oldtime = self._dirmap[dir] + mtime = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME] + if oldtime < mtime: + self._logger.debug('Directory "%s" has changed' % (dir,)) + self._changed.append(dir) + self._dirmap[dir] = mtime + if self._changed == []: + for x in range(self._polltime): + if self._cancel_event.isSet(): + return None + time.sleep(1) + ret = self._changed[0] + self._changed = self._changed[1:] + return ret + +class DnotifyDirectoryNotifier(DirectoryNotifier): + def __init__(self, dirs, logger): + DirectoryNotifier.__init__(self, dirs, logger) + self._queue = Queue.Queue() + dnotify = DnotifyThread(self._queue, self._dirs, self._logger) + dnotify.start() + + def poll(self, timeout=None): + # delete duplicates + i = self._queue.qsize() + self._logger.debug('Queue size: %d', (i,)) + set = {} + while i > 0: + dir = self._queue_get(timeout) + if dir is None: + # We shouldn't have to do this; no one else is reading + # from the queue. But we do it just to be safe. + for key in set.keys(): + self._queue.put(key) + return None + set[dir] = 1 + i -= 1 + for key in set.keys(): + self._queue.put(key) + i = self._queue.qsize() + self._logger.debug('Queue size (after duplicate filter): %d', (i,)) + return self._queue_get(timeout) + + def _queue_get(self, timeout): + if timeout is None: + return self._queue.get() + timeout_time = time.time() + timeout + while 1: + try: + self._queue.get(0) + except Queue.Empty: + if time.time() > timeout_time: + return None + else: + time.sleep(15) + +class DnotifyThread(threading.Thread): + def __init__(self, queue, dirs, logger): + threading.Thread.__init__(self) + self._queue = queue + self._dirs = dirs + self._logger = logger + + def run(self): + self._logger.debug('Starting dnotify reading thread') + (infd, outfd) = os.pipe() + pid = os.fork() + if pid == 0: + os.close(infd) + misc.dup2(outfd, 1) + args = ['dnotify', '-m', '-c', '-d', '-a', '-r'] + list(self._dirs) + ['-e', 'printf', '"{}\\0"'] + os.execv('/usr/bin/dnotify', args) + os.exit(1) + + os.close(outfd) + stdout = os.fdopen(infd) + c = 'x' + while c != '': + curline = '' + c = stdout.read(1) + while c != '' and c != '\0': + curline += c + c = stdout.read(1) + if c == '': + break + self._logger.debug('Directory "%s" changed' % (curline,)) + self._queue.put(curline) + (pid, status) = os.waitpid(pid, 0) + if status is None: + ecode = 0 + else: + ecode = os.WEXITSTATUS(status) + raise DnotifyException("dnotify exited with code %s" % (ecode,)) + +# vim:ts=4:sw=4:et: |