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

Source Code for Module flumotion.admin.gtk.adminwindow

   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,2008 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  """admin window interface, the main interface of flumotion-admin. 
  23   
  24  Here is an overview of the different parts of the admin interface:: 
  25   
  26   +--------------[ AdminWindow ]-------------+ 
  27   | Menubar                                  | 
  28   +------------------------------------------+ 
  29   | Toolbar                                  | 
  30   +--------------------+---------------------+ 
  31   |                    |                     | 
  32   |                    |                     | 
  33   |                    |                     | 
  34   |                    |                     | 
  35   |  ComponentList     |   ComponentView     | 
  36   |                    |                     | 
  37   |                    |                     | 
  38   |                    |                     | 
  39   |                    |                     | 
  40   |                    |                     | 
  41   +--------------------+---------------------+ 
  42   | AdminStatusbar                           | 
  43   +------------------------------------------- 
  44   
  45  The main class which builds everything together is a L{AdminWindow}, 
  46  which is defined in this file: 
  47   
  48    - L{AdminWindow} creates the other UI parts internally, see the 
  49      L{AdminWindow._createUI}. 
  50    - Menubar and Toolbar are created by a GtkUIManager, see 
  51      L{AdminWindow._createUI} and L{MAIN_UI}. 
  52    - L{ComponentList<flumotion.admin.gtk.componentlist.ComponentList>} 
  53      is a list of all components, and is created in the 
  54      L{flumotion.admin.gtk.componentlist} module. 
  55    - L{ComponentView<flumotion.admin.gtk.componentview.ComponentView>} 
  56      contains a component specific view, usually a set of tabs, it is 
  57      created in the L{flumotion.admin.gtk.componentview} module. 
  58    - L{AdminStatus<flumotion.admin.gtk.statusbar.AdminStatus>} is a 
  59      statusbar displaying context specific hints and is defined in the 
  60      L{flumotion.admin.gtk.statusbar} module. 
  61   
  62  """ 
  63   
  64  import gettext 
  65  import os 
  66  import sys 
  67   
  68  import gobject 
  69  import gtk 
  70  from kiwi.ui.delegates import GladeDelegate 
  71  from kiwi.ui.dialogs import yesno 
  72  from twisted.internet import defer, reactor 
  73  from zope.interface import implements 
  74   
  75  from flumotion.admin.admin import AdminModel 
  76  from flumotion.admin.connections import getRecentConnections, \ 
  77       hasRecentConnections 
  78  from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \ 
  79       ProgressDialog, showConnectionErrorDialog 
  80  from flumotion.admin.gtk.connections import ConnectionsDialog 
  81  from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList 
  82  from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog 
  83  from flumotion.admin.gtk.statusbar import AdminStatusbar 
  84  from flumotion.common.common import componentId 
  85  from flumotion.common.connection import PBConnectionInfo 
  86  from flumotion.common.errors import ConnectionRefusedError, \ 
  87       ConnectionFailedError, BusyComponentError 
  88  from flumotion.common.i18n import N_, gettexter 
  89  from flumotion.common.log import Loggable 
  90  from flumotion.common.planet import AdminComponentState, moods 
  91  from flumotion.common.pygobject import gsignal 
  92  from flumotion.configure import configure 
  93  from flumotion.manager import admin # Register types 
  94  from flumotion.twisted.flavors import IStateListener 
  95  from flumotion.ui.trayicon import FluTrayIcon 
  96  from flumotion.wizard.models import AudioProducer, Porter, VideoProducer 
  97   
  98  admin # pyflakes 
  99   
 100  __version__ = "$Rev: 7070 $" 
 101  _ = gettext.gettext 
 102  T_ = gettexter() 
 103   
 104  MAIN_UI = """ 
 105  <ui> 
 106    <menubar name="Menubar"> 
 107      <menu action="Connection"> 
 108        <menuitem action="OpenRecent"/> 
 109        <menuitem action="OpenExisting"/> 
 110        <menuitem action="ImportConfig"/> 
 111        <menuitem action="ExportConfig"/> 
 112        <separator name="sep-conn1"/> 
 113        <placeholder name="Recent"/> 
 114        <separator name="sep-conn2"/> 
 115        <menuitem action="Quit"/> 
 116      </menu> 
 117      <menu action="Manage"> 
 118        <menuitem action="StartComponent"/> 
 119        <menuitem action="StopComponent"/> 
 120        <menuitem action="DeleteComponent"/> 
 121        <separator name="sep-manage1"/> 
 122        <menuitem action="StartAll"/> 
 123        <menuitem action="StopAll"/> 
 124        <menuitem action="ClearAll"/> 
 125        <separator name="sep-manage2"/> 
 126        <menuitem action="AddFormat"/> 
 127        <separator name="sep-manage3"/> 
 128        <menuitem action="RunConfigurationWizard"/> 
 129      </menu> 
 130      <menu action="Debug"> 
 131        <menuitem action="EnableDebugging"/> 
 132        <separator name="sep-debug1"/> 
 133        <menuitem action="StartShell"/> 
 134        <menuitem action="DumpConfiguration"/> 
 135        <menuitem action="WriteDebugMarker"/> 
 136      </menu> 
 137      <menu action="Help"> 
 138        <menuitem action="About"/> 
 139      </menu> 
 140    </menubar> 
 141    <toolbar name="Toolbar"> 
 142      <toolitem action="OpenRecent"/> 
 143      <separator name="sep-toolbar1"/> 
 144      <toolitem action="StartComponent"/> 
 145      <toolitem action="StopComponent"/> 
 146      <toolitem action="DeleteComponent"/> 
 147      <separator name="sep-toolbar2"/> 
 148      <toolitem action="RunConfigurationWizard"/> 
 149    </toolbar> 
 150    <popup name="ComponentContextMenu"> 
 151      <menuitem action="StartComponent"/> 
 152      <menuitem action="StopComponent"/> 
 153      <menuitem action="DeleteComponent"/> 
 154      <menuitem action="KillComponent"/> 
 155    </popup> 
 156  </ui> 
 157  """ 
 158   
 159  RECENT_UI_TEMPLATE = '''<ui> 
 160    <menubar name="Menubar"> 
 161      <menu action="Connection"> 
 162        <placeholder name="Recent"> 
 163        %s 
 164        </placeholder> 
 165      </menu> 
 166    </menubar> 
 167  </ui>''' 
 168   
 169  MAX_RECENT_ITEMS = 4 
 170   
 171   
