Update of /cvsroot/freevo/kaa/notifier/src
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv9774/notifier/src

Added Files:
        .cvsignore __init__.py callback.py popen.py signals.py 
        thread.py 
Log Message:
create notifier wrapper

--- NEW FILE: signals.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# signals.py - Signal handling for the notifier
# -----------------------------------------------------------------------------
# $Id: signals.py,v 1.1 2005/06/28 11:17:47 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-notifier - Notifier Wrapper
# Copyright (C) 2005 Dirk Meyer, et al.
#
# First Version: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/AUTHORS for a complete list of authors.
#
# This program 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 MER-
# CHANTABILITY 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
#
# -----------------------------------------------------------------------------

from signal import *
import notifier

_signal_dict = {}
_signal_list = []

def register(sig, function):
    """
    Register a signal handler.
    """
    _signal_dict[sig] = function
    signal(sig, _signal_catch)


def has_signal():
    """
    Return True if there are signals in the queue.
    """
    return _signal_list


def _signal_handler():
    """
    Call all registered signal handler.
    """
    while _signal_list:
        sig = _signal_list.pop(0)
        _signal_dict[sig](sig)
    return False


def _signal_catch(sig, frame):
    """
    Catch signals to be called from the main loop.
    """
    if not sig in _signal_list:
        # add catched signal to the list
        _signal_list.append(sig)
    # FIXME: let's hope this works because the handler
    # is called asynchron
    notifier.addTimer(0, _signal_handler)
    return True

--- NEW FILE: .cvsignore ---
*.pyc *.pyo

--- NEW FILE: thread.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# thread.py - Thread module for the notifier
# -----------------------------------------------------------------------------
# $Id: thread.py,v 1.1 2005/06/28 11:17:47 dischi Exp $
#
# This module contains some wrapper classes for threading while running the
# notifier main loop. It should only be used when non blocking handling is not
# possible. The main loop itself is not thread save, the the function called in
# the thread should not touch any variables inside the application which are
# not protected by by a lock.
#
# You can create a Thread object with the function and it's
# arguments. After that you can call the start function to start the
# thread. This function has an optional parameter with a callback
# which will be called from the main loop once the thread is
# finished. The result of the thread function is the parameter for the
# callback.
#
# In most cases this module is not needed, please add a good reason why you
# wrap a function in a thread.
#
# If a thread needs to call a function from the main loop the helper function
# call_from_main can be used. It will schedule the function call in the main
# loop. It is not possible to get the return value of that call.
#
# -----------------------------------------------------------------------------
# kaa-notifier - Notifier Wrapper
# Copyright (C) 2005 Dirk Meyer, et al.
#
# First Version: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/AUTHORS for a complete list of authors.
#
# This program 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 MER-
# CHANTABILITY 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
#
# -----------------------------------------------------------------------------

__all__ = [ 'Thread', 'call_from_main' ]

# python imports
import copy
import threading
import notifier
import logging

# get logging object
log = logging.getLogger('fthread')

# internal list of callbacks that needs to be called from the main loop
_callbacks = []

# lock for adding / removing callbacks from _callbacks
_lock = threading.Lock()

class Thread(threading.Thread):
    """
    Notifier aware wrapper for threads.
    """
    def __init__(self, function, *args, **kargs):
        threading.Thread.__init__(self)
        self.callbacks = [ None, None ]
        self.function  = function
        self.args      = args
        self.kargs     = kargs
        self.result    = None
        self.finished  = False
        self.exception = None


    def start(self, callback=None, exception_callback = None):
        """
        Start the thread.
        """
        # append object to list of threads in watcher
        _watcher.append(self)
        # register callback
        self.callbacks = [ callback, exception_callback ]
        # start the thread
        threading.Thread.start(self)


    def run(self):
        """
        Call the function and store the result
        """
        try:
            # run thread function
            self.result = self.function(*self.args, **self.kargs)
        except Exception, e:
            log.exception('Thread crashed:')
            self.exception = e
        # set finished flag
        self.finished = True


    def callback(self):
        """
        Run the callback.
        """
        if self.exception and self.callbacks[1]:
            self.callbacks[1](self.exception)
        elif not self.exception and self.callbacks[0]:
            self.callbacks[0](self.result)



