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 gtk import gdk 
  71  from gtk import keysyms 
  72  from kiwi.ui.delegates import GladeDelegate 
  73  from kiwi.ui.dialogs import yesno 
  74  from twisted.internet import defer, reactor 
  75  from zope.interface import implements 
  76   
  77  from flumotion.admin.admin import AdminModel 
  78  from flumotion.admin.assistant.models import AudioProducer, Porter, \ 
  79       VideoProducer, Muxer 
  80  from flumotion.admin.connections import getRecentConnections, \ 
  81       hasRecentConnections 
  82  from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \ 
  83       ProgressDialog, showConnectionErrorDialog 
  84  from flumotion.admin.gtk.connections import ConnectionsDialog 
  85  from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList 
  86  from flumotion.admin.gtk.componentview import MultipleAdminComponentStates 
  87  from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog 
  88  from flumotion.admin.gtk.statusbar import AdminStatusbar 
  89  from flumotion.common.common import componentId 
  90  from flumotion.common.connection import PBConnectionInfo 
  91  from flumotion.common.errors import ConnectionCancelledError, \ 
  92       ConnectionRefusedError, ConnectionFailedError, BusyComponentError 
  93  from flumotion.common.i18n import N_, gettexter 
  94  from flumotion.common.log import Loggable 
  95  from flumotion.common.planet import AdminComponentState, moods 
  96  from flumotion.common.pygobject import gsignal 
  97  from flumotion.configure import configure 
  98  from flumotion.manager import admin # Register types 
  99  from flumotion.twisted.flavors import IStateListener 
 100  from flumotion.ui.trayicon import FluTrayIcon 
 101   
 102  admin # pyflakes 
 103   
 104  __version__ = "$Rev: 8747 $" 
 105  _ = gettext.gettext 
 106  T_ = gettexter() 
 107   
 108  MAIN_UI = """ 
 109  <ui> 
 110    <menubar name="Menubar"> 
 111      <menu action="Connection"> 
 112        <menuitem action="OpenRecent"/> 
 113        <menuitem action="OpenExisting"/> 
 114        <menuitem action="ImportConfig"/> 
 115        <menuitem action="ExportConfig"/> 
 116        <separator name="sep-conn1"/> 
 117        <placeholder name="Recent"/> 
 118        <separator name="sep-conn2"/> 
 119        <menuitem action="Quit"/> 
 120      </menu> 
 121      <menu action="Manage"> 
 122        <menuitem action="StartComponent"/> 
 123        <menuitem action="StopComponent"/> 
 124        <menuitem action="DeleteComponent"/> 
 125        <separator name="sep-manage1"/> 
 126        <menuitem action="StartAll"/> 
 127        <menuitem action="StopAll"/> 
 128        <menuitem action="ClearAll"/> 
 129        <separator name="sep-manage2"/> 
 130        <menuitem action="AddFormat"/> 
 131        <menuitem action="AddStreamer"/> 
 132        <separator name="sep-manage3"/> 
 133        <menuitem action="RunConfigurationAssistant"/> 
 134      </menu> 
 135      <menu action="Debug"> 
 136        <menuitem action="EnableDebugging"/> 
 137        <separator name="sep-debug1"/> 
 138        <menuitem action="StartShell"/> 
 139        <menuitem action="DumpConfiguration"/> 
 140        <menuitem action="WriteDebugMarker"/> 
 141      </menu> 
 142      <menu action="Help"> 
 143        <menuitem action="Contents"/> 
 144        <menuitem action="About"/> 
 145      </menu> 
 146    </menubar> 
 147    <toolbar name="Toolbar"> 
 148      <toolitem action="OpenRecent"/> 
 149      <separator name="sep-toolbar1"/> 
 150      <toolitem action="StartComponent"/> 
 151      <toolitem action="StopComponent"/> 
 152      <toolitem action="DeleteComponent"/> 
 153      <separator name="sep-toolbar2"/> 
 154      <toolitem action="RunConfigurationAssistant"/> 
 155    </toolbar> 
 156    <popup name="ComponentContextMenu"> 
 157      <menuitem action="StartComponent"/> 
 158      <menuitem action="StopComponent"/> 
 159      <menuitem action="DeleteComponent"/> 
 160      <menuitem action="KillComponent"/> 
 161    </popup> 
 162  </ui> 
 163  """ 
 164   
 165  RECENT_UI_TEMPLATE = '''<ui> 
 166    <menubar name="Menubar"> 
 167      <menu action="Connection"> 
 168        <placeholder name="Recent"> 
 169        %s 
 170        </placeholder> 
 171      </menu> 
 172    </menubar> 
 173  </ui>''' 
 174   
 175  MAX_RECENT_ITEMS = 4 
 176   
 177   