172 -class AdminWindow(Loggable, GladeDelegate):
173 '''Creates the GtkWindow for the user interface. 174 Also connects to the manager on the given host and port. 175 ''' 176 177 # GladeDelegate 178 gladefile = 'admin.glade' 179 toplevel_name = 'main_window' 180 181 # Loggable 182 logCategory = 'adminwindow' 183 184 # Interfaces we implement 185 implements(IStateListener) 186 187 # Signals 188 gsignal('connected') 189
190 - def __init__(self):
191 GladeDelegate.__init__(self) 192 193 self._adminModel = None 194 self._currentComponentStates = None 195 self._componentContextMenu = None 196 self._componentList = None # ComponentList 197 self._componentStates = None # name -> planet.AdminComponentState 198 self._componentView = None 199 self._debugEnabled = False 200 self._debugActions = None 201 self._debugEnableAction = None 202 self._disconnectedDialog = None # set to a dialog when disconnected 203 self._planetState = None 204 self._recentMenuID = None 205 self._trayicon = None 206 self._configurationWizardIsRunning = False 207 208 self._createUI() 209 self._appendRecentConnections() 210 self.setDebugEnabled(False)
211 212 # Public API 213 214 #FIXME: This function may not be called ever. 215 # It has not been properly tested 216 # with the multiselection (ticket #795). 217 # A ticket for reviewing that has been opened #961 218
219 - def stateSet(self, state, key, value):
220 # called by model when state of something changes 221 if not isinstance(state, AdminComponentState): 222 return 223 224 if key == 'message': 225 self.statusbar.set('main', value) 226 elif key == 'mood': 227 self._updateComponentActions() 228 current = self.components_view.getSelectedNames() 229 if value == moods.sleeping.value: 230 if state.get('name') in current: 231 self._messageView.clearMessage(value.id)
232 233 #FIXME: This function may not be called ever. 234 # It has not been properly tested 235 # with the multiselection (ticket #795). 236 # A ticket for reviewing that has been opened #961 237
238 - def componentCallRemoteStatus(self, state, pre, post, fail, 239 methodName, *args, **kwargs):
240 241 def cb(result, self, mid): 242 if mid: 243 self.statusbar.remove('main', mid) 244 if post: 245 self.statusbar.push('main', post % label)
246 247 def eb(failure, self, mid): 248 if mid: 249 self.statusbar.remove('main', mid) 250 self.warning("Failed to execute %s on component %s: %s" 251 % (methodName, label, failure)) 252 if fail: 253 self.statusbar.push('main', fail % label)
254 if not state: 255 states = self.components_view.getSelectedStates() 256 if not states: 257 return 258 for state in states: 259 self.componentCallRemoteStatus(state, pre, post, fail, 260 methodName, args, kwargs) 261 else: 262 label = getComponentLabel(state) 263 if not label: 264 return 265 266 mid = None 267 if pre: 268 mid = self.statusbar.push('main', pre % label) 269 d = self._adminModel.componentCallRemote( 270 state, methodName, *args, **kwargs) 271 d.addCallback(cb, self, mid) 272 d.addErrback(eb, self, mid) 273
274 - def componentCallRemote(self, state, methodName, *args, **kwargs):
275 self.componentCallRemoteStatus(None, None, None, None, 276 methodName, *args, **kwargs)
277
278 - def whsAppend(self, state, key, value):
279 if key == 'names': 280 self.statusbar.set( 281 'main', _('Worker %s logged in.') % value)
282
283 - def whsRemove(self, state, key, value):
284 if key == 'names': 285 self.statusbar.set( 286 'main', _('Worker %s logged out.') % value)
287
288 - def show(self):
289 self._window.show()
290
291 - def setDebugEnabled(self, enabled):
292 """Set if debug should be enabled for the admin client window 293 @param enable: if debug should be enabled 294 """ 295 self._debugEnabled = enabled 296 self._debugActions.set_sensitive(enabled) 297 self._debugEnableAction.set_active(enabled) 298 self._componentView.setDebugEnabled(enabled) 299 self._killComponentAction.set_property('visible', enabled)
300
301 - def getWindow(self):
302 """Get the gtk window for the admin interface 303 @returns: window 304 @rtype: gtk.Window 305 """ 306 return self._window
307
308 - def openConnection(self, info):
309 """Connects to a manager given a connection info 310 @param info: connection info 311 @type info: L{PBConnectionInfo} 312 """ 313 assert isinstance(info, PBConnectionInfo), info 314 return self._openConnection(info)
315 316 # Private 317
318 - def _createUI(self):
319 self.debug('creating UI') 320 321 # Widgets created in admin.glade 322 self._window = self.toplevel 323 self._componentList = ComponentList(self.component_list) 324 del self.component_list 325 self._componentView = self.component_view 326 del self.component_view 327 self._statusbar = AdminStatusbar(self.statusbar) 328 del self.statusbar 329 self._messageView = self.messages_view 330 del self.messages_view 331 332 self._window.set_name("AdminWindow") 333 self._window.connect('delete-event', self._window_delete_event_cb) 334 335 uimgr = gtk.UIManager() 336 uimgr.connect('connect-proxy', 337 self._on_uimanager__connect_proxy) 338 uimgr.connect('disconnect-proxy', 339 self._on_uimanager__disconnect_proxy) 340 341 # Normal actions 342 group = gtk.ActionGroup('Actions') 343 group.add_actions([ 344 # Connection 345 ('Connection', None, _("_Connection")), 346 ('OpenRecent', gtk.STOCK_OPEN, _('_Open Recent Connection...'), 347 None, _('Connect to a recently used connection'), 348 self._connection_open_recent_cb), 349 ('OpenExisting', None, _('Connect to _running manager...'), None, 350 _('Connect to an previously used connection'), 351 self._connection_open_existing_cb), 352 ('ImportConfig', None, _('_Import Configuration...'), None, 353 _('Import configuration from a file'), 354 self._connection_import_configuration_cb), 355 ('ExportConfig', None, _('_Export Configuration...'), None, 356 _('Export current configuration to a file'), 357 self._connection_export_configuration_cb), 358 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None, 359 _('Quit the application and disconnect from the manager'), 360 self._connection_quit_cb), 361 362 # Manage 363 ('Manage', None, _('_Manage')), 364 ('StartComponent', 'flumotion-play', _('_Start Component(s)'), 365 None, _('Start the selected component(s)'), 366 self._manage_start_component_cb), 367 ('StopComponent', 'flumotion-stop', _('St_op Component(s)'), 368 None, _('Stop the selected component(s)'), 369 self._manage_stop_component_cb), 370 ('DeleteComponent', gtk.STOCK_DELETE, _('_Delete Component(s)'), 371 None, _('Delete the selected component(s)'), 372 self._manage_delete_component_cb), 373 ('StartAll', None, _('Start _All'), None, 374 _('Start all components'), 375 self._manage_start_all_cb), 376 ('StopAll', None, _('Stop A_ll'), None, 377 _('Stop all components'), 378 self._manage_stop_all_cb), 379 ('ClearAll', gtk.STOCK_CLEAR, _('_Clear All'), None, 380 _('Remove all components'), 381 self._manage_clear_all_cb), 382 ('AddFormat', gtk.STOCK_ADD, _('Add new encoding _format...'), None, 383 _('Add a new format to the current stream'), 384 self._manage_add_format_cb), 385 ('RunConfigurationWizard', 'flumotion-wizard', _('Run _Wizard'), None, 386 _('Run the configuration wizard'), 387 self._manage_run_wizard_cb), 388 389 # Debug 390 ('Debug', None, _('_Debug')), 391 392 # Help 393 ('Help', None, _('_Help')), 394 ('About', gtk.STOCK_ABOUT, _('_About'), None, 395 _('Displays an about dialog'), 396 self._help_about_cb), 397 398 # Only in context menu 399 ('KillComponent', None, _('_Kill Component'), None, 400 _('Kills the currently selected component'), 401 self._kill_component_cb), 402 403 ]) 404 group.add_toggle_actions([ 405 ('EnableDebugging', None, _('Enable _Debugging'), None, 406 _('Enable debugging in the admin interface'), 407 self._debug_enable_cb), 408 ]) 409 self._debugEnableAction = group.get_action('EnableDebugging') 410 uimgr.insert_action_group(group, 0) 411 412 # Debug actions 413 self._debugActions = gtk.ActionGroup('Actions') 414 self._debugActions.add_actions([ 415 # Debug 416 ('StartShell', gtk.STOCK_EXECUTE, _('Start _Shell'), None, 417 _('Start an interactive debugging shell'), 418 self._debug_start_shell_cb), 419 ('DumpConfiguration', gtk.STOCK_EXECUTE, 420 _('Dump configuration'), None, 421 _('Dumps the current manager configuration'), 422 self._debug_dump_configuration_cb), 423 ('WriteDebugMarker', gtk.STOCK_EXECUTE, 424 _('Write debug marker...'), None, 425 _('Writes a debug marker to all the logs'), 426 self._debug_write_debug_marker_cb) 427 ]) 428 uimgr.insert_action_group(self._debugActions, 0) 429 self._debugActions.set_sensitive(False) 430 431 uimgr.add_ui_from_string(MAIN_UI) 432 self._window.add_accel_group(uimgr.get_accel_group()) 433 434 menubar = uimgr.get_widget('/Menubar') 435 self.main_vbox.pack_start(menubar, expand=False) 436 self.main_vbox.reorder_child(menubar, 0) 437 438 toolbar = uimgr.get_widget('/Toolbar') 439 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) 440 toolbar.set_style(gtk.TOOLBAR_ICONS) 441 self.main_vbox.pack_start(toolbar, expand=False) 442 self.main_vbox.reorder_child(toolbar, 1) 443 444 self._componentContextMenu = uimgr.get_widget('/ComponentContextMenu') 445 self._componentContextMenu.show() 446 447 menubar.show_all() 448 449 self._actiongroup = group 450 self._uimgr = uimgr 451 self._openRecentAction = group.get_action("OpenRecent") 452 self._startComponentAction = group.get_action("StartComponent") 453 self._stopComponentAction = group.get_action("StopComponent") 454 self._deleteComponentAction = group.get_action("DeleteComponent") 455 self._stopAllAction = group.get_action("StopAll") 456 assert self._stopAllAction 457 self._startAllAction = group.get_action("StartAll") 458 assert self._startAllAction 459 self._clearAllAction = group.get_action("ClearAll") 460 assert self._clearAllAction 461 self._addFormatAction = group.get_action("AddFormat") 462 assert self._addFormatAction 463 self._killComponentAction = group.get_action("KillComponent") 464 assert self._killComponentAction 465 466 self._trayicon = FluTrayIcon(self._window) 467 self._trayicon.connect("quit", self._trayicon_quit_cb) 468 self._trayicon.set_tooltip(_('Not connected')) 469 470 self._componentList.connect('selection_changed', 471 self._components_selection_changed_cb) 472 self._componentList.connect('show-popup-menu', 473 self._components_show_popup_menu_cb) 474 475 self._updateComponentActions() 476 self._componentList.connect( 477 'notify::can-start-any', 478 self._components_start_stop_notify_cb) 479 self._componentList.connect( 480 'notify::can-stop-any', 481 self._components_start_stop_notify_cb) 482 self._updateComponentActions() 483 484 self._messageView.hide()
485
486 - def _connectActionProxy(self, action, widget):
487 tooltip = action.get_property('tooltip') 488 if not tooltip: 489 return 490 491 if isinstance(widget, gtk.MenuItem): 492 cid = widget.connect('select', self._on_menu_item__select, 493 tooltip) 494 cid2 = widget.connect('deselect', self._on_menu_item__deselect) 495 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2)) 496 elif isinstance(widget, gtk.ToolButton): 497 cid = widget.child.connect('enter', self._on_tool_button__enter, 498 tooltip) 499 cid2 = widget.child.connect('leave', self._on_tool_button__leave) 500 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
501
502 - def _disconnectActionProxy(self, action, widget):
503 cids = widget.get_data('pygtk-app::proxy-signal-ids') 504 if not cids: 505 return 506 507 if isinstance(widget, gtk.ToolButton): 508 widget = widget.child 509 510 for name, cid in cids: 511 widget.disconnect(cid)
512
513 - def _setAdminModel(self, model):
514 'set the model to which we are a view/controller' 515 # it's ok if we've already been connected 516 self.debug('setting model') 517 518 if self._adminModel: 519 self.debug('Connecting to new model %r' % model) 520 521 self._adminModel = model 522 523 # window gets created after model connects initially, so check 524 # here 525 if self._adminModel.isConnected(): 526 self._connectionOpened(model) 527 528 self._adminModel.connect('connected', 529 self._admin_connected_cb) 530 self._adminModel.connect('disconnected', 531 self._admin_disconnected_cb) 532 self._adminModel.connect('connection-refused', 533 self._admin_connection_refused_cb) 534 self._adminModel.connect('connection-failed', 535 self._admin_connection_failed_cb) 536 self._adminModel.connect('update', self._admin_update_cb)
537
538 - def _openConnection(self, info):
539 self._trayicon.set_tooltip(_("Connecting to %s:%s") % ( 540 info.host, info.port)) 541 542 def connected(model): 543 self._setAdminModel(model) 544 self._appendRecentConnections()
545 546 model = AdminModel() 547 d = model.connectToManager(info) 548 d.addCallback(connected) 549 return d 550
551 - def _openConnectionInternal(self, info):
552 d = self._openConnection(info) 553 554 def errorMessageDisplayed(unused): 555 self._window.set_sensitive(True)
556 557 def connected(model): 558 self._window.set_sensitive(True) 559 560 def errbackConnectionRefusedError(failure): 561 failure.trap(ConnectionRefusedError) 562 d = showConnectionErrorDialog(failure, info, parent=self._window) 563 d.addCallback(errorMessageDisplayed) 564 565 def errbackConnectionFailedError(failure): 566 failure.trap(ConnectionFailedError) 567 d = showConnectionErrorDialog(failure, info, parent=self._window) 568 d.addCallback(errorMessageDisplayed) 569 return d 570 571 d.addCallback(connected) 572 d.addErrback(errbackConnectionRefusedError) 573 d.addErrback(errbackConnectionFailedError) 574 self._window.set_sensitive(False) 575 return d 576
577 - def _appendRecentConnections(self):
578 if self._recentMenuID: 579 self._uimgr.remove_ui(self._recentMenuID) 580 self._uimgr.ensure_update() 581 582 ui = "" 583 connections = getRecentConnections()[:MAX_RECENT_ITEMS] 584 for conn in connections: 585 name = conn.host 586 ui += '<menuitem action="%s"/>' % name 587 action = gtk.Action(name, name, 588 _('Connect to the manager on %s') % conn.host, 589 '') 590 action.connect('activate', self._recent_action_activate_cb, conn) 591 self._actiongroup.add_action(action) 592 593 self._recentMenuID = self._uimgr.add_ui_from_string( 594 RECENT_UI_TEMPLATE % ui) 595 self._openRecentAction.set_sensitive(len(connections))
596
597 - def _quit(self):
598 """Quitting the application in a controlled manner""" 599 self._clearAdmin() 600 self._close()
601
602 - def _close(self, *args):
603 reactor.stop()
604
605 - def _dumpConfig(self, configation):
606 import pprint 607 import cStringIO 608 fd = cStringIO.StringIO() 609 pprint.pprint(configation, fd) 610 fd.seek(0) 611 self.debug('Configuration=%s' % fd.read())
612
613 - def _error(self, message):
614 errorDialog = ErrorDialog(message, self._window, 615 close_on_response=True) 616 errorDialog.show()
617
618 - def _fatalError(self, message, tray=None):
619 if tray: 620 self._trayicon.set_tooltip(tray) 621 622 self.info(message) 623 errorDialog = ErrorDialog(message, self._window) 624 errorDialog.show() 625 errorDialog.connect('response', self._close)
626
627 - def _setStatusbarText(self, text):
628 return self._statusbar.push('main', text)
629
630 - def _clearLastStatusbarText(self):
631 self._statusbar.pop('main')
632
633 - def _wizardFinshed(self, wizard, configuration):
634 wizard.destroy() 635 self._configurationWizardIsRunning = False 636 self._dumpConfig(configuration) 637 self._adminModel.loadConfiguration(configuration) 638 self._clearMessages() 639 self._statusbar.clear(None) 640 self._updateComponentActions() 641 self.show()
642
643 - def _getComponentBy(self, componentType):
644 if componentType is None: 645 raise ValueError 646 componentStates = [] 647 648 for state in self._componentStates.values(): 649 config = state.get('config') 650 if componentType and config['type'] == componentType: 651 componentStates.append(state) 652 653 if not componentStates: 654 return None 655 elif len(componentStates) == 1: 656 return componentStates[0] 657 else: 658 raise AssertionError( 659 "Attempted to fetch a component state by type %r, " 660 "expected one, but got %r" % ( 661 componentType, componentStates))
662
663 - def _getHTTPPorter(self):
664 porterState = self._getComponentBy(componentType='porter') 665 if porterState is None: 666 return None 667 properties = porterState.get('config')['properties'] 668 porter = Porter(worker=None, 669 port=properties['port'], 670 username=properties['username'], 671 password=properties['password'], 672 socketPath=properties['socket-path']) 673 porter.exists = True 674 return porter
675
676 - def _createComponentsByWizardType(self, componentClass, entries):
677 def _getComponents(): 678 for componentState in self._componentStates.values(): 679 componentType = componentState.get('config')['type'] 680 for entry in entries: 681 if entry.componentType == componentType: 682 yield (componentState, entry)
683 684 685 for componentState, entry in _getComponents(): 686 component = componentClass() 687 component.componentType = entry.componentType 688 component.description = entry.description 689 component.exists = True 690 config = componentState.get('config') 691 for key, value in config['properties'].items(): 692 component.properties[key] = value 693 yield component 694
695 - def _runAddNewFormatWizard(self):
696 from flumotion.admin.gtk.addformatwizard import AddFormatWizard 697 addFormatWizard = AddFormatWizard(self._window) 698 def cb(entries): 699 entryDict = {} 700 for entry in entries: 701 entryDict.setdefault(entry.type, []).append(entry) 702 703 audioProducers = self._createComponentsByWizardType( 704 AudioProducer, entryDict['audio-producer'], ) 705 videoProducers = self._createComponentsByWizardType( 706 VideoProducer, entryDict['video-producer']) 707 addFormatWizard.setAudioProducers(audioProducers) 708 addFormatWizard.setVideoProducers(videoProducers) 709 self._runWizard(addFormatWizard)
710 711 d = self._adminModel.getWizardEntries( 712 wizardTypes=['audio-producer', 'video-producer']) 713 d.addCallback(cb) 714
715 - def _runConfigurationWizard(self):
716 from flumotion.wizard.configurationwizard import ConfigurationWizard 717 718 def runWizard(): 719 configurationWizard = ConfigurationWizard(self._window) 720 self._runWizard(configurationWizard) 721 self._configurationWizardIsRunning = True
722 723 if not self._componentStates: 724 runWizard() 725 return 726 727 if yesno(_("Running the Configuration Wizard again will remove " 728 "all components from the current stream and create " 729 "a new one."), 730 parent=self._window, 731 buttons=((_("Keep the current stream"), gtk.RESPONSE_NO), 732 (_("Run the Wizard anyway"), gtk.RESPONSE_YES)) 733 ) != gtk.RESPONSE_YES: 734 return 735 736 d = self._clearAllComponents() 737 d.addCallback(lambda unused: runWizard()) 738
739 - def _runWizard(self, wizard):
740 workerHeavenState = self._adminModel.getWorkerHeavenState() 741 if not workerHeavenState.get('names'): 742 self._error( 743 _('The wizard cannot be run because no workers are ' 744 'logged in.')) 745 return 746 747 wizard.setExistingComponentNames( 748 self._componentList.getComponentNames()) 749 wizard.setAdminModel(self._adminModel) 750 wizard.setWorkerHeavenState(workerHeavenState) 751 httpPorter = self._getHTTPPorter() 752 if httpPorter: 753 wizard.setHTTPPorter(httpPorter) 754 wizard.connect('finished', self._wizard_finished_cb) 755 wizard.run(main=False)
756
757 - def _clearAdmin(self):
758 if not self._adminModel: 759 return 760 761 self._adminModel.disconnect_by_func(self._admin_connected_cb) 762 self._adminModel.disconnect_by_func(self._admin_disconnected_cb) 763 self._adminModel.disconnect_by_func(self._admin_connection_refused_cb) 764 self._adminModel.disconnect_by_func(self._admin_connection_failed_cb) 765 self._adminModel.disconnect_by_func(self._admin_update_cb) 766 self._adminModel = None
767
768 - def _updateConnectionActions(self):
769 self._openRecentAction.set_sensitive(hasRecentConnections())
770
771 - def _updateComponentActions(self):
772 canStart = self._componentList.canStart() 773 canStop = self._componentList.canStop() 774 canDelete = bool(self._currentComponentStates and canStart) 775 self._startComponentAction.set_sensitive(canStart) 776 self._stopComponentAction.set_sensitive(canStop) 777 self._deleteComponentAction.set_sensitive(canDelete) 778 self.debug('can start %r, can stop %r' % (canStart, canStop)) 779 canStartAll = self._componentList.get_property('can-start-any') 780 canStopAll = self._componentList.get_property('can-stop-any') 781 782 # they're all in sleeping or lost 783 canClearAll = canStartAll and not canStopAll 784 self._stopAllAction.set_sensitive(canStopAll) 785 self._startAllAction.set_sensitive(canStartAll) 786 self._clearAllAction.set_sensitive(canClearAll) 787 self._killComponentAction.set_sensitive(canStop) 788 789 hasProducer = self._hasProducerComponent() 790 self._addFormatAction.set_sensitive(hasProducer)
791
792 - def _updateComponents(self):
793 self._componentList.clearAndRebuild(self._componentStates) 794 self._trayicon.update(self._componentStates)
795
796 - def _hasProducerComponent(self):
797 for state in self._componentList.getComponentStates(): 798 if state is None: 799 continue 800 # FIXME: Not correct, should expose wizard state from 801 # the registry. 802 name = state.get('name') 803 if 'producer' in name: 804 return True 805 return False
806
807 - def _clearMessages(self):
808 self._messageView.clear() 809 pstate = self._planetState 810 if pstate and pstate.hasKey('messages'): 811 for message in pstate.get('messages').values(): 812 self._messageView.addMessage(message)
813
814 - def _setPlanetState(self, planetState):
815 816 def flowStateAppend(state, key, value): 817 self.debug('flow state append: key %s, value %r' % (key, value)) 818 if key == 'components': 819 self._componentStates[value.get('name')] = value 820 # FIXME: would be nicer to do this incrementally instead 821 self._updateComponents()
822 823 def flowStateRemove(state, key, value): 824 if key == 'components': 825 self._removeComponent(value) 826 827 def atmosphereStateAppend(state, key, value): 828 if key == 'components': 829 self._componentStates[value.get('name')] = value 830 # FIXME: would be nicer to do this incrementally instead 831 self._updateComponents() 832 833 def atmosphereStateRemove(state, key, value): 834 if key == 'components': 835 self._removeComponent(value) 836 837 def planetStateAppend(state, key, value): 838 if key == 'flows': 839 if value != state.get('flows')[0]: 840 self.warning('flumotion-admin can only handle one ' 841 'flow, ignoring /%s', value.get('name')) 842 return 843 self.debug('%s flow started', value.get('name')) 844 value.addListener(self, append=flowStateAppend, 845 remove=flowStateRemove) 846 for c in value.get('components'): 847 flowStateAppend(value, 'components', c) 848 self._updateComponents() 849 850 def planetStateRemove(state, key, value): 851 self.debug('something got removed from the planet') 852 853 def planetStateSetitem(state, key, subkey, value): 854 if key == 'messages': 855 self._messageView.addMessage(value) 856 857 def planetStateDelitem(state, key, subkey, value): 858 if key == 'messages': 859 self._messageView.clearMessage(value.id) 860 861 self.debug('parsing planetState %r' % planetState) 862 self._planetState = planetState 863 864 # clear and rebuild list of components that interests us 865 self._componentStates = {} 866 867 planetState.addListener(self, append=planetStateAppend, 868 remove=planetStateRemove, 869 setitem=planetStateSetitem, 870 delitem=planetStateDelitem) 871 872 self._clearMessages() 873 874 a = planetState.get('atmosphere') 875 a.addListener(self, append=atmosphereStateAppend, 876 remove=atmosphereStateRemove) 877 for c in a.get('components'): 878 atmosphereStateAppend(a, 'components', c) 879 880 for f in planetState.get('flows'): 881 planetStateAppend(planetState, 'flows', f) 882
883 - def _clearAllComponents(self):
884 d = self._adminModel.cleanComponents() 885 def busyComponentError(failure): 886 failure.trap(BusyComponentError) 887 self._error( 888 _("Some component(s) are still busy and cannot be removed.\n" 889 "Try again later."))
890 d.addErrback(busyComponentError) 891 return d 892 893 # component view activation functions 894
895 - def _removeComponent(self, state):
896 name = state.get('name') 897 self.debug('removing component %s' % name) 898 del self._componentStates[name] 899 900 # if this component was selected, clear selection 901 if self._currentComponentStates and state \ 902 in self._currentComponentStates: 903 self._currentComponentStates.remove(state) 904 # FIXME: would be nicer to do this incrementally instead 905 self._updateComponents() 906 # a component being removed means our selected component could 907 # have gone away 908 self._updateComponentActions()
909
910 - def _componentStop(self, state):
911 """ 912 @returns: a L{twisted.internet.defer.Deferred} 913 """ 914 return self._componentDo(state, 'componentStop', 915 'Stop', 'Stopping', 'Stopped')
916
917 - def _componentStart(self, state):
918 """ 919 @returns: a L{twisted.internet.defer.Deferred} 920 """ 921 return self._componentDo(state, 'componentStart', 922 'Start', 'Starting', 'Started')
923
924 - def _componentDelete(self, state):
925 """ 926 @returns: a L{twisted.internet.defer.Deferred} 927 """ 928 return self._componentDo(state, 'deleteComponent', 929 'Delete', 'Deleting', 'Deleted')
930
931 - def _componentDo(self, state, methodName, action, doing, done):
932 """Do something with a component and update the statusbar 933 @param state: componentState 934 @type state: L{AdminComponentState} 935 @param methodName: name of the method to call 936 @type methodName: str 937 @param action: string used to explain that to do 938 @type action: str 939 @param doing: string used to explain that the action started 940 @type doing: str 941 @param done: string used to explain that the action was completed 942 @type done: str 943 """ 944 if state is None: 945 states = self._componentList.getSelectedStates() 946 else: 947 states = [state] 948 949 if not states: 950 return