Package flumotion :: Package admin :: Package gtk :: Module client
[hide private]

Source Code for Module flumotion.admin.gtk.client

   1  # -*- Mode: Python -*- 
   2  # -*- coding: UTF-8 -*- 
   3  # vi:si:et:sw=4:sts=4:ts=4 
   4  # 
   5  # Flumotion - a streaming media server 
   6  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
   7  # All rights reserved. 
   8   
   9  # This file may be distributed and/or modified under the terms of 
  10  # the GNU General Public License version 2 as published by 
  11  # the Free Software Foundation. 
  12  # This file is distributed without any warranty; without even the implied 
  13  # warranty of merchantability or fitness for a particular purpose. 
  14  # See "LICENSE.GPL" in the source distribution for more information. 
  15   
  16  # Licensees having purchased or holding a valid Flumotion Advanced 
  17  # Streaming Server license may use this file in accordance with the 
  18  # Flumotion Advanced Streaming Server Commercial License Agreement. 
  19  # See "LICENSE.Flumotion" in the source distribution for more information. 
  20   
  21  # Headers in this file shall remain intact. 
  22   
  23  import os 
  24  import os.path 
  25  import sys 
  26   
  27  from gettext import gettext as _ 
  28   
  29  import gobject 
  30  from gtk import gdk 
  31  import gtk 
  32  import gtk.glade 
  33   
  34  from twisted.internet import reactor, defer 
  35  from twisted.python import rebuild 
  36   
  37  from flumotion.admin.admin import AdminModel 
  38  from flumotion.admin import connections 
  39  from flumotion.admin.gtk import dialogs, parts, message 
  40  from flumotion.admin.gtk import connections as gtkconnections 
  41  from flumotion.configure import configure 
  42  from flumotion.common import errors, log, worker, planet, common, pygobject 
  43  from flumotion.manager import admin # Register types 
  44  from flumotion.twisted import flavors, reflect 
  45  from flumotion.twisted.compat import implements 
  46  from flumotion.ui import icons, trayicon 
  47   
  48  from flumotion.common.planet import moods 
  49  from flumotion.common.pygobject import gsignal 
  50   
  51  from flumotion.common import messages 
  52  from flumotion.common.messages import N_ 
  53  T_ = messages.gettexter('flumotion') 
  54   
