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, StatusClient 
 31  from buildbot.util import now 
 32   
 33  from buildbot.status.results 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, events="steps", username="statusClient", passwd="clientpw"):
501 self.master = master 502 self.username = username 503 self.passwd = passwd 504 self.listener = StatusClient(events) 505 506 w = gtk.Window() 507 self.w = w 508 #w.set_size_request(64,64) 509 w.connect('destroy', lambda win: gtk.main_quit()) 510 self.vb = gtk.VBox(False, 2) 511 self.status = gtk.Label("unconnected") 512 self.vb.add(self.status) 513 self.listener = self.ClientClass(self) 514 w.add(self.vb) 515 w.show_all()
516
517 - def connected(self, ref):
518 self.status.set_text("connected") 519 TextClient.connected(self, ref)
520 521 """ 522 def addBuilder(self, name, builder): 523 Client.addBuilder(self, name, builder) 524 self.pane.addBuilder(builder) 525 def removeBuilder(self, name): 526 self.pane.removeBuilder(name, self.builders[name]) 527 Client.removeBuilder(self, name) 528 529 def startConnecting(self, master): 530 self.master = master 531 Client.startConnecting(self, master) 532 self.status.set_text("connecting to %s.." % master) 533 def connected(self, remote): 534 Client.connected(self, remote) 535 self.status.set_text(self.master) 536 remote.notifyOnDisconnect(self.disconnected) 537 def disconnected(self, remote): 538 self.status.set_text("disconnected, will retry") 539 """ 540
541 -def main():
542 master = "localhost:8007" 543 if len(sys.argv) > 1: 544 master = sys.argv[1] 545 c = GtkClient(master) 546 c.run()
547 548 if __name__ == '__main__': 549 main() 550