def call_from_main(function, *args, **kwargs):
    """
    Call a function from the main loop. The function isn't called when this
    function is called, it is called when the watcher in the main loop is
    called by the notifier.
    """
    _lock.acquire()
    _callbacks.append((function, args, kwargs))
    _lock.release()


class Watcher(object):
    """
    Watcher for running threads.
    """
    def __init__(self):
        self.__threads = []
        self.__timer = None


    def append(self, thread):
        """
        Append a thread to the watcher.
        """
        self.__threads.append(thread)
        if not self.__timer:
            self.__timer = notifier.addTimer( 10, self.check )


    def check(self):
        """
        Check for finished threads and callbacks that needs to be called from
        the main loop.
        """
        finished = []
        # check if callbacks needs to be called from the main loop
        if _callbacks:
            # acquire lock
            _lock.acquire()
            # copy callback list
            cb = copy.copy(_callbacks)
            while _callbacks:
                # delete callbacks
                _callbacks.pop()
            # release lock
            _lock.release()

            # call callback functions
            for function, args, kwargs in cb:
                function(*args, **kwargs)

        for thread in self.__threads:
            # check all threads
            if thread.finished:
                finished.append(thread)

        if not finished:
            # no finished thread, return
            return True

        # check all finished threads
        for thread in finished:
            # remove thread from list
            self.__threads.remove(thread)
            # call callback
            thread.callback()
            # join thread
            thread.join()

        if not self.__threads:
            # remove watcher from notifier
            self.__timer = None
            return False
        return True


# the global watcher object
_watcher = Watcher()

--- NEW FILE: popen.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# popen.py - process control using notifier
# -----------------------------------------------------------------------------
# $Id: popen.py,v 1.1 2005/06/28 11:17:47 dischi Exp $
#
# This module defines a process class similar to the once defined in popen2
# except that this class is aware of the notifier loop.
#
# When creating a Process object you can add files for logging the stdout and
# stderr of the process, you can send data to it and add a callback to be
# called when the process is dead. See the class member functions for more
# details.
#
# By inherting from the class you can also override the functions stdout_cb
# and stderr_cb to process stdout and stderr line by line.
#
# The killall function of this class can be called at the end of the programm
# to stop all running processes.
#
# -----------------------------------------------------------------------------
# kaa-notifier - Notifier Wrapper
# Copyright (C) 2005 Dirk Meyer, et al.
#
# First Version: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Based on code by Krister Lagerstrom and Andreas Büsching
# Please see the file doc/AUTHORS for a complete list of authors.
#
# This program 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 MER-
# CHANTABILITY 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
#
# -----------------------------------------------------------------------------

__all__ = [ 'Process', 'killall' ]

# python imports
import os
import fcntl
import popen2
import glob
import re
import logging

# notifier
import notifier

# get logging object
log = logging.getLogger('popen')