55 -class Window(log.Loggable, gobject.GObject):
56 ''' 57 Creates the GtkWindow for the user interface. 58 Also connects to the manager on the given host and port. 59 ''' 60 61 implements(flavors.IStateListener) 62 63 logCategory = 'adminview' 64 gsignal('connected') 65
66 - def __init__(self, model):
67 self.__gobject_init__() 68 69 self.widgets = {} 70 self.debug('creating UI') 71 self._trayicon = None 72 73 # current component's UI; 74 # L{flumotion.component.base.admin_gtk.BaseAdminGtk} 75 self.current_component = None 76 self.current_component_state = None # its state 77 78 self._create_ui() 79 80 self._append_recent_connections() 81 82 self._disconnected_dialog = None # set to a dialog if we're 83 # disconnected 84 85 self._planetState = None 86 self._components = None # name -> planet.AdminComponentState 87 88 self.debug('setting model') 89 self.admin = None 90 self.wizard = None 91 self._setAdminModel(model)
92
93 - def _setAdminModel(self, model):
94 'set the model to which we are a view/controller' 95 # it's ok if we've already been connected 96 if self.admin: 97 self.debug('Connecting to new model %r' % model) 98 if self.wizard: 99 self.wizard.destroy() 100 101 self.admin = model 102 103 # window gets created after model connects initially, so check 104 # here 105 if self.admin.isConnected(): 106 self.admin_connected_cb(model) 107 108 self.admin.connect('connected', self.admin_connected_cb) 109 self.admin.connect('disconnected', self.admin_disconnected_cb) 110 self.admin.connect('connection-refused', 111 self.admin_connection_refused_cb) 112 self.admin.connect('connection-failed', 113 self.admin_connection_failed_cb) 114 self.admin.connect('component-property-changed', 115 self.property_changed_cb) 116 self.admin.connect('update', self.admin_update_cb)
117 118 # default Errback
119 - def _defaultErrback(self, failure):
120 self.warning('Errback: unhandled failure: %s' % 121 failure.getErrorMessage()) 122 return failure
123
124 - def _create_ui(self):
125 # returns the window 126 # called from __init__ 127 wtree = gtk.glade.XML(os.path.join(configure.gladedir, 'admin.glade')) 128 wtree.signal_autoconnect(self) 129 130 for widget in wtree.get_widget_prefix(''): 131 self.widgets[widget.get_name()] = widget 132 widgets = self.widgets 133 134 window = self.window = widgets['main_window'] 135 136 def set_icon(proc, size, name): 137 i = gtk.Image() 138 i.set_from_stock('flumotion-'+name, size) 139 proc(i) 140 i.show()
141 142 def make_menu_proc(m): # $%^& pychecker! 143 return lambda f: m.set_property('image', f)
144 def menu_set_icon(m, name): 145 set_icon(make_menu_proc(m), gtk.ICON_SIZE_MENU, name) 146 m.show() 147 148 def tool_set_icon(m, name): 149 set_icon(m.set_icon_widget, gtk.ICON_SIZE_SMALL_TOOLBAR, name) 150 151 menu_set_icon(widgets['menuitem_manage_run_wizard'], 'wizard') 152 tool_set_icon(widgets['toolbutton_wizard'], 'wizard') 153 menu_set_icon(widgets['menuitem_manage_start_component'], 'play') 154 tool_set_icon(widgets['toolbutton_start_component'], 'play') 155 menu_set_icon(widgets['menuitem_manage_stop_component'], 'pause') 156 tool_set_icon(widgets['toolbutton_stop_component'], 'pause') 157 158 self._trayicon = trayicon.FluTrayIcon(self) 159 self._trayicon.set_tooltip(_('Not connected')) 160 161 # the widget containing the component view 162 self._component_view = widgets['component_view'] 163 self._component_view_clear() 164 165 window.connect('delete-event', self.close) 166 167 self.components_view = parts.ComponentsView(widgets['components_view']) 168 self.components_view.connect('has-selection', 169 self._components_view_has_selection_cb) 170 self.components_view.connect('activated', 171 self._components_view_activated_cb) 172 self.statusbar = parts.AdminStatusbar(widgets['statusbar']) 173 self._set_stop_start_component_sensitive() 174 self.components_view.connect('notify::can-start-any', 175 self.start_stop_notify_cb) 176 self.components_view.connect('notify::can-stop-any', 177 self.start_stop_notify_cb) 178 self.start_stop_notify_cb() 179 180 self._messages_view = widgets['messages_view'] 181 self._messages_view.hide() 182 183 return window 184
185 - def on_open_connection(self, connectionInfo):
186 i = connectionInfo 187 model = AdminModel(i.authenticator) 188 d = model.connectToHost(i.host, i.port, not i.use_ssl) 189 self._trayicon.set_tooltip(_("Connecting to %s:%s") % 190 (i.host, i.port)) 191 192 def connected(model): 193 self.window.set_sensitive(True) 194 self._setAdminModel(model) 195 self._append_recent_connections()
196 197 def refused(failure): 198 if failure.check(errors.ConnectionRefusedError): 199 d = dialogs.connection_refused_message(i.host, 200 self.window) 201 else: 202 d = dialogs.connection_failed_message(i.host, 203 self.window) 204 d.addCallback(lambda _: self.window.set_sensitive(True)) 205 206 d.addCallbacks(connected, refused) 207 self.window.set_sensitive(False) 208
209 - def on_recent_activate(self, widget, connectionInfo):
210 self.on_open_connection(connectionInfo)
211
212 - def _append_recent_connections(self):
213 menu = self.widgets['connection_menu'].get_submenu() 214 215 # first clean any old entries 216 kids = menu.get_children() 217 while True: 218 w = kids.pop() 219 if w.get_name() == 'file_quit': 220 break 221 else: 222 menu.remove(w) 223 224 clist = connections.get_recent_connections() 225 if not clist: 226 return 227 228 def append(i): 229 i.show() 230 gtk.MenuShell.append(menu, i) # $%^&* pychecker
231 def append_txt(c, n): 232 i = gtk.MenuItem(c['name']) 233 i.connect('activate', self.on_recent_activate, c['info']) 234 append(i) 235 236 append(gtk.SeparatorMenuItem()) 237 map(append_txt, clist[:4], range(1,len(clist[:4])+1)) 238 239 # UI helper functions
240 - def show_error_dialog(self, message, parent=None, close_on_response=True):
241 if not parent: 242 parent = self.window 243 d = dialogs.ErrorDialog(message, parent, close_on_response) 244 d.show_all() 245 return d
246 247 # FIXME(wingo): use common.bundleclient 248 # FIXME: this method uses a file and a methodname as entries 249 # FIXME: do we want to switch to imports instead so the whole file 250 # is available in its namespace ? 251 # FIXME: factor this out into a ComponentView or SidePaneView class 252 # so we can reuse it
253 - def show_component(self, state, entryPath, fileName, methodName, data):
254 """ 255 Show the user interface for this component. 256 Searches data for the given methodName global, 257 then instantiates an object from that class, 258 and calls the render() method. 259 260 @type state: L{flumotion.common.planet.AdminComponentState} 261 @param entryPath: absolute path to the cached base directory 262 @param fileName: path to the file with the entry point, under 263 entryPath 264 @param methodName: name of the method to instantiate the 265 L{flumotion.component.base.admin_gtk.BaseAdminGtk} 266 UI view 267 @param data: the python code to load 268 """ 269 # methodName has historically been GUIClass 270 271 instance = None 272 273 name = state.get('name') 274 self.statusbar.set('main', _("Loading UI for %s ...") % name) 275 276 moduleName = common.pathToModuleName(fileName) 277 statement = 'import %s' % moduleName 278 self.debug('running %s' % statement) 279 try: 280 exec(statement) 281 except SyntaxError, e: 282 # the syntax error can happen in the entry file, or any import 283 where = getattr(e, 'filename', "<entry file>") 284 lineno = getattr(e, 'lineno', 0) 285 msg = "Syntax Error at %s:%d while executing %s" % ( 286 where, lineno, fileName) 287 self.warning(msg) 288 raise errors.EntrySyntaxError(msg) 289 except NameError, e: 290 msg = "NameError at while executing %s: %s" % ( 291 fileName, " ".join(e.args)) 292 raise 293 self.warning(msg) 294 raise errors.EntrySyntaxError(msg) 295 except ImportError, e: 296 msg = "ImportError while executing %s: %s" % (fileName, 297 " ".join(e.args)) 298 self.warning(msg) 299 raise errors.EntrySyntaxError(msg) 300 301 # make sure we're running the latest version 302 module = reflect.namedAny(moduleName) 303 rebuild.rebuild(module) 304 305 # check if we have the method 306 if not hasattr(module, methodName): 307 msg = 'method %s not found in file %s' % ( 308 methodName, fileName) 309 self.warning(msg) 310 311 m = messages.Error(T_( 312 N_("This component has a UI bug.")), 313 debug=msg, 314 id=methodName) 315 self._messages_view.add_message(m) 316 317 # FIXME: something more detailed as an error ? 318 raise errors.FlumotionError(msg) 319 klass = getattr(module, methodName) 320 321 # instantiate the GUIClass 322 instance = klass(state, self.admin) 323 self.debug("Created entry instance %r" % instance) 324 self._instanceSetup(instance, klass, name)
325
326 - def _instanceSetup(self, instance, klass, name):
327 self.debug('Setting up instance %r' % instance) 328 msg = None 329 d = None 330 try: 331 d = instance.setup() 332 except Exception, e: 333 msg = log.getExceptionMessage(e) 334 self.debug('Setup instance %r' % instance) 335 if not d: 336 msg = "%r.setup() should return a deferred" % klass 337 338 if msg: 339 self.warning('Component UI bug: %s' % msg) 340 m = messages.Error(T_( 341 N_("This component has a UI bug.")), 342 debug=msg, 343 id=name) 344 self._messages_view.add_message(m) 345 return 346 347 d.addCallback(self._setupCallback, name, instance) 348 d.addErrback(self._setupErrback, name)
349
350 - def _setupCallback(self, result, name, instance):
351 notebook = gtk.Notebook() 352 nodeWidgets = {} 353 nodes = instance.getNodes() 354 self.statusbar.clear('main') 355 # create pages for all nodes, and just show a loading label for 356 # now 357 for node in nodes.values(): 358 self.debug("Creating node for %s" % node.title) 359 label = gtk.Label(_('Loading UI for %s ...') % node.title) 360 table = gtk.Table(1, 1) 361 table.add(label) 362 nodeWidgets[node.title] = table 363 364 notebook.append_page(table, gtk.Label(node.title)) 365 366 # put "loading" widget in 367 self._component_view_set_widget(notebook) 368 369 # trigger node rendering 370 d = defer.Deferred() 371 372 for node in nodes.values(): 373 mid = self.statusbar.push('notebook', 374 _("Loading tab %s for %s ...") % (node.title, name)) 375 node.statusbar = self.statusbar # hack 376 self.debug('adding callback for %s node.render()' % node.title) 377 d.addCallback(lambda _, n: n.render(), node) 378 d.addCallback(self._nodeRenderCallback, node.title, 379 nodeWidgets, mid) 380 d.addErrback(self._nodeRenderErrback, node.title) 381 382 d.addCallback(self._setCurrentComponentCallback, instance) 383 384 d.callback(None) 385 return d
386
387 - def _setupErrback(self, failure, name):
388 self.warning('Could not setup component %s' % name) 389 msg = 'Could not setup component %s: %s' % (name, 390 log.getFailureMessage(failure)) 391 self.debug(msg) 392 m = messages.Error(T_( 393 N_("This component has a UI bug.")), 394 debug=msg, 395 id=name) 396 self._messages_view.add_message(m)
397 398 # called when one node gets rendered
399 - def _nodeRenderCallback(self, widget, nodeName, nodeWidgets, mid):
400 # used by show_component 401 self.debug("Got sub widget %r" % widget) 402 self.statusbar.remove('notebook', mid) 403 404 # clear out any old node widgets with the same name 405 table = nodeWidgets[nodeName] 406 for w in table.get_children(): 407 table.remove(w) 408 409 if not widget: 410 self.warning(".render() did not return an object") 411 widget = gtk.Label(_('%s does not have a UI yet') % nodeName) 412 else: 413 parent = widget.get_parent() 414 if parent: 415 parent.remove(widget) 416 417 table.add(widget) 418 widget.show()
419
420 - def _nodeRenderErrback(self, failure, nodeName):
421 self.warning('Could not render node %s' % nodeName) 422 debug = log.getFailureMessage(failure) 423 if failure.check(errors.NoBundleError): 424 debug = "Could not get bundle %s" % failure.value.args[0] 425 msg = 'Could not render node %s: %s' % (nodeName, debug) 426 self.debug(msg) 427 m = messages.Error(T_( 428 N_("This component has a UI bug in the %s tab."), nodeName), 429 debug=msg, 430 id=nodeName) 431 self._messages_view.add_message(m)
432
433 - def _setCurrentComponentCallback(self, _, instance):
434 self.debug('setting current_component to %r' % instance) 435 self.current_component = instance
436
437 - def componentCallRemoteStatus(self, state, pre, post, fail, 438 methodName, *args, **kwargs):
439 if not state: 440 state = self.components_view.get_selected_state() 441 if not state: 442 return 443 name = state.get('name') 444 if not name: 445 return 446 447 mid = None 448 if pre: 449 mid = self.statusbar.push('main', pre % name) 450 d = self.admin.componentCallRemote(state, methodName, *args, **kwargs) 451 452 def cb(result, self, mid): 453 if mid: 454 self.statusbar.remove('main', mid) 455 if post: 456 self.statusbar.push('main', post % name)
457 def eb(failure, self, mid): 458 if mid: 459 self.statusbar.remove('main', mid) 460 self.warning("Failed to execute %s on component %s: %s" 461 % (methodName, name, failure)) 462 if fail: 463 self.statusbar.push('main', fail % name) 464 465 d.addCallback(cb, self, mid) 466 d.addErrback(eb, self, mid) 467
468 - def componentCallRemote(self, state, methodName, *args, **kwargs):
469 self.componentCallRemoteStatus(None, None, None, None, 470 methodName, *args, **kwargs)
471
472 - def setPlanetState(self, planetState):
473 def flowStateAppend(state, key, value): 474 self.debug('flow state append: key %s, value %r' % (key, value)) 475 if key == 'components': 476 self._components[value.get('name')] = value 477 # FIXME: would be nicer to do this incrementally instead 478 self.update_components()
479 480 def flowStateRemove(state, key, value): 481 if key == 'components': 482 self._remove_component(value) 483 484 def atmosphereStateAppend(state, key, value): 485 if key == 'components': 486 self._components[value.get('name')] = value 487 # FIXME: would be nicer to do this incrementally instead 488 self.update_components() 489 490 def atmosphereStateRemove(state, key, value): 491 if key == 'components': 492 self._remove_component(value) 493 494 def planetStateAppend(state, key, value): 495 if key == 'flows': 496 if value != state.get('flows')[0]: 497 self.warning('flumotion-admin can only handle one ' 498 'flow, ignoring /%s', value.get('name')) 499 return 500 self.debug('%s flow started', value.get('name')) 501 value.addListener(self, append=flowStateAppend, 502 remove=flowStateRemove) 503 for c in value.get('components'): 504 flowStateAppend(value, 'components', c) 505 506 def planetStateRemove(state, key, value): 507 self.debug('something got removed from the planet') 508 509 self.debug('parsing planetState %r' % planetState) 510 self._planetState = planetState 511 512 # clear and rebuild list of components that interests us 513 self._components = {} 514 515 planetState.addListener(self, append=planetStateAppend, 516 remove=planetStateRemove) 517 518 a = planetState.get('atmosphere') 519 a.addListener(self, append=atmosphereStateAppend, 520 remove=atmosphereStateRemove) 521 for c in a.get('components'): 522 atmosphereStateAppend(a, 'components', c) 523 524 for f in planetState.get('flows'): 525 planetStateAppend(planetState, 'flows', f) 526
527 - def stateSet(self, state, key, value):
528 # called by model when state of something changes 529 if not isinstance(state, planet.AdminComponentState): 530 return 531 532 if key == 'message': 533 self.statusbar.set('main', value) 534 elif key == 'mood': 535 self._set_stop_start_component_sensitive() 536 current = self.components_view.get_selected_name() 537 if value == moods.sleeping.value: 538 if state.get('name') == current: 539 self._messages_view.clear() 540 self._component_view_clear()
541
542 - def whsAppend(self, state, key, value):
543 if key == 'names': 544 self.statusbar.set('main', 'Worker %s logged in.' % value)
545
546 - def whsRemove(self, state, key, value):
547 if key == 'names': 548 self.statusbar.set('main', 'Worker %s logged out.' % value)
549
550 - def _remove_component(self, state):
551 name = state.get('name') 552 self.debug('removing component %s' % name) 553 del self._components[name] 554 555 # if this component was selected, clear selection 556 if self.current_component_state == state: 557 self.debug('removing currently selected component state') 558 self.current_component = None 559 self.current_component_state = None 560 # FIXME: would be nicer to do this incrementally instead 561 self.update_components() 562 563 # a component being removed means our selected component could 564 # have gone away 565 self._set_stop_start_component_sensitive()
566 567 ### admin model callbacks
568 - def admin_connected_cb(self, admin):
569 if self._planetState: 570 self._planetState.removeListener(self) 571 self._planetState = None 572 573 self.info('Connected to manager') 574 if self._disconnected_dialog: 575 self._disconnected_dialog.destroy() 576 self._disconnected_dialog = None 577 578 # FIXME: have a method for this 579 self.window.set_title(_('%s - Flumotion Administration') % 580 self.admin.adminInfoStr()) 581 self._trayicon.set_tooltip(self.admin.adminInfoStr()) 582 583 self.emit('connected') 584 585 # get initial info we need 586 self.setPlanetState(self.admin.planet) 587 588 if not self._components: 589 self.debug('no components detected, running wizard') 590 # ensure our window is shown 591 self.show() 592 self.runWizard()
593
594 - def admin_disconnected_cb(self, admin):
595 message = _("Lost connection to manager, reconnecting ...") 596 d = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT, 597 gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, message) 598 # FIXME: move this somewhere 599 RESPONSE_REFRESH = 1 600 d.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH) 601 d.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 602 d.connect("response", self._dialog_disconnected_response_cb) 603 d.show_all() 604 self._disconnected_dialog = d
605
606 - def _dialog_disconnected_response_cb(self, dialog, id):
607 if id == gtk.RESPONSE_CANCEL: 608 # FIXME: notify admin of cancel 609 dialog.destroy() 610 return 611 elif id == 1: 612 self.admin.reconnect()
613
614 - def admin_connection_refused_later(self, admin):
615 message = _("Connection to manager on %s was refused.") % \ 616 admin.connectionInfoStr() 617 self._trayicon.set_tooltip(_("Connection to %s was refused") % 618 self.admin.adminInfoStr()) 619 self.info(message) 620 d = dialogs.ErrorDialog(message, self) 621 d.show_all() 622 d.connect('response', self.close)
623
624 - def admin_connection_refused_cb(self, admin):
625 log.debug('adminclient', "handling connection-refused") 626 reactor.callLater(0, self.admin_connection_refused_later, admin) 627 log.debug('adminclient', "handled connection-refused")
628
629 - def admin_connection_failed_later(self, admin, reason):
630 message = (_("Connection to manager on %s failed (%s).") 631 % (admin.connectionInfoStr(), reason)) 632 self._trayicon.set_tooltip("Connection to %s failed" % 633 self.admin.adminInfoStr()) 634 self.info(message) 635 d = dialogs.ErrorDialog(message, self.window) 636 d.show_all() 637 d.connect('response', self.close)
638
639 - def admin_connection_failed_cb(self, admin, reason):
640 log.debug('adminclient', "handling connection-failed") 641 reactor.callLater(0, self.admin_connection_failed_later, admin, reason) 642 log.debug('adminclient', "handled connection-failed")
643 644 # FIXME: deprecated
645 - def property_changed_cb(self, admin, componentName, propertyName, value):
646 # called when a property for that component has changed 647 current = self.components_view.get_selected_name() 648 if current != componentName: 649 return 650 651 comp = self.current_component 652 if comp: 653 comp.propertyChanged(propertyName, value)
654
655 - def start_stop_notify_cb(self, *args):
656 can_start = self.components_view.get_property('can-start-any') 657 can_stop = self.components_view.get_property('can-stop-any') 658 self.widgets['menuitem_manage_stop_all'].set_sensitive(can_stop) 659 self.widgets['menuitem_manage_start_all'].set_sensitive(can_start) 660 # they're all in sleeping or lost 661 s = self.widgets['menuitem_manage_clear_all'].set_sensitive 662 s(can_start and not can_stop)
663
664 - def admin_update_cb(self, admin):
665 self.update_components()
666
667 - def update_components(self):
668 self.components_view.update(self._components) 669 self._trayicon.update(self._components)
670
671 - def _set_stop_start_component_sensitive(self):
672 state = self.current_component_state 673 d = self.widgets 674 can_start = bool(state 675 and moods.get(state.get('mood')).name == 'sleeping') 676 d['menuitem_manage_start_component'].set_sensitive(can_start) 677 d['toolbutton_start_component'].set_sensitive(can_start) 678 679 moodname = state and moods.get(state.get('mood')).name 680 can_stop = bool(moodname and moodname!='sleeping' and moodname!='lost') 681 d['menuitem_manage_stop_component'].set_sensitive(can_stop) 682 d['toolbutton_stop_component'].set_sensitive(can_stop) 683 self.debug('can start %r, can stop %r' % (can_start, can_stop))
684 685 # clear the component view in the sidepane. Called when the current 686 # component goes sleeping
687 - def _component_view_clear(self):
688 empty = gtk.Label("") 689 self._component_view_set_widget(empty)
690 691 # set the given widget in the component view
692 - def _component_view_set_widget(self, widget):
693 for c in self._component_view.get_children(): 694 self._component_view.remove(c) 695 self._component_view.add(widget) 696 widget.show_all()
697 698 ### ui callbacks
699 - def _components_view_has_selection_cb(self, view, state):
700 def compSet(state, key, value): 701 if key == 'message': 702 self.statusbar.set('main', value) 703 elif key == 'mood': 704 self._set_stop_start_component_sensitive() 705 current = self.components_view.get_selected_name() 706 if value == moods.sleeping.value: 707 if state.get('name') == current: 708 self._messages_view.clear() 709 self._component_view_clear()
710 711 def compAppend(state, key, value): 712 name = state.get('name') 713 self.debug('stateAppend on component state of %s' % name) 714 if key == 'messages': 715 current = self.components_view.get_selected_name() 716 if name == current: 717 self._messages_view.add_message(value) 718 719 def compRemove(state, key, value): 720 name = state.get('name') 721 self.debug('stateRemove on component state of %s' % name) 722 if key == 'messages': 723 current = self.components_view.get_selected_name() 724 if name == current: 725 self._messages_view.clear_message(value.id) 726 self._set_stop_start_component_sensitive() 727 728 if self.current_component_state: 729 self.current_component_state.removeListener(self) 730 self.current_component_state = state 731 if self.current_component_state: 732 self.current_component_state.addListener(self, compSet, 733 compAppend, 734 compRemove) 735 736 self._set_stop_start_component_sensitive() 737 738 if not state: 739 self.debug('no state, returning') 740 return 741 742 name = state.get('name') 743 mood = state.get('mood') 744 messages = state.get('messages') 745 self._messages_view.clear() 746 self._component_view_clear() 747 748 if messages: 749 for m in messages: 750 self.debug('have message %r' % m) 751 self._messages_view.add_message(m) 752 753 if mood == moods.sad.value: 754 self.debug('component %s is sad' % name) 755 self.statusbar.set('main', 756 _("Component %s is sad") % name) 757 758 return 759 760 def gotEntryCallback(result): 761 entryPath, filename, methodName = result 762 763 self.statusbar.set('main', _('Showing UI for %s') % name) 764 765 filepath = os.path.join(entryPath, filename) 766 self.debug("Got the UI, lives in %s" % filepath) 767 # FIXME: this is a silent assumption that the glade file 768 # lives in the same directory as the entry point 769 self.uidir = os.path.split(filepath)[0] 770 handle = open(filepath, "r") 771 data = handle.read() 772 handle.close() 773 # FIXME: is name (of component) needed ? 774 self.debug("showing admin UI for component %s" % name) 775 # callLater to avoid any errors going to our errback 776 reactor.callLater(0, self.show_component, 777 state, entryPath, filename, methodName, data) 778 779 def gotEntryNoBundleErrback(failure): 780 failure.trap(errors.NoBundleError) 781 self.debug("Making generic UI for component %s" % name) 782 783 # make a generic ui 784 from flumotion.component.base import admin_gtk 785 instance = admin_gtk.BaseAdminGtk(state, self.admin) 786 self._instanceSetup(instance, admin_gtk.BaseAdminGtk, name) 787 788 def gotEntrySleepingComponentErrback(failure): 789 failure.trap(errors.SleepingComponentError) 790 791 self.statusbar.set('main', 792 _("Component %s is still sleeping") % name) 793 794 self.statusbar.set('main', _("Requesting UI for %s ...") % name) 795 # if there's a current component being shown, give it a chance 796 # to clean up 797 if self.current_component: 798 if hasattr(self.current_component, 'cleanup'): 799 self.debug('Cleaning up current component view') 800 self.current_component.cleanup() 801 self.current_component = None 802 803 d = self.admin.getEntry(state, 'admin/gtk') 804 d.addCallback(gotEntryCallback) 805 d.addErrback(gotEntryNoBundleErrback) 806 d.addErrback(gotEntrySleepingComponentErrback) 807
808 - def _components_view_activated_cb(self, view, state, action):
809 self.debug('action %s on component %s' % (action, state.get('name'))) 810 method_name = '_component_' + action 811 if hasattr(self, method_name): 812 getattr(self, method_name)(state) 813 else: 814 self.warning("No method '%s' implemented" % method_name)
815 816 ### glade callbacks
817 - def close(self, *args):
818 reactor.stop()
819
820 - def _logConfig(self, configation):
821 import pprint 822 import cStringIO 823 fd = cStringIO.StringIO() 824 pprint.pprint(configation, fd) 825 fd.seek(0) 826 self.debug('Configuration=%s' % fd.read())
827
828 - def runWizard(self):
829 if self.wizard: 830 self.wizard.present() 831 return 832 833 from flumotion.wizard import wizard 834 835 def _wizard_finished_cb(wizard, configuration): 836 wizard.destroy() 837 self._logConfig(configuration) 838 self.admin.loadConfiguration(configuration) 839 self.show()
840 841 def nullwizard(*args): 842 self.wizard = None 843 844 state = self.admin.getWorkerHeavenState() 845 if not state.get('names'): 846 self.show_error_dialog( 847 _('The wizard cannot be run because no workers are logged in.')) 848 return 849 850 wiz = wizard.Wizard(self.window, self.admin) 851 wiz.connect('finished', _wizard_finished_cb) 852 wiz.run(True, state, False) 853 854 self.wizard = wiz 855 self.wizard.connect('destroy', nullwizard) 856 857 # component view activation functions
858 - def _component_modify(self, state):
859 def propertyErrback(failure): 860 failure.trap(errors.PropertyError) 861 self.show_error_dialog("%s." % failure.getErrorMessage()) 862 return None
863 864 def after_getProperty(value, dialog): 865 self.debug('got value %r' % value) 866 dialog.update_value_entry(value) 867 868 def dialog_set_cb(dialog, element, property, value, state): 869 cb = self.admin.setProperty(state, element, property, value) 870 cb.addErrback(propertyErrback) 871 def dialog_get_cb(dialog, element, property, state): 872 cb = self.admin.getProperty(state, element, property) 873 cb.addCallback(after_getProperty, dialog) 874 cb.addErrback(propertyErrback) 875 876 name = state.get('name') 877 d = dialogs.PropertyChangeDialog(name, self.window) 878 d.connect('get', dialog_get_cb, state) 879 d.connect('set', dialog_set_cb, state) 880 d.run() 881
882 - def _component_reload(self, state):
883 name = state.get('name') 884 if not name: 885 return 886 887 dialog = dialogs.ProgressDialog("Reloading", 888 _("Reloading component code for %s") % name, self.window) 889 d = self.admin.reloadComponent(state) 890 d.addCallback(lambda result, d: d.destroy(), dialog) 891 # add error 892 d.addErrback(lambda failure, d: d.destroy(), dialog) 893 dialog.start()
894
895 - def _component_stop(self, state):
896 """ 897 @returns: a L{twisted.internet.defer.Deferred} 898 """ 899 return self._component_do(state, 'Stop', 'Stopping', 'Stopped')
900
901 - def _component_start(self, state):
902 """ 903 @returns: a L{twisted.internet.defer.Deferred} 904 """ 905 return self._component_do(state, 'Start', 'Starting', 'Started')
906
907 - def _component_restart(self, state):
908 """ 909 @returns: a L{twisted.internet.defer.Deferred} 910 """ 911 d = self._component_stop(state) 912 d.addCallback(lambda r: self._component_start(state)) 913 return d
914
915 - def _component_do(self, state, action, doing, done):
916 if not state: 917 state = self.components_view.get_selected_state() 918 if not state: 919 self.statusbar.push('main', _("No component selected.")) 920 return None 921 922 name = state.get('name') 923 if not name: 924 return None 925 926 mid = self.statusbar.push('main', "%s component %s" % (doing, name)) 927 d = self.admin.callRemote('component' + action, state) 928 929 def _actionCallback(result, self, mid): 930 self.statusbar.remove('main', mid) 931 self.statusbar.push('main', "%s component %s" % (done, name))
932 def _actionErrback(failure, self, mid): 933 self.statusbar.remove('main', mid) 934 self.warning("Failed to %s component %s: %s" % ( 935 action, name, failure)) 936 self.statusbar.push('main', _("Failed to %s component %s") % ( 937 action, name)) 938 939 d.addCallback(_actionCallback, self, mid) 940 d.addErrback(_actionErrback, self, mid) 941 942 return d 943 944 945 # menubar/toolbar callbacks
946 - def on_have_connection(self, d, connectionInfo):
947 d.destroy() 948 self.on_open_connection(connectionInfo)
949
950 - def file_open_cb(self, button):
951 d = gtkconnections.ConnectionsDialog(self.window) 952 d.show() 953 d.connect('have-connection', self.on_have_connection)
954
955 - def on_import_response(self, d, response):
956 if response==gtk.RESPONSE_ACCEPT: 957 name = d.get_filename() 958 conf_xml = open(name, 'r').read() 959 self.admin.loadConfiguration(conf_xml) 960 d.destroy()
961
962 - def file_import_configuration_cb(self, button):
963 d = gtk.FileChooserDialog(_("Import Configuration..."), self.window, 964 gtk.FILE_CHOOSER_ACTION_OPEN, 965 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 966 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) 967 d.set_default_response(gtk.RESPONSE_ACCEPT) 968 d.show() 969 d.connect('response', self.on_import_response)
970
971 - def getConfiguration_cb(self, conf_xml, name, chooser):
972 file_exists = True 973 if os.path.exists(name): 974 d = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, 975 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, 976 _("File already exists.\nOverwrite?")) 977 d.connect("response", lambda self, response: d.hide()) 978 if d.run() == gtk.RESPONSE_YES: 979 file_exists = False 980 else: 981 file_exists = False 982 983 if not file_exists: 984 f = open(name, 'w') 985 f.write(conf_xml) 986 f.close() 987 chooser.destroy()
988
989 - def on_export_response(self, d, response):
990 if response==gtk.RESPONSE_ACCEPT: 991 deferred = self.admin.getConfiguration() 992 name = d.get_filename() 993 deferred.addCallback(self.getConfiguration_cb, name, d) 994 else: 995 d.destroy()
996
997 - def file_export_configuration_cb(self, button):
998 d = gtk.FileChooserDialog(_("Export Configuration..."), self.window, 999 gtk.FILE_CHOOSER_ACTION_SAVE, 1000 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 1001 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) 1002 d.set_default_response(gtk.RESPONSE_ACCEPT) 1003 d.show() 1004 d.connect('response', self.on_export_response)
1005
1006 - def file_quit_cb(self, button):
1007 self.close()
1008
1009 - def manage_start_component_cb(self, button):
1010 self._component_start(None)
1011
1012 - def manage_stop_component_cb(self, button):
1013 self._component_stop(None)
1014
1015 - def manage_start_all_cb(self, button):
1016 for c in self._components.values(): 1017 self._component_start(c)
1018
1019 - def manage_stop_all_cb(self, button):
1020 for c in self._components.values(): 1021 self._component_stop(c)
1022
1023 - def manage_clear_all_cb(self, button):
1024 self.admin.cleanComponents()
1025
1026 - def manage_run_wizard_cb(self, x):
1027 self.runWizard()
1028
1029 - def debug_reload_manager_cb(self, button):
1030 self.admin.reloadManager()
1031
1032 - def debug_reload_admin_cb(self, button):
1033 self.admin.reloadAdmin()
1034
1035 - def debug_reload_all_cb(self, button):
1036 # FIXME: move all of the reloads over to this dialog 1037 def _stop(dialog): 1038 dialog.stop() 1039 dialog.destroy()
1040 1041 def _syntaxErrback(failure, self, progress): 1042 failure.trap(errors.ReloadSyntaxError) 1043 _stop(progress) 1044 self.show_error_dialog( 1045 _("Could not reload component:\n%s.") % 1046 failure.getErrorMessage()) 1047 return None 1048 1049 def _callLater(admin, dialog): 1050 deferred = self.admin.reload() 1051 deferred.addCallback(lambda result, d: _stop(d), dialog) 1052 deferred.addErrback(_syntaxErrback, self, dialog) 1053 deferred.addErrback(self._defaultErrback) 1054 1055 dialog = dialogs.ProgressDialog(_("Reloading ..."), 1056 _("Reloading client code"), self.window) 1057 l = lambda admin, text, dialog: dialog.message( 1058 _("Reloading %s code") % text) 1059 self.admin.connect('reloading', l, dialog) 1060 dialog.start() 1061 reactor.callLater(0.2, _callLater, self.admin, dialog) 1062
1063 - def debug_start_shell_cb(self, button):
1064 if sys.version_info[1] >= 4: 1065 from flumotion.extern import code 1066 else: 1067 import code 1068 1069 vars = \ 1070 { 1071 "admin": self.admin, 1072 "components": self._components 1073 } 1074 message = (" Flumotion Admin Debug Shell\n" 1075 "\n" 1076 "Local variables are:\n" 1077 " admin (flumotion.admin.admin.AdminModel)\n" 1078 " components (dict: name -> flumotion.common.planet.AdminComponentState)\n" 1079 "\n" 1080 "You can do remote component calls using:\n" 1081 " admin.componentCallRemote(components['component-name'],\n" 1082 " 'methodName', arg1, arg2)\n\n") 1083 1084 code.interact(local=vars, banner=message)
1085
1086 - def help_about_cb(self, button):
1087 dialog = gtk.Dialog(_('About Flumotion'), self.window, 1088 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 1089 (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) 1090 dialog.set_has_separator(False) 1091 dialog.set_resizable(False) 1092 dialog.set_border_width(12) 1093 dialog.vbox.set_spacing(6) 1094 1095 image = gtk.Image() 1096 dialog.vbox.pack_start(image) 1097 image.set_from_file(os.path.join(configure.imagedir, 'fluendo.png')) 1098 image.show() 1099 1100 version = gtk.Label('<span size="xx-large"><b>Flumotion %s</b></span>' % configure.version) 1101 version.set_selectable(True) 1102 dialog.vbox.pack_start(version) 1103 version.set_use_markup(True) 1104 version.show() 1105 1106 text = _('Flumotion is a streaming media server.\n\n' 1107 '© 2004, 2005, 2006, 2007 Fluendo S.L.') 1108 authors = ('Andy Wingo', 1109 'Johan Dahlin', 1110 'Mike Smith', 1111 'Thomas Vander Stichele', 1112 'Wim Taymans', 1113 'Zaheer Abbas Merali', 1114 'Sébastien Merle' 1115 ) 1116 text += '\n\n<small>' + _('Authors') + ':\n' 1117 for author in authors: 1118 text += ' %s\n' % author 1119 text += '</small>' 1120 info = gtk.Label(text) 1121 dialog.vbox.pack_start(info) 1122 info.set_use_markup(True) 1123 info.set_selectable(True) 1124 info.set_justify(gtk.JUSTIFY_FILL) 1125 info.set_line_wrap(True) 1126 info.show() 1127 1128 dialog.show() 1129 dialog.run() 1130 dialog.destroy()
1131
1132 - def show(self):
1133 # XXX: Use show() 1134 self.window.show()
1135 1136 pygobject.type_register(Window) 1137