1  import time 
  2  from xml.dom import minidom 
  3   
  4  from twisted.python import log, failure 
  5  from twisted.internet import reactor 
  6  from twisted.internet.task import LoopingCall 
  7  from twisted.web.client import getPage 
  8   
  9  from buildbot.changes import base, changes 
 10   
 12 -    def __init__(self, value="InvalidResultError"): 
  15          return repr(self.value) 
   16   
 19   
 22   
 25   
 27      """I hold a list of CiNodes""" 
 30   
 32          if len(self.nodes) != len(other.nodes): 
 33              return False 
 34          for i in range(len(self.nodes)): 
 35              if self.nodes[i].log != other.nodes[i].log \ 
 36                or self.nodes[i].who != other.nodes[i].who \ 
 37                or self.nodes[i].date != other.nodes[i].date \ 
 38                or len(self.nodes[i].files) != len(other.nodes[i].files): 
 39                  return -1 
 40   
 41                  for j in range(len(self.nodes[i].files)): 
 42                      if self.nodes[i].files[j].revision \ 
 43                        != other.nodes[i].files[j].revision \ 
 44                        or self.nodes[i].files[j].filename \ 
 45                        != other.nodes[i].files[j].filename: 
 46                          return -1 
 47   
 48          return 0 
   49   
 51      """I hold information baout one <ci> node, including a list of files""" 
 52 -    def __init__(self, log="", who="", date=0, files=[]): 
  53          self.log = log 
 54          self.who = who 
 55          self.date = date 
 56          self.files = files 
   57   
 59      """I hold information about one <f> node""" 
 60 -    def __init__(self, revision="", filename=""): 
   63   
 65      """I parse the XML result from a bonsai cvsquery.""" 
 66   
 68          try: 
 69           
 70           
 71           
 72           
 73              data = data.decode("latin1") 
 74              data = data.encode("ascii", "replace") 
 75              self.dom = minidom.parseString(data) 
 76              log.msg(data) 
 77          except: 
 78              raise InvalidResultError("Malformed XML in result") 
 79   
 80          self.ciNodes = self.dom.getElementsByTagName("ci") 
 81          self.currentCiNode = None  
 82          self.fileNodes = None  
 83          self.currentFileNode = None  
 84          self.bonsaiResult = self._parseData() 
  85   
 87          return self.bonsaiResult 
  88   
 90          """Returns data from a Bonsai cvsquery in a BonsaiResult object""" 
 91          nodes = [] 
 92          try: 
 93              while self._nextCiNode(): 
 94                  files = [] 
 95                  try: 
 96                      while self._nextFileNode(): 
 97                          files.append(FileNode(self._getRevision(), 
 98                                                self._getFilename())) 
 99                  except NoMoreFileNodes: 
100                      pass 
101                  except InvalidResultError: 
102                      raise 
103                  cinode = CiNode(self._getLog(), self._getWho(), 
104                                  self._getDate(), files) 
105                   
106                  if not cinode.log and nodes and \ 
107                          not nodes[-1].log and \ 
108                          cinode.who == nodes[-1].who and \ 
109                          cinode.date == nodes[-1].date: 
110                      nodes[-1].files += cinode.files 
111                  else: 
112                      nodes.append(cinode) 
113   
114          except NoMoreCiNodes: 
115              pass 
116          except InvalidResultError, EmptyResult: 
117              raise 
118   
119          return BonsaiResult(nodes) 
 120   
121   
123          """Iterates to the next <ci> node and fills self.fileNodes with 
124             child <f> nodes""" 
125          try: 
126              self.currentCiNode = self.ciNodes.pop(0) 
127              if len(self.currentCiNode.getElementsByTagName("files")) > 1: 
128                  raise InvalidResultError("Multiple <files> for one <ci>") 
129   
130              self.fileNodes = self.currentCiNode.getElementsByTagName("f") 
131          except IndexError: 
132               
133              if not self.currentCiNode: 
134                  raise EmptyResult 
135              else: 
136                  raise NoMoreCiNodes 
137   
138          return True 
 139   
141          """Iterates to the next <f> node""" 
142          try: 
143              self.currentFileNode = self.fileNodes.pop(0) 
144          except IndexError: 
145              raise NoMoreFileNodes 
146   
147          return True 
 148   