class Process(object):
    """
    Base class for started child processes
    """
    def __init__( self, app, debugname = None, callback = None ):
        """
        Init the child process 'app'. This can either be a string or a list
        of arguments (similar to popen2). If debugname is given, the stdout
        and stderr will also be written. To get the stdout and stderr, you
        need to inherit from this class and override stdout_cb and stderr_cb.
        If callback is set, the given function will be called after the child
        is dead.
        """
        if isinstance(app, str):
            # app is a string to execute. It will be executed by 'sh -c '
            # inside the popen code
            self.binary = app.lstrip()

            start_str = app
        else:
            # app is a list
            while '' in app:
                app.remove( '' )

            self.binary = str( ' ' ).join( app )
            start_str = app

        self.__kill_timer = None
        self.stopping = False
        self.__dead = False
        self.callback = callback
        self.child = popen2.Popen3( start_str, True, 100 )

        log.info('running %s (pid=%s)' % ( self.binary, self.child.pid ) )

        # IO_Handler for stdout
        self.stdout = IO_Handler( 'stdout', self.child.fromchild,
                                  self.stdout_cb, debugname )
        # IO_Handler for stderr
        self.stderr = IO_Handler( 'stderr', self.child.childerr,
                                  self.stderr_cb, debugname )

        # add child to watcher
        _watcher.append( self, self.__child_died )


    def write( self, line ):
        """
        Write a string to the app.
        """
        try:
            self.child.tochild.write(line)
            self.child.tochild.flush()
        except (IOError, ValueError):
            pass


    def is_alive( self ):
        """
        Return True if the app is still running
        """
        return not self.__dead


    def stop( self, cmd = '', wait = True ):
        """
        Stop the child. If 'cmd' is given, this stop command will send to
        the app to stop itself. If this is not working, kill -15 and kill -9
        will be used to kill the app. If wait is True, this function will
        call notifier.step until the child is dead.
        """
        if self.stopping:
            return

        self.stopping = True

        if self.is_alive() and not self.__kill_timer:
            if cmd:
                log.info('sending exit command to app')
                self.write(cmd)
                cb = notifier.Callback( self.__kill, 15 )
                self.__kill_timer = notifier.addTimer( 3000, cb )
            else:
                cb = notifier.Callback( self.__kill, 15 )
                self.__kill_timer = notifier.addTimer( 0, cb )

            while wait and not self.__dead:
                notifier.step()


    def __kill( self, signal ):
        """
        Internal kill helper function
        """
        if not self.is_alive():
            self.__dead = True
            return False
        # child needs some assistance with dying ...
        try:
            os.kill( self.child.pid, signal )
        except OSError:
            pass

        if signal == 15:
            cb = notifier.Callback( self.__kill, 9 )
        else:
            cb = notifier.Callback( self.__killall, 15 )

        self.__kill_timer = notifier.addTimer( 3000, cb )
        return False


    def __killall( self, signal ):
        """
        Internal killall helper function
        """
        if not self.is_alive():
            self.__dead = True
            return False
        # child needs some assistance with dying ...
        try:
            # kill all applications with the string <appname> in their
            # commandline. This implementation uses the /proc filesystem,
            # it is Linux-dependent.
            unify_name = re.compile('[^A-Za-z0-9]').sub
            appname = unify_name('', self.binary)

            cmdline_filenames = glob.glob('/proc/[0-9]*/cmdline')

            for cmdline_filename in cmdline_filenames:
                try:
                    fd = open(cmdline_filename)
                    cmdline = fd.read()
                    fd.close()
                except IOError:
                    continue
                if unify_name('', cmdline).find(appname) != -1:
                    # Found one, kill it
                    pid = int(cmdline_filename.split('/')[2])
                    try:
                        os.kill(pid, signal)
                    except:
                        pass
        except OSError:
            pass

        log.info('kill -%d %s' % ( signal, self.binary ))
        if signal == 15:
            cb = notifier.Callback( self.__killall, 9 )
            self.__kill_timer = notifier.addTimer( 2000, cb )
        else:
            log.critical('PANIC %s' % self.binary)

        return False


    def __child_died( self ):
        """
        Callback from watcher when the child died.
        """
        self.__dead = True
        # close IO handler and kill timer
        self.stdout.close()
        self.stderr.close()
        if self.__kill_timer:
            notifier.removeTimer( self.__kill_timer )
        if self.callback:
            # call external callback on stop
            self.callback()


    def stdout_cb( self, line ):
        """
        Override this method to receive stdout from the child app
        The function receives complete lines
        """
        pass


    def stderr_cb( self, line ):
        """
        Override this method to receive stderr from the child app
        The function receives complete lines
        """
        pass


