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

Source Code for Module flumotion.admin.gtk.client

   1  # -*- Mode: Python -*- 
   2  # vi:si:et:sw=4:sts=4:ts=4 
   3  # 
   4  # Flumotion - a streaming media server 
   5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
   6  # All rights reserved. 
   7   
   8  # This file may be distributed and/or modified under the terms of 
   9  # the GNU General Public License version 2 as published by 
  10  # the Free Software Foundation. 
  11  # This file is distributed without any warranty; without even the implied 
  12  # warranty of merchantability or fitness for a particular purpose. 
  13  # See "LICENSE.GPL" in the source distribution for more information. 
  14   
  15  # Licensees having purchased or holding a valid Flumotion Advanced 
  16  # Streaming Server license may use this file in accordance with the 
  17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
  18  # See "LICENSE.Flumotion" in the source distribution for more information. 
  19   
  20  # Headers in this file shall remain intact. 
  21   
  22  import gettext 
  23  import os 
  24  import sys 
  25   
  26  import gobject 
  27  import gtk 
  28  import gtk.glade 
  29  from twisted.internet import reactor 
  30  from twisted.internet.defer import maybeDeferred 
  31  from zope.interface import implements 
  32   
  33  from flumotion.admin.admin import AdminModel 
  34  from flumotion.admin import connections 
  35  from flumotion.admin.gtk import dialogs, parts 
  36  from flumotion.admin.gtk.parts import getComponentLabel 
  37  from flumotion.admin.gtk import connections as gtkconnections 
  38  from flumotion.configure import configure 
  39  from flumotion.common import errors, log, planet, pygobject 
  40  from flumotion.common import connection 
  41  from flumotion.manager import admin # Register types 
  42  from flumotion.twisted import flavors, pb as fpb 
  43  from flumotion.ui import trayicon 
  44  from flumotion.common.planet import moods 
  45  from flumotion.common.pygobject import gsignal 
  46   
  47  from flumotion.common import messages 
  48   
  49  _ = gettext.gettext 
  50  T_ = messages.gettexter('flumotion') 
  51   
  52  MAIN_UI = """ 
  53  <ui> 
  54    <menubar name="menubar"> 
  55      <menu action="connection"> 
  56        <menuitem action="open-recent"/> 
  57        <menuitem action="open-existing"/> 
  58        <menuitem action="import-config"/> 
  59        <menuitem action="export-config"/> 
  60        <separator name="sep1"/> 
  61        <placeholder name="recent"/> 
  62        <separator name="sep2"/> 
  63        <menuitem action="quit"/> 
  64      </menu> 
  65      <menu action="manage"> 
  66        <menuitem action="start-component"/> 
  67        <menuitem action="stop-component"/> 
  68        <menuitem action="delete-component"/> 
  69        <separator name="sep3"/> 
  70        <menuitem action="start-all"/> 
  71        <menuitem action="stop-all"/> 
  72        <menuitem action="clear-all"/> 
  73        <separator name="sep4"/> 
  74        <menuitem action="run-wizard"/> 
  75      </menu> 
  76      <menu action="debug"> 
  77        <menuitem action="reload-manager"/> 
  78        <menuitem action="reload-admin"/> 
  79        <menuitem action="reload-all"/> 
  80        <menuitem action="start-shell"/> 
  81      </menu> 
  82      <menu action="help"> 
  83        <menuitem action="about"/> 
  84      </menu> 
  85    </menubar> 
  86    <toolbar name="toolbar"> 
  87      <toolitem action="open-recent"/> 
  88      <separator name="sep5"/> 
  89      <toolitem action="start-component"/> 
  90      <toolitem action="stop-component"/> 
  91      <toolitem action="delete-component"/> 
  92      <separator name="sep6"/> 
  93      <toolitem action="run-wizard"/> 
  94    </toolbar> 
  95  </ui> 
  96  """ 
  97   
  98  RECENT_UI_TEMPLATE = '''<ui> 
  99    <menubar name="menubar"> 
 100      <menu action="connection"> 
 101        <placeholder name="recent"> 
 102        %s 
 103        </placeholder> 
 104      </menu> 
 105    </menubar> 
 106  </ui>''' 
 107   
 108  MAX_RECENT_ITEMS = 4 
 109   
 110   