150          """Returns the log of the current <ci> node""" 
151          logs = self.currentCiNode.getElementsByTagName("log") 
152          if len(logs) < 1: 
153              raise InvalidResultError("No log present") 
154          elif len(logs) > 1: 
155              raise InvalidResultError("Multiple logs present") 
156   
157           
158          if logs[0].firstChild: 
159              return logs[0].firstChild.data 
160          return '' 
 161   
163          """Returns the e-mail address of the commiter""" 
164           
165          return str(self.currentCiNode.getAttribute("who")) 
 166   
168          """Returns the date (unix time) of the commit""" 
169           
170          try: 
171              commitDate = int(self.currentCiNode.getAttribute("date")) 
172          except ValueError: 
173              raise InvalidResultError 
174   
175          return commitDate 
 176   
178          """Returns the filename of the current <f> node""" 
179          try: 
180              filename = self.currentFileNode.firstChild.data 
181          except AttributeError: 
182              raise InvalidResultError("Missing filename") 
183   
184          return filename 
 185   
187          return self.currentFileNode.getAttribute("rev") 
  188   
189   
191      """This source will poll a bonsai server for changes and submit 
192      them to the change master.""" 
193   
194      compare_attrs = ["bonsaiURL", "pollInterval", "tree", 
195                       "module", "branch", "cvsroot"] 
196   
197      parent = None  
198      loop = None 
199      volatile = ['loop'] 
200      working = False 
201   
202 -    def __init__(self, bonsaiURL, module, branch, tree="default", 
203                   cvsroot="/cvsroot", pollInterval=30): 
 204          """ 
205          @type   bonsaiURL:      string 
206          @param  bonsaiURL:      The base URL of the Bonsai server 
207                                  (ie. http://bonsai.mozilla.org) 
208          @type   module:         string 
209          @param  module:         The module to look for changes in. Commonly 
210                                  this is 'all' 
211          @type   branch:         string 
212          @param  branch:         The branch to look for changes in. This must 
213                                  match the 
214                                  'branch' option for the Scheduler. 
215          @type   tree:           string 
216          @param  tree:           The tree to look for changes in. Commonly this 
217                                  is 'all' 
218          @type   cvsroot:        string 
219          @param  cvsroot:        The cvsroot of the repository. Usually this is 
220                                  '/cvsroot' 
221          @type   pollInterval:   int 
222          @param  pollInterval:   The time (in seconds) between queries for 
223                                  changes 
224          """ 
225   
226          self.bonsaiURL = bonsaiURL 
227          self.module = module 
228          self.branch = branch 
229          self.tree = tree 
230          self.cvsroot = cvsroot 
231          self.pollInterval = pollInterval 
232          self.lastChange = time.time() 
233          self.lastPoll = time.time() 
 234   
240   
244   
246          str = "" 
247          str += "Getting changes from the Bonsai service running at %s " \ 
248                  % self.bonsaiURL 
249          str += "<br>Using tree: %s, branch: %s, and module: %s" % (self.tree, \ 
250                  self.branch, self.module) 
251          return str 
 252   
254          if self.working: 
255              log.msg("Not polling Bonsai because last poll is still working") 
256          else: 
257              self.working = True 
258              d = self._get_changes() 
259              d.addCallback(self._process_changes) 
260              d.addCallbacks(self._finished_ok, self._finished_failure) 
261          return 
 262   
264          assert self.working 
265          self.working = False 
266   
267           
268           
269          if isinstance(res, failure.Failure): 
270              log.msg("Bonsai poll failed: %s" % res) 
271          return res 
 272   
274          log.msg("Bonsai poll failed: %s" % res) 
275          assert self.working 
276          self.working = False 
277          return None  
 278   
280          args = ["treeid=%s" % self.tree, "module=%s" % self.module, 
281                  "branch=%s" % self.branch, "branchtype=match", 
282                  "sortby=Date", "date=explicit", 
283                  "mindate=%d" % self.lastChange, 
284                  "maxdate=%d" % int(time.time()), 
285                  "cvsroot=%s" % self.cvsroot, "xml=1"] 
286           
287          url = self.bonsaiURL 
288          url += "/cvsquery.cgi?" 
289          url += "&".join(args) 
290   
291          return url 
 292   
294          url = self._make_url() 
295          log.msg("Polling Bonsai tree at %s" % url) 
296   
297          self.lastPoll = time.time() 
298           
299          return getPage(url, timeout=self.pollInterval) 
 300   
 321