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.settings import getSettings 
  83  from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \ 
  84       ProgressDialog, showConnectionErrorDialog 
  85  from flumotion.admin.gtk.connections import ConnectionsDialog 
  86  from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList 
  87  from flumotion.admin.gtk.componentview import MultipleAdminComponentStates 
  88  from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog 
  89  from flumotion.admin.gtk.statusbar import AdminStatusbar 
  90  from flumotion.common.common import componentId 
  91  from flumotion.common.connection import PBConnectionInfo 
  92  from flumotion.common.errors import ConnectionCancelledError, \ 
  93       ConnectionRefusedError, ConnectionFailedError, BusyComponentError 
  94  from flumotion.common.i18n import N_, gettexter 
  95  from flumotion.common.log import Loggable 
  96  from flumotion.common.planet import AdminComponentState, moods 
  97  from flumotion.common.pygobject import gsignal 
  98  from flumotion.configure import configure 
  99  from flumotion.manager import admin # Register types 
 100  from flumotion.twisted.flavors import IStateListener 
 101  from flumotion.ui.trayicon import FluTrayIcon 
 102   
 103  admin # pyflakes 
 104   
 105  __version__ = "$Rev$" 
 106  _ = gettext.gettext 
 107  T_ = gettexter() 
 108   
 109  MAIN_UI = """ 
 110  <ui> 
 111    <menubar name="Menubar"> 
 112      <menu action="Connection"> 
 113        <menuitem action="OpenRecent"/> 
 114        <menuitem action="OpenExisting"/> 
 115        <menuitem action="ImportConfig"/> 
 116        <menuitem action="ExportConfig"/> 
 117        <separator name="sep-conn1"/> 
 118        <placeholder name="Recent"/> 
 119        <separator name="sep-conn2"/> 
 120        <menuitem action="Quit"/> 
 121      </menu> 
 122      <menu action="Manage"> 
 123        <menuitem action="StartComponent"/> 
 124        <menuitem action="StopComponent"/> 
 125        <menuitem action="DeleteComponent"/> 
 126        <separator name="sep-manage1"/> 
 127        <menuitem action="StartAll"/> 
 128        <menuitem action="StopAll"/> 
 129        <menuitem action="ClearAll"/> 
 130        <separator name="sep-manage2"/> 
 131        <menuitem action="AddFormat"/> 
 132        <menuitem action="AddStreamer"/> 
 133        <separator name="sep-manage3"/> 
 134        <menuitem action="RunConfigurationAssistant"/> 
 135      </menu> 
 136      <menu action="Debug"> 
 137        <menuitem action="EnableDebugging"/> 
 138        <separator name="sep-debug1"/> 
 139        <menuitem action="StartShell"/> 
 140        <menuitem action="DumpConfiguration"/> 
 141        <menuitem action="WriteDebugMarker"/> 
 142      </menu> 
 143      <menu action="Help"> 
 144        <menuitem action="Contents"/> 
 145        <menuitem action="About"/> 
 146      </menu> 
 147    </menubar> 
 148    <toolbar name="Toolbar"> 
 149      <toolitem action="OpenRecent"/> 
 150      <separator name="sep-toolbar1"/> 
 151      <toolitem action="StartComponent"/> 
 152      <toolitem action="StopComponent"/> 
 153      <toolitem action="DeleteComponent"/> 
 154      <separator name="sep-toolbar2"/> 
 155      <toolitem action="RunConfigurationAssistant"/> 
 156    </toolbar> 
 157    <popup name="ComponentContextMenu"> 
 158      <menuitem action="StartComponent"/> 
 159      <menuitem action="StopComponent"/> 
 160      <menuitem action="DeleteComponent"/> 
 161      <menuitem action="KillComponent"/> 
 162    </popup> 
 163  </ui> 
 164  """ 
 165   
 166  RECENT_UI_TEMPLATE = '''<ui> 
 167    <menubar name="Menubar"> 
 168      <menu action="Connection"> 
 169        <placeholder name="Recent"> 
 170        %s 
 171        </placeholder> 
 172      </menu> 
 173    </menubar> 
 174  </ui>''' 
 175   
 176  MAX_RECENT_ITEMS = 4 
 177   
 178   
