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