class IO_Handler(object):
    """
    Reading data from socket (stdout or stderr)
    """
    def __init__( self, name, fp, callback, logger = None):
        self.name = name
        self.fp = fp
        fcntl.fcntl( self.fp.fileno(), fcntl.F_SETFL, os.O_NONBLOCK )
        self.callback = callback
        self.logger = None
        self.saved = ''
        notifier.addSocket( fp, self._handle_input )
        if logger:
            logger = '%s-%s.log' % ( logger, name )
            try:
                try:
                    os.unlink(logger)
                except:
                    pass
                self.logger = open(logger, 'w')
                log.info('logging child to "%s"' % logger)
            except IOError:
                log.warning('Error: Cannot open "%s" for logging' % logger)


    def close( self ):
        """
        Close the IO to the child.
        """
        notifier.removeSocket( self.fp )
        self.fp.close()
        if self.logger:
            self.logger.close()
            self.logger = None

    def _handle_input( self, socket ):
        """
        Handle data input from socket.
        """
        data = self.fp.read( 10000 )
        if not data:
            log.info('No data on %s for pid %s.' % ( self.name, os.getpid()))
            notifier.removeSocket( self.fp )
            self.fp.close()
            if self.logger:
                self.logger.close()
            return False

        data  = data.replace('\r', '\n')
        lines = data.split('\n')

        # Only one partial line?
        if len(lines) == 1:
            self.saved += data
            return True

        # Combine saved data and first line, send to app
        if self.logger:
            self.logger.write( self.saved + lines[ 0 ] + '\n' )
        self.callback( self.saved + lines[ 0 ] )
        self.saved = ''

        # There's one or more lines + possibly a partial line
        if lines[ -1 ] != '':
            # The last line is partial, save it for the next time
            self.saved = lines[ -1 ]

            # Send all lines except the last partial line to the app
            for line in lines[ 1 : -1 ]:
                if not line:
                    continue
                if self.logger:
                    self.logger.write( line + '\n' )
                self.callback( line )
        else:
            # Send all lines to the app
            for line in lines[ 1 : ]:
                if not line:
                    continue
                if self.logger:
                    self.logger.write( line + '\n' )
                self.callback( line )
        return True


class Watcher(object):
    def __init__( self ):
        log.info('new process watcher instance')
        self.__processes = {}
        self.__timer = None


    def append( self, proc, cb ):
        self.__processes[ proc ] = cb
        if not self.__timer:
            log.info('start process watching')
            self.__timer = notifier.addTimer(50, self.check)


    def check( self ):
        remove_proc = []

        # check all processes
        for p in self.__processes:
            try:
                if isinstance( p.child, popen2.Popen3 ):
                    pid, status = os.waitpid( p.child.pid, os.WNOHANG )
                else:
                    pid, status = os.waitpid( p.pid, os.WNOHANG )
            except OSError:
                remove_proc.append( p )
                continue
            if not pid:
                continue
            log.info('Dead child: %s (%s)' % ( pid, status ))
            if status == -1:
                log.error('error retrieving process information from %d' % p)
            elif os.WIFEXITED( status ) or os.WIFSIGNALED( status ) or \
                     os.WCOREDUMP( status ):
                remove_proc.append( p )

        # remove dead processes
        for p in remove_proc:
            if p in self.__processes:
                # call stopped callback
                self.__processes[ p ]()
                del self.__processes[ p ]

        # check if this function needs to be called again
        if not self.__processes:
            # no process left, remove timer
            self.__timer = None
            log.info('stop process watching')
            return False

        # return True to be called again
        return True


    def killall( self ):
        # stop all childs without waiting
        for p in self.__processes:
            p.stop(wait=False)

        # now wait until all childs are dead
        while self.__processes:
            notifier.step()


# global watcher instance
_watcher = Watcher()

# global killall function
killall = _watcher.killall

--- NEW FILE: __init__.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# __init__.py - Interface to kaa.notifier
# -----------------------------------------------------------------------------
# $Id: __init__.py,v 1.1 2005/06/28 11:17:47 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-notifier - Notifier Wrapper
# Copyright (C) 2005 Dirk Meyer, et al.
#
# First Version: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/AUTHORS for a complete list of authors.
#
# This program 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 MER-
# CHANTABILITY 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
#
# -----------------------------------------------------------------------------

# python imports
import notifier

# the real notifier module
import logging

# kaa.notifier imports
from signals import *
from signals import register as signal
from popen import Process
from popen import killall as kill_processes
from thread import Thread, call_from_main
from callback import Callback, Function, CallbackObject

# get logging object
log = logging.getLogger('notifier')

# variable to check if the notifier is running
running = False

# variable to check if the notifier is initialized
initialized = False


def init(type = notifier.GENERIC):
    """
    Init the notifier module.
    """
    global initialized
    initialized = True
    notifier.init(type)
    for var in globals():
        if var in _notifier_vars:
            globals()[var] = getattr(notifier, var)


