[Buildbot-commits] buildbot/buildbot/changes bonsaipoller.py, NONE, 1.1

Brian Warner warner at users.sourceforge.net
Mon Sep 25 07:21:04 UTC 2006


Update of /cvsroot/buildbot/buildbot/buildbot/changes
In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv7627/buildbot/changes

Added Files:
	bonsaipoller.py 
Log Message:
[project @ add Ben Hearsum's BonsaiPoller changesource]

Original author: warner at lothar.com
Date: 2006-09-25 07:20:26

--- NEW FILE: bonsaipoller.py ---

import time
from urllib import urlopen
from xml.dom import minidom, Node

from twisted.python import log, failure
from twisted.internet import defer, reactor
from twisted.internet.task import LoopingCall

from buildbot.changes import base, changes


class BonsaiResult:
    """I hold a list of CiNodes"""
    def __init__(self):
        self.nodes = []

class CiNode:
    """I hold information about one Ci node, including a list of files"""
    def __init__(self):
        self.log = ""
        self.who = ""
        self.date = ""
        self.files = []

class FileNode:
    """I hold information about one file node"""
    def __init__(self):
        self.revision = ""
        self.filename = ""

class BonsaiParser:
    """I parse the XML result from a Bonsai cvsquery.
    Typical usage is as follows::

     bp = BonsaiParser(urlopen(bonsaiURL))
     data = bp.getData()
     for cinode in data.nodes:
         print cinode.who, cinode.log, cinode.date
         for file in cinode.files:
             print file.filename, file.revision
    """

    def __init__(self, bonsaiQuery):
        try:
            self.dom = minidom.parse(bonsaiQuery)
        except:
            self.dom = None
            return
        self.currentCiNode = None
        self.currentFileNode = None

    def getData(self):
        """I return data from a Bonsai cvsquery"""
        data = BonsaiResult()
        while self._nextCiNode():
            ci = CiNode()
            ci.log = self._getLog()
            ci.who = self._getWho()
            ci.date = self._getDate()
            while self._nextFileNode():
                fn = FileNode()
                fn.revision = self._getRevision()
                fn.filename = self._getFilename()
                ci.files.append(fn)

            data.nodes.append(ci)

        return data


    def _nextCiNode(self):
        try:
            # first <ci> node?
            if not self.currentCiNode:
                # every other sibling is a <ci>, so jump 2 ahead
                self.currentCiNode = self.dom.getElementsByTagName("ci")[0]
            else:
                self.currentCiNode = self.currentCiNode.nextSibling.nextSibling
        except (AttributeError,IndexError):
            self.currentCiNode = None

        if self.currentCiNode:
            return True
        else:
            return False

    def _getLog(self):
        log = ""
        for child in self.currentCiNode.childNodes:
            if child.nodeType == Node.ELEMENT_NODE and child.tagName == "log":
                log = child.firstChild.data
        return str(log)


    def _getWho(self):
        """Returns the e-mail address of the commit'er"""
        return str(self.currentCiNode.getAttribute("who").replace("%", "@"))

    def _getDate(self):
        """Returns the date (unix time) of the commit"""
        return int(self.currentCiNode.getAttribute("date"))


    def _firstFileNode(self):
        for child in self.currentCiNode.childNodes:
            if child.nodeType == Node.ELEMENT_NODE and child.tagName == "files":
                # child is now the <files> element
                for c in child.childNodes:
                    if c.nodeType == Node.ELEMENT_NODE and c.tagName == "f":
                        return c

    def _nextFileNode(self):
        # every other sibling is a <f>, so go two past the current one
        try:
            # first <f> node?
            if not self.currentFileNode:
                self.currentFileNode = self._firstFileNode()
            else:
                self.currentFileNode = self.currentFileNode.nextSibling.nextSibling
        except AttributeError:
            self.currentFileNode = None

        if self.currentFileNode:
            return True
        else:
            return False

    def _getFilename(self):
        return str(self.currentFileNode.firstChild.data)

    def _getRevision(self):
        return float(self.currentFileNode.getAttribute("rev"))



class BonsaiPoller(base.ChangeSource):
    """This source will poll a bonsai server for changes and submit
    them to the change master."""

    compare_attrs = ["bonsaiURL", "pollInterval", "tree",
                     "module", "branch", "cvsroot"]

    parent = None # filled in when we're added
    loop = None
    volatile = ['loop']
    working = False

    def __init__(self, bonsaiURL, module, branch, tree="default",
                 cvsroot="/cvsroot", pollInterval=30):
        """
        @type   bonsaiURL:      string
        @param  bonsaiURL:      The base URL of the Bonsai server
                                (ie. http://bonsai.mozilla.org)
        @type   module:         string
        @param  module:         The module to look for changes in. Commonly
                                this is 'all'
        @type   branch:         string
        @param  branch:         The branch to look for changes in. This must
                                match the
                                'branch' option for the Scheduler.
        @type   tree:           string
        @param  tree:           The tree to look for changes in. Commonly this
                                is 'all'
        @type   cvsroot:        string
        @param  cvsroot:        The cvsroot of the repository. Usually this is
                                '/cvsroot'
        @type   pollInterval:   int
        @param  pollInterval:   The time (in seconds) between queries for changes
        """

        self.bonsaiURL = bonsaiURL
        self.module = module
        self.branch = branch
        self.tree = tree
        self.cvsroot = cvsroot
        self.pollInterval = pollInterval
        self.lastChange = time.time()
        self.lastPoll = time.time()

    def startService(self):
        self.loop = LoopingCall(self.poll)
        base.ChangeSource.startService(self)

        reactor.callLater(0, self.loop.start, self.pollInterval)

    def stopService(self):
        self.loop.stop()
        return base.ChangeSource.stopService(self)

    def describe(self):
        str = ""
        str += "Getting changes from the Bonsai service running at %s " \
                % self.bonsaiURL
        str += "<br>Using tree: %s, branch: %s, and module: %s" % (self.tree, \
                self.branch, self.module)
        return str

    def poll(self):
        if self.working:
            log.msg("Not polling Bonsai because last poll is still working")
        else:
            self.working = True
            d = self._get_changes()
            d.addCallback(self._process_changes)
            d.addBoth(self._finished)
        return

    def _finished(self, res):
        assert self.working
        self.working = False

        # check for failure
        if isinstance(res, failure.Failure):
            log.msg("Bonsai poll failed: %s" % res)
        return res

    def _get_changes(self):
        args = ["treeid=%s" % self.tree, "module=%s" % self.module,
                "branch=%s" % self.branch, "branchtype=match",
                "sortby=Date", "date=explicit",
                "mindate=%d" % self.lastChange,
                "maxdate=%d" % int(time.time()),
                "cvsroot=%s" % self.cvsroot, "xml=1"]
        # build the bonsai URL
        url = self.bonsaiURL
        url += "/cvsquery.cgi?"
        url += "&".join(args)
        log.msg("Polling Bonsai tree at %s" % url)

        self.lastPoll = time.time()
        # get the page, in XML format
        return defer.maybeDeferred(urlopen, url)

    def _process_changes(self, query):
        bp = BonsaiParser(query)
        files = []
        data = bp.getData()
        for cinode in data.nodes:
            for file in cinode.files:
                files.append(file)
            c = changes.Change(who = cinode.who,
                               files = files,
                               comments = cinode.log,
                               when = cinode.date,
                               branch = self.branch)
            self.parent.addChange(c)
            self.lastChange = self.lastPoll






More information about the Commits mailing list