1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
80 from flumotion.admin.connections import getRecentConnections, \
81 hasRecentConnections
82 from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \
83 ProgressDialog, showConnectionErrorDialog
84 from flumotion.admin.gtk.connections import ConnectionsDialog
85 from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList
86 from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog
87 from flumotion.admin.gtk.statusbar import AdminStatusbar
88 from flumotion.common.common import componentId
89 from flumotion.common.connection import PBConnectionInfo
90 from flumotion.common.errors import ConnectionCancelledError, \
91 ConnectionRefusedError, ConnectionFailedError, BusyComponentError
92 from flumotion.common.i18n import N_, gettexter
93 from flumotion.common.log import Loggable
94 from flumotion.common.planet import AdminComponentState, moods
95 from flumotion.common.pygobject import gsignal
96 from flumotion.configure import configure
97 from flumotion.manager import admin
98 from flumotion.twisted.flavors import IStateListener
99 from flumotion.ui.trayicon import FluTrayIcon
100
101 admin
102
103 __version__ = "$Rev: 8152 $"
104 _ = gettext.gettext
105 T_ = gettexter()
106
107 MAIN_UI = """
108 <ui>
109 <menubar name="Menubar">
110 <menu action="Connection">
111 <menuitem action="OpenRecent"/>
112 <menuitem action="OpenExisting"/>
113 <menuitem action="ImportConfig"/>
114 <menuitem action="ExportConfig"/>
115 <separator name="sep-conn1"/>
116 <placeholder name="Recent"/>
117 <separator name="sep-conn2"/>
118 <menuitem action="Quit"/>
119 </menu>
120 <menu action="Manage">
121 <menuitem action="StartComponent"/>
122 <menuitem action="StopComponent"/>
123 <menuitem action="DeleteComponent"/>
124 <separator name="sep-manage1"/>
125 <menuitem action="StartAll"/>
126 <menuitem action="StopAll"/>
127 <menuitem action="ClearAll"/>
128 <separator name="sep-manage2"/>
129 <menuitem action="AddFormat"/>
130 <separator name="sep-manage3"/>
131 <menuitem action="RunConfigurationAssistant"/>
132 </menu>
133 <menu action="Debug">
134 <menuitem action="EnableDebugging"/>
135 <separator name="sep-debug1"/>
136 <menuitem action="StartShell"/>
137 <menuitem action="DumpConfiguration"/>
138 <menuitem action="WriteDebugMarker"/>
139 </menu>
140 <menu action="Help">
141 <menuitem action="Contents"/>
142 <menuitem action="About"/>
143 </menu>
144 </menubar>
145 <toolbar name="Toolbar">
146 <toolitem action="OpenRecent"/>
147 <separator name="sep-toolbar1"/>
148 <toolitem action="StartComponent"/>
149 <toolitem action="StopComponent"/>
150 <toolitem action="DeleteComponent"/>
151 <separator name="sep-toolbar2"/>
152 <toolitem action="RunConfigurationAssistant"/>
153 </toolbar>
154 <popup name="ComponentContextMenu">
155 <menuitem action="StartComponent"/>
156 <menuitem action="StopComponent"/>
157 <menuitem action="DeleteComponent"/>
158 <menuitem action="KillComponent"/>
159 </popup>
160 </ui>
161 """
162
163 RECENT_UI_TEMPLATE = '''<ui>
164 <menubar name="Menubar">
165 <menu action="Connection">
166 <placeholder name="Recent">
167 %s
168 </placeholder>
169 </menu>
170 </menubar>
171 </ui>'''
172
173 MAX_RECENT_ITEMS = 4
174
175
177 '''Creates the GtkWindow for the user interface.
178 Also connects to the manager on the given host and port.
179 '''
180
181
182 gladefile = 'admin.glade'
183 toplevel_name = 'main_window'
184
185
186 logCategory = 'adminwindow'
187
188
189 implements(IStateListener)
190
191
192 gsignal('connected')
193
195 GladeDelegate.__init__(self)
196
197 self._adminModel = None
198 self._currentComponentStates = None
199 self._componentContextMenu = None
200 self._componentList = None
201 self._componentStates = None
202 self._componentView = None
203 self._componentNameToSelect = None
204 self._debugEnabled = False
205 self._debugActions = None
206 self._debugEnableAction = None
207 self._disconnectedDialog = None
208 self._planetState = None
209 self._recentMenuID = None
210 self._trayicon = None
211 self._configurationAssistantIsRunning = False
212
213 self._createUI()
214 self._appendRecentConnections()
215 self.setDebugEnabled(False)
216
217
218
219
220
221
222
223
238
239
240
241
242
243
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
283
289
295
298
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
310 """Get the gtk window for the admin interface.
311
312 @returns: window
313 @rtype: L{gtk.Window}
314 """
315 return self._window
316
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 return self._openConnection(info)
325
326
327
329 self.debug('creating UI')
330
331
332 self._window = self.toplevel
333 self._componentList = ComponentList(self.component_list)
334 del self.component_list
335 self._componentView = self.component_view
336 del self.component_view
337 self._statusbar = AdminStatusbar(self.statusbar)
338 del self.statusbar
339 self._messageView = self.messages_view
340 del self.messages_view
341
342 self._window.set_name("AdminWindow")
343 self._window.connect('delete-event',
344 self._window_delete_event_cb)
345 self._window.connect('key-press-event',
346 self._window_key_press_event_cb)
347
348 uimgr = gtk.UIManager()
349 uimgr.connect('connect-proxy',
350 self._on_uimanager__connect_proxy)
351 uimgr.connect('disconnect-proxy',
352 self._on_uimanager__disconnect_proxy)
353
354
355 group = gtk.ActionGroup('Actions')
356 group.add_actions([
357
358 ('Connection', None, _("_Connection")),
359 ('OpenRecent', gtk.STOCK_OPEN, _('_Open Recent Connection...'),
360 None, _('Connect to a recently used connection'),
361 self._connection_open_recent_cb),
362 ('OpenExisting', None, _('Connect to _running manager...'), None,
363 _('Connect to a previously used connection'),
364 self._connection_open_existing_cb),
365 ('ImportConfig', None, _('_Import Configuration...'), None,
366 _('Import a configuration from a file'),
367 self._connection_import_configuration_cb),
368 ('ExportConfig', None, _('_Export Configuration...'), None,
369 _('Export the current configuration to a file'),
370 self._connection_export_configuration_cb),
371 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None,
372 _('Quit the application and disconnect from the manager'),
373 self._connection_quit_cb),
374
375
376 ('Manage', None, _('_Manage')),
377 ('StartComponent', gtk.STOCK_MEDIA_PLAY, _('_Start Component(s)'),
378 None, _('Start the selected component(s)'),
379 self._manage_start_component_cb),
380 ('StopComponent', gtk.STOCK_MEDIA_STOP, _('St_op Component(s)'),
381 None, _('Stop the selected component(s)'),
382 self._manage_stop_component_cb),
383 ('DeleteComponent', gtk.STOCK_DELETE, _('_Delete Component(s)'),
384 None, _('Delete the selected component(s)'),
385 self._manage_delete_component_cb),
386 ('StartAll', None, _('Start _All'), None,
387 _('Start all components'),
388 self._manage_start_all_cb),
389 ('StopAll', None, _('Stop A_ll'), None,
390 _('Stop all components'),
391 self._manage_stop_all_cb),
392 ('ClearAll', gtk.STOCK_CLEAR, _('_Clear All'), None,
393 _('Remove all components'),
394 self._manage_clear_all_cb),
395 ('AddFormat', gtk.STOCK_ADD, _('Add new encoding _format...'),
396 None,
397 _('Add a new format to the current stream'),
398 self._manage_add_format_cb),
399 ('RunConfigurationAssistant', 'flumotion.admin.gtk',
400 _('Run _Assistant'), None,
401 _('Run the configuration assistant'),
402 self._manage_run_assistant_cb),
403
404
405 ('Debug', None, _('_Debug')),
406
407
408 ('Help', None, _('_Help')),
409 ('Contents', gtk.STOCK_HELP, _('_Contents'), 'F1',
410 _('Open the Flumotion manual'),
411 self._help_contents_cb),
412 ('About', gtk.STOCK_ABOUT, _('_About'), None,
413 _('About this software'),
414 self._help_about_cb),
415
416
417 ('KillComponent', None, _('_Kill Component'), None,
418 _('Kills the currently selected component'),
419 self._kill_component_cb),
420
421 ])
422 group.add_toggle_actions([
423 ('EnableDebugging', None, _('Enable _Debugging'), None,
424 _('Enable debugging in the admin interface'),
425 self._debug_enable_cb),
426 ])
427 self._debugEnableAction = group.get_action('EnableDebugging')
428 uimgr.insert_action_group(group, 0)
429
430
431 self._debugActions = gtk.ActionGroup('Actions')
432 self._debugActions.add_actions([
433
434 ('StartShell', gtk.STOCK_EXECUTE, _('Start _Shell'), None,
435 _('Start an interactive debugging shell'),
436 self._debug_start_shell_cb),
437 ('DumpConfiguration', gtk.STOCK_EXECUTE,
438 _('Dump configuration'), None,
439 _('Dumps the current manager configuration'),
440 self._debug_dump_configuration_cb),
441 ('WriteDebugMarker', gtk.STOCK_EXECUTE,
442 _('Write debug marker...'), None,
443 _('Writes a debug marker to all the logs'),
444 self._debug_write_debug_marker_cb)])
445 uimgr.insert_action_group(self._debugActions, 0)
446 self._debugActions.set_sensitive(False)
447
448 uimgr.add_ui_from_string(MAIN_UI)
449 self._window.add_accel_group(uimgr.get_accel_group())
450
451 menubar = uimgr.get_widget('/Menubar')
452 self.main_vbox.pack_start(menubar, expand=False)
453 self.main_vbox.reorder_child(menubar, 0)
454
455 toolbar = uimgr.get_widget('/Toolbar')
456 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
457 toolbar.set_style(gtk.TOOLBAR_ICONS)
458 self.main_vbox.pack_start(toolbar, expand=False)
459 self.main_vbox.reorder_child(toolbar, 1)
460
461 self._componentContextMenu = uimgr.get_widget('/ComponentContextMenu')
462 self._componentContextMenu.show()
463
464 menubar.show_all()
465
466 self._actiongroup = group
467 self._uimgr = uimgr
468 self._openRecentAction = group.get_action("OpenRecent")
469 self._startComponentAction = group.get_action("StartComponent")
470 self._stopComponentAction = group.get_action("StopComponent")
471 self._deleteComponentAction = group.get_action("DeleteComponent")
472 self._stopAllAction = group.get_action("StopAll")
473 assert self._stopAllAction
474 self._startAllAction = group.get_action("StartAll")
475 assert self._startAllAction
476 self._clearAllAction = group.get_action("ClearAll")
477 assert self._clearAllAction
478 self._addFormatAction = group.get_action("AddFormat")
479 self._addFormatAction.set_sensitive(False)
480 self._runConfigurationAssistantAction = (
481 group.get_action("RunConfigurationAssistant"))
482 self._runConfigurationAssistantAction.set_sensitive(False)
483 self._killComponentAction = group.get_action("KillComponent")
484 assert self._killComponentAction
485
486 self._trayicon = FluTrayIcon(self._window)
487 self._trayicon.connect("quit", self._trayicon_quit_cb)
488 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
489
490 self._componentList.connect('selection_changed',
491 self._components_selection_changed_cb)
492 self._componentList.connect('show-popup-menu',
493 self._components_show_popup_menu_cb)
494
495 self._updateComponentActions()
496 self._componentList.connect(
497 'notify::can-start-any',
498 self._components_start_stop_notify_cb)
499 self._componentList.connect(
500 'notify::can-stop-any',
501 self._components_start_stop_notify_cb)
502 self._updateComponentActions()
503
504 self._messageView.hide()
505
507 tooltip = action.get_property('tooltip')
508 if not tooltip:
509 return
510
511 if isinstance(widget, gtk.MenuItem):
512 cid = widget.connect('select', self._on_menu_item__select,
513 tooltip)
514 cid2 = widget.connect('deselect', self._on_menu_item__deselect)
515 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
516 elif isinstance(widget, gtk.ToolButton):
517 cid = widget.child.connect('enter', self._on_tool_button__enter,
518 tooltip)
519 cid2 = widget.child.connect('leave', self._on_tool_button__leave)
520 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
521
523 cids = widget.get_data('pygtk-app::proxy-signal-ids')
524 if not cids:
525 return
526
527 if isinstance(widget, gtk.ToolButton):
528 widget = widget.child
529
530 for cid in cids:
531 widget.disconnect(cid)
532
561
569
570 model = AdminModel()
571 d = model.connectToManager(info)
572 d.addCallback(connected)
573 return d
574
576 d = self._openConnection(info)
577
578 def errorMessageDisplayed(unused):
579 self._window.set_sensitive(True)
580
581 def connected(model):
582 self._window.set_sensitive(True)
583
584 def errbackConnectionRefusedError(failure):
585 failure.trap(ConnectionRefusedError)
586 d = showConnectionErrorDialog(failure, info, parent=self._window)
587 d.addCallback(errorMessageDisplayed)
588
589 def errbackConnectionFailedError(failure):
590 failure.trap(ConnectionFailedError)
591 d = showConnectionErrorDialog(failure, info, parent=self._window)
592 d.addCallback(errorMessageDisplayed)
593 return d
594
595 d.addCallback(connected)
596 d.addErrback(errbackConnectionRefusedError)
597 d.addErrback(errbackConnectionFailedError)
598 self._window.set_sensitive(False)
599 return d
600
620
622 """Quitting the application in a controlled manner"""
623 self._clearAdmin()
624 self._close()
625
628
630 import pprint
631 import cStringIO
632 fd = cStringIO.StringIO()
633 pprint.pprint(configation, fd)
634 fd.seek(0)
635 self.debug('Configuration=%s' % fd.read())
636
641
642 - def _setStatusbarText(self, text):
643 return self._statusbar.push('main', text)
644
646 self._statusbar.pop('main')
647
659
661 """
662 Obtains the components according a given type.
663
664 @param componentType: The type of the components to get
665 @type componentType: str
666
667 @rtype : list of L{flumotion.common.component.AdminComponentState}
668 """
669 if componentType is None:
670 raise ValueError
671
672 componentStates = []
673
674 for state in self._componentStates.values():
675 config = state.get('config')
676 if componentType and config['type'] == componentType:
677 componentStates.append(state)
678
679 return componentStates
680
702
704 """
705 Sets the mount points currently used on the flow so they can not
706 be used for others servers or streamers.
707
708 @param wizard : An assistant that wants to know the used mount_points
709 @type wizard : L{ConfigurationAssistant}
710 """
711 streamerStates = self._getComponentsBy(componentType='http-streamer')
712 serverStates = self._getComponentsBy(componentType='http-server')
713 porterStates = self._getComponentsBy(componentType='porter')
714
715 for porter in porterStates:
716 properties = porter.get('config')['properties']
717 for streamer in streamerStates + serverStates:
718 streamerProperties = streamer.get('config')['properties']
719 socketPath = streamerProperties['porter-socket-path']
720
721 if socketPath == properties['socket-path']:
722 worker = streamer.get('workerRequested')
723 port = int(properties['port'])
724 mount_point = streamerProperties['mount-point']
725 wizard.addMountPoint(worker, port, mount_point)
726
735
736 for componentState, entry in _getComponents():
737 component = componentClass()
738 component.componentType = entry.componentType
739 component.description = entry.description
740 component.exists = True
741 component.name = componentState.get('name')
742 config = componentState.get('config')
743 for key, value in config['properties'].items():
744 component.properties[key] = value
745 yield component
746
772
773 def gotBundledFunction(function):
774 scenario = function()
775 scenario.setMode('addformat')
776 scenario.addSteps(configurationAssistant)
777 configurationAssistant.setScenario(scenario)
778 httpPorters = self._getHTTPPorters()
779 self._setMountPoints(configurationAssistant)
780 if httpPorters:
781 configurationAssistant.setHTTPPorters(httpPorters)
782
783 return self._adminModel.getWizardEntries(
784 wizardTypes=['audio-producer', 'video-producer'])
785
786 d = self._adminModel.getBundledFunction(
787 'flumotion.scenario.live.wizard_gtk',
788 'LiveAssistantPlugin')
789
790 d.addCallback(gotBundledFunction)
791 d.addCallback(gotWizardEntries)
792
806
807 if not self._componentStates:
808 runAssistant()
809 return
810
811 for componentState in self._componentList.getComponentStates():
812 if componentState.get('mood') == moods.lost.value:
813 self._error(
814 _("Cannot run the configuration assistant since there "
815 "is at least one component in the lost state"))
816 return
817
818 if yesno(_("Running the Configuration Assistant again will remove "
819 "all components from the current stream and create "
820 "a new one."),
821 parent=self._window,
822 buttons=((_("Keep the current stream"),
823 gtk.RESPONSE_NO),
824 (_("Run the Assistant anyway"),
825 gtk.RESPONSE_YES))) != gtk.RESPONSE_YES:
826 return
827
828 d = self._clearAllComponents()
829
830
831 d.addCallback(lambda list: list and runAssistant())
832
856
868
870 self._window.set_sensitive(connected)
871 group = self._actiongroup
872 group.get_action('ImportConfig').set_sensitive(connected)
873 group.get_action('ExportConfig').set_sensitive(connected)
874 group.get_action('EnableDebugging').set_sensitive(connected)
875
876 self._clearLastStatusbarText()
877 if connected:
878 self._window.set_title(_('%s - Flumotion Administration') %
879 self._adminModel.adminInfoStr())
880 self._trayicon.set_tooltip(_('Flumotion: %s') % (
881 self._adminModel.adminInfoStr(), ))
882 else:
883 self._setStatusbarText(_('Not connected'))
884 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
885
888
890 canStart = self._componentList.canStart()
891 canStop = self._componentList.canStop()
892 canDelete = self._componentList.canDelete()
893 self._startComponentAction.set_sensitive(canStart)
894 self._stopComponentAction.set_sensitive(canStop)
895 self._deleteComponentAction.set_sensitive(canDelete)
896 self.debug('can start %r, can stop %r, can delete %r' % (
897 canStart, canStop, canDelete))
898 canStartAll = self._componentList.get_property('can-start-any')
899 canStopAll = self._componentList.get_property('can-stop-any')
900
901
902 canClearAll = canStartAll and not canStopAll
903 self._stopAllAction.set_sensitive(canStopAll)
904 self._startAllAction.set_sensitive(canStartAll)
905 self._clearAllAction.set_sensitive(canClearAll)
906 self._killComponentAction.set_sensitive(canStop)
907
908 hasProducer = self._hasProducerComponent()
909 self._addFormatAction.set_sensitive(hasProducer)
910
912 self._componentList.clearAndRebuild(self._componentStates,
913 self._componentNameToSelect)
914 self._trayicon.update(self._componentStates)
915
921
932
934 self._messageView.clear()
935 pstate = self._planetState
936 if pstate and pstate.hasKey('messages'):
937 for message in pstate.get('messages').values():
938 self._messageView.addMessage(message)
939
941
942 def flowStateAppend(state, key, value):
943 self.debug('flow state append: key %s, value %r' % (key, value))
944 if key == 'components':
945 self._appendComponent(value)
946
947 def flowStateRemove(state, key, value):
948 if key == 'components':
949 self._removeComponent(value)
950
951 def atmosphereStateAppend(state, key, value):
952 if key == 'components':
953 self._appendComponent(value)
954
955 def atmosphereStateRemove(state, key, value):
956 if key == 'components':
957 self._removeComponent(value)
958
959 def planetStateAppend(state, key, value):
960 if key == 'flows':
961 if value != state.get('flows')[0]:
962 self.warning('flumotion-admin can only handle one '
963 'flow, ignoring /%s', value.get('name'))
964 return
965 self.debug('%s flow started', value.get('name'))
966 value.addListener(self, append=flowStateAppend,
967 remove=flowStateRemove)
968
969 self._componentStates.update(
970 dict((c.get('name'), c) for c in value.get('components')))
971 self._updateComponents()
972
973 def planetStateRemove(state, key, value):
974 self.debug('something got removed from the planet')
975
976 def planetStateSetitem(state, key, subkey, value):
977 if key == 'messages':
978 self._messageView.addMessage(value)
979
980 def planetStateDelitem(state, key, subkey, value):
981 if key == 'messages':
982 self._messageView.clearMessage(value.id)
983
984 self.debug('parsing planetState %r' % planetState)
985 self._planetState = planetState
986
987
988 self._componentStates = {}
989
990 planetState.addListener(self, append=planetStateAppend,
991 remove=planetStateRemove,
992 setitem=planetStateSetitem,
993 delitem=planetStateDelitem)
994
995 self._clearMessages()
996
997 a = planetState.get('atmosphere')
998 a.addListener(self, append=atmosphereStateAppend,
999 remove=atmosphereStateRemove)
1000
1001 self._componentStates.update(
1002 dict((c.get('name'), c) for c in a.get('components')))
1003
1004 for f in planetState.get('flows'):
1005 planetStateAppend(planetState, 'flows', f)
1006
1007 if not planetState.get('flows'):
1008 self._updateComponents()
1009
1011 if not self._adminModel.isConnected():
1012 return
1013
1014 d = self._adminModel.cleanComponents()
1015
1016 def busyComponentError(failure):
1017 failure.trap(BusyComponentError)
1018 self._error(
1019 _("Some component(s) are still busy and cannot be removed.\n"
1020 "Try again later."))
1021 d.addErrback(busyComponentError)
1022 return d
1023
1024
1025
1039
1041 """
1042 @returns: a L{twisted.internet.defer.Deferred}
1043 """
1044 self.debug('stopping component %r' % state)
1045 return self._componentDo(state, 'componentStop',
1046 'Stop', 'Stopping', 'Stopped')
1047
1049 """
1050 @returns: a L{twisted.internet.defer.Deferred}
1051 """
1052 return self._componentDo(state, 'componentStart',
1053 'Start', 'Starting', 'Started')
1054
1056 """
1057 @returns: a L{twisted.internet.defer.Deferred}
1058 """
1059 return self._componentDo(state, 'deleteComponent',
1060 'Delete', 'Deleting', 'Deleted')
1061
1063
1064
1065 if state is None:
1066 states = self._componentList.getSelectedStates()
1067 else:
1068 states = [state]
1069
1070 return states
1071
1072 - def _componentDo(self, state, methodName, action, doing, done):
1073 """Do something with a component and update the statusbar.
1074
1075 @param state: componentState; if not specified, will use the
1076 currently selected component(s)
1077 @type state: L{AdminComponentState} or None
1078 @param methodName: name of the method to call
1079 @type methodName: str
1080 @param action: string used to explain that to do
1081 @type action: str
1082 @param doing: string used to explain that the action started
1083 @type doing: str
1084 @param done: string used to explain that the action was completed
1085 @type done: str
1086
1087 @rtype: L{twisted.internet.defer.Deferred}
1088 @returns: a deferred that will fire when the action is completed.
1089 """
1090 states = self._getStatesFromState(state)
1091
1092 if not states:
1093 return
1094
1095 def callbackSingle(result, self, mid, name):
1096 self._statusbar.remove('main', mid)
1097 self._setStatusbarText(
1098 _("%s component %s") % (done, name))
1099
1100 def errbackSingle(failure, self, mid, name):
1101 self._statusbar.remove('main', mid)
1102 self.warning("Failed to %s component %s: %s" % (
1103 action.lower(), name, failure))
1104 self._setStatusbarText(
1105 _("Failed to %(action)s component %(name)s.") % {
1106 'action': action.lower(),
1107 'name': name,
1108 })
1109
1110 def callbackMultiple(results, self, mid):
1111 self._statusbar.remove('main', mid)
1112 self._setStatusbarText(
1113 _("%s components.") % (done, ))
1114
1115 def errbackMultiple(failure, self, mid):
1116 self._statusbar.remove('main', mid)
1117 self.warning("Failed to %s some components: %s." % (
1118 action.lower(), failure))
1119 self._setStatusbarText(
1120 _("Failed to %s some components.") % (action, ))
1121
1122 f = gettext.dngettext(
1123 configure.PACKAGE,
1124
1125
1126 N_("%s component %s"),
1127
1128
1129
1130 N_("%s components %s"), len(states))
1131 statusText = f % (doing,
1132 ', '.join([getComponentLabel(s) for s in states]))
1133 mid = self._setStatusbarText(statusText)
1134
1135 if len(states) == 1:
1136 state = states[0]
1137 name = getComponentLabel(state)
1138 d = self._adminModel.callRemote(methodName, state)
1139 d.addCallback(callbackSingle, self, mid, name)
1140 d.addErrback(errbackSingle, self, mid, name)
1141 else:
1142 deferreds = []
1143 for state in states:
1144 d = self._adminModel.callRemote(methodName, state)
1145 deferreds.append(d)
1146 d = defer.DeferredList(deferreds)
1147 d.addCallback(callbackMultiple, self, mid)
1148 d.addErrback(errbackMultiple, self, mid)
1149 return d
1150
1158
1160 self.debug('component %s has selection', states)
1161
1162 def compSet(state, key, value):
1163 if key == 'mood':
1164 self.debug('state %r has mood set to %r' % (state, value))
1165 self._updateComponentActions()
1166
1167 def compAppend(state, key, value):
1168 name = state.get('name')
1169 self.debug('stateAppend on component state of %s' % name)
1170 if key == 'messages':
1171 current = self._componentList.getSelectedNames()
1172 if name in current:
1173 self._messageView.addMessage(value)
1174
1175 def compRemove(state, key, value):
1176 name = state.get('name')
1177 self.debug('stateRemove on component state of %s' % name)
1178 if key == 'messages':
1179 current = self._componentList.getSelectedNames()
1180 if name in current:
1181 self._messageView.clearMessage(value.id)
1182
1183 if self._currentComponentStates:
1184 for currentComponentState in self._currentComponentStates:
1185 currentComponentState.removeListener(self)
1186 self._currentComponentStates = states
1187 if self._currentComponentStates:
1188 for currentComponentState in self._currentComponentStates:
1189 currentComponentState.addListener(
1190 self, set=compSet, append=compAppend, remove=compRemove)
1191
1192 self._updateComponentActions()
1193 self._clearMessages()
1194 state = None
1195 if states:
1196 if len(states) == 1:
1197 self.debug(
1198 "only one component is selected on the components view")
1199 state = states[0]
1200 elif states:
1201 self.debug("more than one components are selected in the "
1202 "components view")
1203 self._componentView.activateComponent(state)
1204
1205 statusbarMessage = " "
1206 for state in states:
1207 name = getComponentLabel(state)
1208 messages = state.get('messages')
1209 if messages:
1210 for m in messages:
1211 self.debug('have message %r', m)
1212 self.debug('message id %s', m.id)
1213 self._messageView.addMessage(m)
1214
1215 if state.get('mood') == moods.sad.value:
1216 self.debug('component %s is sad' % name)
1217 statusbarMessage = statusbarMessage + \
1218 _("Component %s is sad. ") % name
1219 if statusbarMessage != " ":
1220 self._setStatusbarText(statusbarMessage)
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1235 self._componentContextMenu.popup(None, None, None,
1236 event_button, event_time)
1237
1263
1275
1276 d = self._adminModel.reconnect(keepTrying=True)
1277 d.addErrback(errback)
1278 else:
1279 self._disconnectedDialog.destroy()
1280 self._disconnectedDialog = None
1281 self._adminModel.disconnectFromManager()
1282 self._window.set_sensitive(True)
1283
1284 dialog = ProgressDialog(
1285 _("Reconnecting ..."),
1286 _("Lost connection to manager %s. Reconnecting ...")
1287 % (self._adminModel.adminInfoStr(), ), self._window)
1288
1289 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
1290 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH)
1291 dialog.connect("response", response)
1292 dialog.start()
1293 self._disconnectedDialog = dialog
1294 self._window.set_sensitive(False)
1295
1306
1316
1317 d.connect('have-connection', on_have_connection)
1318 d.show()
1319
1329
1330 def cancel(failure):
1331 failure.trap(WizardCancelled)
1332 wiz.stop()
1333
1334 d = wiz.runAsync()
1335 d.addCallback(got_state, wiz)
1336 d.addErrback(cancel)
1337
1339 dialog = gtk.FileChooserDialog(
1340 _("Import Configuration..."), self._window,
1341 gtk.FILE_CHOOSER_ACTION_OPEN,
1342 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1343 _('Import'), gtk.RESPONSE_ACCEPT))
1344 dialog.set_modal(True)
1345 dialog.set_default_response(gtk.RESPONSE_ACCEPT)
1346 ffilter = gtk.FileFilter()
1347 ffilter.set_name(_("Flumotion XML configuration files"))
1348 ffilter.add_pattern("*.xml")
1349 dialog.add_filter(ffilter)
1350 ffilter = gtk.FileFilter()
1351 ffilter.set_name(_("All files"))
1352 ffilter.add_pattern("*")
1353 dialog.add_filter(ffilter)
1354
1355 def response(dialog, response):
1356 if response == gtk.RESPONSE_ACCEPT:
1357 name = dialog.get_filename()
1358 conf_xml = open(name, 'r').read()
1359 self._adminModel.loadConfiguration(conf_xml)
1360 dialog.destroy()
1361
1362 dialog.connect('response', response)
1363 dialog.show()
1364
1366 d = gtk.FileChooserDialog(
1367 _("Export Configuration..."), self._window,
1368 gtk.FILE_CHOOSER_ACTION_SAVE,
1369 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1370 _('Export'), gtk.RESPONSE_ACCEPT))
1371 d.set_modal(True)
1372 d.set_default_response(gtk.RESPONSE_ACCEPT)
1373 d.set_current_name("configuration.xml")
1374
1375 def getConfiguration(conf_xml, name, chooser):
1376 if not name.endswith('.xml'):
1377 name += '.xml'
1378
1379 file_exists = True
1380 if os.path.exists(name):
1381 d = gtk.MessageDialog(
1382 self._window, gtk.DIALOG_MODAL,
1383 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO,
1384 _("File already exists.\nOverwrite?"))
1385 d.connect("response", lambda self, response: d.hide())
1386 if d.run() == gtk.RESPONSE_YES:
1387 file_exists = False
1388 else:
1389 file_exists = False
1390
1391 if not file_exists:
1392 f = open(name, 'w')
1393 f.write(conf_xml)
1394 f.close()
1395 chooser.destroy()
1396
1397 def response(d, response):
1398 if response == gtk.RESPONSE_ACCEPT:
1399 deferred = self._adminModel.getConfiguration()
1400 name = d.get_filename()
1401 deferred.addCallback(getConfiguration, name, d)
1402 else:
1403 d.destroy()
1404
1405 d.connect('response', response)
1406 d.show()
1407
1409 if sys.version_info >= (2, 4):
1410 from flumotion.extern import code
1411 code
1412 else:
1413 import code
1414
1415 ns = {"admin": self._adminModel,
1416 "components": self._componentStates}
1417 message = """Flumotion Admin Debug Shell
1418
1419 Local variables are:
1420 admin (flumotion.admin.admin.AdminModel)
1421 components (dict: name -> flumotion.common.planet.AdminComponentState)
1422
1423 You can do remote component calls using:
1424 admin.componentCallRemote(components['component-name'],
1425 'methodName', arg1, arg2)
1426
1427 """
1428 code.interact(local=ns, banner=message)
1429
1431
1432 def gotConfiguration(xml):
1433 print xml
1434 d = self._adminModel.getConfiguration()
1435 d.addCallback(gotConfiguration)
1436
1438
1439 def setMarker(_, marker, level):
1440 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1441 debugMarkerDialog = DebugMarkerDialog()
1442 debugMarkerDialog.connect('set-marker', setMarker)
1443 debugMarkerDialog.show()
1444
1449
1451 for path in os.environ['PATH'].split(':'):
1452 executable = os.path.join(path, 'gnome-help')
1453 if os.path.exists(executable):
1454 break
1455 else:
1456 self._error(
1457 _("Cannot find a program to display the Flumotion manual."))
1458 return
1459 gobject.spawn_async([executable,
1460 'ghelp:%s' % (configure.PACKAGE, )])
1461
1462
1463
1466
1469
1472
1473
1474
1477
1480
1483
1486
1489
1492
1495
1497 self._configurationAssistantIsRunning = False
1498
1501
1509
1512
1515
1518
1521
1524
1525
1526
1529
1532
1535
1538
1541
1544
1547
1550
1553
1557
1559 for c in self._componentStates.values():
1560 self._componentStop(c)
1561
1564
1567
1570
1573
1576
1579
1580 - def _help_contents_cb(self, action):
1582
1585
1588
1589 gobject.type_register(AdminWindow)
1590