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