# utils.py vi:ts=4:sw=4:expandtab:
#
# Copyright (c) 2006 Three Rings Design, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright owner nor the names of contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from twisted.internet import reactor, defer
import shutil, os, stat

class ExecutionFailureContext(object):
    def __init__(self, context, failure):
        self.executionContext = context
        self.originalFailure = failure 

class ExecutionUnit(object):
    """
    An execution unit to be passed to an OrderedExecutor
    """
    def __init__(self, context, callable, *args, **kwargs):
        """
        Initialize an ExecutionUnit
        @param context: User-specified context
        @param callable: Callable function or method
        @param *args: Arguments to callable
        @param **kwargs: Keyword arguments to callable.
        """
        self.context = context
        self.callable = (callable, args, kwargs)

class OrderedExecutor(object):
    """
    Serialized execution of a list of deferred-returning callables
    according to the order in which they are added.
    """
    def __init__(self):
        self.eunits = []

    def appendExecutionUnit(self, eunit):
        """
        Append an ExecutionUnit to the end of the ordered list
        """
        self.eunits.append(eunit)

    def _nextWorker(self):
        """
        Callable generator
        """
        for eunit in self.eunits:
            yield eunit

    def _handleFailure(self, failure, context, d):
        d.errback(ExecutionFailureContext(context, failure))

    def run(self):
        """
        Run all callables serially. Stop if an error occurs.
        @result A deferred that while fire when all callables have been run,
        or an error has occured.
        """
        d = defer.Deferred()
        work = iter(self._nextWorker())

        # In our nested callback, iterate over callables returned by our
        # generator
        def cb(result):
            try:
                eunit = work.next()
                new = eunit.callable[0](*eunit.callable[1], **eunit.callable[2])
            except StopIteration:
                # Iteration complete
                d.callback(None)
            else:
                # Add our nested callback to the new deferred
                new.addCallback(cb)
                new.addErrback(self._handleFailure, eunit.context, d)

        # Kick-off the looping deferred calls
        try:
            cb(None)
        except Exception, e:
            d.errback(e)

        return d

class Error(EnvironmentError):
    pass

def copyRecursive(src, dst, symlinks=False):
    """
    Recursively copy a directory tree using preserving ownership.

    Code adapted from the python shutil.copytree implementation.
    """
    names = os.listdir(src)
    os.makedirs(dst)
    errors = []
    for name in names:
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        try:
            if symlinks and os.path.islink(srcname):
                linkto = os.readlink(srcname)
                os.symlink(linkto, dstname)
            elif os.path.isdir(srcname):
                copyRecursive(srcname, dstname, symlinks)
            else:
                copyWithOwnership(srcname, dstname)
        except (IOError, os.error), why:
            errors.append((srcname, dstname, why))
        except Error, err:
            errors.extend(err.args[0])
    shutil.copystat(src, dst)
    _copyOwnership(src, dst)
    if errors:
        raise Error, errors

def copyWithOwnership(src, dst):
    """
    Teach copy2 to preserve ownership
    """
    shutil.copy2(src, dst)
    _copyOwnership(src, dst)

def _copyOwnership(src, dst):
    """
    Copy uid and gid. Code adapted from the python 
    shutil.copystat implementation. 
    """
    st = os.stat(src)
    os.chown(dst, st.st_uid, st.st_gid)


syntax highlighted by Code2HTML, v. 0.9.1