Package buildbot :: Package clients :: Module gtkPanes
[frames] | no frames]

Source Code for Module buildbot.clients.gtkPanes

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16   
 17  from twisted.internet import gtk2reactor 
 18  gtk2reactor.install() #@UndefinedVariable 
 19   
 20  import sys, time 
 21   
 22  import pygtk #@UnresolvedImport 
 23  pygtk.require("2.0") 
 24  import gobject, gtk #@UnresolvedImport 
 25  assert(gtk.Window) # in gtk1 it's gtk.GtkWindow 
 26   
 27  from twisted.spread import pb 
 28   
 29  #from buildbot.clients.base import Builder, Client 
 30  from buildbot.clients.base import StatusClient 
 31  from buildbot.clients.text import TextClient 
 32  from buildbot.util import now 
 33   
 34  from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, EXCEPTION 
 35   
 36  ''' 
 37  class Pane: 
 38      def __init__(self): 
 39          pass 
 40   
 41  class OneRow(Pane): 
 42      """This is a one-row status bar. It has one square per Builder, and that 
 43      square is either red, yellow, or green. """ 
 44   
 45      def __init__(self): 
 46          Pane.__init__(self) 
 47          self.widget = gtk.VBox(gtk.FALSE, 2) 
 48          self.nameBox = gtk.HBox(gtk.TRUE) 
 49          self.statusBox = gtk.HBox(gtk.TRUE) 
 50          self.widget.add(self.nameBox) 
 51          self.widget.add(self.statusBox) 
 52          self.widget.show_all() 
 53          self.builders = [] 
 54           
 55      def getWidget(self): 
 56          return self.widget 
 57      def addBuilder(self, builder): 
 58          print "OneRow.addBuilder" 
 59          # todo: ordering. Should follow the order in which they were added 
 60          # to the original BotMaster 
 61          self.builders.append(builder) 
 62          # add the name to the left column, and a label (with background) to 
 63          # the right 
 64          name = gtk.Label(builder.name) 
 65          status = gtk.Label('??') 
 66          status.set_size_request(64,64) 
 67          box = gtk.EventBox() 
 68          box.add(status) 
 69          name.show() 
 70          box.show_all() 
 71          self.nameBox.add(name) 
 72          self.statusBox.add(box) 
 73          builder.haveSomeWidgets([name, status, box]) 
 74       
 75  class R2Builder(Builder): 
 76      def start(self): 
 77          self.nameSquare.set_text(self.name) 
 78          self.statusSquare.set_text("???") 
 79          self.subscribe() 
 80      def haveSomeWidgets(self, widgets): 
 81          self.nameSquare, self.statusSquare, self.statusBox = widgets 
 82   
 83      def remote_newLastBuildStatus(self, event): 
 84          color = None 
 85          if event: 
 86              text = "\n".join(event.text) 
 87              color = event.color 
 88          else: 
 89              text = "none" 
 90          self.statusSquare.set_text(text) 
 91          if color: 
 92              print "color", color 
 93              self.statusBox.modify_bg(gtk.STATE_NORMAL, 
 94                                       gtk.gdk.color_parse(color)) 
 95   
 96      def remote_currentlyOffline(self): 
 97          self.statusSquare.set_text("offline") 
 98      def remote_currentlyIdle(self): 
 99          self.statusSquare.set_text("idle") 
100      def remote_currentlyWaiting(self, seconds): 
101          self.statusSquare.set_text("waiting") 
102      def remote_currentlyInterlocked(self): 
103          self.statusSquare.set_text("interlocked") 
104      def remote_currentlyBuilding(self, eta): 
105          self.statusSquare.set_text("building") 
106   
107   
108  class CompactRow(Pane): 
109      def __init__(self): 
110          Pane.__init__(self) 
111          self.widget = gtk.VBox(gtk.FALSE, 3) 
112          self.nameBox = gtk.HBox(gtk.TRUE, 2) 
113          self.lastBuildBox = gtk.HBox(gtk.TRUE, 2) 
114          self.statusBox = gtk.HBox(gtk.TRUE, 2) 
115          self.widget.add(self.nameBox) 
116          self.widget.add(self.lastBuildBox) 
117          self.widget.add(self.statusBox) 
118          self.widget.show_all() 
119          self.builders = [] 
120           
121      def getWidget(self): 
122          return self.widget 
123           
124      def addBuilder(self, builder): 
125          self.builders.append(builder) 
126   
127          name = gtk.Label(builder.name) 
128          name.show() 
129          self.nameBox.add(name) 
130   
131          last = gtk.Label('??') 
132          last.set_size_request(64,64) 
133          lastbox = gtk.EventBox() 
134          lastbox.add(last) 
135          lastbox.show_all() 
136          self.lastBuildBox.add(lastbox) 
137   
138          status = gtk.Label('??') 
139          status.set_size_request(64,64) 
140          statusbox = gtk.EventBox() 
141          statusbox.add(status) 
142          statusbox.show_all() 
143          self.statusBox.add(statusbox) 
144   
145          builder.haveSomeWidgets([name, last, lastbox, status, statusbox]) 
146   
147      def removeBuilder(self, name, builder): 
148          self.nameBox.remove(builder.nameSquare) 
149          self.lastBuildBox.remove(builder.lastBuildBox) 
150          self.statusBox.remove(builder.statusBox) 
151          self.builders.remove(builder) 
152       
153  class CompactBuilder(Builder): 
154      def setup(self): 
155          self.timer = None 
156          self.text = [] 
157          self.eta = None 
158      def start(self): 
159          self.nameSquare.set_text(self.name) 
160          self.statusSquare.set_text("???") 
161          self.subscribe() 
162      def haveSomeWidgets(self, widgets): 
163          (self.nameSquare, 
164           self.lastBuildSquare, self.lastBuildBox, 
165           self.statusSquare, self.statusBox) = widgets 
166           
167      def remote_currentlyOffline(self): 
168          self.eta = None 
169          self.stopTimer() 
170          self.statusSquare.set_text("offline") 
171          self.statusBox.modify_bg(gtk.STATE_NORMAL, 
172                                   gtk.gdk.color_parse("red")) 
173      def remote_currentlyIdle(self): 
174          self.eta = None 
175          self.stopTimer() 
176          self.statusSquare.set_text("idle") 
177      def remote_currentlyWaiting(self, seconds): 
178          self.nextBuild = now() + seconds 
179          self.startTimer(self.updateWaiting) 
180      def remote_currentlyInterlocked(self): 
181          self.stopTimer() 
182          self.statusSquare.set_text("interlocked") 
183      def startTimer(self, func): 
184          # the func must clear self.timer and return gtk.FALSE when the event 
185          # has arrived 
186          self.stopTimer() 
187          self.timer = gtk.timeout_add(1000, func) 
188          func() 
189      def stopTimer(self): 
190          if self.timer: 
191              gtk.timeout_remove(self.timer) 
192              self.timer = None 
193      def updateWaiting(self): 
194          when = self.nextBuild 
195          if now() < when: 
196              next = time.strftime("%H:%M:%S", time.localtime(when)) 
197              secs = "[%d seconds]" % (when - now()) 
198              self.statusSquare.set_text("waiting\n%s\n%s" % (next, secs)) 
199              return gtk.TRUE # restart timer 
200          else: 
201              # done 
202              self.statusSquare.set_text("waiting\n[RSN]") 
203              self.timer = None 
204              return gtk.FALSE 
205   
206      def remote_currentlyBuilding(self, eta): 
207          self.stopTimer() 
208          self.statusSquare.set_text("building") 
209          if eta: 
210              d = eta.callRemote("subscribe", self, 5) 
211   
212      def remote_newLastBuildStatus(self, event): 
213          color = None 
214          if event: 
215              text = "\n".join(event.text) 
216              color = event.color 
217          else: 
218              text = "none" 
219          if not color: color = "gray" 
220          self.lastBuildSquare.set_text(text) 
221          self.lastBuildBox.modify_bg(gtk.STATE_NORMAL, 
222                                      gtk.gdk.color_parse(color)) 
223   
224      def remote_newEvent(self, event): 
225          assert(event.__class__ == GtkUpdatingEvent) 
226          self.current = event 
227          event.builder = self 
228          self.text = event.text 
229          if not self.text: self.text = ["idle"] 
230          self.eta = None 
231          self.stopTimer() 
232          self.updateText() 
233          color = event.color 
234          if not color: color = "gray" 
235          self.statusBox.modify_bg(gtk.STATE_NORMAL, 
236                                   gtk.gdk.color_parse(color)) 
237   
238      def updateCurrent(self): 
239          text = self.current.text 
240          if text: 
241              self.text = text 
242              self.updateText() 
243          color = self.current.color 
244          if color: 
245              self.statusBox.modify_bg(gtk.STATE_NORMAL, 
246                                       gtk.gdk.color_parse(color)) 
247      def updateText(self): 
248          etatext = [] 
249          if self.eta: 
250              etatext = [time.strftime("%H:%M:%S", time.localtime(self.eta))] 
251              if now() > self.eta: 
252                  etatext += ["RSN"] 
253              else: 
254                  seconds = self.eta - now() 
255                  etatext += ["[%d secs]" % seconds] 
256          text = "\n".join(self.text + etatext) 
257          self.statusSquare.set_text(text) 
258      def updateTextTimer(self): 
259          self.updateText() 
260          return gtk.TRUE # restart timer 
261       
262      def remote_progress(self, seconds): 
263          if seconds == None: 
264              self.eta = None 
265          else: 
266              self.eta = now() + seconds 
267          self.startTimer(self.updateTextTimer) 
268          self.updateText() 
269      def remote_finished(self, eta): 
270          self.eta = None 
271          self.stopTimer() 
272          self.updateText() 
273          eta.callRemote("unsubscribe", self) 
274  ''' 
275   
276 -class Box:
277 - def __init__(self, text="?"):
278 self.text = text 279 self.box = gtk.EventBox() 280 self.label = gtk.Label(text) 281 self.box.add(self.label) 282 self.box.set_size_request(64,64) 283 self.timer = None
284
285 - def getBox(self):
286 return self.box
287
288 - def setText(self, text):
289 self.text = text 290 self.label.set_text(text)
291
292 - def setColor(self, color):
293 if not color: 294 return 295 self.box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
296
297 - def setETA(self, eta):
298 if eta: 299 self.when = now() + eta 300 self.startTimer() 301 else: 302 self.stopTimer()
303
304 - def startTimer(self):
305 self.stopTimer() 306 self.timer = gobject.timeout_add(1000, self.update) 307 self.update()
308
309 - def stopTimer(self):
310 if self.timer: 311 gobject.source_remove(self.timer) 312 self.timer = None 313 self.label.set_text(self.text)
314
315 - def update(self):
316 if now() < self.when: 317 next = time.strftime("%H:%M:%S", time.localtime(self.when)) 318 secs = "[%d secs]" % (self.when - now()) 319 self.label.set_text("%s\n%s\n%s" % (self.text, next, secs)) 320 return True # restart timer 321 else: 322 # done 323 self.label.set_text("%s\n[soon]\n[overdue]" % (self.text,)) 324 self.timer = None 325 return False
326 327 328
329 -class ThreeRowBuilder:
330 - def __init__(self, name, ref):
331 self.name = name 332 333 self.last = Box() 334 self.current = Box() 335 self.step = Box("idle") 336 self.step.setColor("white") 337 338 self.ref = ref
339
340 - def getBoxes(self):
341 return self.last.getBox(), self.current.getBox(), self.step.getBox()
342
343 - def getLastBuild(self):
344 d = self.ref.callRemote("getLastFinishedBuild") 345 d.addCallback(self.gotLastBuild)
346 - def gotLastBuild(self, build):
347 if build: 348 build.callRemote("getText").addCallback(self.gotLastText) 349 build.callRemote("getResults").addCallback(self.gotLastResult)
350
351 - def gotLastText(self, text):
352 print "Got text", text 353 self.last.setText("\n".join(text))
354
355 - def gotLastResult(self, result):
356 colormap = {SUCCESS: 'green', 357 FAILURE: 'red', 358 WARNINGS: 'orange', 359 EXCEPTION: 'purple', 360 } 361 self.last.setColor(colormap[result])
362
363 - def getState(self):
364 self.ref.callRemote("getState").addCallback(self.gotState)
365 - def gotState(self, res):
366 state, ETA, builds = res 367 # state is one of: offline, idle, waiting, interlocked, building 368 # TODO: ETA is going away, you have to look inside the builds to get 369 # that value 370 currentmap = {"offline": "red", 371 "idle": "white", 372 "waiting": "yellow", 373 "interlocked": "yellow", 374 "building": "yellow",} 375 text = state 376 self.current.setColor(currentmap[state]) 377 if ETA is not None: 378 text += "\nETA=%s secs" % ETA 379 self.current.setText(state)
380
381 - def buildStarted(self, build):
382 print "[%s] buildStarted" % (self.name,) 383 self.current.setColor("yellow")
384
385 - def buildFinished(self, build, results):
386 print "[%s] buildFinished: %s" % (self.name, results) 387 self.gotLastBuild(build) 388 self.current.setColor("white") 389 self.current.stopTimer()
390
391 - def buildETAUpdate(self, eta):
392 print "[%s] buildETAUpdate: %s" % (self.name, eta) 393 self.current.setETA(eta)
394 395
396 - def stepStarted(self, stepname, step):
397 print "[%s] stepStarted: %s" % (self.name, stepname) 398 self.step.setText(stepname) 399 self.step.setColor("yellow")
400 - def stepFinished(self, stepname, step, results):
401 print "[%s] stepFinished: %s %s" % (self.name, stepname, results) 402 self.step.setText("idle") 403 self.step.setColor("white") 404 self.step.stopTimer()
405 - def stepETAUpdate(self, stepname, eta):
406 print "[%s] stepETAUpdate: %s %s" % (self.name, stepname, eta) 407 self.step.setETA(eta)
408 409
410 -class ThreeRowClient(pb.Referenceable):
411 - def __init__(self, window):
412 self.window = window 413 self.buildernames = [] 414 self.builders = {}
415
416 - def connected(self, ref):
417 print "connected" 418 self.ref = ref 419 self.pane = gtk.VBox(False, 2) 420 self.table = gtk.Table(1+3, 1) 421 self.pane.add(self.table) 422 self.window.vb.add(self.pane) 423 self.pane.show_all() 424 ref.callRemote("subscribe", "logs", 5, self)
425
426 - def removeTable(self):
427 for child in self.table.get_children(): 428 self.table.remove(child) 429 self.pane.remove(self.table)
430
431 - def makeTable(self):
432 columns = len(self.builders) 433 self.table = gtk.Table(2, columns) 434 self.pane.add(self.table) 435 for i in range(len(self.buildernames)): 436 name = self.buildernames[i] 437 b = self.builders[name] 438 last,current,step = b.getBoxes() 439 self.table.attach(gtk.Label(name), i, i+1, 0, 1) 440 self.table.attach(last, i, i+1, 1, 2, 441 xpadding=1, ypadding=1) 442 self.table.attach(current, i, i+1, 2, 3, 443 xpadding=1, ypadding=1) 444 self.table.attach(step, i, i+1, 3, 4, 445 xpadding=1, ypadding=1) 446 self.table.show_all()
447
448 - def rebuildTable(self):
449 self.removeTable() 450 self.makeTable()
451
452 - def remote_builderAdded(self, buildername, builder):
453 print "builderAdded", buildername 454 assert buildername not in self.buildernames 455 self.buildernames.append(buildername) 456 457 b = ThreeRowBuilder(buildername, builder) 458 self.builders[buildername] = b 459 self.rebuildTable() 460 b.getLastBuild() 461 b.getState()
462
463 - def remote_builderRemoved(self, buildername):
464 del self.builders[buildername] 465 self.buildernames.remove(buildername) 466 self.rebuildTable()
467
468 - def remote_builderChangedState(self, name, state, eta):
469 self.builders[name].gotState((state, eta, None))
470 - def remote_buildStarted(self, name, build):
471 self.builders[name].buildStarted(build)
472 - def remote_buildFinished(self, name, build, results):
473 self.builders[name].buildFinished(build, results)
474
475 - def remote_buildETAUpdate(self, name, build, eta):
476 self.builders[name].buildETAUpdate(eta)
477 - def remote_stepStarted(self, name, build, stepname, step):
478 self.builders[name].stepStarted(stepname, step)
479 - def remote_stepFinished(self, name, build, stepname, step, results):
480 self.builders[name].stepFinished(stepname, step, results)
481
482 - def remote_stepETAUpdate(self, name, build, stepname, step, 483 eta, expectations):
484 # expectations is a list of (metricname, current_value, 485 # expected_value) tuples, so that we could show individual progress 486 # meters for each metric 487 self.builders[name].stepETAUpdate(stepname, eta)
488
489 - def remote_logStarted(self, buildername, build, stepname, step, 490 logname, log):
491 pass
492
493 - def remote_logFinished(self, buildername, build, stepname, step, 494 logname, log):
495 pass
496 497
498 -class GtkClient(TextClient):
499 ClientClass = ThreeRowClient 500
501 - def __init__(self, master, events="steps", username="statusClient", passwd="clientpw"):
502 self.master = master 503 self.username = username 504 self.passwd = passwd 505 self.listener = StatusClient(events) 506 507 w = gtk.Window() 508 self.w = w 509 #w.set_size_request(64,64) 510 w.connect('destroy', lambda win: gtk.main_quit()) 511 self.vb = gtk.VBox(False, 2) 512 self.status = gtk.Label("unconnected") 513 self.vb.add(self.status) 514 self.listener = self.ClientClass(self) 515 w.add(self.vb) 516 w.show_all()
517
518 - def connected(self, ref):
519 self.status.set_text("connected") 520 TextClient.connected(self, ref)
521 522 """ 523 def addBuilder(self, name, builder): 524 Client.addBuilder(self, name, builder) 525 self.pane.addBuilder(builder) 526 def removeBuilder(self, name): 527 self.pane.removeBuilder(name, self.builders[name]) 528 Client.removeBuilder(self, name) 529 530 def startConnecting(self, master): 531 self.master = master 532 Client.startConnecting(self, master) 533 self.status.set_text("connecting to %s.." % master) 534 def connected(self, remote): 535 Client.connected(self, remote) 536 self.status.set_text(self.master) 537 remote.notifyOnDisconnect(self.disconnected) 538 def disconnected(self, remote): 539 self.status.set_text("disconnected, will retry") 540 """ 541
542 -def main():
543 master = "localhost:8007" 544 if len(sys.argv) > 1: 545 master = sys.argv[1] 546 c = GtkClient(master) 547 c.run()
548 549 if __name__ == '__main__': 550 main() 551