178 -class AdminWindow(Loggable, GladeDelegate):
179 '''Creates the GtkWindow for the user interface. 180 Also connects to the manager on the given host and port. 181 ''' 182 183 # GladeDelegate 184 gladefile = 'admin.glade' 185 toplevel_name = 'main_window' 186 187 # Loggable 188 logCategory = 'adminwindow' 189 190 # Interfaces we implement 191 implements(IStateListener) 192 193 # Signals 194 gsignal('connected') 195
196 - def __init__(self):
197 GladeDelegate.__init__(self) 198 199 self._adminModel = None 200 self._currentComponentStates = None 201 self._componentContextMenu = None 202 self._componentList = None # ComponentList 203 self._componentStates = None # name -> planet.AdminComponentState 204 self._componentView = None 205 self._componentNameToSelect = None 206 self._debugEnabled = False 207 self._debugActions = None 208 self._debugEnableAction = None 209 self._disconnectedDialog = None # set to a dialog when disconnected 210 self._planetState = None 211 self._recentMenuID = None 212 self._trayicon = None 213 self._configurationAssistantIsRunning = False 214 self._currentDir = None 215 self._managerSpawner = None 216 217 self._createUI() 218 self._appendRecentConnections() 219 self.setDebugEnabled(False)
220 221 # Public API 222 223 #FIXME: This function may not be called ever. 224 # It has not been properly tested 225 # with the multiselection (ticket #795). 226 # A ticket for reviewing that has been opened #961 227
228 - def stateSet(self, state, key, value):
229 # called by model when state of something changes 230 if not isinstance(state, AdminComponentState): 231 return 232 233 if key == 'message': 234 self.statusbar.set('main', value) 235 elif key == 'mood': 236 self.debug('state %r has mood set to %r' % (state, value)) 237 self._updateComponentActions() 238 current = self.components_view.getSelectedNames() 239 if value == moods.sleeping.value: 240 if state.get('name') in current: 241 self._messageView.clearMessage(value.id)
242 243 #FIXME: This function may not be called ever. 244 # It has not been properly tested 245 # with the multiselection (ticket #795). 246 # A ticket for reviewing that has been opened #961 247
248 - def componentCallRemoteStatus(self, state, pre, post, fail, 249 methodName, *args, **kwargs):
250 251 def cb(result, self, mid): 252 if mid: 253 self.statusbar.remove('main', mid) 254 if post: 255 self.statusbar.push('main', post % label)
256 257 def eb(failure, self, mid): 258 if mid: 259 self.statusbar.remove('main', mid) 260 self.warning("Failed to execute %s on component %s: %s" 261 % (methodName, label, failure)) 262 if fail: 263 self.statusbar.push('main', fail % label)
264 if not state: 265 states = self.components_view.getSelectedStates() 266 if not states: 267 return 268 for state in states: 269 self.componentCallRemoteStatus(state, pre, post, fail, 270 methodName, args, kwargs) 271 else: 272 label = getComponentLabel(state) 273 if not label: 274 return 275 276 mid = None 277 if pre: 278 mid = self.statusbar.push('main', pre % label) 279 d = self._adminModel.componentCallRemote( 280 state, methodName, *args, **kwargs) 281 d.addCallback(cb, self, mid) 282 d.addErrback(eb, self, mid) 283
284 - def componentCallRemote(self, state, methodName, *args, **kwargs):
285 self.componentCallRemoteStatus(None, None, None, None, 286 methodName, *args, **kwargs)
287
288 - def whsAppend(self, state, key, value):
289 if key == 'names': 290 self._componentList.workerAppend(value) 291 self._clearLastStatusbarText() 292 self._setStatusbarText(_('Worker %s logged in.') % value)
293
294 - def whsRemove(self, state, key, value):
295 if key == 'names': 296 self._componentList.workerRemove(value) 297 self._clearLastStatusbarText() 298 self._setStatusbarText(_('Worker %s logged out.') % value)
299
300 - def show(self):
301 self._window.show()
302
303 - def setDebugEnabled(self, enabled):
304 """Set if debug should be enabled for the admin client window 305 @param enable: if debug should be enabled 306 """ 307 self._debugEnabled = enabled 308 self._debugActions.set_sensitive(enabled) 309 self._debugEnableAction.set_active(enabled) 310 self._componentView.setDebugEnabled(enabled) 311 self._killComponentAction.set_property('visible', enabled)
312
313 - def getWindow(self):
314 """Get the gtk window for the admin interface. 315 316 @returns: window 317 @rtype: L{gtk.Window} 318 """ 319 return self._window
320
321 - def openConnection(self, info, managerSpawner=None):
322 """Connects to a manager given a connection info. 323 324 @param info: connection info 325 @type info: L{PBConnectionInfo} 326 """ 327 assert isinstance(info, PBConnectionInfo), info 328 self._managerSpawner = managerSpawner 329 return self._openConnection(info)
330 331 # Private 332
333 - def _resize_vpaned(self, widget, minimize):
334 if minimize: 335 self._eat_resize_id = self._vpaned.connect( 336 'button-press-event', self._eat_resize_vpaned_event) 337 self._vpaned.set_position(-1) 338 else: 339 self._vpaned.disconnect(self._eat_resize_id)
340
341 - def _eat_resize_vpaned_event(self, *args, **kwargs):
342 # Eat button-press-event not to allow resize of the vpaned 343 return True
344
345 - def _createUI(self):
346 self.debug('creating UI') 347 348 # Widgets created in admin.glade 349 self._window = self.toplevel 350 self._componentList = ComponentList(self.component_list) 351 del self.component_list 352 self._componentView = self.component_view 353 del self.component_view 354 self._statusbar = AdminStatusbar(self.statusbar) 355 del self.statusbar 356 self._messageView = self.messages_view 357 del self.messages_view 358 359 self._messageView.connect('resize-event', self._resize_vpaned) 360 self._vpaned = self.vpaned 361 del self.vpaned 362 self._eat_resize_id = self._vpaned.connect( 363 'button-press-event', self._eat_resize_vpaned_event) 364 365 self._window.set_name("AdminWindow") 366 self._window.connect('delete-event', 367 self._window_delete_event_cb) 368 self._window.connect('key-press-event', 369 self._window_key_press_event_cb) 370 371 uimgr = gtk.UIManager() 372 uimgr.connect('connect-proxy', 373 self._on_uimanager__connect_proxy) 374 uimgr.connect('disconnect-proxy', 375 self._on_uimanager__disconnect_proxy) 376 377 # Normal actions 378 group = gtk.ActionGroup('Actions') 379 group.add_actions([ 380 # Connection 381 ('Connection', None, _("_Connection")), 382 ('OpenRecent', gtk.STOCK_OPEN, _('_Open Recent Connection...'), 383 None, _('Connect to a recently used connection'), 384 self._connection_open_recent_cb), 385 ('OpenExisting', None, _('Connect to _running manager...'), None, 386 _('Connect to a previously used connection'), 387 self._connection_open_existing_cb), 388 ('ImportConfig', None, _('_Import Configuration...'), None, 389 _('Import a configuration from a file'), 390 self._connection_import_configuration_cb), 391 ('ExportConfig', None, _('_Export Configuration...'), None, 392 _('Export the current configuration to a file'), 393 self._connection_export_configuration_cb), 394 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None, 395 _('Quit the application and disconnect from the manager'), 396 self._connection_quit_cb), 397 398 # Manage 399 ('Manage', None, _('_Manage')), 400 ('StartComponent', gtk.STOCK_MEDIA_PLAY, _('_Start Component(s)'), 401 None, _('Start the selected component(s)'), 402 self._manage_start_component_cb), 403 ('StopComponent', gtk.STOCK_MEDIA_STOP, _('St_op Component(s)'), 404 None, _('Stop the selected component(s)'), 405 self._manage_stop_component_cb), 406 ('DeleteComponent', gtk.STOCK_DELETE, _('_Delete Component(s)'), 407 None, _('Delete the selected component(s)'), 408 self._manage_delete_component_cb), 409 ('StartAll', None, _('Start _All'), None, 410 _('Start all components'), 411 self._manage_start_all_cb), 412 ('StopAll', None, _('Stop A_ll'), None, 413 _('Stop all components'), 414 self._manage_stop_all_cb), 415 ('ClearAll', gtk.STOCK_CLEAR, _('_Clear All'), None, 416 _('Remove all components'), 417 self._manage_clear_all_cb), 418 ('AddFormat', gtk.STOCK_ADD, _('Add new encoding _format...'), 419 None, 420 _('Add a new format to the current stream'), 421 self._manage_add_format_cb), 422 ('AddStreamer', gtk.STOCK_ADD, _('Add new _streamer...'), 423 None, 424 _('Add a new streamer to the flow'), 425 self._manage_add_streamer_cb), 426 ('RunConfigurationAssistant', 'flumotion.admin.gtk', 427 _('Run _Assistant'), None, 428 _('Run the configuration assistant'), 429 self._manage_run_assistant_cb), 430 431 # Debug 432 ('Debug', None, _('_Debug')), 433 434 # Help 435 ('Help', None, _('_Help')), 436 ('Contents', gtk.STOCK_HELP, _('_Contents'), 'F1', 437 _('Open the Flumotion manual'), 438 self._help_contents_cb), 439 ('About', gtk.STOCK_ABOUT, _('_About'), None, 440 _('About this software'), 441 self._help_about_cb), 442 443 # Only in context menu 444 ('KillComponent', None, _('_Kill Component'), None, 445 _('Kills the currently selected component'), 446 self._kill_component_cb), 447 448 ]) 449 group.add_toggle_actions([ 450 ('EnableDebugging', None, _('Enable _Debugging'), None, 451 _('Enable debugging in the admin interface'), 452 self._debug_enable_cb), 453 ]) 454 self._debugEnableAction = group.get_action('EnableDebugging') 455 uimgr.insert_action_group(group, 0) 456 457 # Debug actions 458 self._debugActions = gtk.ActionGroup('Actions') 459 self._debugActions.add_actions([ 460 # Debug 461 ('StartShell', gtk.STOCK_EXECUTE, _('Start _Shell'), None, 462 _('Start an interactive debugging shell'), 463 self._debug_start_shell_cb), 464 ('DumpConfiguration', gtk.STOCK_EXECUTE, 465 _('Dump configuration'), None, 466 _('Dumps the current manager configuration'), 467 self._debug_dump_configuration_cb), 468 ('WriteDebugMarker', gtk.STOCK_EXECUTE, 469 _('Write debug marker...'), None, 470 _('Writes a debug marker to all the logs'), 471 self._debug_write_debug_marker_cb)]) 472 uimgr.insert_action_group(self._debugActions, 0) 473 self._debugActions.set_sensitive(False) 474 475 uimgr.add_ui_from_string(MAIN_UI) 476 self._window.add_accel_group(uimgr.get_accel_group()) 477 478 menubar = uimgr.get_widget('/Menubar') 479 self.main_vbox.pack_start(menubar, expand=False) 480 self.main_vbox.reorder_child(menubar, 0) 481 482 toolbar = uimgr.get_widget('/Toolbar') 483 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) 484 toolbar.set_style(gtk.TOOLBAR_ICONS) 485 self.main_vbox.pack_start(toolbar, expand=False) 486 self.main_vbox.reorder_child(toolbar, 1) 487 488 self._componentContextMenu = uimgr.get_widget('/ComponentContextMenu') 489 self._componentContextMenu.show() 490 491 menubar.show_all() 492 493 self._actiongroup = group 494 self._uimgr = uimgr 495 self._openRecentAction = group.get_action("OpenRecent") 496 self._startComponentAction = group.get_action("StartComponent") 497 self._stopComponentAction = group.get_action("StopComponent") 498 self._deleteComponentAction = group.get_action("DeleteComponent") 499 self._stopAllAction = group.get_action("StopAll") 500 assert self._stopAllAction 501 self._startAllAction = group.get_action("StartAll") 502 assert self._startAllAction 503 self._clearAllAction = group.get_action("ClearAll") 504 assert self._clearAllAction 505 self._addFormatAction = group.get_action("AddFormat") 506 self._addFormatAction.set_sensitive(False) 507 self._addStreamerAction = group.get_action("AddStreamer") 508 self._addStreamerAction.set_sensitive(False) 509 self._runConfigurationAssistantAction = ( 510 group.get_action("RunConfigurationAssistant")) 511 self._runConfigurationAssistantAction.set_sensitive(False) 512 self._killComponentAction = group.get_action("KillComponent") 513 assert self._killComponentAction 514 515 self._trayicon = FluTrayIcon(self._window) 516 self._trayicon.connect("quit", self._trayicon_quit_cb) 517 self._trayicon.set_tooltip(_('Flumotion: Not connected')) 518 519 self._componentList.connect('selection_changed', 520 self._components_selection_changed_cb) 521 self._componentList.connect('show-popup-menu', 522 self._components_show_popup_menu_cb) 523 524 self._updateComponentActions() 525 self._componentList.connect( 526 'notify::can-start-any', 527 self._components_start_stop_notify_cb) 528 self._componentList.connect( 529 'notify::can-stop-any', 530 self._components_start_stop_notify_cb) 531 self._updateComponentActions() 532 533 self._messageView.hide()
534
535 - def _connectActionProxy(self, action, widget):
536 tooltip = action.get_property('tooltip') 537 if not tooltip: 538 return 539 540 if isinstance(widget, gtk.MenuItem): 541 cid = widget.connect('select', self._on_menu_item__select, 542 tooltip) 543 cid2 = widget.connect('deselect', self._on_menu_item__deselect) 544 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2)) 545 elif isinstance(widget, gtk.ToolButton): 546 cid = widget.child.connect('enter', self._on_tool_button__enter, 547 tooltip) 548 cid2 = widget.child.connect('leave', self._on_tool_button__leave) 549 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
550
551 - def _disconnectActionProxy(self, action, widget):
552 cids = widget.get_data('pygtk-app::proxy-signal-ids') 553 if not cids: 554 return 555 556 if isinstance(widget, gtk.ToolButton): 557 widget = widget.child 558 559 for cid in cids: 560 widget.disconnect(cid)
561
562 - def _setAdminModel(self, model):
563 'set the model to which we are a view/controller' 564 # it's ok if we've already been connected 565 self.debug('setting model') 566 567 if self._adminModel is not None: 568 self._adminModel.disconnectFromManager() 569 self.debug('Connecting to new model %r' % model) 570 571 self._adminModel = model 572 573 whs = self._adminModel.getWorkerHeavenState() 574 whs.addListener(self, append=self.whsAppend, remove=self.whsRemove) 575 for worker in whs.get('names'): 576 self._componentList.workerAppend(worker) 577 578 # window gets created after model connects initially, so check 579 # here 580 if self._adminModel.isConnected(): 581 self._connectionOpened(model) 582 583 self._adminModel.connect('connected', 584 self._admin_connected_cb) 585 self._adminModel.connect('disconnected', 586 self._admin_disconnected_cb) 587 self._adminModel.connect('update', self._admin_update_cb) 588 589 self._runConfigurationAssistantAction.set_sensitive(True)
590
591 - def _openConnection(self, info):
592 self._trayicon.set_tooltip(_("Flumotion: Connecting to %s:%s") % ( 593 info.host, info.port)) 594 595 def connected(model): 596 self._setAdminModel(model) 597 self._appendRecentConnections()
598 599 model = AdminModel() 600 d = model.connectToManager(info) 601 d.addCallback(connected) 602 return d 603
604 - def _openConnectionInternal(self, info):
605 d = self._openConnection(info) 606 607 def errorMessageDisplayed(unused): 608 self._window.set_sensitive(True)
609 610 def connected(model): 611 self._window.set_sensitive(True) 612 613 def errbackConnectionRefusedError(failure): 614 failure.trap(ConnectionRefusedError) 615 d = showConnectionErrorDialog(failure, info, parent=self._window) 616 d.addCallback(errorMessageDisplayed) 617 618 def errbackConnectionFailedError(failure): 619 failure.trap(ConnectionFailedError) 620 d = showConnectionErrorDialog(failure, info, parent=self._window) 621 d.addCallback(errorMessageDisplayed) 622 return d 623 624 d.addCallback(connected) 625 d.addErrback(errbackConnectionRefusedError) 626 d.addErrback(errbackConnectionFailedError) 627 self._window.set_sensitive(False) 628 return d 629
630 - def _appendRecentConnections(self):
631 if self._recentMenuID: 632 self._uimgr.remove_ui(self._recentMenuID) 633 self._uimgr.ensure_update() 634 635 ui = "" 636 connections = getRecentConnections()[:MAX_RECENT_ITEMS] 637 for conn in connections: 638 name = conn.host 639 ui += '<menuitem action="%s"/>' % name 640 action = gtk.Action(name, name, 641 _('Connect to the manager on %s') % conn.host, 642 '') 643 action.connect('activate', self._recent_action_activate_cb, conn) 644 self._actiongroup.add_action(action) 645 646 self._recentMenuID = self._uimgr.add_ui_from_string( 647 RECENT_UI_TEMPLATE % ui) 648 self._openRecentAction.set_sensitive(len(connections))
649
650 - def _quit(self):
651 """Quitting the application in a controlled manner""" 652 self._clearAdmin() 653 654 def clearAndClose(unused): 655 self._close()
656 if self._managerSpawner and self._promptForShutdown(): 657 r = self._managerSpawner.stop(True) 658 r.addCallback(clearAndClose) 659 else: 660 clearAndClose('') 661
662 - def _close(self, *args):
663 reactor.stop()
664
665 - def _dumpConfig(self, configation):
666 import pprint 667 import cStringIO 668 fd = cStringIO.StringIO() 669 pprint.pprint(configation, fd) 670 fd.seek(0) 671 self.debug('Configuration=%s' % fd.read())
672
673 - def _error(self, message):
674 errorDialog = ErrorDialog(message, self._window, 675 close_on_response=True) 676 errorDialog.show()
677
678 - def _setStatusbarText(self, text):
679 return self._statusbar.push('main', text)
680
681 - def _clearLastStatusbarText(self):
682 self._statusbar.pop('main')
683
684 - def _assistantFinshed(self, assistant, configuration):
685 assistant.destroy() 686 self._configurationAssistantIsRunning = False 687 self._dumpConfig(configuration) 688 self._adminModel.loadConfiguration(configuration) 689 self._clearMessages() 690 self._statusbar.clear(None) 691 self._updateComponentActions() 692 scenario = assistant.getScenario() 693 self._componentNameToSelect = scenario.getSelectComponentName() 694 self.show()
695
696 - def _getComponentsBy(self, componentType):
697 """ 698 Obtains the components according a given type. 699 700 @param componentType: The type of the components to get 701 @type componentType: str 702 703 @rtype : list of L{flumotion.common.component.AdminComponentState} 704 """ 705 if componentType is None: 706 raise ValueError 707 708 componentStates = [] 709 710 for state in self._componentStates.values(): 711 config = state.get('config') 712 if componentType and config['type'] == componentType: 713 componentStates.append(state) 714 715 return componentStates
716
717 - def _getHTTPPorters(self):
718 """ 719 Obtains the porters currently configured on the running flow. 720 721 @rtype : list of L{flumotion.admin.assistant.models.Porter} 722 """ 723 porterList = [] 724 porterStates = self._getComponentsBy(componentType='porter') 725 726 for porter in porterStates: 727 properties = porter.get('config')['properties'] 728 porterModel = Porter(worker=porter.get('workerName') or 729 porter.get('workerRequested'), 730 port=properties['port'], 731 username=properties['username'], 732 password=properties['password'], 733 socketPath=properties['socket-path']) 734 porterModel.exists = True 735 porterList.append(porterModel) 736 737 return porterList
738
739 - def _setMountPoints(self, wizard):
740 """ 741 Sets the mount points currently used on the flow so they can not 742 be used for others servers or streamers. 743 744 @param wizard : An assistant that wants to know the used mount_points 745 @type wizard : L{ConfigurationAssistant} 746 """ 747 streamerStates = self._getComponentsBy(componentType='http-streamer') 748 serverStates = self._getComponentsBy(componentType='http-server') 749 porterStates = self._getComponentsBy(componentType='porter') 750 751 for porter in porterStates: 752 properties = porter.get('config')['properties'] 753 for streamer in streamerStates + serverStates: 754 streamerProperties = streamer.get('config')['properties'] 755 socketPath = streamerProperties['porter-socket-path'] 756 757 if socketPath == properties['socket-path']: 758 worker = streamer.get('workerRequested') 759 port = int(properties['port']) 760 mount_point = streamerProperties['mount-point'] 761 wizard.addMountPoint(worker, port, mount_point)
762
763 - def _createComponentsByAssistantType(self, componentClass, entries):
764 765 def _getComponents(): 766 for componentState in self._componentStates.values(): 767 componentType = componentState.get('config')['type'] 768 for entry in entries: 769 if entry.componentType == componentType: 770 yield (componentState, entry)
771 772 for componentState, entry in _getComponents(): 773 component = componentClass() 774 component.componentType = entry.componentType 775 component.description = entry.description 776 component.exists = True 777 component.name = componentState.get('name') 778 config = componentState.get('config') 779 for key, value in config['properties'].items(): 780 component.properties[key] = value 781 yield component 782
783 - def _runAddNew(self, addition):
784 if not self._adminModel.isConnected(): 785 self._error( 786 _('Cannot run assistant without being connected to a manager')) 787 return 788 789 from flumotion.admin.gtk.configurationassistant import \ 790 ConfigurationAssistant 791 792 configurationAssistant = ConfigurationAssistant(self._window) 793 794 def gotWizardEntries(entries): 795 entryDict = {} 796 for entry in entries: 797 entryDict.setdefault(entry.type, []).append(entry) 798 799 if addition == 'format': 800 audioProducers = self._createComponentsByAssistantType( 801 AudioProducer, entryDict['audio-producer'], ) 802 videoProducers = self._createComponentsByAssistantType( 803 VideoProducer, entryDict['video-producer']) 804 scenario = configurationAssistant.getScenario() 805 scenario.setAudioProducers(audioProducers) 806 scenario.setVideoProducers(videoProducers) 807 elif addition == 'streamer': 808 muxers = self._createComponentsByAssistantType( 809 Muxer, entryDict['muxer'], ) 810 scenario = configurationAssistant.getScenario() 811 scenario.setMuxers(muxers) 812 813 self._runAssistant(configurationAssistant)
814 815 def gotBundledFunction(function): 816 scenario = function() 817 scenario.setMode('add%s' % addition) 818 scenario.addSteps(configurationAssistant) 819 configurationAssistant.setScenario(scenario) 820 httpPorters = self._getHTTPPorters() 821 self._setMountPoints(configurationAssistant) 822 if httpPorters: 823 configurationAssistant.setHTTPPorters(httpPorters) 824 825 if addition == 'format': 826 return self._adminModel.getWizardEntries( 827 wizardTypes=['audio-producer', 'video-producer']) 828 elif addition == 'streamer': 829 return self._adminModel.getWizardEntries( 830 wizardTypes=['muxer']) 831 832 d = self._adminModel.getBundledFunction( 833 'flumotion.scenario.live.wizard_gtk', 834 'LiveAssistantPlugin') 835 836 d.addCallback(gotBundledFunction) 837 d.addCallback(gotWizardEntries) 838
839 - def _runConfigurationAssistant(self):
840 if not self._adminModel.isConnected(): 841 self._error( 842 _('Cannot run assistant without being connected to a manager')) 843 return 844 845 from flumotion.admin.gtk.configurationassistant import \ 846 ConfigurationAssistant 847 848 def runAssistant(): 849 configurationAssistant = ConfigurationAssistant(self._window) 850 configurationAssistant.addInitialSteps() 851 self._runAssistant(configurationAssistant)
852 853 if not self._componentStates: 854 runAssistant() 855 return 856 857 for componentState in self._componentList.getComponentStates(): 858 if componentState.get('mood') == moods.lost.value: 859 self._error( 860 _("Cannot run the configuration assistant since there " 861 "is at least one component in the lost state")) 862 return 863 864 if yesno(_("Running the Configuration Assistant again will remove " 865 "all components from the current stream and create " 866 "a new one."), 867 parent=self._window, 868 buttons=((_("Keep the current stream"), 869 gtk.RESPONSE_NO), 870 (_("Run the Assistant anyway"), 871 gtk.RESPONSE_YES))) != gtk.RESPONSE_YES: 872 return 873 874 d = self._clearAllComponents() 875 # The remote call returns a list with the results of the cleaning. 876 # None if there has been an error during the processs. 877 d.addCallback(lambda list: list and runAssistant()) 878
879 - def _runAssistant(self, assistant):
880 if self._adminModel is None: 881 return 882 883 workerHeavenState = self._adminModel.getWorkerHeavenState() 884 if not workerHeavenState.get('names'): 885 self._error( 886 _('The assistant cannot be run because no workers are ' 887 'logged in.')) 888 return 889 890 self._configurationAssistantIsRunning = True 891 assistant.setExistingComponentNames( 892 self._componentList.getComponentNames()) 893 assistant.setAdminModel(self._adminModel) 894 assistant.setWorkerHeavenState(workerHeavenState) 895 httpPorters = self._getHTTPPorters() 896 if httpPorters: 897 assistant.setHTTPPorters(httpPorters) 898 assistant.connect('finished', self._assistant_finished_cb) 899 assistant.connect('destroy', self.on_assistant_destroy) 900 901 assistant.run(main=False)
902
903 - def _clearAdmin(self):
904 if self._adminModel is None: 905 return 906 907 self._adminModel.disconnectByFunction(self._admin_connected_cb) 908 self._adminModel.disconnectByFunction(self._admin_disconnected_cb) 909 self._adminModel.disconnectByFunction(self._admin_update_cb) 910 self._adminModel = None 911 912 self._addFormatAction.set_sensitive(False) 913 self._addStreamerAction.set_sensitive(False) 914 self._runConfigurationAssistantAction.set_sensitive(False)
915
916 - def _updateUIStatus(self, connected):
917 self._window.set_sensitive(connected) 918 group = self._actiongroup 919 group.get_action('ImportConfig').set_sensitive(connected) 920 group.get_action('ExportConfig').set_sensitive(connected) 921 group.get_action('EnableDebugging').set_sensitive(connected) 922 923 self._clearLastStatusbarText() 924 if connected: 925 self._window.set_title(_('%s - Flumotion Administration') % 926 self._adminModel.adminInfoStr()) 927 self._trayicon.set_tooltip(_('Flumotion: %s') % ( 928 self._adminModel.adminInfoStr(), )) 929 else: 930 self._setStatusbarText(_('Not connected')) 931 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
932
933 - def _updateConnectionActions(self):
934 self._openRecentAction.set_sensitive(hasRecentConnections())
935
936 - def _updateComponentActions(self):
937 canStart = self._componentList.canStart() 938 canStop = self._componentList.canStop() 939 canDelete = self._componentList.canDelete() 940 self._startComponentAction.set_sensitive(canStart) 941 self._stopComponentAction.set_sensitive(canStop) 942 self._deleteComponentAction.set_sensitive(canDelete) 943 self.debug('can start %r, can stop %r, can delete %r' % ( 944 canStart, canStop, canDelete)) 945 canStartAll = self._componentList.get_property('can-start-any') 946 canStopAll = self._componentList.get_property('can-stop-any') 947 948 # they're all in sleeping or lost 949 canClearAll = canStartAll and not canStopAll 950 self._stopAllAction.set_sensitive(canStopAll) 951 self._startAllAction.set_sensitive(canStartAll) 952 self._clearAllAction.set_sensitive(canClearAll) 953 self._killComponentAction.set_sensitive(canStop) 954 955 hasProducer = self._hasProducerComponent() 956 self._addFormatAction.set_sensitive(hasProducer) 957 self._addStreamerAction.set_sensitive(hasProducer)
958
959 - def _updateComponents(self):
960 self._componentList.clearAndRebuild(self._componentStates, 961 self._componentNameToSelect) 962 self._trayicon.update(self._componentStates)
963
964 - def _appendComponent(self, component):
965 self._componentStates[component.get('name')] = component 966 self._componentList.appendComponent(component, 967 self._componentNameToSelect) 968 self._trayicon.update(self._componentStates)
969
970 - def _hasProducerComponent(self):
971 for state in self._componentList.getComponentStates(): 972 if state is None: 973 continue 974 # FIXME: Not correct, should expose assistant state from 975 # the registry. 976 name = state.get('name') 977 if 'producer' in name: 978 return True 979 return False
980
981 - def _clearMessages(self):
982 self._messageView.clear() 983 pstate = self._planetState 984 if pstate and pstate.hasKey('messages'): 985 for message in pstate.get('messages').values(): 986 self._messageView.addMessage(message)
987
988 - def _setPlanetState(self, planetState):
989 990 def flowStateAppend(state, key, value): 991 self.debug('flow state append: key %s, value %r' % (key, value)) 992 if key == 'components': 993 self._appendComponent(value)
994 995 def flowStateRemove(state, key, value): 996 if key == 'components': 997 self._removeComponent(value) 998 999 def atmosphereStateAppend(state, key, value): 1000 if key == 'components': 1001 self._appendComponent(value) 1002 1003 def atmosphereStateRemove(state, key, value): 1004 if key == 'components': 1005 self._removeComponent(value) 1006 1007 def planetStateAppend(state, key, value): 1008 if key == 'flows': 1009 if value != state.get('flows')[0]: 1010 self.warning('flumotion-admin can only handle one ' 1011 'flow, ignoring /%s', value.get('name')) 1012 return 1013 self.debug('%s flow started', value.get('name')) 1014 value.addListener(self, append=flowStateAppend, 1015 remove=flowStateRemove) 1016 1017 self._componentStates.update( 1018 dict((c.get('name'), c) for c in value.get('components'))) 1019 self._updateComponents() 1020 1021 def planetStateRemove(state, key, value): 1022 self.debug('something got removed from the planet') 1023 1024 def planetStateSetitem(state, key, subkey, value): 1025 if key == 'messages': 1026 self._messageView.addMessage(value) 1027 1028 def planetStateDelitem(state, key, subkey, value): 1029 if key == 'messages': 1030 self._messageView.clearMessage(value.id) 1031 1032 self.debug('parsing planetState %r' % planetState) 1033 self._planetState = planetState 1034 1035 # clear and rebuild list of components that interests us 1036 self._componentStates = {} 1037 1038 planetState.addListener(self, append=planetStateAppend, 1039 remove=planetStateRemove, 1040 setitem=planetStateSetitem, 1041 delitem=planetStateDelitem) 1042 1043 self._clearMessages() 1044 1045 a = planetState.get('atmosphere') 1046 a.addListener(self, append=atmosphereStateAppend, 1047 remove=atmosphereStateRemove) 1048 1049 self._componentStates.update( 1050 dict((c.get('name'), c) for c in a.get('components'))) 1051 1052 for f in planetState.get('flows'): 1053 planetStateAppend(planetState, 'flows', f) 1054 1055 if not planetState.get('flows'): 1056 self._updateComponents() 1057
1058 - def _clearAllComponents(self):
1059 if not self._adminModel.isConnected(): 1060 return 1061 1062 d = self._adminModel.cleanComponents() 1063 1064 def busyComponentError(failure): 1065 failure.trap(BusyComponentError) 1066 self._error( 1067 _("Some component(s) are still busy and cannot be removed.\n" 1068 "Try again later."))
1069 d.addErrback(busyComponentError) 1070 return d 1071 1072 # component view activation functions 1073
1074 - def _removeComponent(self, state):
1075 name = state.get('name') 1076 self.debug('removing component %s' % name) 1077 del self._componentStates[name] 1078 1079 # if this component was selected, clear selection 1080 if self._currentComponentStates and state \ 1081 in self._currentComponentStates: 1082 self._currentComponentStates.remove(state) 1083 self._componentList.removeComponent(state) 1084 # a component being removed means our selected component could 1085 # have gone away 1086 self._updateComponentActions()
1087
1088 - def _componentStop(self, state):
1089 """ 1090 @returns: a L{twisted.internet.defer.Deferred} 1091 """ 1092 self.debug('stopping component %r' % state) 1093 return self._componentDo(state, 'componentStop', 1094 'Stop', 'Stopping', 'Stopped')
1095
1096 - def _componentStart(self, state):
1097 """ 1098 @returns: a L{twisted.internet.defer.Deferred} 1099 """ 1100 return self._componentDo(state, 'componentStart', 1101 'Start', 'Starting', 'Started')
1102
1103 - def _componentDelete(self, state):
1104 """ 1105 @returns: a L{twisted.internet.defer.Deferred} 1106 """ 1107 return self._componentDo(state, 'deleteComponent', 1108 'Delete', 'Deleting', 'Deleted')
1109
1110 - def _getStatesFromState(self, state):
1111 # componentDo can be called on a None state, which means 1112 # 'look at the current selection' 1113 if state is None: 1114 states = self._componentList.getSelectedStates() 1115 self._componentView.activateComponent(None) 1116 else: 1117 states = [state] 1118 1119 return states
1120
1121 - def _componentDo(self, state, methodName, action, doing, done):
1122 """Do something with a component and update the statusbar. 1123 1124 @param state: componentState; if not specified, will use the 1125 currently selected component(s) 1126 @type state: L{AdminComponentState} or None 1127 @param methodName: name of the method to call 1128 @type methodName: str 1129 @param action: string used to explain that to do 1130 @type action: str 1131 @param doing: string used to explain that the action started 1132 @type doing: str 1133 @param done: string used to explain that the action was completed 1134 @type done: str 1135 1136 @rtype: L{twisted.internet.defer.Deferred} 1137 @returns: a deferred that will fire when the action is completed. 1138 """ 1139 states = self._getStatesFromState(state) 1140 1141 if not states: 1142 return 1143 1144 def callbackSingle(result, self, mid, name): 1145 self._statusbar.remove('main', mid) 1146 self._setStatusbarText( 1147 _("%s component %s") % (done, name))
1148 1149 def errbackSingle(failure, self, mid, name): 1150 self._statusbar.remove('main', mid) 1151 self.warning("Failed to %s component %s: %s" % ( 1152 action.lower(), name, failure)) 1153 self._setStatusbarText( 1154 _("Failed to %(action)s component %(name)s.") % { 1155 'action': action.lower(), 1156 'name': name, 1157 }) 1158 1159 def callbackMultiple(results, self, mid): 1160 self._statusbar.remove('main', mid) 1161 self._setStatusbarText( 1162 _("%s components.") % (done, )) 1163 1164 def errbackMultiple(failure, self, mid): 1165 self._statusbar.remove('main', mid) 1166 self.warning("Failed to %s some components: %s." % ( 1167 action.lower(), failure)) 1168 self._setStatusbarText( 1169 _("Failed to %s some components.") % (action, )) 1170 1171 f = gettext.dngettext( 1172 configure.PACKAGE, 1173 # first %s is one of Stopping/Starting/Deleting 1174 # second %s is a component name like "audio-producer" 1175 N_("%s component %s"), 1176 # first %s is one of Stopping/Starting/Deleting 1177 # second %s is a list of component names, like 1178 # "audio-producer, video-producer" 1179 N_("%s components %s"), len(states)) 1180 statusText = f % (doing, 1181 ', '.join([getComponentLabel(s) for s in states])) 1182 mid = self._setStatusbarText(statusText) 1183 1184 if len(states) == 1: 1185 state = states[0] 1186 name = getComponentLabel(state) 1187 d = self._adminModel.callRemote(methodName, state) 1188 d.addCallback(callbackSingle, self, mid, name) 1189 d.addErrback(errbackSingle, self, mid, name) 1190 else: 1191 deferreds = [] 1192 for state in states: 1193 d = self._adminModel.callRemote(methodName, state) 1194 deferreds.append(d) 1195 d = defer.DeferredList(deferreds) 1196 d.addCallback(callbackMultiple, self, mid) 1197 d.addErrback(errbackMultiple, self, mid) 1198 return d 1199
1200 - def _killSelectedComponents(self):
1201 for state in self._componentList.getSelectedStates(): 1202 workerName = state.get('workerRequested') 1203 avatarId = componentId(state.get('parent').get('name'), 1204 state.get('name')) 1205 self._adminModel.callRemote( 1206 'workerCallRemote', workerName, 'killJob', avatarId)
1207
1208 - def _componentSelectionChanged(self, states):
1209 self.debug('component %s has selection', states) 1210 1211 def compSet(state, key, value): 1212 if key == 'mood': 1213 self.debug('state %r has mood set to %r' % (state, value)) 1214 self._updateComponentActions()
1215 1216 def compAppend(state, key, value): 1217 name = state.get('name') 1218 self.debug('stateAppend on component state of %s' % name) 1219 if key == 'messages': 1220 current = self._componentList.getSelectedNames() 1221 if name in current: 1222 self._messageView.addMessage(value) 1223 1224 def compRemove(state, key, value): 1225 name = state.get('name') 1226 self.debug('stateRemove on component state of %s' % name) 1227 if key == 'messages': 1228 current = self._componentList.getSelectedNames() 1229 if name in current: 1230 self._messageView.clearMessage(value.id) 1231 1232 if self._currentComponentStates: 1233 for currentComponentState in self._currentComponentStates: 1234 currentComponentState.removeListener(self) 1235 self._currentComponentStates = states 1236 if self._currentComponentStates: 1237 for currentComponentState in self._currentComponentStates: 1238 currentComponentState.addListener( 1239 self, set_=compSet, append=compAppend, remove=compRemove) 1240 1241 self._updateComponentActions() 1242 self._clearMessages() 1243 state = None 1244 if states: 1245 if len(states) == 1: 1246 self.debug( 1247 "only one component is selected on the components view") 1248 state = states[0] 1249 elif states: 1250 self.debug("more than one components are selected in the " 1251 "components view") 1252 state = MultipleAdminComponentStates(states) 1253 self._componentView.activateComponent(state) 1254 1255 statusbarMessage = " " 1256 for state in states: 1257 name = getComponentLabel(state) 1258 messages = state.get('messages') 1259 if messages: 1260 for m in messages: 1261 self.debug('have message %r', m) 1262 self.debug('message id %s', m.id) 1263 self._messageView.addMessage(m) 1264 1265 if state.get('mood') == moods.sad.value: 1266 self.debug('component %s is sad' % name) 1267 statusbarMessage = statusbarMessage + \ 1268 _("Component %s is sad. ") % name 1269 if statusbarMessage != " ": 1270 self._setStatusbarText(statusbarMessage) 1271 1272 1273 # FIXME: show statusbar things 1274 # self._statusbar.set('main', _('Showing UI for %s') % name) 1275 # self._statusbar.set('main', 1276 # _("Component %s is still sleeping") % name) 1277 # self._statusbar.set('main', _("Requesting UI for %s ...") % name) 1278 # self._statusbar.set('main', _("Loading UI for %s ...") % name) 1279 # self._statusbar.clear('main') 1280 # mid = self._statusbar.push('notebook', 1281 # _("Loading tab %s for %s ...") % (node.title, name)) 1282 # node.statusbar = self._statusbar # hack 1283
1284 - def _componentShowPopupMenu(self, event_button, event_time):
1285 self._componentContextMenu.popup(None, None, None, 1286 event_button, event_time)
1287
1288 - def _connectionOpened(self, admin):
1289 self.info('Connected to manager') 1290 if self._disconnectedDialog: 1291 self._disconnectedDialog.destroy() 1292 self._disconnectedDialog = None 1293 1294 self._updateUIStatus(connected=True) 1295 1296 self.emit('connected') 1297 1298 self._componentView.setSingleAdmin(admin) 1299 1300 self._setPlanetState(admin.planet) 1301 self._updateConnectionActions() 1302 self._updateComponentActions() 1303 1304 if (not self._componentStates and 1305 not self._configurationAssistantIsRunning): 1306 self.debug('no components detected, running assistant') 1307 # ensure our window is shown 1308 self._componentList.clearAndRebuild(self._componentStates) 1309 self.show() 1310 self._runConfigurationAssistant() 1311 else: 1312 self.show()
1313
1314 - def _showConnectionLostDialog(self):
1315 RESPONSE_REFRESH = 1 1316 1317 def response(dialog, response_id): 1318 if response_id == RESPONSE_REFRESH: 1319 1320 def errback(failure): 1321 # Swallow connection errors. We keep trying 1322 failure.trap(ConnectionCancelledError, 1323 ConnectionFailedError, 1324 ConnectionRefusedError)
1325 1326 d = self._adminModel.reconnect(keepTrying=True) 1327 d.addErrback(errback) 1328 else: 1329 self._disconnectedDialog.destroy() 1330 self._disconnectedDialog = None 1331 self._adminModel.disconnectFromManager() 1332 self._window.set_sensitive(True) 1333 1334 dialog = ProgressDialog( 1335 _("Reconnecting ..."), 1336 _("Lost connection to manager %s. Reconnecting ...") 1337 % (self._adminModel.adminInfoStr(), ), self._window) 1338 1339 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 1340 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH) 1341 dialog.connect("response", response) 1342 dialog.start() 1343 self._disconnectedDialog = dialog 1344 self._window.set_sensitive(False) 1345
1346 - def _connectionLost(self):
1347 self._componentStates = {} 1348 self._updateComponents() 1349 self._clearMessages() 1350 if self._planetState: 1351 self._planetState.removeListener(self) 1352 self._planetState = None 1353 1354 self._showConnectionLostDialog() 1355 self._updateUIStatus(connected=False)
1356
1357 - def _openRecentConnection(self):
1358 d = ConnectionsDialog(parent=self._window) 1359 1360 def on_have_connection(d, connectionInfo): 1361 d.destroy() 1362 if connectionInfo: 1363 self._openConnectionInternal(connectionInfo.info) 1364 connectionInfo.updateTimestamp() 1365 self._updateConnectionActions()
1366 1367 d.connect('have-connection', on_have_connection) 1368 d.show() 1369
1370 - def _openExistingConnection(self):
1371 from flumotion.admin.gtk.greeter import ConnectExisting 1372 from flumotion.ui.simplewizard import WizardCancelled 1373 wiz = ConnectExisting(parent=self._window) 1374 1375 def got_state(state, g): 1376 g.set_sensitive(False) 1377 g.destroy() 1378 self._openConnectionInternal(state['connectionInfo'])
1379 1380 def cancel(failure): 1381 failure.trap(WizardCancelled) 1382 wiz.stop() 1383 1384 d = wiz.runAsync() 1385 d.addCallback(got_state, wiz) 1386 d.addErrback(cancel) 1387
1388 - def _importConfiguration(self):
1389 dialog = gtk.FileChooserDialog( 1390 _("Import Configuration..."), self._window, 1391 gtk.FILE_CHOOSER_ACTION_OPEN, 1392 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 1393 _('Import'), gtk.RESPONSE_ACCEPT)) 1394 dialog.set_modal(True) 1395 dialog.set_default_response(gtk.RESPONSE_ACCEPT) 1396 dialog.set_select_multiple(True) 1397 1398 ffilter = gtk.FileFilter() 1399 ffilter.set_name(_("Flumotion XML configuration files")) 1400 ffilter.add_pattern("*.xml") 1401 dialog.add_filter(ffilter) 1402 ffilter = gtk.FileFilter() 1403 ffilter.set_name(_("All files")) 1404 ffilter.add_pattern("*") 1405 dialog.add_filter(ffilter) 1406 1407 if self._currentDir: 1408 dialog.set_current_folder_uri(self._currentDir) 1409 1410 def response(dialog, response): 1411 if response == gtk.RESPONSE_ACCEPT: 1412 self._currentDir = dialog.get_current_folder_uri() 1413 for name in dialog.get_filenames(): 1414 conf_xml = open(name, 'r').read() 1415 self._adminModel.loadConfiguration(conf_xml) 1416 dialog.destroy()
1417 1418 dialog.connect('response', response) 1419 dialog.show() 1420
1421 - def _exportConfiguration(self):
1422 d = gtk.FileChooserDialog( 1423 _("Export Configuration..."), self._window, 1424 gtk.FILE_CHOOSER_ACTION_SAVE, 1425 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 1426 _('Export'), gtk.RESPONSE_ACCEPT)) 1427 d.set_modal(True) 1428 d.set_default_response(gtk.RESPONSE_ACCEPT) 1429 d.set_current_name("configuration.xml") 1430 1431 if self._currentDir: 1432 d.set_current_folder_uri(self._currentDir) 1433 1434 def getConfiguration(conf_xml, name, chooser): 1435 if not name.endswith('.xml'): 1436 name += '.xml' 1437 1438 file_exists = True 1439 if os.path.exists(name): 1440 d = gtk.MessageDialog( 1441 self._window, gtk.DIALOG_MODAL, 1442 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, 1443 _("File already exists.\nOverwrite?")) 1444 d.connect("response", lambda self, response: d.hide()) 1445 if d.run() == gtk.RESPONSE_YES: 1446 file_exists = False 1447 else: 1448 file_exists = False 1449 1450 if not file_exists: 1451 try: 1452 f = open(name, 'w') 1453 f.write(conf_xml) 1454 f.close() 1455 chooser.destroy() 1456 except IOError, e: 1457 self._error(_("Could not open configuration file %s " 1458 "for writing (%s)" % (name, e[1])))
1459 1460 def response(d, response): 1461 if response == gtk.RESPONSE_ACCEPT: 1462 self._currentDir = d.get_current_folder_uri() 1463 deferred = self._adminModel.getConfiguration() 1464 name = d.get_filename() 1465 deferred.addCallback(getConfiguration, name, d) 1466 else: 1467 d.destroy() 1468 1469 d.connect('response', response) 1470 d.show() 1471
1472 - def _startShell(self):
1473 if sys.version_info >= (2, 4): 1474 from flumotion.extern import code 1475 code # pyflakes 1476 else: 1477 import code 1478 1479 ns = {"admin": self._adminModel, 1480 "components": self._componentStates} 1481 message = """Flumotion Admin Debug Shell 1482 1483 Local variables are: 1484 admin (flumotion.admin.admin.AdminModel) 1485 components (dict: name -> flumotion.common.planet.AdminComponentState) 1486 1487 You can do remote component calls using: 1488 admin.componentCallRemote(components['component-name'], 1489 'methodName', arg1, arg2) 1490 1491 """ 1492 code.interact(local=ns, banner=message)
1493
1494 - def _dumpConfiguration(self):
1495 1496 def gotConfiguration(xml): 1497 print xml
1498 d = self._adminModel.getConfiguration() 1499 d.addCallback(gotConfiguration) 1500
1501 - def _setDebugMarker(self):
1502 1503 def setMarker(_, marker, level): 1504 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1505 debugMarkerDialog = DebugMarkerDialog() 1506 debugMarkerDialog.connect('set-marker', setMarker) 1507 debugMarkerDialog.show() 1508
1509 - def _about(self):
1510 about = AboutDialog(self._window) 1511 about.run() 1512 about.destroy()
1513
1514 - def _showHelp(self):
1515 for path in os.environ['PATH'].split(':'): 1516 executable = os.path.join(path, 'gnome-help') 1517 if os.path.exists(executable): 1518 break 1519 else: 1520 self._error( 1521 _("Cannot find a program to display the Flumotion manual.")) 1522 return 1523 gobject.spawn_async([executable, 1524 'ghelp:%s' % (configure.PACKAGE, )])
1525
1526 - def _promptForShutdown(self):
1527 d = gtk.MessageDialog( 1528 self._window, gtk.DIALOG_MODAL, 1529 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 1530 _("Do you want to shutdown manager and worker " 1531 "before exiting?")) 1532 response = d.run() 1533 d.destroy() 1534 return response == gtk.RESPONSE_YES
1535 1536 ### admin model callbacks 1537
1538 - def _admin_connected_cb(self, admin):
1539 self._connectionOpened(admin)
1540
1541 - def _admin_disconnected_cb(self, admin):
1542 self._connectionLost()
1543
1544 - def _admin_update_cb(self, admin):
1545 self._updateComponents()
1546 1547 ### ui callbacks 1548
1549 - def _on_uimanager__connect_proxy(self, uimgr, action, widget):
1550 self._connectActionProxy(action, widget)
1551
1552 - def _on_uimanager__disconnect_proxy(self, uimgr, action, widget):
1553 self._disconnectActionProxy(action, widget)
1554
1555 - def _on_menu_item__select(self, menuitem, tooltip):
1556 self._setStatusbarText(tooltip)
1557
1558 - def _on_menu_item__deselect(self, menuitem):
1559 self._clearLastStatusbarText()
1560
1561 - def _on_tool_button__enter(self, toolbutton, tooltip):
1562 self._setStatusbarText(tooltip)
1563
1564 - def _on_tool_button__leave(self, toolbutton):
1565 self._clearLastStatusbarText()
1566
1567 - def _assistant_finished_cb(self, assistant, configuration):
1568 self._assistantFinshed(assistant, configuration)
1569
1570 - def on_assistant_destroy(self, assistant):
1571 self._configurationAssistantIsRunning = False
1572
1573 - def _window_delete_event_cb(self, window, event):
1574 self._quit()
1575
1576 - def _window_key_press_event_cb(self, window, event):
1577 # This should be removed if we're going to support connecting 1578 # to multiple managers in the same application (MDI/tabs) 1579 state = event.state & (gtk.gdk.MODIFIER_MASK ^ gtk.gdk.MOD2_MASK) 1580 1581 if state == gdk.CONTROL_MASK and event.keyval == keysyms.w: 1582 self._quit()
1583
1584 - def _trayicon_quit_cb(self, trayicon):
1585 self._quit()
1586
1587 - def _recent_action_activate_cb(self, action, conn):
1588 self._openConnectionInternal(conn.info)
1589
1590 - def _components_show_popup_menu_cb(self, clist, event_button, event_time):
1591 self._componentShowPopupMenu(event_button, event_time)
1592
1593 - def _components_selection_changed_cb(self, clist, state):
1594 self._componentSelectionChanged(state)
1595
1596 - def _components_start_stop_notify_cb(self, clist, pspec):
1597 self._updateComponentActions()
1598 1599 ### action callbacks 1600
1601 - def _debug_write_debug_marker_cb(self, action):
1602 self._setDebugMarker()
1603
1604 - def _connection_open_recent_cb(self, action):
1605 self._openRecentConnection()
1606
1607 - def _connection_open_existing_cb(self, action):
1608 self._openExistingConnection()
1609
1610 - def _connection_import_configuration_cb(self, action):
1611 self._importConfiguration()
1612
1613 - def _connection_export_configuration_cb(self, action):
1614 self._exportConfiguration()
1615
1616 - def _connection_quit_cb(self, action):
1617 self._quit()
1618
1619 - def _manage_start_component_cb(self, action):
1620 self._componentStart(None)
1621
1622 - def _manage_stop_component_cb(self, action):
1623 self._componentStop(None)
1624
1625 - def _manage_delete_component_cb(self, action):
1626 self._componentDelete(None)
1627
1628 - def _manage_start_all_cb(self, action):
1629 for c in self._componentStates.values(): 1630 self._componentStart(c)
1631
1632 - def _manage_stop_all_cb(self, action):
1633 for c in self._componentStates.values(): 1634 self._componentStop(c)
1635
1636 - def _manage_clear_all_cb(self, action):
1637 self._clearAllComponents()
1638
1639 - def _manage_add_format_cb(self, action):
1640 self._runAddNew('format')
1641
1642 - def _manage_add_streamer_cb(self, action):
1643 self._runAddNew('streamer')
1644
1645 - def _manage_run_assistant_cb(self, action):
1646 self._runConfigurationAssistant()
1647
1648 - def _debug_enable_cb(self, action):
1649 self.setDebugEnabled(action.get_active())
1650
1651 - def _debug_start_shell_cb(self, action):
1652 self._startShell()
1653
1654 - def _debug_dump_configuration_cb(self, action):
1655 self._dumpConfiguration()
1656
1657 - def _help_contents_cb(self, action):
1658 self._showHelp()
1659
1660 - def _help_about_cb(self, action):
1661 self._about()
1662
1663 - def _kill_component_cb(self, action):
1664 self._killSelectedComponents()
1665 1666 gobject.type_register(AdminWindow) 1667