179 -class AdminWindow(Loggable, GladeDelegate):
180 '''Creates the GtkWindow for the user interface. 181 Also connects to the manager on the given host and port. 182 ''' 183 184 # GladeDelegate 185 gladefile = 'admin.glade' 186 toplevel_name = 'main_window' 187 188 # Loggable 189 logCategory = 'adminwindow' 190 191 # Interfaces we implement 192 implements(IStateListener) 193 194 # Signals 195 gsignal('connected') 196
197 - def __init__(self):
198 GladeDelegate.__init__(self) 199 200 self._adminModel = None 201 self._currentComponentStates = None 202 self._componentContextMenu = None 203 self._componentList = None # ComponentList 204 self._componentStates = None # name -> planet.AdminComponentState 205 self._componentView = None 206 self._componentNameToSelect = None 207 self._debugEnabled = False 208 self._debugActions = None 209 self._debugEnableAction = None 210 self._disconnectedDialog = None # set to a dialog when disconnected 211 self._planetState = None 212 self._recentMenuID = None 213 self._trayicon = None 214 self._configurationAssistantIsRunning = False 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 self._trayicon.update(self._componentStates)
1088
1089 - def _componentStop(self, state):
1090 """ 1091 @returns: a L{twisted.internet.defer.Deferred} 1092 """ 1093 self.debug('stopping component %r' % state) 1094 return self._componentDo(state, 'componentStop', 1095 'Stop', 'Stopping', 'Stopped')
1096
1097 - def _componentStart(self, state):
1098 """ 1099 @returns: a L{twisted.internet.defer.Deferred} 1100 """ 1101 return self._componentDo(state, 'componentStart', 1102 'Start', 'Starting', 'Started')
1103
1104 - def _componentDelete(self, state):
1105 """ 1106 @returns: a L{twisted.internet.defer.Deferred} 1107 """ 1108 return self._componentDo(state, 'deleteComponent', 1109 'Delete', 'Deleting', 'Deleted')
1110
1111 - def _getStatesFromState(self, state):
1112 # componentDo can be called on a None state, which means 1113 # 'look at the current selection' 1114 if state is None: 1115 states = self._componentList.getSelectedStates() 1116 self._componentView.activateComponent(None) 1117 else: 1118 states = [state] 1119 1120 return states
1121
1122 - def _componentDo(self, state, methodName, action, doing, done):
1123 """Do something with a component and update the statusbar. 1124 1125 @param state: componentState; if not specified, will use the 1126 currently selected component(s) 1127 @type state: L{AdminComponentState} or None 1128 @param methodName: name of the method to call 1129 @type methodName: str 1130 @param action: string used to explain that to do 1131 @type action: str 1132 @param doing: string used to explain that the action started 1133 @type doing: str 1134 @param done: string used to explain that the action was completed 1135 @type done: str 1136 1137 @rtype: L{twisted.internet.defer.Deferred} 1138 @returns: a deferred that will fire when the action is completed. 1139 """ 1140 states = self._getStatesFromState(state) 1141 1142 if not states: 1143 return 1144 1145 def callbackSingle(result, self, mid, name): 1146 self._statusbar.remove('main', mid) 1147 self._setStatusbarText( 1148 _("%s component %s") % (done, name))
1149 1150 def errbackSingle(failure, self, mid, name): 1151 self._statusbar.remove('main', mid) 1152 self.warning("Failed to %s component %s: %s" % ( 1153 action.lower(), name, failure)) 1154 self._setStatusbarText( 1155 _("Failed to %(action)s component %(name)s.") % { 1156 'action': action.lower(), 1157 'name': name, 1158 }) 1159 1160 def callbackMultiple(results, self, mid): 1161 self._statusbar.remove('main', mid) 1162 self._setStatusbarText( 1163 _("%s components.") % (done, )) 1164 1165 def errbackMultiple(failure, self, mid): 1166 self._statusbar.remove('main', mid) 1167 self.warning("Failed to %s some components: %s." % ( 1168 action.lower(), failure)) 1169 self._setStatusbarText( 1170 _("Failed to %s some components.") % (action, )) 1171 1172 f = gettext.dngettext( 1173 configure.PACKAGE, 1174 # first %s is one of Stopping/Starting/Deleting 1175 # second %s is a component name like "audio-producer" 1176 N_("%s component %s"), 1177 # first %s is one of Stopping/Starting/Deleting 1178 # second %s is a list of component names, like 1179 # "audio-producer, video-producer" 1180 N_("%s components %s"), len(states)) 1181 statusText = f % (doing, 1182 ', '.join([getComponentLabel(s) for s in states])) 1183 mid = self._setStatusbarText(statusText) 1184 1185 if len(states) == 1: 1186 state = states[0] 1187 name = getComponentLabel(state) 1188 d = self._adminModel.callRemote(methodName, state) 1189 d.addCallback(callbackSingle, self, mid, name) 1190 d.addErrback(errbackSingle, self, mid, name) 1191 else: 1192 deferreds = [] 1193 for state in states: 1194 d = self._adminModel.callRemote(methodName, state) 1195 deferreds.append(d) 1196 d = defer.DeferredList(deferreds) 1197 d.addCallback(callbackMultiple, self, mid) 1198 d.addErrback(errbackMultiple, self, mid) 1199 return d 1200
1201 - def _killSelectedComponents(self):
1202 for state in self._componentList.getSelectedStates(): 1203 workerName = state.get('workerRequested') 1204 avatarId = componentId(state.get('parent').get('name'), 1205 state.get('name')) 1206 self._adminModel.callRemote( 1207 'workerCallRemote', workerName, 'killJob', avatarId)
1208
1209 - def _componentSelectionChanged(self, states):
1210 self.debug('component %s has selection', states) 1211 1212 def compSet(state, key, value): 1213 if key == 'mood': 1214 self.debug('state %r has mood set to %r' % (state, value)) 1215 self._updateComponentActions()
1216 1217 def compAppend(state, key, value): 1218 name = state.get('name') 1219 self.debug('stateAppend on component state of %s' % name) 1220 if key == 'messages': 1221 current = self._componentList.getSelectedNames() 1222 if name in current: 1223 self._messageView.addMessage(value) 1224 1225 def compRemove(state, key, value): 1226 name = state.get('name') 1227 self.debug('stateRemove on component state of %s' % name) 1228 if key == 'messages': 1229 current = self._componentList.getSelectedNames() 1230 if name in current: 1231 self._messageView.clearMessage(value.id) 1232 1233 if self._currentComponentStates: 1234 for currentComponentState in self._currentComponentStates: 1235 currentComponentState.removeListener(self) 1236 self._currentComponentStates = states 1237 if self._currentComponentStates: 1238 for currentComponentState in self._currentComponentStates: 1239 currentComponentState.addListener( 1240 self, set_=compSet, append=compAppend, remove=compRemove) 1241 1242 self._updateComponentActions() 1243 self._clearMessages() 1244 state = None 1245 if states: 1246 if len(states) == 1: 1247 self.debug( 1248 "only one component is selected on the components view") 1249 state = states[0] 1250 elif states: 1251 self.debug("more than one components are selected in the " 1252 "components view") 1253 state = MultipleAdminComponentStates(states) 1254 self._componentView.activateComponent(state) 1255 1256 statusbarMessage = " " 1257 for state in states: 1258 name = getComponentLabel(state) 1259 messages = state.get('messages') 1260 if messages: 1261 for m in messages: 1262 self.debug('have message %r', m) 1263 self.debug('message id %s', m.id) 1264 self._messageView.addMessage(m) 1265 1266 if state.get('mood') == moods.sad.value: 1267 self.debug('component %s is sad' % name) 1268 statusbarMessage = statusbarMessage + \ 1269 _("Component %s is sad. ") % name 1270 if statusbarMessage != " ": 1271 self._setStatusbarText(statusbarMessage) 1272 1273 1274 # FIXME: show statusbar things 1275 # self._statusbar.set('main', _('Showing UI for %s') % name) 1276 # self._statusbar.set('main', 1277 # _("Component %s is still sleeping") % name) 1278 # self._statusbar.set('main', _("Requesting UI for %s ...") % name) 1279 # self._statusbar.set('main', _("Loading UI for %s ...") % name) 1280 # self._statusbar.clear('main') 1281 # mid = self._statusbar.push('notebook', 1282 # _("Loading tab %s for %s ...") % (node.title, name)) 1283 # node.statusbar = self._statusbar # hack 1284
1285 - def _componentShowPopupMenu(self, event_button, event_time):
1286 self._componentContextMenu.popup(None, None, None, 1287 event_button, event_time)
1288
1289 - def _connectionOpened(self, admin):
1290 self.info('Connected to manager') 1291 if self._disconnectedDialog: 1292 self._disconnectedDialog.destroy() 1293 self._disconnectedDialog = None 1294 1295 self._updateUIStatus(connected=True) 1296 1297 self.emit('connected') 1298 1299 self._componentView.setSingleAdmin(admin) 1300 1301 self._setPlanetState(admin.planet) 1302 self._updateConnectionActions() 1303 self._updateComponentActions() 1304 1305 if (not self._componentStates and 1306 not self._configurationAssistantIsRunning): 1307 self.debug('no components detected, running assistant') 1308 # ensure our window is shown 1309 self._componentList.clearAndRebuild(self._componentStates) 1310 self.show() 1311 self._runConfigurationAssistant() 1312 else: 1313 self.show()
1314
1315 - def _showConnectionLostDialog(self):
1316 RESPONSE_REFRESH = 1 1317 1318 def response(dialog, response_id): 1319 if response_id == RESPONSE_REFRESH: 1320 1321 def errback(failure): 1322 # Swallow connection errors. We keep trying 1323 failure.trap(ConnectionCancelledError, 1324 ConnectionFailedError, 1325 ConnectionRefusedError)
1326 1327 d = self._adminModel.reconnect(keepTrying=True) 1328 d.addErrback(errback) 1329 else: 1330 self._disconnectedDialog.destroy() 1331 self._disconnectedDialog = None 1332 self._adminModel.disconnectFromManager() 1333 self._window.set_sensitive(True) 1334 1335 dialog = ProgressDialog( 1336 _("Reconnecting ..."), 1337 _("Lost connection to manager %s. Reconnecting ...") 1338 % (self._adminModel.adminInfoStr(), ), self._window) 1339 1340 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 1341 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH) 1342 dialog.connect("response", response) 1343 dialog.start() 1344 self._disconnectedDialog = dialog 1345 self._window.set_sensitive(False) 1346
1347 - def _connectionLost(self):
1348 self._componentStates = {} 1349 self._updateComponents() 1350 self._clearMessages() 1351 if self._planetState: 1352 self._planetState.removeListener(self) 1353 self._planetState = None 1354 1355 self._showConnectionLostDialog() 1356 self._updateUIStatus(connected=False)
1357
1358 - def _openRecentConnection(self):
1359 d = ConnectionsDialog(parent=self._window) 1360 1361 def on_have_connection(d, connectionInfo): 1362 d.destroy() 1363 if connectionInfo: 1364 self._openConnectionInternal(connectionInfo.info) 1365 connectionInfo.updateTimestamp() 1366 self._updateConnectionActions()
1367 1368 d.connect('have-connection', on_have_connection) 1369 d.show() 1370
1371 - def _openExistingConnection(self):
1372 from flumotion.admin.gtk.greeter import ConnectExisting 1373 from flumotion.ui.simplewizard import WizardCancelled 1374 wiz = ConnectExisting(parent=self._window) 1375 1376 def got_state(state, g): 1377 g.set_sensitive(False) 1378 g.destroy() 1379 self._openConnectionInternal(state['connectionInfo'])
1380 1381 def cancel(failure): 1382 failure.trap(WizardCancelled) 1383 wiz.stop() 1384 1385 d = wiz.runAsync() 1386 d.addCallback(got_state, wiz) 1387 d.addErrback(cancel) 1388
1389 - def _importConfiguration(self):
1390 dialog = gtk.FileChooserDialog( 1391 _("Import Configuration..."), self._window, 1392 gtk.FILE_CHOOSER_ACTION_OPEN, 1393 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 1394 _('Import'), gtk.RESPONSE_ACCEPT)) 1395 dialog.set_modal(True) 1396 dialog.set_default_response(gtk.RESPONSE_ACCEPT) 1397 dialog.set_select_multiple(True) 1398 1399 ffilter = gtk.FileFilter() 1400 ffilter.set_name(_("Flumotion XML configuration files")) 1401 ffilter.add_pattern("*.xml") 1402 dialog.add_filter(ffilter) 1403 ffilter = gtk.FileFilter() 1404 ffilter.set_name(_("All files")) 1405 ffilter.add_pattern("*") 1406 dialog.add_filter(ffilter) 1407 1408 settings = getSettings() 1409 if settings.hasValue('import_dir'): 1410 dialog.set_current_folder_uri(settings.getValue('import_dir')) 1411 1412 def response(dialog, response): 1413 if response == gtk.RESPONSE_ACCEPT: 1414 if settings.getValue('import_dir') != \ 1415 dialog.get_current_folder_uri(): 1416 settings.setValue('import_dir', 1417 dialog.get_current_folder_uri()) 1418 settings.save() 1419 for name in dialog.get_filenames(): 1420 conf_xml = open(name, 'r').read() 1421 self._adminModel.loadConfiguration(conf_xml) 1422 dialog.destroy()
1423 1424 dialog.connect('response', response) 1425 dialog.show() 1426
1427 - def _exportConfiguration(self):
1428 d = gtk.FileChooserDialog( 1429 _("Export Configuration..."), self._window, 1430 gtk.FILE_CHOOSER_ACTION_SAVE, 1431 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 1432 _('Export'), gtk.RESPONSE_ACCEPT)) 1433 d.set_modal(True) 1434 d.set_default_response(gtk.RESPONSE_ACCEPT) 1435 d.set_current_name("configuration.xml") 1436 1437 settings = getSettings() 1438 if settings.hasValue('export_dir'): 1439 d.set_current_folder_uri(settings.getValue('export_dir')) 1440 1441 def getConfiguration(conf_xml, name, chooser): 1442 if not name.endswith('.xml'): 1443 name += '.xml' 1444 1445 file_exists = True 1446 if os.path.exists(name): 1447 d = gtk.MessageDialog( 1448 self._window, gtk.DIALOG_MODAL, 1449 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, 1450 _("File already exists.\nOverwrite?")) 1451 d.connect("response", lambda self, response: d.hide()) 1452 if d.run() == gtk.RESPONSE_YES: 1453 file_exists = False 1454 else: 1455 file_exists = False 1456 1457 if not file_exists: 1458 try: 1459 f = open(name, 'w') 1460 f.write(conf_xml) 1461 f.close() 1462 chooser.destroy() 1463 except IOError, e: 1464 self._error(_("Could not open configuration file %s " 1465 "for writing (%s)" % (name, e[1])))
1466 1467 def response(d, response): 1468 if response == gtk.RESPONSE_ACCEPT: 1469 self._currentDir = d.get_current_folder_uri() 1470 deferred = self._adminModel.getConfiguration() 1471 name = d.get_filename() 1472 if settings.getValue('export_dir') != \ 1473 d.get_current_folder_uri(): 1474 settings.setValue('export_dir', 1475 d.get_current_folder_uri()) 1476 settings.save() 1477 deferred.addCallback(getConfiguration, name, d) 1478 else: 1479 d.destroy() 1480 1481 d.connect('response', response) 1482 d.show() 1483
1484 - def _startShell(self):
1485 if sys.version_info >= (2, 4): 1486 from flumotion.extern import code 1487 code # pyflakes 1488 else: 1489 import code 1490 1491 ns = {"admin": self._adminModel, 1492 "components": self._componentStates} 1493 message = """Flumotion Admin Debug Shell 1494 1495 Local variables are: 1496 admin (flumotion.admin.admin.AdminModel) 1497 components (dict: name -> flumotion.common.planet.AdminComponentState) 1498 1499 You can do remote component calls using: 1500 admin.componentCallRemote(components['component-name'], 1501 'methodName', arg1, arg2) 1502 1503 """ 1504 code.interact(local=ns, banner=message)
1505
1506 - def _dumpConfiguration(self):
1507 1508 def gotConfiguration(xml): 1509 print xml
1510 d = self._adminModel.getConfiguration() 1511 d.addCallback(gotConfiguration) 1512
1513 - def _setDebugMarker(self):
1514 1515 def setMarker(_, marker, level): 1516 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1517 debugMarkerDialog = DebugMarkerDialog() 1518 debugMarkerDialog.connect('set-marker', setMarker) 1519 debugMarkerDialog.show() 1520
1521 - def _about(self):
1522 about = AboutDialog(self._window) 1523 about.run() 1524 about.destroy()
1525
1526 - def _showHelp(self):
1527 for path in os.environ['PATH'].split(':'): 1528 executable = os.path.join(path, 'gnome-help') 1529 if os.path.exists(executable): 1530 break 1531 else: 1532 self._error( 1533 _("Cannot find a program to display the Flumotion manual.")) 1534 return 1535 gobject.spawn_async([executable, 1536 'ghelp:%s' % (configure.PACKAGE, )])
1537
1538 - def _promptForShutdown(self):
1539 d = gtk.MessageDialog( 1540 self._window, gtk.DIALOG_MODAL, 1541 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 1542 _("Do you want to shutdown manager and worker " 1543 "before exiting?")) 1544 response = d.run() 1545 d.destroy() 1546 return response == gtk.RESPONSE_YES
1547 1548 ### admin model callbacks 1549
1550 - def _admin_connected_cb(self, admin):
1551 self._connectionOpened(admin)
1552
1553 - def _admin_disconnected_cb(self, admin):
1554 self._connectionLost()
1555
1556 - def _admin_update_cb(self, admin):
1557 self._updateComponents()
1558 1559 ### ui callbacks 1560
1561 - def _on_uimanager__connect_proxy(self, uimgr, action, widget):
1562 self._connectActionProxy(action, widget)
1563
1564 - def _on_uimanager__disconnect_proxy(self, uimgr, action, widget):
1565 self._disconnectActionProxy(action, widget)
1566
1567 - def _on_menu_item__select(self, menuitem, tooltip):
1568 self._setStatusbarText(tooltip)
1569
1570 - def _on_menu_item__deselect(self, menuitem):
1571 self._clearLastStatusbarText()
1572
1573 - def _on_tool_button__enter(self, toolbutton, tooltip):
1574 self._setStatusbarText(tooltip)
1575
1576 - def _on_tool_button__leave(self, toolbutton):
1577 self._clearLastStatusbarText()
1578
1579 - def _assistant_finished_cb(self, assistant, configuration):
1580 self._assistantFinshed(assistant, configuration)
1581
1582 - def on_assistant_destroy(self, assistant):
1583 self._configurationAssistantIsRunning = False
1584
1585 - def _window_delete_event_cb(self, window, event):
1586 self._quit()
1587
1588 - def _window_key_press_event_cb(self, window, event):
1589 # This should be removed if we're going to support connecting 1590 # to multiple managers in the same application (MDI/tabs) 1591 state = event.state & (gtk.gdk.MODIFIER_MASK ^ gtk.gdk.MOD2_MASK) 1592 1593 if state == gdk.CONTROL_MASK and event.keyval == keysyms.w: 1594 self._quit()
1595
1596 - def _trayicon_quit_cb(self, trayicon):
1597 self._quit()
1598
1599 - def _recent_action_activate_cb(self, action, conn):
1600 self._openConnectionInternal(conn.info)
1601
1602 - def _components_show_popup_menu_cb(self, clist, event_button, event_time):
1603 self._componentShowPopupMenu(event_button, event_time)
1604
1605 - def _components_selection_changed_cb(self, clist, state):
1606 self._componentSelectionChanged(state)
1607
1608 - def _components_start_stop_notify_cb(self, clist, pspec):
1609 self._updateComponentActions()
1610 1611 ### action callbacks 1612
1613 - def _debug_write_debug_marker_cb(self, action):
1614 self._setDebugMarker()
1615
1616 - def _connection_open_recent_cb(self, action):
1617 self._openRecentConnection()
1618
1619 - def _connection_open_existing_cb(self, action):
1620 self._openExistingConnection()
1621
1622 - def _connection_import_configuration_cb(self, action):
1623 self._importConfiguration()
1624
1625 - def _connection_export_configuration_cb(self, action):
1626 self._exportConfiguration()
1627
1628 - def _connection_quit_cb(self, action):
1629 self._quit()
1630
1631 - def _manage_start_component_cb(self, action):
1632 self._componentStart(None)
1633
1634 - def _manage_stop_component_cb(self, action):
1635 self._componentStop(None)
1636
1637 - def _manage_delete_component_cb(self, action):
1638 self._componentDelete(None)
1639
1640 - def _manage_start_all_cb(self, action):
1641 for c in self._componentStates.values(): 1642 self._componentStart(c)
1643
1644 - def _manage_stop_all_cb(self, action):
1645 for c in self._componentStates.values(): 1646 self._componentStop(c)
1647
1648 - def _manage_clear_all_cb(self, action):
1649 self._clearAllComponents()
1650
1651 - def _manage_add_format_cb(self, action):
1652 self._runAddNew('format')
1653
1654 - def _manage_add_streamer_cb(self, action):
1655 self._runAddNew('streamer')
1656
1657 - def _manage_run_assistant_cb(self, action):
1658 self._runConfigurationAssistant()
1659
1660 - def _debug_enable_cb(self, action):
1661 self.setDebugEnabled(action.get_active())
1662
1663 - def _debug_start_shell_cb(self, action):
1664 self._startShell()
1665
1666 - def _debug_dump_configuration_cb(self, action):
1667 self._dumpConfiguration()
1668
1669 - def _help_contents_cb(self, action):
1670 self._showHelp()
1671
1672 - def _help_about_cb(self, action):
1673 self._about()
1674
1675 - def _kill_component_cb(self, action):
1676 self._killSelectedComponents()
1677 1678 gobject.type_register(AdminWindow) 1679