Update of /cvsroot/freevo/freevo/src/util
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv28417/util

Added Files:
        popen.py 
Log Message:
move basic process handling from childapp to util.popen

--- NEW FILE: popen.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# popen.py - process control using pyNotifier
# -----------------------------------------------------------------------------
# $Id: popen.py,v 1.1 2004/12/18 13:27:54 dischi Exp $
#
#
# -----------------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002-2004 Krister Lagerstrom, Dirk Meyer, et al.
#
# First Edition: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file freevo/Docs/CREDITS 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 copy
import popen2
import glob
import re
import logging

# notifier
import notifier

log = logging.getLogger('popen')

# the global watcher object
watcher = None


def killall():
    """
    killall running processes
    """
    watcher.killall()


class Process:
    """
    Base class for started child processes
    """
    def __init__( self, app, debugname = 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 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
            
            log.info('running %s' % self.binary)
        
        self.__kill_timer = None
        self.stopping = False
        self.dead = False
        self.child = popen2.Popen3( start_str, True, 100 )
        # 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 )
        watcher.add( 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 isAlive( self ):
        """
        Return True if the app is still running
        """
        return not self.dead
        

    def stop( self, cmd = '' ):
        """
        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 self.stopping:
            return

        self.stopping = True

        if self.isAlive() 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 not self.dead:
                notifier.step()


    def _kill( self, signal ):
        """
        Internal kill helper function
        """
        if not self.isAlive():
            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.isAlive():
            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 = vfs.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, proc ):
        """
        Callback from watcher when the child died.
        """
        self.dead = True
        # cleanup IO handler and kill timer
        self.stdout.cleanup()
        self.stderr.cleanup()
        if self.__kill_timer:
            notifier.removeTimer( self.__kill_timer )
        self.finished()

        
    def finished(self):
        """
        Override this method to handle cleanup / notifications after the child
        is dead.
        """
        pass

        
    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:
    """
    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 ):
        notifier.removeSocket( self.fp )
        self.fp.close()
        if self.logger:
            self.logger.close()
        

    def cleanup( self ):
        notifier.removeSocket( self.fp )


    def _handle_input( self, socket ):
        data = self.fp.read( 10000 )
        if not data:
            log.debug( '%s: No data, stopping (pid %s)!' % \
                       ( self.name, os.getpid() ) )
            notifier.removeSocket( self.fp )
            self.fp.close()
            if self.logger:
                self.logger.close()
        else:
            data  = data.replace('\r', '\n')
            lines = data.split('\n')

            # Only one partial line?
            if len(lines) == 1:
                self.saved += data
            else:
                # 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 self.logger:
                            self.logger.write( line + '\n' )
                        self.callback( line )
                else:
                    # Send all lines to the app
                    for line in lines[ 1 : ]:
                        if self.logger:
                            self.logger.write( line + '\n' )
                        self.callback( line )
        return True
    

class _Watcher:
    def __init__( self ):
        log.info('new process watcher instance')
        self.__processes = {}


    def add( self, proc, cb ):
        self.__processes[ proc ] = cb

    def remove( self, proc ):
        if self.__processes.has_key():
            del self.__processes[ proc ]
    
    def step( self ):
        remove_proc = []
        for p in copy.copy( 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 ):
                self.__processes[ p ]( p )
                remove_proc.append( p )

        # remove dead processes
        for p in remove_proc: del self.__processes[ p ]

    def killall( self ):
        for p in copy.copy(self.__processes):
            p.stop()


# init global watcher object
watcher = _Watcher()

# add checking for dead children to the notifier
notifier.addDispatcher( watcher.step )



-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now. 
http://productguide.itmanagersjournal.com/
_______________________________________________
Freevo-cvslog mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to