def loop():
    """
    Notifier main loop function. It will loop until an exception
    is raised or sys.exit is called.
    """
    global running
    running = True
    while 1:
        try:
            notifier.loop()
        except KeyboardInterrupt:
            break
        except Exception, e:
            if has_signal():
                log.info('Call Signal Handler')
            else:
                running = False
                raise e
    running = False


def step(*args, **kwargs):
    """
    Notifier step function with signal support.
    """
    try:
        notifier.step(*args, **kwargs)
    except KeyboardInterrupt:
        pass
    except Exception, e:
        if has_signal():
            log.info('Call Signal Handler')
        else:
            raise e


def addTimer(interval, function, *args, **kwargs):
    """
    The first argument specifies an interval in milliseconds, the
    second argument a function. Optional parameters specify parameters
    to the called function. This is function is called after interval
    seconds. If it returns true it's called again after interval
    seconds, otherwise it is removed from the scheduler.
    This function returns an unique identifer which can be used to remove this
    timer.
    """
    if args or kwargs:
        # create callback object to be passed to the notifier
        function = Callback(function, *args, **kwargs)
    return notifier.addTimer(interval, function)


def timer(interval, function, *args, **kwargs):
    """
    The first argument specifies an interval in milliseconds, the
    second argument a function. Optional parameters specify parameters
    to the called function. This is function is called after interval
    seconds. If it returns true it's called again after interval
    seconds, otherwise it is removed from the scheduler.
    This function returns a callback object with a remove function to remove
    the timer from the notifier.
    """
    t = CallbackObject(function, *args, **kwargs)
    t.register('Timer', interval)
    return t


# Import all notifier variables that are needed by the user of this
# module. Mark them in _notifier_vars to be overwritten later in init.
_notifier_vars = []
for var in dir(notifier):
    if getattr(notifier, var) == None and not var in globals():
        _notifier_vars.append(var)
        exec('%s = notifier.%s' % (var, var))
    if var == var.upper() and not var in _notifier_vars:
        exec('%s = notifier.%s' % (var, var))

--- NEW FILE: callback.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# callback.py - Callback classes for the notifier
# -----------------------------------------------------------------------------
# $Id: callback.py,v 1.1 2005/06/28 11:17:47 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-notifier - Notifier Wrapper
# Copyright (C) 2005 Dirk Meyer, et al.
#
# First Version: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/AUTHORS for a complete list of authors.
#
# This program 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 MER-
# CHANTABILITY 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
#
# -----------------------------------------------------------------------------

__all__ = [ 'Callback', 'Function', 'CallbackObject' ]

# import pyNotifier
import notifier

class Callback(object):
    """
    Wrapper for functions calls with arguments inside the notifier. The
    function passed to this objects get the parameter passed to this object
    and after that the args and kwargs defined in the init function.
    """
    def __init__(self, function, *args, **kwargs):
        self.function = function
        self.args = args
        self.kwargs = kwargs


    def __call__(self, *args):
        """
        Call the callback function.
        """
        return self.function(*(list(args) + list(self.args)), **self.kwargs)


class Function(Callback):
    """
    Wrapper for functions calls with arguments inside the notifier. The
    function passed to this objects get the only args and kwargs defined in
    the init function, parameter passed to the object on call are dropped.
    """

    def __call__(self, *args, **kwargs):
        """
        Call the callback function.
        """
        return self.function(*self.args, **self.kwargs)


class CallbackObject(Callback):
    """
    Object to wrap notifier function calls with a remove function to remove
    the timer / socket later. Do not create an object like this outside the
    kaa.notifier source.
    """

    def register(self, type, *args):
        """
        Register the callback. Do not use this function directly.
        """
        self.type = type
        self.id = getattr(notifier, 'add' + type)(*(list(args) + [self]))


    def remove(self):
        """
        Remove the callback from the notifier.
        """
        if not hasattr(self, 'id'):
            raise AttributeError('Callback not registered')
        getattr(notifier, 'remove' + self.type)(self.id)



-------------------------------------------------------
SF.Net email is sponsored by: Discover Easy Linux Migration Strategies
from IBM. Find simple to follow Roadmaps, straightforward articles,
informative Webcasts and more! Get everything you need to get up to
speed, fast. http://ads.osdn.com/?ad_id=7477&alloc_id=16492&op=click
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to