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, Muxer
80 from flumotion.admin.connections import getRecentConnections, \
81 hasRecentConnections
82 from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \
83 ProgressDialog, showConnectionErrorDialog
84 from flumotion.admin.gtk.connections import ConnectionsDialog
85 from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList
86 from flumotion.admin.gtk.componentview import MultipleAdminComponentStates
87 from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog
88 from flumotion.admin.gtk.statusbar import AdminStatusbar
89 from flumotion.common.common import componentId
90 from flumotion.common.connection import PBConnectionInfo
91 from flumotion.common.errors import ConnectionCancelledError, \
92 ConnectionRefusedError, ConnectionFailedError, BusyComponentError
93 from flumotion.common.i18n import N_, gettexter
94 from flumotion.common.log import Loggable
95 from flumotion.common.planet import AdminComponentState, moods
96 from flumotion.common.pygobject import gsignal
97 from flumotion.configure import configure
98 from flumotion.manager import admin
99 from flumotion.twisted.flavors import IStateListener
100 from flumotion.ui.trayicon import FluTrayIcon
101
102 admin
103
104 __version__ = "$Rev: 8747 $"
105 _ = gettext.gettext
106 T_ = gettexter()
107
108 MAIN_UI = """
109 <ui>
110 <menubar name="Menubar">
111 <menu action="Connection">
112 <menuitem action="OpenRecent"/>
113 <menuitem action="OpenExisting"/>
114 <menuitem action="ImportConfig"/>
115 <menuitem action="ExportConfig"/>
116 <separator name="sep-conn1"/>
117 <placeholder name="Recent"/>
118 <separator name="sep-conn2"/>
119 <menuitem action="Quit"/>
120 </menu>
121 <menu action="Manage">
122 <menuitem action="StartComponent"/>
123 <menuitem action="StopComponent"/>
124 <menuitem action="DeleteComponent"/>
125 <separator name="sep-manage1"/>
126 <menuitem action="StartAll"/>
127 <menuitem action="StopAll"/>
128 <menuitem action="ClearAll"/>
129 <separator name="sep-manage2"/>
130 <menuitem action="AddFormat"/>
131 <menuitem action="AddStreamer"/>
132 <separator name="sep-manage3"/>
133 <menuitem action="RunConfigurationAssistant"/>
134 </menu>
135 <menu action="Debug">
136 <menuitem action="EnableDebugging"/>
137 <separator name="sep-debug1"/>
138 <menuitem action="StartShell"/>
139 <menuitem action="DumpConfiguration"/>
140 <menuitem action="WriteDebugMarker"/>
141 </menu>
142 <menu action="Help">
143 <menuitem action="Contents"/>
144 <menuitem action="About"/>
145 </menu>
146 </menubar>
147 <toolbar name="Toolbar">
148 <toolitem action="OpenRecent"/>
149 <separator name="sep-toolbar1"/>
150 <toolitem action="StartComponent"/>
151 <toolitem action="StopComponent"/>
152 <toolitem action="DeleteComponent"/>
153 <separator name="sep-toolbar2"/>
154 <toolitem action="RunConfigurationAssistant"/>
155 </toolbar>
156 <popup name="ComponentContextMenu">
157 <menuitem action="StartComponent"/>
158 <menuitem action="StopComponent"/>
159 <menuitem action="DeleteComponent"/>
160 <menuitem action="KillComponent"/>
161 </popup>
162 </ui>
163 """
164
165 RECENT_UI_TEMPLATE = '''<ui>
166 <menubar name="Menubar">
167 <menu action="Connection">
168 <placeholder name="Recent">
169 %s
170 </placeholder>
171 </menu>
172 </menubar>
173 </ui>'''
174
175 MAX_RECENT_ITEMS = 4
176
177
179 '''Creates the GtkWindow for the user interface.
180 Also connects to the manager on the given host and port.
181 '''
182
183
184 gladefile = 'admin.glade'
185 toplevel_name = 'main_window'
186
187
188 logCategory = 'adminwindow'
189
190
191 implements(IStateListener)
192
193
194 gsignal('connected')
195
197 GladeDelegate.__init__(self)
198
199 self._adminModel = None
200 self._currentComponentStates = None
201 self._componentContextMenu = None
202 self._componentList = None
203 self._componentStates = None
204 self._componentView = None
205 self._componentNameToSelect = None
206 self._debugEnabled = False
207 self._debugActions = None
208 self._debugEnableAction = None
209 self._disconnectedDialog = None
210 self._planetState = None
211 self._recentMenuID = None
212 self._trayicon = None
213 self._configurationAssistantIsRunning = False
214 self._currentDir = None
215 self._managerSpawner = None
216
217 self._createUI()
218 self._appendRecentConnections()
219 self.setDebugEnabled(False)
220
221
222
223
224
225
226
227
242
243
244
245
246
247
250
251 def cb(result, self, mid):
252 if mid:
253 self.statusbar.remove('main', mid)
254 if post:
255 self.statusbar.push('main', post % label)
256
257 def eb(failure, self, mid):
258 if mid:
259 self.statusbar.remove('main', mid)
260 self.warning("Failed to execute %s on component %s: %s"
261 % (methodName, label, failure))
262 if fail:
263 self.statusbar.push('main', fail % label)
264 if not state:
265 states = self.components_view.getSelectedStates()
266 if not states:
267 return
268 for state in states:
269 self.componentCallRemoteStatus(state, pre, post, fail,
270 methodName, args, kwargs)
271 else:
272 label = getComponentLabel(state)
273 if not label:
274 return
275
276 mid = None
277 if pre:
278 mid = self.statusbar.push('main', pre % label)
279 d = self._adminModel.componentCallRemote(
280 state, methodName, *args, **kwargs)
281 d.addCallback(cb, self, mid)
282 d.addErrback(eb, self, mid)
283
287
293
299
302
304 """Set if debug should be enabled for the admin client window
305 @param enable: if debug should be enabled
306 """
307 self._debugEnabled = enabled
308 self._debugActions.set_sensitive(enabled)
309 self._debugEnableAction.set_active(enabled)
310 self._componentView.setDebugEnabled(enabled)
311 self._killComponentAction.set_property('visible', enabled)
312
314 """Get the gtk window for the admin interface.
315
316 @returns: window
317 @rtype: L{gtk.Window}
318 """
319 return self._window
320
322 """Connects to a manager given a connection info.
323
324 @param info: connection info
325 @type info: L{PBConnectionInfo}
326 """
327 assert isinstance(info, PBConnectionInfo), info
328 self._managerSpawner = managerSpawner
329 return self._openConnection(info)
330
331
332
334 if minimize:
335 self._eat_resize_id = self._vpaned.connect(
336 'button-press-event', self._eat_resize_vpaned_event)
337 self._vpaned.set_position(-1)
338 else:
339 self._vpaned.disconnect(self._eat_resize_id)
340
344
346 self.debug('creating UI')
347
348
349 self._window = self.toplevel
350 self._componentList = ComponentList(self.component_list)
351 del self.component_list
352 self._componentView = self.component_view
353 del self.component_view
354 self._statusbar = AdminStatusbar(self.statusbar)
355 del self.statusbar
356 self._messageView = self.messages_view
357 del self.messages_view
358
359 self._messageView.connect('resize-event', self._resize_vpaned)
360 self._vpaned = self.vpaned
361 del self.vpaned
362 self._eat_resize_id = self._vpaned.connect(
363 'button-press-event', self._eat_resize_vpaned_event)
364
365 self._window.set_name("AdminWindow")
366 self._window.connect('delete-event',
367 self._window_delete_event_cb)
368 self._window.connect('key-press-event',
369 self._window_key_press_event_cb)
370
371 uimgr = gtk.UIManager()
372 uimgr.connect('connect-proxy',
373 self._on_uimanager__connect_proxy)
374 uimgr.connect('disconnect-proxy',
375 self._on_uimanager__disconnect_proxy)
376
377
378 group = gtk.ActionGroup('Actions')
379 group.add_actions([
380
381 ('Connection', None, _("_Connection")),
382 ('OpenRecent', gtk.STOCK_OPEN, _('_Open Recent Connection...'),
383 None, _('Connect to a recently used connection'),
384 self._connection_open_recent_cb),
385 ('OpenExisting', None, _('Connect to _running manager...'), None,
386 _('Connect to a previously used connection'),
387 self._connection_open_existing_cb),
388 ('ImportConfig', None, _('_Import Configuration...'), None,
389 _('Import a configuration from a file'),
390 self._connection_import_configuration_cb),
391 ('ExportConfig', None, _('_Export Configuration...'), None,
392 _('Export the current configuration to a file'),
393 self._connection_export_configuration_cb),
394 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None,
395 _('Quit the application and disconnect from the manager'),
396 self._connection_quit_cb),
397
398
399 ('Manage', None, _('_Manage')),
400 ('StartComponent', gtk.STOCK_MEDIA_PLAY, _('_Start Component(s)'),
401 None, _('Start the selected component(s)'),
402 self._manage_start_component_cb),
403 ('StopComponent', gtk.STOCK_MEDIA_STOP, _('St_op Component(s)'),
404 None, _('Stop the selected component(s)'),
405 self._manage_stop_component_cb),
406 ('DeleteComponent', gtk.STOCK_DELETE, _('_Delete Component(s)'),
407 None, _('Delete the selected component(s)'),
408 self._manage_delete_component_cb),
409 ('StartAll', None, _('Start _All'), None,
410 _('Start all components'),
411 self._manage_start_all_cb),
412 ('StopAll', None, _('Stop A_ll'), None,
413 _('Stop all components'),
414 self._manage_stop_all_cb),
415 ('ClearAll', gtk.STOCK_CLEAR, _('_Clear All'), None,
416 _('Remove all components'),
417 self._manage_clear_all_cb),
418 ('AddFormat', gtk.STOCK_ADD, _('Add new encoding _format...'),
419 None,
420 _('Add a new format to the current stream'),
421 self._manage_add_format_cb),
422 ('AddStreamer', gtk.STOCK_ADD, _('Add new _streamer...'),
423 None,
424 _('Add a new streamer to the flow'),
425 self._manage_add_streamer_cb),
426 ('RunConfigurationAssistant', 'flumotion.admin.gtk',
427 _('Run _Assistant'), None,
428 _('Run the configuration assistant'),
429 self._manage_run_assistant_cb),
430
431
432 ('Debug', None, _('_Debug')),
433
434
435 ('Help', None, _('_Help')),
436 ('Contents', gtk.STOCK_HELP, _('_Contents'), 'F1',
437 _('Open the Flumotion manual'),
438 self._help_contents_cb),
439 ('About', gtk.STOCK_ABOUT, _('_About'), None,
440 _('About this software'),
441 self._help_about_cb),
442
443
444 ('KillComponent', None, _('_Kill Component'), None,
445 _('Kills the currently selected component'),
446 self._kill_component_cb),
447
448 ])
449 group.add_toggle_actions([
450 ('EnableDebugging', None, _('Enable _Debugging'), None,
451 _('Enable debugging in the admin interface'),
452 self._debug_enable_cb),
453 ])
454 self._debugEnableAction = group.get_action('EnableDebugging')
455 uimgr.insert_action_group(group, 0)
456
457
458 self._debugActions = gtk.ActionGroup('Actions')
459 self._debugActions.add_actions([
460
461 ('StartShell', gtk.STOCK_EXECUTE, _('Start _Shell'), None,
462 _('Start an interactive debugging shell'),
463 self._debug_start_shell_cb),
464 ('DumpConfiguration', gtk.STOCK_EXECUTE,
465 _('Dump configuration'), None,
466 _('Dumps the current manager configuration'),
467 self._debug_dump_configuration_cb),
468 ('WriteDebugMarker', gtk.STOCK_EXECUTE,
469 _('Write debug marker...'), None,
470 _('Writes a debug marker to all the logs'),
471 self._debug_write_debug_marker_cb)])
472 uimgr.insert_action_group(self._debugActions, 0)
473 self._debugActions.set_sensitive(False)
474
475 uimgr.add_ui_from_string(MAIN_UI)
476 self._window.add_accel_group(uimgr.get_accel_group())
477
478 menubar = uimgr.get_widget('/Menubar')
479 self.main_vbox.pack_start(menubar, expand=False)
480 self.main_vbox.reorder_child(menubar, 0)
481
482 toolbar = uimgr.get_widget('/Toolbar')
483 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
484 toolbar.set_style(gtk.TOOLBAR_ICONS)
485 self.main_vbox.pack_start(toolbar, expand=False)
486 self.main_vbox.reorder_child(toolbar, 1)
487
488 self._componentContextMenu = uimgr.get_widget('/ComponentContextMenu')
489 self._componentContextMenu.show()
490
491 menubar.show_all()
492
493 self._actiongroup = group
494 self._uimgr = uimgr
495 self._openRecentAction = group.get_action("OpenRecent")
496 self._startComponentAction = group.get_action("StartComponent")
497 self._stopComponentAction = group.get_action("StopComponent")
498 self._deleteComponentAction = group.get_action("DeleteComponent")
499 self._stopAllAction = group.get_action("StopAll")
500 assert self._stopAllAction
501 self._startAllAction = group.get_action("StartAll")
502 assert self._startAllAction
503 self._clearAllAction = group.get_action("ClearAll")
504 assert self._clearAllAction
505 self._addFormatAction = group.get_action("AddFormat")
506 self._addFormatAction.set_sensitive(False)
507 self._addStreamerAction = group.get_action("AddStreamer")
508 self._addStreamerAction.set_sensitive(False)
509 self._runConfigurationAssistantAction = (
510 group.get_action("RunConfigurationAssistant"))
511 self._runConfigurationAssistantAction.set_sensitive(False)
512 self._killComponentAction = group.get_action("KillComponent")
513 assert self._killComponentAction
514
515 self._trayicon = FluTrayIcon(self._window)
516 self._trayicon.connect("quit", self._trayicon_quit_cb)
517 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
518
519 self._componentList.connect('selection_changed',
520 self._components_selection_changed_cb)
521 self._componentList.connect('show-popup-menu',
522 self._components_show_popup_menu_cb)
523
524 self._updateComponentActions()
525 self._componentList.connect(
526 'notify::can-start-any',
527 self._components_start_stop_notify_cb)
528 self._componentList.connect(
529 'notify::can-stop-any',
530 self._components_start_stop_notify_cb)
531 self._updateComponentActions()
532
533 self._messageView.hide()
534
536 tooltip = action.get_property('tooltip')
537 if not tooltip:
538 return
539
540 if isinstance(widget, gtk.MenuItem):
541 cid = widget.connect('select', self._on_menu_item__select,
542 tooltip)
543 cid2 = widget.connect('deselect', self._on_menu_item__deselect)
544 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
545 elif isinstance(widget, gtk.ToolButton):
546 cid = widget.child.connect('enter', self._on_tool_button__enter,
547 tooltip)
548 cid2 = widget.child.connect('leave', self._on_tool_button__leave)
549 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
550
552 cids = widget.get_data('pygtk-app::proxy-signal-ids')
553 if not cids:
554 return
555
556 if isinstance(widget, gtk.ToolButton):
557 widget = widget.child
558
559 for cid in cids:
560 widget.disconnect(cid)
561
590
598
599 model = AdminModel()
600 d = model.connectToManager(info)
601 d.addCallback(connected)
602 return d
603
605 d = self._openConnection(info)
606
607 def errorMessageDisplayed(unused):
608 self._window.set_sensitive(True)
609
610 def connected(model):
611 self._window.set_sensitive(True)
612
613 def errbackConnectionRefusedError(failure):
614 failure.trap(ConnectionRefusedError)
615 d = showConnectionErrorDialog(failure, info, parent=self._window)
616 d.addCallback(errorMessageDisplayed)
617
618 def errbackConnectionFailedError(failure):
619 failure.trap(ConnectionFailedError)
620 d = showConnectionErrorDialog(failure, info, parent=self._window)
621 d.addCallback(errorMessageDisplayed)
622 return d
623
624 d.addCallback(connected)
625 d.addErrback(errbackConnectionRefusedError)
626 d.addErrback(errbackConnectionFailedError)
627 self._window.set_sensitive(False)
628 return d
629
649
651 """Quitting the application in a controlled manner"""
652 self._clearAdmin()
653
654 def clearAndClose(unused):
655 self._close()
656 if self._managerSpawner and self._promptForShutdown():
657 r = self._managerSpawner.stop(True)
658 r.addCallback(clearAndClose)
659 else:
660 clearAndClose('')
661
664
666 import pprint
667 import cStringIO
668 fd = cStringIO.StringIO()
669 pprint.pprint(configation, fd)
670 fd.seek(0)
671 self.debug('Configuration=%s' % fd.read())
672
677
678 - def _setStatusbarText(self, text):
679 return self._statusbar.push('main', text)
680
682 self._statusbar.pop('main')
683
695
697 """
698 Obtains the components according a given type.
699
700 @param componentType: The type of the components to get
701 @type componentType: str
702
703 @rtype : list of L{flumotion.common.component.AdminComponentState}
704 """
705 if componentType is None:
706 raise ValueError
707
708 componentStates = []
709
710 for state in self._componentStates.values():
711 config = state.get('config')
712 if componentType and config['type'] == componentType:
713 componentStates.append(state)
714
715 return componentStates
716
738
740 """
741 Sets the mount points currently used on the flow so they can not
742 be used for others servers or streamers.
743
744 @param wizard : An assistant that wants to know the used mount_points
745 @type wizard : L{ConfigurationAssistant}
746 """
747 streamerStates = self._getComponentsBy(componentType='http-streamer')
748 serverStates = self._getComponentsBy(componentType='http-server')
749 porterStates = self._getComponentsBy(componentType='porter')
750
751 for porter in porterStates:
752 properties = porter.get('config')['properties']
753 for streamer in streamerStates + serverStates:
754 streamerProperties = streamer.get('config')['properties']
755 socketPath = streamerProperties['porter-socket-path']
756
757 if socketPath == properties['socket-path']:
758 worker = streamer.get('workerRequested')
759 port = int(properties['port'])
760 mount_point = streamerProperties['mount-point']
761 wizard.addMountPoint(worker, port, mount_point)
762
771
772 for componentState, entry in _getComponents():
773 component = componentClass()
774 component.componentType = entry.componentType
775 component.description = entry.description
776 component.exists = True
777 component.name = componentState.get('name')
778 config = componentState.get('config')
779 for key, value in config['properties'].items():
780 component.properties[key] = value
781 yield component
782
814
815 def gotBundledFunction(function):
816 scenario = function()
817 scenario.setMode('add%s' % addition)
818 scenario.addSteps(configurationAssistant)
819 configurationAssistant.setScenario(scenario)
820 httpPorters = self._getHTTPPorters()
821 self._setMountPoints(configurationAssistant)
822 if httpPorters:
823 configurationAssistant.setHTTPPorters(httpPorters)
824
825 if addition == 'format':
826 return self._adminModel.getWizardEntries(
827 wizardTypes=['audio-producer', 'video-producer'])
828 elif addition == 'streamer':
829 return self._adminModel.getWizardEntries(
830 wizardTypes=['muxer'])
831
832 d = self._adminModel.getBundledFunction(
833 'flumotion.scenario.live.wizard_gtk',
834 'LiveAssistantPlugin')
835
836 d.addCallback(gotBundledFunction)
837 d.addCallback(gotWizardEntries)
838
852
853 if not self._componentStates:
854 runAssistant()
855 return
856
857 for componentState in self._componentList.getComponentStates():
858 if componentState.get('mood') == moods.lost.value:
859 self._error(
860 _("Cannot run the configuration assistant since there "
861 "is at least one component in the lost state"))
862 return
863
864 if yesno(_("Running the Configuration Assistant again will remove "
865 "all components from the current stream and create "
866 "a new one."),
867 parent=self._window,
868 buttons=((_("Keep the current stream"),
869 gtk.RESPONSE_NO),
870 (_("Run the Assistant anyway"),
871 gtk.RESPONSE_YES))) != gtk.RESPONSE_YES:
872 return
873
874 d = self._clearAllComponents()
875
876
877 d.addCallback(lambda list: list and runAssistant())
878
902
915
917 self._window.set_sensitive(connected)
918 group = self._actiongroup
919 group.get_action('ImportConfig').set_sensitive(connected)
920 group.get_action('ExportConfig').set_sensitive(connected)
921 group.get_action('EnableDebugging').set_sensitive(connected)
922
923 self._clearLastStatusbarText()
924 if connected:
925 self._window.set_title(_('%s - Flumotion Administration') %
926 self._adminModel.adminInfoStr())
927 self._trayicon.set_tooltip(_('Flumotion: %s') % (
928 self._adminModel.adminInfoStr(), ))
929 else:
930 self._setStatusbarText(_('Not connected'))
931 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
932
935
937 canStart = self._componentList.canStart()
938 canStop = self._componentList.canStop()
939 canDelete = self._componentList.canDelete()
940 self._startComponentAction.set_sensitive(canStart)
941 self._stopComponentAction.set_sensitive(canStop)
942 self._deleteComponentAction.set_sensitive(canDelete)
943 self.debug('can start %r, can stop %r, can delete %r' % (
944 canStart, canStop, canDelete))
945 canStartAll = self._componentList.get_property('can-start-any')
946 canStopAll = self._componentList.get_property('can-stop-any')
947
948
949 canClearAll = canStartAll and not canStopAll
950 self._stopAllAction.set_sensitive(canStopAll)
951 self._startAllAction.set_sensitive(canStartAll)
952 self._clearAllAction.set_sensitive(canClearAll)
953 self._killComponentAction.set_sensitive(canStop)
954
955 hasProducer = self._hasProducerComponent()
956 self._addFormatAction.set_sensitive(hasProducer)
957 self._addStreamerAction.set_sensitive(hasProducer)
958
960 self._componentList.clearAndRebuild(self._componentStates,
961 self._componentNameToSelect)
962 self._trayicon.update(self._componentStates)
963
969
980
982 self._messageView.clear()
983 pstate = self._planetState
984 if pstate and pstate.hasKey('messages'):
985 for message in pstate.get('messages').values():
986 self._messageView.addMessage(message)
987
989
990 def flowStateAppend(state, key, value):
991 self.debug('flow state append: key %s, value %r' % (key, value))
992 if key == 'components':
993 self._appendComponent(value)
994
995 def flowStateRemove(state, key, value):
996 if key == 'components':
997 self._removeComponent(value)
998
999 def atmosphereStateAppend(state, key, value):
1000 if key == 'components':
1001 self._appendComponent(value)
1002
1003 def atmosphereStateRemove(state, key, value):
1004 if key == 'components':
1005 self._removeComponent(value)
1006
1007 def planetStateAppend(state, key, value):
1008 if key == 'flows':
1009 if value != state.get('flows')[0]:
1010 self.warning('flumotion-admin can only handle one '
1011 'flow, ignoring /%s', value.get('name'))
1012 return
1013 self.debug('%s flow started', value.get('name'))
1014 value.addListener(self, append=flowStateAppend,
1015 remove=flowStateRemove)
1016
1017 self._componentStates.update(
1018 dict((c.get('name'), c) for c in value.get('components')))
1019 self._updateComponents()
1020
1021 def planetStateRemove(state, key, value):
1022 self.debug('something got removed from the planet')
1023
1024 def planetStateSetitem(state, key, subkey, value):
1025 if key == 'messages':
1026 self._messageView.addMessage(value)
1027
1028 def planetStateDelitem(state, key, subkey, value):
1029 if key == 'messages':
1030 self._messageView.clearMessage(value.id)
1031
1032 self.debug('parsing planetState %r' % planetState)
1033 self._planetState = planetState
1034
1035
1036 self._componentStates = {}
1037
1038 planetState.addListener(self, append=planetStateAppend,
1039 remove=planetStateRemove,
1040 setitem=planetStateSetitem,
1041 delitem=planetStateDelitem)
1042
1043 self._clearMessages()
1044
1045 a = planetState.get('atmosphere')
1046 a.addListener(self, append=atmosphereStateAppend,
1047 remove=atmosphereStateRemove)
1048
1049 self._componentStates.update(
1050 dict((c.get('name'), c) for c in a.get('components')))
1051
1052 for f in planetState.get('flows'):
1053 planetStateAppend(planetState, 'flows', f)
1054
1055 if not planetState.get('flows'):
1056 self._updateComponents()
1057
1059 if not self._adminModel.isConnected():
1060 return
1061
1062 d = self._adminModel.cleanComponents()
1063
1064 def busyComponentError(failure):
1065 failure.trap(BusyComponentError)
1066 self._error(
1067 _("Some component(s) are still busy and cannot be removed.\n"
1068 "Try again later."))
1069 d.addErrback(busyComponentError)
1070 return d
1071
1072
1073
1087
1089 """
1090 @returns: a L{twisted.internet.defer.Deferred}
1091 """
1092 self.debug('stopping component %r' % state)
1093 return self._componentDo(state, 'componentStop',
1094 'Stop', 'Stopping', 'Stopped')
1095
1097 """
1098 @returns: a L{twisted.internet.defer.Deferred}
1099 """
1100 return self._componentDo(state, 'componentStart',
1101 'Start', 'Starting', 'Started')
1102
1104 """
1105 @returns: a L{twisted.internet.defer.Deferred}
1106 """
1107 return self._componentDo(state, 'deleteComponent',
1108 'Delete', 'Deleting', 'Deleted')
1109
1120
1121 - def _componentDo(self, state, methodName, action, doing, done):
1122 """Do something with a component and update the statusbar.
1123
1124 @param state: componentState; if not specified, will use the
1125 currently selected component(s)
1126 @type state: L{AdminComponentState} or None
1127 @param methodName: name of the method to call
1128 @type methodName: str
1129 @param action: string used to explain that to do
1130 @type action: str
1131 @param doing: string used to explain that the action started
1132 @type doing: str
1133 @param done: string used to explain that the action was completed
1134 @type done: str
1135
1136 @rtype: L{twisted.internet.defer.Deferred}
1137 @returns: a deferred that will fire when the action is completed.
1138 """
1139 states = self._getStatesFromState(state)
1140
1141 if not states:
1142 return
1143
1144 def callbackSingle(result, self, mid, name):
1145 self._statusbar.remove('main', mid)
1146 self._setStatusbarText(
1147 _("%s component %s") % (done, name))
1148
1149 def errbackSingle(failure, self, mid, name):
1150 self._statusbar.remove('main', mid)
1151 self.warning("Failed to %s component %s: %s" % (
1152 action.lower(), name, failure))
1153 self._setStatusbarText(
1154 _("Failed to %(action)s component %(name)s.") % {
1155 'action': action.lower(),
1156 'name': name,
1157 })
1158
1159 def callbackMultiple(results, self, mid):
1160 self._statusbar.remove('main', mid)
1161 self._setStatusbarText(
1162 _("%s components.") % (done, ))
1163
1164 def errbackMultiple(failure, self, mid):
1165 self._statusbar.remove('main', mid)
1166 self.warning("Failed to %s some components: %s." % (
1167 action.lower(), failure))
1168 self._setStatusbarText(
1169 _("Failed to %s some components.") % (action, ))
1170
1171 f = gettext.dngettext(
1172 configure.PACKAGE,
1173
1174
1175 N_("%s component %s"),
1176
1177
1178
1179 N_("%s components %s"), len(states))
1180 statusText = f % (doing,
1181 ', '.join([getComponentLabel(s) for s in states]))
1182 mid = self._setStatusbarText(statusText)
1183
1184 if len(states) == 1:
1185 state = states[0]
1186 name = getComponentLabel(state)
1187 d = self._adminModel.callRemote(methodName, state)
1188 d.addCallback(callbackSingle, self, mid, name)
1189 d.addErrback(errbackSingle, self, mid, name)
1190 else:
1191 deferreds = []
1192 for state in states:
1193 d = self._adminModel.callRemote(methodName, state)
1194 deferreds.append(d)
1195 d = defer.DeferredList(deferreds)
1196 d.addCallback(callbackMultiple, self, mid)
1197 d.addErrback(errbackMultiple, self, mid)
1198 return d
1199
1207
1209 self.debug('component %s has selection', states)
1210
1211 def compSet(state, key, value):
1212 if key == 'mood':
1213 self.debug('state %r has mood set to %r' % (state, value))
1214 self._updateComponentActions()
1215
1216 def compAppend(state, key, value):
1217 name = state.get('name')
1218 self.debug('stateAppend on component state of %s' % name)
1219 if key == 'messages':
1220 current = self._componentList.getSelectedNames()
1221 if name in current:
1222 self._messageView.addMessage(value)
1223
1224 def compRemove(state, key, value):
1225 name = state.get('name')
1226 self.debug('stateRemove on component state of %s' % name)
1227 if key == 'messages':
1228 current = self._componentList.getSelectedNames()
1229 if name in current:
1230 self._messageView.clearMessage(value.id)
1231
1232 if self._currentComponentStates:
1233 for currentComponentState in self._currentComponentStates:
1234 currentComponentState.removeListener(self)
1235 self._currentComponentStates = states
1236 if self._currentComponentStates:
1237 for currentComponentState in self._currentComponentStates:
1238 currentComponentState.addListener(
1239 self, set_=compSet, append=compAppend, remove=compRemove)
1240
1241 self._updateComponentActions()
1242 self._clearMessages()
1243 state = None
1244 if states:
1245 if len(states) == 1:
1246 self.debug(
1247 "only one component is selected on the components view")
1248 state = states[0]
1249 elif states:
1250 self.debug("more than one components are selected in the "
1251 "components view")
1252 state = MultipleAdminComponentStates(states)
1253 self._componentView.activateComponent(state)
1254
1255 statusbarMessage = " "
1256 for state in states:
1257 name = getComponentLabel(state)
1258 messages = state.get('messages')
1259 if messages:
1260 for m in messages:
1261 self.debug('have message %r', m)
1262 self.debug('message id %s', m.id)
1263 self._messageView.addMessage(m)
1264
1265 if state.get('mood') == moods.sad.value:
1266 self.debug('component %s is sad' % name)
1267 statusbarMessage = statusbarMessage + \
1268 _("Component %s is sad. ") % name
1269 if statusbarMessage != " ":
1270 self._setStatusbarText(statusbarMessage)
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1285 self._componentContextMenu.popup(None, None, None,
1286 event_button, event_time)
1287
1313
1325
1326 d = self._adminModel.reconnect(keepTrying=True)
1327 d.addErrback(errback)
1328 else:
1329 self._disconnectedDialog.destroy()
1330 self._disconnectedDialog = None
1331 self._adminModel.disconnectFromManager()
1332 self._window.set_sensitive(True)
1333
1334 dialog = ProgressDialog(
1335 _("Reconnecting ..."),
1336 _("Lost connection to manager %s. Reconnecting ...")
1337 % (self._adminModel.adminInfoStr(), ), self._window)
1338
1339 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
1340 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH)
1341 dialog.connect("response", response)
1342 dialog.start()
1343 self._disconnectedDialog = dialog
1344 self._window.set_sensitive(False)
1345
1356
1366
1367 d.connect('have-connection', on_have_connection)
1368 d.show()
1369
1379
1380 def cancel(failure):
1381 failure.trap(WizardCancelled)
1382 wiz.stop()
1383
1384 d = wiz.runAsync()
1385 d.addCallback(got_state, wiz)
1386 d.addErrback(cancel)
1387
1389 dialog = gtk.FileChooserDialog(
1390 _("Import Configuration..."), self._window,
1391 gtk.FILE_CHOOSER_ACTION_OPEN,
1392 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1393 _('Import'), gtk.RESPONSE_ACCEPT))
1394 dialog.set_modal(True)
1395 dialog.set_default_response(gtk.RESPONSE_ACCEPT)
1396 dialog.set_select_multiple(True)
1397
1398 ffilter = gtk.FileFilter()
1399 ffilter.set_name(_("Flumotion XML configuration files"))
1400 ffilter.add_pattern("*.xml")
1401 dialog.add_filter(ffilter)
1402 ffilter = gtk.FileFilter()
1403 ffilter.set_name(_("All files"))
1404 ffilter.add_pattern("*")
1405 dialog.add_filter(ffilter)
1406
1407 if self._currentDir:
1408 dialog.set_current_folder_uri(self._currentDir)
1409
1410 def response(dialog, response):
1411 if response == gtk.RESPONSE_ACCEPT:
1412 self._currentDir = dialog.get_current_folder_uri()
1413 for name in dialog.get_filenames():
1414 conf_xml = open(name, 'r').read()
1415 self._adminModel.loadConfiguration(conf_xml)
1416 dialog.destroy()
1417
1418 dialog.connect('response', response)
1419 dialog.show()
1420
1422 d = gtk.FileChooserDialog(
1423 _("Export Configuration..."), self._window,
1424 gtk.FILE_CHOOSER_ACTION_SAVE,
1425 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1426 _('Export'), gtk.RESPONSE_ACCEPT))
1427 d.set_modal(True)
1428 d.set_default_response(gtk.RESPONSE_ACCEPT)
1429 d.set_current_name("configuration.xml")
1430
1431 if self._currentDir:
1432 d.set_current_folder_uri(self._currentDir)
1433
1434 def getConfiguration(conf_xml, name, chooser):
1435 if not name.endswith('.xml'):
1436 name += '.xml'
1437
1438 file_exists = True
1439 if os.path.exists(name):
1440 d = gtk.MessageDialog(
1441 self._window, gtk.DIALOG_MODAL,
1442 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO,
1443 _("File already exists.\nOverwrite?"))
1444 d.connect("response", lambda self, response: d.hide())
1445 if d.run() == gtk.RESPONSE_YES:
1446 file_exists = False
1447 else:
1448 file_exists = False
1449
1450 if not file_exists:
1451 try:
1452 f = open(name, 'w')
1453 f.write(conf_xml)
1454 f.close()
1455 chooser.destroy()
1456 except IOError, e:
1457 self._error(_("Could not open configuration file %s "
1458 "for writing (%s)" % (name, e[1])))
1459
1460 def response(d, response):
1461 if response == gtk.RESPONSE_ACCEPT:
1462 self._currentDir = d.get_current_folder_uri()
1463 deferred = self._adminModel.getConfiguration()
1464 name = d.get_filename()
1465 deferred.addCallback(getConfiguration, name, d)
1466 else:
1467 d.destroy()
1468
1469 d.connect('response', response)
1470 d.show()
1471
1473 if sys.version_info >= (2, 4):
1474 from flumotion.extern import code
1475 code
1476 else:
1477 import code
1478
1479 ns = {"admin": self._adminModel,
1480 "components": self._componentStates}
1481 message = """Flumotion Admin Debug Shell
1482
1483 Local variables are:
1484 admin (flumotion.admin.admin.AdminModel)
1485 components (dict: name -> flumotion.common.planet.AdminComponentState)
1486
1487 You can do remote component calls using:
1488 admin.componentCallRemote(components['component-name'],
1489 'methodName', arg1, arg2)
1490
1491 """
1492 code.interact(local=ns, banner=message)
1493
1495
1496 def gotConfiguration(xml):
1497 print xml
1498 d = self._adminModel.getConfiguration()
1499 d.addCallback(gotConfiguration)
1500
1502
1503 def setMarker(_, marker, level):
1504 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1505 debugMarkerDialog = DebugMarkerDialog()
1506 debugMarkerDialog.connect('set-marker', setMarker)
1507 debugMarkerDialog.show()
1508
1513
1515 for path in os.environ['PATH'].split(':'):
1516 executable = os.path.join(path, 'gnome-help')
1517 if os.path.exists(executable):
1518 break
1519 else:
1520 self._error(
1521 _("Cannot find a program to display the Flumotion manual."))
1522 return
1523 gobject.spawn_async([executable,
1524 'ghelp:%s' % (configure.PACKAGE, )])
1525
1527 d = gtk.MessageDialog(
1528 self._window, gtk.DIALOG_MODAL,
1529 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
1530 _("Do you want to shutdown manager and worker "
1531 "before exiting?"))
1532 response = d.run()
1533 d.destroy()
1534 return response == gtk.RESPONSE_YES
1535
1536
1537
1540
1543
1546
1547
1548
1551
1554
1557
1560
1563
1566
1569
1571 self._configurationAssistantIsRunning = False
1572
1575
1583
1586
1589
1592
1595
1598
1599
1600
1603
1606
1609
1612
1615
1618
1621
1624
1627
1631
1633 for c in self._componentStates.values():
1634 self._componentStop(c)
1635
1638
1641
1644
1647
1650
1653
1656
1657 - def _help_contents_cb(self, action):
1659
1662
1665
1666 gobject.type_register(AdminWindow)
1667