#
# CastPodder thread management code
# Copyright (c) 2005-2006 Scott Grayban and the CastPodder Team
#
# 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
# 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
#
# $Id: threads.py 96 2006-07-10 19:50:46Z sgrayban $

import threading
import logging
import hooks
import sys

COM = False
try: 
    import pythoncom
    COM = True
except ImportError: 
    pass

log = logging.getLogger('iPodder')

# TODO: add all the COM initialisation stuff

def mklogmethod(level): 
    """Make a log method for `SelfLogger`."""
    def logmethod(self, *a, **kw): 
        self.log(level, *a, **kw)
    logmethod.__doc__ = "Issue tagged level %d log record." % level
    return logmethod

class SelfLogger(object): 
    """Mixin class to give objects the ability to log via their own
    methods. It's useful for threads wanting to identify themselves 
    when they log, for example."""

    def __init__(self, log=None, tag=None):
        """Initialise the generic thread."""

        if log is None: 
            self.__log = logging.getLogger('iPodder')
        else: 
            self.__log = log
            
        classname = self.__class__.__name__
        if tag is None: 
            self.__tag = "%s %d" % (classname, id(self))
        else: 
            self.__tag = "%s %d %s" % (classname, id(self), repr(tag))

    def log(self, level, msg, *args, **kwargs): 
        """Issue a tagged log entry via our logger."""
        msg = "%s reports: " + msg
        args = list(args) # take a copy
        args.insert(0, self.__tag)
        self.__log.log(level, msg, *args, **kwargs)

    # Construct logging methods at various levels
    fatal = mklogmethod(logging.CRITICAL)
    critical = mklogmethod(logging.CRITICAL)
    error = mklogmethod(logging.ERROR)
    warning = mklogmethod(logging.WARNING)
    warn = warning
    info = mklogmethod(logging.INFO)
    debug = mklogmethod(logging.DEBUG)
    spam = mklogmethod(int(logging.DEBUG/2)) # heh

    def exception(self, msg, *args, **kwargs): 
        kwargs['exc_info'] = 1
        self.error(msg, *args, **kwargs)

class OurThread(threading.Thread, SelfLogger): 
    """Generic thread."""

    def __init__(self, log=None, target=None, *a, **kw): 
        """Initialise the generic thread."""

        self.hooks = hooks.HookCollection()
        self.exc_info = None
        self.target = target

        # if we have a name, steal it
        self.name = kw.get('name')

        # Initialise our parents
        SelfLogger.__init__(self, log=log, tag=self.name)
        threading.Thread.__init__(self, *a, **kw)

    def run(self): 
        """Run .our_run(), catching exceptions."""
        target = self.target
        if target is None: 
            target = self.our_run
        try: 
            if COM: 
                pythoncom.CoInitialize()
            try: 
                target()
            except: 
                self.exc_info = sys.exc_info() 
                self.exception("Uncaught exception.")
                raise # just in case threading.Thread catches it in some 
                      # standard way in a later version of Python
        finally: 
            if COM: 
                pythoncom.CoUninitialize()

    def catch(self): 
        """Check for exceptions and re-raise them in the calling thread."""
        if self.exc_info is not None: 
            one, two, three = self.exc_info
            raise one, two, three

if __name__ == '__main__': 
    logging.basicConfig()
    log.setLevel(logging.DEBUG)
    sl = SelfLogger(log=log, tag="soon to die")
    sl.fatal("I can see the fnords!")

    def boom(): 
        raise AssertionError, "KABOOM!"
    
    ot = OurThread(target=boom)
    ot.fatal("Boo?")
    ot.start()
    ot.join()
    ot.catch()