111 -class Window(log.Loggable, gobject.GObject):
112 ''' 113 Creates the GtkWindow for the user interface. 114 Also connects to the manager on the given host and port. 115 ''' 116 117 implements(flavors.IStateListener) 118 119 logCategory = 'adminview' 120 gsignal('connected') 121
122 - def __init__(self):
123 gobject.GObject.__init__(self) 124 125 self._trayicon = None 126 self._current_component_state = None 127 self._disconnected_dialog = None # set to a dialog when disconnected 128 self._planetState = None 129 self._components = None # name -> planet.AdminComponentState 130 self._wizard = None 131 self._admin = None 132 self._widgets = {} 133 self._window = None 134 self._recent_menu_uid = None 135 136 self._create_ui() 137 self._append_recent_connections()
138 139 # Public API 140
141 - def stateSet(self, state, key, value):
142 # called by model when state of something changes 143 if not isinstance(state, planet.AdminComponentState): 144 return 145 146 if key == 'message': 147 self.statusbar.set('main', value) 148 elif key == 'mood': 149 self._update_component_actions() 150 current = self.components_view.get_selected_name() 151 if value == moods.sleeping.value: 152 if state.get('name') == current: 153 self._clear_messages()
154
155 - def componentCallRemoteStatus(self, state, pre, post, fail, 156 methodName, *args, **kwargs):
157 if not state: 158 state = self.components_view.get_selected_state() 159 if not state: 160 return 161 162 label = getComponentLabel(state) 163 if not label: 164 return 165 166 mid = None 167 if pre: 168 mid = self.statusbar.push('main', pre % label) 169 d = self._admin.componentCallRemote(state, methodName, *args, **kwargs) 170 171 def cb(result, self, mid): 172 if mid: 173 self.statusbar.remove('main', mid) 174 if post: 175 self.statusbar.push('main', post % label)
176 def eb(failure, self, mid): 177 if mid: 178 self.statusbar.remove('main', mid) 179 self.warning("Failed to execute %s on component %s: %s" 180 % (methodName, label, failure)) 181 if fail: 182 self.statusbar.push('main', fail % label)
183 184 d.addCallback(cb, self, mid) 185 d.addErrback(eb, self, mid) 186
187 - def componentCallRemote(self, state, methodName, *args, **kwargs):
188 self.componentCallRemoteStatus(None, None, None, None, 189 methodName, *args, **kwargs)
190
191 - def whsAppend(self, state, key, value):
192 if key == 'names': 193 self.statusbar.set('main', 'Worker %s logged in.' % value)
194
195 - def whsRemove(self, state, key, value):
196 if key == 'names': 197 self.statusbar.set('main', 'Worker %s logged out.' % value)
198
199 - def show(self):
200 self._window.show()
201
202 - def setAdminModel(self, model):
203 'set the model to which we are a view/controller' 204 # it's ok if we've already been connected 205 self.debug('setting model') 206 207 if self._admin: 208 self.debug('Connecting to new model %r' % model) 209 if self._wizard: 210 self._wizard.destroy() 211 212 self._admin = model 213 214 # window gets created after model connects initially, so check 215 # here 216 if self._admin.isConnected(): 217 self._admin_connected_cb(model) 218 219 self._admin.connect('connected', self._admin_connected_cb) 220 self._admin.connect('disconnected', self._admin_disconnected_cb) 221 self._admin.connect('connection-refused', 222 self._admin_connection_refused_cb) 223 self._admin.connect('connection-failed', 224 self._admin_connection_failed_cb) 225 self._admin.connect('update', self._admin_update_cb)
226 227 # Private 228
229 - def _create_ui(self):
230 self.debug('creating UI') 231 # returns the window 232 # called from __init__ 233 wtree = gtk.glade.XML(os.path.join(configure.gladedir, 'admin.glade')) 234 wtree.signal_autoconnect(self) 235 236 widgets = self._widgets 237 for widget in wtree.get_widget_prefix(''): 238 widgets[widget.get_name()] = widget 239 240 window = self._window = widgets['main_window'] 241 vbox = widgets['vbox1'] 242 window.connect('delete-event', self._window_delete_event_cb) 243 244 actions = [ 245 # Connection 246 ('connection', None, _("_Connection")), 247 ('open-recent', gtk.STOCK_OPEN, _('_Open Recent Connection...'), None, 248 None, self._connection_open_recent_cb), 249 ('open-existing', None, _('Open _Existing Connection...'), None, 250 None, self._connection_open_existing_cb), 251 ('import-config', None, _('_Import Configuration...'), None, 252 None, self._connection_import_configuration_cb), 253 ('export-config', None, _('_Export Configuration...'), None, 254 None, self._connection_export_configuration_cb), 255 ('quit', gtk.STOCK_QUIT, _('_Quit'), None, 256 None, self._connection_quit_cb), 257 258 # Manage 259 ('manage', None, _('_Manage')), 260 ('start-component', 'flumotion-play', _('_S_tart Component'), None, 261 None, self._manage_start_component_cb), 262 ('stop-component', 'flumotion-pause', _('St_op Component'), None, 263 None, self._manage_stop_component_cb), 264 ('delete-component', gtk.STOCK_DELETE, _('_Delete Component'), None, 265 None, self._manage_delete_component_cb), 266 ('start-all', None, _('Start _All'), None, 267 None, self._manage_start_all_cb), 268 ('stop-all', None, _('Stop A_ll'), None, 269 None, self._manage_stop_all_cb), 270 ('clear-all', gtk.STOCK_CLEAR, _('_Clear All'), None, 271 None, self._manage_clear_all_cb), 272 ('run-wizard', 'flumotion-wizard', _('Run _Wizard'), None, 273 None, self._manage_run_wizard_cb), 274 275 # Debug 276 ('debug', None, _('_Debug')), 277 ('reload-manager', gtk.STOCK_REFRESH, _('Reload _Manager'), None, 278 None, self._debug_reload_manager_cb), 279 ('reload-admin', gtk.STOCK_REFRESH, _('Reload _Admin'), None, 280 None, self._debug_reload_admin_cb), 281 ('reload-all', gtk.STOCK_REFRESH, _('Reload A_ll'), None, 282 None, self._debug_reload_all_cb), 283 ('start-shell', gtk.STOCK_EXECUTE, _('Start _Shell'), None, 284 None, self._debug_start_shell_cb), 285 286 # Help 287 ('help', None, _('_Help')), 288 ('about', gtk.STOCK_ABOUT, _('_About'), None, 289 None, self._help_about_cb), 290 ] 291 uimgr = gtk.UIManager() 292 group = gtk.ActionGroup('actions') 293 group.add_actions(actions) 294 uimgr.insert_action_group(group, 0) 295 uimgr.add_ui_from_string(MAIN_UI) 296 window.add_accel_group(uimgr.get_accel_group()) 297 menubar = uimgr.get_widget('/menubar') 298 vbox.pack_start(menubar, expand=False) 299 vbox.reorder_child(menubar, 0) 300 301 toolbar = uimgr.get_widget('/toolbar') 302 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) 303 toolbar.set_style(gtk.TOOLBAR_ICONS) 304 vbox.pack_start(toolbar, expand=False) 305 vbox.reorder_child(toolbar, 1) 306 307 menubar.show_all() 308 309 self._actiongroup = group 310 self._uimgr = uimgr 311 self._start_component_action = group.get_action("start-component") 312 self._stop_component_action = group.get_action("stop-component") 313 self._delete_component_action = group.get_action("delete-component") 314 self._stop_all_action = group.get_action("stop-all") 315 self._start_all_action = group.get_action("start-all") 316 self._clear_all_action = group.get_action("clear-all") 317 318 self._trayicon = trayicon.FluTrayIcon(window) 319 self._trayicon.connect("quit", self._trayicon_quit_cb) 320 self._trayicon.set_tooltip(_('Not connected')) 321 322 # the widget containing the component view 323 self._component_view = widgets['component_view'] 324 325 self.components_view = parts.ComponentsView(widgets['components_view']) 326 self.components_view.connect('selection_changed', 327 self._components_view_selection_changed_cb) 328 self.components_view.connect('activated', 329 self._components_view_activated_cb) 330 self.statusbar = parts.AdminStatusbar(widgets['statusbar']) 331 self._update_component_actions() 332 self.components_view.connect( 333 'notify::can-start-any', 334 self._component_view_start_stop_notify_cb) 335 self.components_view.connect( 336 'notify::can-stop-any', 337 self._component_view_start_stop_notify_cb) 338 self._component_view_start_stop_notify_cb() 339 340 self._messages_view = widgets['messages_view'] 341 self._messages_view.hide() 342 343 return window
344
345 - def _append_recent_connections(self):
346 if self._recent_menu_uid: 347 self._uimgr.remove_ui(self._recent_menu_uid) 348 self._uimgr.ensure_update() 349 350 def recent_activate(action, conn): 351 self._open_connection(conn['info'])
352 353 ui = "" 354 for conn in connections.get_recent_connections()[:MAX_RECENT_ITEMS]: 355 name = conn['name'] 356 ui += '<menuitem action="%s"/>' % name 357 action = gtk.Action(name, name, '', '') 358 action.connect('activate', recent_activate, conn) 359 self._actiongroup.add_action(action) 360 361 self._recent_menu_uid = self._uimgr.add_ui_from_string( 362 RECENT_UI_TEMPLATE % ui) 363
364 - def _close(self, *args):
365 reactor.stop()
366
367 - def _dump_config(self, configation):
368 import pprint 369 import cStringIO 370 fd = cStringIO.StringIO() 371 pprint.pprint(configation, fd) 372 fd.seek(0) 373 self.debug('Configuration=%s' % fd.read())
374
375 - def _run_wizard(self):
376 if self._wizard: 377 self._wizard.present() 378 379 def _wizard_finished_cb(wizard, configuration): 380 wizard.destroy() 381 self._dump_config(configuration) 382 self._admin.loadConfiguration(configuration) 383 self.show()
384 385 def nullwizard(*args): 386 self._wizard = None 387 388 state = self._admin.getWorkerHeavenState() 389 if not state.get('names'): 390 self._error( 391 _('The wizard cannot be run because no workers are logged in.')) 392 return 393 394 from flumotion.wizard.configurationwizard import ConfigurationWizard 395 wizard = ConfigurationWizard(self._window, self._admin) 396 wizard.connect('finished', _wizard_finished_cb) 397 wizard.run(True, state, False) 398 399 self._wizard = wizard 400 self._wizard.connect('destroy', nullwizard) 401
402 - def _open_connection(self, connectionInfo):
403 i = connectionInfo 404 model = AdminModel() 405 d = model.connectToManager(i) 406 407 self._trayicon.set_tooltip(_("Connecting to %(host)s:%(port)s") % { 408 'host': i.host, 409 'port': i.port, 410 }) 411 412 def connected(model): 413 self._window.set_sensitive(True) 414 self.setAdminModel(model) 415 self._append_recent_connections()
416 417 def refused(failure): 418 if failure.check(errors.ConnectionRefusedError): 419 d = dialogs.connection_refused_message(i.host, 420 self._window) 421 else: 422 d = dialogs.connection_failed_message(i, str(failure), 423 self._window) 424 d.addCallback(lambda _: self._window.set_sensitive(True)) 425 426 d.addCallbacks(connected, refused) 427 self._window.set_sensitive(False) 428
429 - def _update_component_actions(self):
430 state = self._current_component_state 431 if state: 432 moodname = moods.get(state.get('mood')).name 433 can_start = moodname == 'sleeping' 434 can_stop = moodname != 'sleeping' 435 else: 436 can_start = False 437 can_stop = False 438 can_delete = bool(state and not can_stop) 439 can_start_all = self.components_view.get_property('can-start-any') 440 can_stop_all = self.components_view.get_property('can-stop-any') 441 # they're all in sleeping or lost 442 can_clear_all = can_start_all and not can_stop_all 443 444 self._stop_all_action.set_sensitive(can_stop_all) 445 self._start_all_action.set_sensitive(can_start_all) 446 self._clear_all_action.set_sensitive(can_clear_all) 447 self._start_component_action.set_sensitive(can_start) 448 self._stop_component_action.set_sensitive(can_stop) 449 self._delete_component_action.set_sensitive(can_delete) 450 self.debug('can start %r, can stop %r' % (can_start, can_stop))
451
452 - def _update_components(self):
453 self.components_view.update(self._components) 454 self._trayicon.update(self._components)
455
456 - def _clear_messages(self):
457 self._messages_view.clear() 458 pstate = self._planetState 459 if pstate and pstate.hasKey('messages'): 460 for message in pstate.get('messages').values(): 461 self._messages_view.add_message(message)
462
463 - def _set_planet_state(self, planetState):
464 465 def flowStateAppend(state, key, value): 466 self.debug('flow state append: key %s, value %r' % (key, value)) 467 if key == 'components': 468 self._components[value.get('name')] = value 469 # FIXME: would be nicer to do this incrementally instead 470 self._update_components()
471 472 def flowStateRemove(state, key, value): 473 if key == 'components': 474 self._remove_component(value) 475 476 def atmosphereStateAppend(state, key, value): 477 if key == 'components': 478 self._components[value.get('name')] = value 479 # FIXME: would be nicer to do this incrementally instead 480 self._update_components() 481 482 def atmosphereStateRemove(state, key, value): 483 if key == 'components': 484 self._remove_component(value) 485 486 def planetStateAppend(state, key, value): 487 if key == 'flows': 488 if value != state.get('flows')[0]: 489 self.warning('flumotion-admin can only handle one ' 490 'flow, ignoring /%s', value.get('name')) 491 return 492 self.debug('%s flow started', value.get('name')) 493 value.addListener(self, append=flowStateAppend, 494 remove=flowStateRemove) 495 for c in value.get('components'): 496 flowStateAppend(value, 'components', c) 497 498 def planetStateRemove(state, key, value): 499 self.debug('something got removed from the planet') 500 501 def planetStateSetitem(state, key, subkey, value): 502 if key == 'messages': 503 self._messages_view.add_message(value) 504 505 def planetStateDelitem(state, key, subkey, value): 506 if key == 'messages': 507 self._messages_view.clear_message(value.id) 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 setitem=planetStateSetitem, 518 delitem=planetStateDelitem) 519 520 self._clear_messages() 521 522 a = planetState.get('atmosphere') 523 a.addListener(self, append=atmosphereStateAppend, 524 remove=atmosphereStateRemove) 525 for c in a.get('components'): 526 atmosphereStateAppend(a, 'components', c) 527 528 for f in planetState.get('flows'): 529 planetStateAppend(planetState, 'flows', f) 530 531 # component view activation functions 532
533 - def _component_modify(self, state):
534 def propertyErrback(failure): 535 failure.trap(errors.PropertyError) 536 self._error("%s." % failure.getErrorMessage()) 537 return None
538 539 def after_getProperty(value, dialog): 540 self.debug('got value %r' % value) 541 dialog.update_value_entry(value) 542 543 def dialog_set_cb(dialog, element, property, value, state): 544 cb = self._admin.setProperty(state, element, property, value) 545 cb.addErrback(propertyErrback) 546 def dialog_get_cb(dialog, element, property, state): 547 cb = self._admin.getProperty(state, element, property) 548 cb.addCallback(after_getProperty, dialog) 549 cb.addErrback(propertyErrback) 550 551 name = state.get('name') 552 d = dialogs.PropertyChangeDialog(name, self._window) 553 d.connect('get', dialog_get_cb, state) 554 d.connect('set', dialog_set_cb, state) 555 d.run() 556
557 - def _remove_component(self, state):
558 name = state.get('name') 559 self.debug('removing component %s' % name) 560 del self._components[name] 561 562 # if this component was selected, clear selection 563 if self._current_component_state == state: 564 self.debug('removing currently selected component state') 565 self._current_component_state = None 566 # FIXME: would be nicer to do this incrementally instead 567 self._update_components() 568 569 # a component being removed means our selected component could 570 # have gone away 571 self._update_component_actions()
572
573 - def _component_reload(self, state):
574 name = getComponentLabel(state) 575 576 dialog = dialogs.ProgressDialog("Reloading", 577 _("Reloading component code for %s") % name, self._window) 578 d = self._admin.callRemote('reloadComponent', state) 579 d.addCallback(lambda result, dlg: dlg.destroy(), dialog) 580 # FIXME: better error handling 581 d.addErrback(lambda failure, dlg: dlg.destroy(), dialog) 582 dialog.start()
583
584 - def _component_stop(self, state):
585 """ 586 @returns: a L{twisted.internet.defer.Deferred} 587 """ 588 return self._component_do(state, 'Stop', 'Stopping', 'Stopped')
589
590 - def _component_start(self, state):
591 """ 592 @returns: a L{twisted.internet.defer.Deferred} 593 """ 594 return self._component_do(state, 'Start', 'Starting', 'Started')
595
596 - def _component_restart(self, state):
597 """ 598 @returns: a L{twisted.internet.defer.Deferred} 599 """ 600 d = self._component_stop(state) 601 d.addCallback(lambda r: self._component_start(state)) 602 return d
603
604 - def _component_delete(self, state):
605 """ 606 @returns: a L{twisted.internet.defer.Deferred} 607 """ 608 return self._component_do(state, '', 'Deleting', 'Deleted', 609 'deleteComponent')
610
611 - def _component_do(self, state, action, doing, done, 612 remoteMethodPrefix="component"):
613 """ 614 @param remoteMethodPrefix: prefix for remote method to run 615 """ 616 if not state: 617 state = self.components_view.get_selected_state() 618 if not state: 619 self.statusbar.push('main', _("No component selected.")) 620 return None 621 622 name = getComponentLabel(state) 623 if not name: 624 return None 625 626 mid = self.statusbar.push('main', "%s component %s" % (doing, name)) 627 d = self._admin.callRemote(remoteMethodPrefix + action, state) 628 629 def _actionCallback(result, self, mid): 630 self.statusbar.remove('main', mid) 631 self.statusbar.push('main', "%s component %s" % (done, name))
632 def _actionErrback(failure, self, mid): 633 self.statusbar.remove('main', mid) 634 self.warning("Failed to %s component %s: %s" % ( 635 action.lower(), name, failure)) 636 self.statusbar.push('main', 637 _("Failed to %(action)s component %(name)s") % { 638 'action': action.lower(), 639 'name': name, 640 }) 641 642 d.addCallback(_actionCallback, self, mid) 643 d.addErrback(_actionErrback, self, mid) 644 645 return d 646
647 - def _component_activate(self, state, action):
648 self.debug('action %s on component %s' % (action, 649 state.get('name'))) 650 method_name = '_component_' + action 651 if hasattr(self, method_name): 652 getattr(self, method_name)(state) 653 else: 654 self.warning("No method '%s' implemented" % method_name)
655
656 - def _component_selection_changed(self, state):
657 self.debug('component %s has selection', state) 658 def compSet(state, key, value): 659 if key == 'mood': 660 self._update_component_actions()
661 662 def compAppend(state, key, value): 663 name = state.get('name') 664 self.debug('stateAppend on component state of %s' % name) 665 if key == 'messages': 666 current = self.components_view.get_selected_name() 667 if name == current: 668 self._messages_view.add_message(value) 669 670 def compRemove(state, key, value): 671 name = state.get('name') 672 self.debug('stateRemove on component state of %s' % name) 673 if key == 'messages': 674 current = self.components_view.get_selected_name() 675 if name == current: 676 self._messages_view.clear_message(value.id) 677 678 if self._current_component_state: 679 self._current_component_state.removeListener(self) 680 self._current_component_state = state 681 if self._current_component_state: 682 self._current_component_state.addListener( 683 self, compSet, compAppend, compRemove) 684 685 self._update_component_actions() 686 self._component_view.show_object(state) 687 self._clear_messages() 688 689 if state: 690 name = getComponentLabel(state) 691 692 messages = state.get('messages') 693 if messages: 694 for m in messages: 695 self.debug('have message %r' % m) 696 self._messages_view.add_message(m) 697 698 if state.get('mood') == moods.sad.value: 699 self.debug('component %s is sad' % name) 700 self.statusbar.set('main', 701 _("Component %s is sad") % name) 702 703 # FIXME: show statusbar things 704 # self.statusbar.set('main', _('Showing UI for %s') % name) 705 # self.statusbar.set('main', 706 # _("Component %s is still sleeping") % name) 707 # self.statusbar.set('main', _("Requesting UI for %s ...") % name) 708 # self.statusbar.set('main', _("Loading UI for %s ...") % name) 709 # self.statusbar.clear('main') 710 # mid = self.statusbar.push('notebook', 711 # _("Loading tab %s for %s ...") % (node.title, name)) 712 # node.statusbar = self.statusbar # hack 713
714 - def _connection_opened(self, admin):
715 self.info('Connected to manager') 716 if self._disconnected_dialog: 717 self._disconnected_dialog.destroy() 718 self._disconnected_dialog = None 719 720 # FIXME: have a method for this 721 self._window.set_title(_('%s - Flumotion Administration') % 722 self._admin.adminInfoStr()) 723 self._trayicon.set_tooltip(self._admin.adminInfoStr()) 724 725 self.emit('connected') 726 727 self._component_view.set_single_admin(admin) 728 729 self._set_planet_state(admin.planet) 730 731 if not self._components: 732 self.debug('no components detected, running wizard') 733 # ensure our window is shown 734 self.show() 735 self._run_wizard()
736
737 - def _connection_lost(self):
738 self._components = {} 739 self._update_components() 740 self._clear_messages() 741 if self._planetState: 742 self._planetState.removeListener(self) 743 self._planetState = None 744 745 def response(dialog, id): 746 if id == gtk.RESPONSE_CANCEL: 747 # FIXME: notify admin of cancel 748 dialog.destroy() 749 return 750 elif id == 1: 751 self._admin.reconnect()
752 753 message = _("Lost connection to manager, reconnecting ...") 754 d = gtk.MessageDialog(self._window, gtk.DIALOG_DESTROY_WITH_PARENT, 755 gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, message) 756 # FIXME: move this somewhere 757 RESPONSE_REFRESH = 1 758 d.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH) 759 d.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 760 d.connect("response", response) 761 d.show_all() 762 self._disconnected_dialog = d 763
764 - def _connection_refused(self):
765 def refused_later(): 766 message = _("Connection to manager on %s was refused.") % \ 767 self._admin.connectionInfoStr() 768 self._trayicon.set_tooltip(_("Connection to %s was refused") % 769 self._admin.adminInfoStr()) 770 self.info(message) 771 d = dialogs.ErrorDialog(message, self) 772 d.show_all() 773 d.connect('response', self._close)
774 775 log.debug('adminclient', "handling connection-refused") 776 reactor.callLater(0, refused_later) 777 log.debug('adminclient', "handled connection-refused") 778
779 - def _connection_failed(self, reason):
780 def failed_later(): 781 message = ( 782 _("Connection to manager on %(conn)s failed (%(reason)s).") % { 783 'conn': self._admin.connectionInfoStr(), 784 'reason': reason, 785 }) 786 self._trayicon.set_tooltip("Connection to %s failed" % 787 self._admin.adminInfoStr()) 788 self.info(message) 789 d = dialogs.ErrorDialog(message, self._window) 790 d.show_all() 791 d.connect('response', self._close)
792 793 log.debug('adminclient', "handling connection-failed") 794 reactor.callLater(0, failed_later) 795 log.debug('adminclient', "handled connection-failed") 796
797 - def _error(self, message):
798 d = dialogs.ErrorDialog(message, self._window, 799 close_on_response=True) 800 d.show_all()
801
802 - def _open_recent_connection(self):
803 d = gtkconnections.ConnectionsDialog(self._window) 804 805 def on_have_connection(d, connectionInfo): 806 d.destroy() 807 self._open_connection(connectionInfo)
808 809 d.connect('have-connection', on_have_connection) 810 d.show() 811
812 - def _open_existing_connection(self):
813 from flumotion.admin.gtk import greeter 814 from flumotion.admin.gtk.wizard import WizardCancelled 815 wiz = greeter.ConnectExisting() 816 817 def got_state(state, g): 818 g.set_sensitive(False) 819 authenticator = fpb.Authenticator(username=state['user'], 820 password=state['passwd']) 821 info = connection.PBConnectionInfo(state['host'], state['port'], 822 not state['use_insecure'], 823 authenticator) 824 g.destroy() 825 self._open_connection(info)
826 827 def cancel(failure): 828 failure.trap(WizardCancelled) 829 wiz.stop() 830 831 d = wiz.run_async() 832 d.addCallback(got_state, wiz) 833 d.addErrback(cancel) 834
835 - def _import_configuration(self):
836 d = gtk.FileChooserDialog(_("Import Configuration..."), self._window, 837 gtk.FILE_CHOOSER_ACTION_OPEN, 838 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 839 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) 840 d.set_default_response(gtk.RESPONSE_ACCEPT) 841 842 def response(d, response): 843 if response == gtk.RESPONSE_ACCEPT: 844 name = d.get_filename() 845 conf_xml = open(name, 'r').read() 846 self._admin.loadConfiguration(conf_xml) 847 d.destroy()
848 849 d.connect('response', response) 850 d.show() 851
852 - def _export_configuration(self):
853 d = gtk.FileChooserDialog(_("Export Configuration..."), self._window, 854 gtk.FILE_CHOOSER_ACTION_SAVE, 855 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 856 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) 857 d.set_default_response(gtk.RESPONSE_ACCEPT) 858 859 def get_configuration(conf_xml, name, chooser): 860 file_exists = True 861 if os.path.exists(name): 862 d = gtk.MessageDialog(self._window, gtk.DIALOG_MODAL, 863 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, 864 _("File already exists.\nOverwrite?")) 865 d.connect("response", lambda self, response: d.hide()) 866 if d.run() == gtk.RESPONSE_YES: 867 file_exists = False 868 else: 869 file_exists = False 870 871 if not file_exists: 872 f = open(name, 'w') 873 f.write(conf_xml) 874 f.close() 875 chooser.destroy()
876 877 def response(d, response): 878 if response == gtk.RESPONSE_ACCEPT: 879 deferred = self._admin.getConfiguration() 880 name = d.get_filename() 881 deferred.addCallback(get_configuration, name, d) 882 else: 883 d.destroy() 884 885 d.connect('response', response) 886 d.show() 887
888 - def _reload_manager(self):
889 return self._admin.callRemote('reloadManager')
890
891 - def _reload_admin(self):
892 self.info('Reloading admin code') 893 from flumotion.common.reload import reload as freload 894 freload() 895 self.info('Reloaded admin code')
896
897 - def _reload_all(self):
898 dialog = dialogs.ProgressDialog(_("Reloading ..."), 899 _("Reloading client code"), self._window) 900 901 # FIXME: move all of the reloads over to this dialog 902 def _stopCallback(result): 903 dialog.stop() 904 dialog.destroy()
905 906 def _syntaxErrback(failure): 907 failure.trap(errors.ReloadSyntaxError) 908 dialog.stop() 909 dialog.destroy() 910 self._error( 911 _("Could not reload component:\n%s.") % 912 failure.getErrorMessage()) 913 return None 914 915 def _defaultErrback(failure): 916 self.warning('Errback: unhandled failure: %s' % 917 failure.getErrorMessage()) 918 return failure 919 920 def _callLater(): 921 d = maybeDeferred(self._reload_admin) 922 d.addCallback(lambda _: self._reload_manager()) 923 # stack callbacks so that a new one only gets sent after the 924 # previous one has completed 925 for c in self._components.values(): 926 # FIXME: race condition if components log in or out. 927 d.addCallback(lambda _, c: self._component_reload(c), c) 928 d.addCallback(_stopCallback) 929 d.addErrback(_syntaxErrback) 930 d.addErrback(_defaultErrback) 931 # FIXME: errback to close the reloading dialog? 932 933 def _reloadCallback(admin, text): 934 dialog.message(_("Reloading %s code") % text) 935 936 self._admin.connect('reloading', _reloadCallback) 937 dialog.start() 938 reactor.callLater(0.2, _callLater) 939
940 - def _start_shell(self):
941 if sys.version_info >= (2, 4): 942 from flumotion.extern import code 943 else: 944 import code 945 946 vars = \ 947 { 948 "admin": self._admin, 949 "components": self._components 950 } 951 message = """Flumotion Admin Debug Shell 952 953 Local variables are: 954 admin (flumotion.admin.admin.AdminModel) 955 components (dict: name -> flumotion.common.planet.AdminComponentState) 956 957 You can do remote component calls using: 958 admin.componentCallRemote(components['component-name'], 959 'methodName', arg1, arg2) 960 961 """ 962 code.interact(local=vars, banner=message)
963
964 - def _about(self):
965 about = dialogs.AboutDialog(self._window) 966 about.run() 967 about.destroy()
968 969 ### admin model callbacks 970
971 - def _admin_connected_cb(self, admin):
972 self._connection_opened(admin)
973
974 - def _admin_disconnected_cb(self, admin):
975 self._connection_lost()
976
977 - def _admin_connection_refused_cb(self, admin):
978 self._connection_refused()
979
980 - def _admin_connection_failed_cb(self, admin, reason):
981 self._connection_failed(admin, reason)
982
983 - def _admin_update_cb(self, admin):
984 self._update_components()
985 986 ### ui callbacks 987
988 - def _window_delete_event_cb(self, window, event):
989 self._close()
990
991 - def _trayicon_quit_cb(self, trayicon):
992 self._close()
993
994 - def _components_view_selection_changed_cb(self, view, state):
995 self._component_selection_changed(state)
996
997 - def _components_view_activated_cb(self, view, state, action):
998 self._component_activate(state, action)
999
1000 - def _component_view_start_stop_notify_cb(self, *args):
1001 self._update_component_actions()
1002 1003 ### action callbacks 1004
1005 - def _connection_open_recent_cb(self, action):
1006 self._open_recent_connection()
1007
1008 - def _connection_open_existing_cb(self, action):
1009 self._open_existing_connection()
1010
1011 - def _connection_import_configuration_cb(self, action):
1012 self._import_configuration()
1013
1014 - def _connection_export_configuration_cb(self, action):
1015 self._export_configuration()
1016
1017 - def _connection_quit_cb(self, action):
1018 self._close()
1019
1020 - def _manage_start_component_cb(self, action):
1021 self._component_start(None)
1022
1023 - def _manage_stop_component_cb(self, action):
1024 self._component_stop(None)
1025
1026 - def _manage_delete_component_cb(self, action):
1027 self._component_delete(None)
1028
1029 - def _manage_start_all_cb(self, action):
1030 for c in self._components.values(): 1031 self._component_start(c)
1032
1033 - def _manage_stop_all_cb(self, action):
1034 for c in self._components.values(): 1035 self._component_stop(c)
1036
1037 - def _manage_clear_all_cb(self, action):
1038 self._admin.cleanComponents()
1039
1040 - def _manage_run_wizard_cb(self, action):
1041 self._run_wizard()
1042
1043 - def _debug_reload_manager_cb(self, action):
1044 self._reload_manager()
1045
1046 - def _debug_reload_admin_cb(self, action):
1047 self._reload_admin()
1048
1049 - def _debug_reload_all_cb(self, action):
1050 self._reload_all()
1051
1052 - def _debug_start_shell_cb(self, action):
1053 self._start_shell()
1054
1055 - def _help_about_cb(self, action):
1056 self._about()
1057 1058 pygobject.type_register(Window) 1059