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.settings import getSettings
83 from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \
84 ProgressDialog, showConnectionErrorDialog
85 from flumotion.admin.gtk.connections import ConnectionsDialog
86 from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList
87 from flumotion.admin.gtk.componentview import MultipleAdminComponentStates
88 from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog
89 from flumotion.admin.gtk.statusbar import AdminStatusbar
90 from flumotion.common.common import componentId
91 from flumotion.common.connection import PBConnectionInfo
92 from flumotion.common.errors import ConnectionCancelledError, \
93 ConnectionRefusedError, ConnectionFailedError, BusyComponentError
94 from flumotion.common.i18n import N_, gettexter
95 from flumotion.common.log import Loggable
96 from flumotion.common.planet import AdminComponentState, moods
97 from flumotion.common.pygobject import gsignal
98 from flumotion.configure import configure
99 from flumotion.manager import admin
100 from flumotion.twisted.flavors import IStateListener
101 from flumotion.ui.trayicon import FluTrayIcon
102
103 admin
104
105 __version__ = "$Rev$"
106 _ = gettext.gettext
107 T_ = gettexter()
108
109 MAIN_UI = """
110 <ui>
111 <menubar name="Menubar">
112 <menu action="Connection">
113 <menuitem action="OpenRecent"/>
114 <menuitem action="OpenExisting"/>
115 <menuitem action="ImportConfig"/>
116 <menuitem action="ExportConfig"/>
117 <separator name="sep-conn1"/>
118 <placeholder name="Recent"/>
119 <separator name="sep-conn2"/>
120 <menuitem action="Quit"/>
121 </menu>
122 <menu action="Manage">
123 <menuitem action="StartComponent"/>
124 <menuitem action="StopComponent"/>
125 <menuitem action="DeleteComponent"/>
126 <separator name="sep-manage1"/>
127 <menuitem action="StartAll"/>
128 <menuitem action="StopAll"/>
129 <menuitem action="ClearAll"/>
130 <separator name="sep-manage2"/>
131 <menuitem action="AddFormat"/>
132 <menuitem action="AddStreamer"/>
133 <separator name="sep-manage3"/>
134 <menuitem action="RunConfigurationAssistant"/>
135 </menu>
136 <menu action="Debug">
137 <menuitem action="EnableDebugging"/>
138 <separator name="sep-debug1"/>
139 <menuitem action="StartShell"/>
140 <menuitem action="DumpConfiguration"/>
141 <menuitem action="WriteDebugMarker"/>
142 </menu>
143 <menu action="Help">
144 <menuitem action="Contents"/>
145 <menuitem action="About"/>
146 </menu>
147 </menubar>
148 <toolbar name="Toolbar">
149 <toolitem action="OpenRecent"/>
150 <separator name="sep-toolbar1"/>
151 <toolitem action="StartComponent"/>
152 <toolitem action="StopComponent"/>
153 <toolitem action="DeleteComponent"/>
154 <separator name="sep-toolbar2"/>
155 <toolitem action="RunConfigurationAssistant"/>
156 </toolbar>
157 <popup name="ComponentContextMenu">
158 <menuitem action="StartComponent"/>
159 <menuitem action="StopComponent"/>
160 <menuitem action="DeleteComponent"/>
161 <menuitem action="KillComponent"/>
162 </popup>
163 </ui>
164 """
165
166 RECENT_UI_TEMPLATE = '''<ui>
167 <menubar name="Menubar">
168 <menu action="Connection">
169 <placeholder name="Recent">
170 %s
171 </placeholder>
172 </menu>
173 </menubar>
174 </ui>'''
175
176 MAX_RECENT_ITEMS = 4
177
178
180 '''Creates the GtkWindow for the user interface.
181 Also connects to the manager on the given host and port.
182 '''
183
184
185 gladefile = 'admin.glade'
186 toplevel_name = 'main_window'
187
188
189 logCategory = 'adminwindow'
190
191
192 implements(IStateListener)
193
194
195 gsignal('connected')
196
198 GladeDelegate.__init__(self)
199
200 self._adminModel = None
201 self._currentComponentStates = None
202 self._componentContextMenu = None
203 self._componentList = None
204 self._componentStates = None
205 self._componentView = None
206 self._componentNameToSelect = None
207 self._debugEnabled = False
208 self._debugActions = None
209 self._debugEnableAction = None
210 self._disconnectedDialog = None
211 self._planetState = None
212 self._recentMenuID = None
213 self._trayicon = None
214 self._configurationAssistantIsRunning = False
215 self._managerSpawner = None
216
217 self._createUI()
218 self._appendRecentConnections()
219 self.setDebugEnabled(False)
220
221
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
1088
1090 """
1091 @returns: a L{twisted.internet.defer.Deferred}
1092 """
1093 self.debug('stopping component %r' % state)
1094 return self._componentDo(state, 'componentStop',
1095 'Stop', 'Stopping', 'Stopped')
1096
1098 """
1099 @returns: a L{twisted.internet.defer.Deferred}
1100 """
1101 return self._componentDo(state, 'componentStart',
1102 'Start', 'Starting', 'Started')
1103
1105 """
1106 @returns: a L{twisted.internet.defer.Deferred}
1107 """
1108 return self._componentDo(state, 'deleteComponent',
1109 'Delete', 'Deleting', 'Deleted')
1110
1121
1122 - def _componentDo(self, state, methodName, action, doing, done):
1123 """Do something with a component and update the statusbar.
1124
1125 @param state: componentState; if not specified, will use the
1126 currently selected component(s)
1127 @type state: L{AdminComponentState} or None
1128 @param methodName: name of the method to call
1129 @type methodName: str
1130 @param action: string used to explain that to do
1131 @type action: str
1132 @param doing: string used to explain that the action started
1133 @type doing: str
1134 @param done: string used to explain that the action was completed
1135 @type done: str
1136
1137 @rtype: L{twisted.internet.defer.Deferred}
1138 @returns: a deferred that will fire when the action is completed.
1139 """
1140 states = self._getStatesFromState(state)
1141
1142 if not states:
1143 return
1144
1145 def callbackSingle(result, self, mid, name):
1146 self._statusbar.remove('main', mid)
1147 self._setStatusbarText(
1148 _("%s component %s") % (done, name))
1149
1150 def errbackSingle(failure, self, mid, name):
1151 self._statusbar.remove('main', mid)
1152 self.warning("Failed to %s component %s: %s" % (
1153 action.lower(), name, failure))
1154 self._setStatusbarText(
1155 _("Failed to %(action)s component %(name)s.") % {
1156 'action': action.lower(),
1157 'name': name,
1158 })
1159
1160 def callbackMultiple(results, self, mid):
1161 self._statusbar.remove('main', mid)
1162 self._setStatusbarText(
1163 _("%s components.") % (done, ))
1164
1165 def errbackMultiple(failure, self, mid):
1166 self._statusbar.remove('main', mid)
1167 self.warning("Failed to %s some components: %s." % (
1168 action.lower(), failure))
1169 self._setStatusbarText(
1170 _("Failed to %s some components.") % (action, ))
1171
1172 f = gettext.dngettext(
1173 configure.PACKAGE,
1174
1175
1176 N_("%s component %s"),
1177
1178
1179
1180 N_("%s components %s"), len(states))
1181 statusText = f % (doing,
1182 ', '.join([getComponentLabel(s) for s in states]))
1183 mid = self._setStatusbarText(statusText)
1184
1185 if len(states) == 1:
1186 state = states[0]
1187 name = getComponentLabel(state)
1188 d = self._adminModel.callRemote(methodName, state)
1189 d.addCallback(callbackSingle, self, mid, name)
1190 d.addErrback(errbackSingle, self, mid, name)
1191 else:
1192 deferreds = []
1193 for state in states:
1194 d = self._adminModel.callRemote(methodName, state)
1195 deferreds.append(d)
1196 d = defer.DeferredList(deferreds)
1197 d.addCallback(callbackMultiple, self, mid)
1198 d.addErrback(errbackMultiple, self, mid)
1199 return d
1200
1208
1210 self.debug('component %s has selection', states)
1211
1212 def compSet(state, key, value):
1213 if key == 'mood':
1214 self.debug('state %r has mood set to %r' % (state, value))
1215 self._updateComponentActions()
1216
1217 def compAppend(state, key, value):
1218 name = state.get('name')
1219 self.debug('stateAppend on component state of %s' % name)
1220 if key == 'messages':
1221 current = self._componentList.getSelectedNames()
1222 if name in current:
1223 self._messageView.addMessage(value)
1224
1225 def compRemove(state, key, value):
1226 name = state.get('name')
1227 self.debug('stateRemove on component state of %s' % name)
1228 if key == 'messages':
1229 current = self._componentList.getSelectedNames()
1230 if name in current:
1231 self._messageView.clearMessage(value.id)
1232
1233 if self._currentComponentStates:
1234 for currentComponentState in self._currentComponentStates:
1235 currentComponentState.removeListener(self)
1236 self._currentComponentStates = states
1237 if self._currentComponentStates:
1238 for currentComponentState in self._currentComponentStates:
1239 currentComponentState.addListener(
1240 self, set_=compSet, append=compAppend, remove=compRemove)
1241
1242 self._updateComponentActions()
1243 self._clearMessages()
1244 state = None
1245 if states:
1246 if len(states) == 1:
1247 self.debug(
1248 "only one component is selected on the components view")
1249 state = states[0]
1250 elif states:
1251 self.debug("more than one components are selected in the "
1252 "components view")
1253 state = MultipleAdminComponentStates(states)
1254 self._componentView.activateComponent(state)
1255
1256 statusbarMessage = " "
1257 for state in states:
1258 name = getComponentLabel(state)
1259 messages = state.get('messages')
1260 if messages:
1261 for m in messages:
1262 self.debug('have message %r', m)
1263 self.debug('message id %s', m.id)
1264 self._messageView.addMessage(m)
1265
1266 if state.get('mood') == moods.sad.value:
1267 self.debug('component %s is sad' % name)
1268 statusbarMessage = statusbarMessage + \
1269 _("Component %s is sad. ") % name
1270 if statusbarMessage != " ":
1271 self._setStatusbarText(statusbarMessage)
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1286 self._componentContextMenu.popup(None, None, None,
1287 event_button, event_time)
1288
1314
1326
1327 d = self._adminModel.reconnect(keepTrying=True)
1328 d.addErrback(errback)
1329 else:
1330 self._disconnectedDialog.destroy()
1331 self._disconnectedDialog = None
1332 self._adminModel.disconnectFromManager()
1333 self._window.set_sensitive(True)
1334
1335 dialog = ProgressDialog(
1336 _("Reconnecting ..."),
1337 _("Lost connection to manager %s. Reconnecting ...")
1338 % (self._adminModel.adminInfoStr(), ), self._window)
1339
1340 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
1341 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH)
1342 dialog.connect("response", response)
1343 dialog.start()
1344 self._disconnectedDialog = dialog
1345 self._window.set_sensitive(False)
1346
1357
1367
1368 d.connect('have-connection', on_have_connection)
1369 d.show()
1370
1380
1381 def cancel(failure):
1382 failure.trap(WizardCancelled)
1383 wiz.stop()
1384
1385 d = wiz.runAsync()
1386 d.addCallback(got_state, wiz)
1387 d.addErrback(cancel)
1388
1390 dialog = gtk.FileChooserDialog(
1391 _("Import Configuration..."), self._window,
1392 gtk.FILE_CHOOSER_ACTION_OPEN,
1393 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1394 _('Import'), gtk.RESPONSE_ACCEPT))
1395 dialog.set_modal(True)
1396 dialog.set_default_response(gtk.RESPONSE_ACCEPT)
1397 dialog.set_select_multiple(True)
1398
1399 ffilter = gtk.FileFilter()
1400 ffilter.set_name(_("Flumotion XML configuration files"))
1401 ffilter.add_pattern("*.xml")
1402 dialog.add_filter(ffilter)
1403 ffilter = gtk.FileFilter()
1404 ffilter.set_name(_("All files"))
1405 ffilter.add_pattern("*")
1406 dialog.add_filter(ffilter)
1407
1408 settings = getSettings()
1409 if settings.hasValue('import_dir'):
1410 dialog.set_current_folder_uri(settings.getValue('import_dir'))
1411
1412 def response(dialog, response):
1413 if response == gtk.RESPONSE_ACCEPT:
1414 if settings.getValue('import_dir') != \
1415 dialog.get_current_folder_uri():
1416 settings.setValue('import_dir',
1417 dialog.get_current_folder_uri())
1418 settings.save()
1419 for name in dialog.get_filenames():
1420 conf_xml = open(name, 'r').read()
1421 self._adminModel.loadConfiguration(conf_xml)
1422 dialog.destroy()
1423
1424 dialog.connect('response', response)
1425 dialog.show()
1426
1428 d = gtk.FileChooserDialog(
1429 _("Export Configuration..."), self._window,
1430 gtk.FILE_CHOOSER_ACTION_SAVE,
1431 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1432 _('Export'), gtk.RESPONSE_ACCEPT))
1433 d.set_modal(True)
1434 d.set_default_response(gtk.RESPONSE_ACCEPT)
1435 d.set_current_name("configuration.xml")
1436
1437 settings = getSettings()
1438 if settings.hasValue('export_dir'):
1439 d.set_current_folder_uri(settings.getValue('export_dir'))
1440
1441 def getConfiguration(conf_xml, name, chooser):
1442 if not name.endswith('.xml'):
1443 name += '.xml'
1444
1445 file_exists = True
1446 if os.path.exists(name):
1447 d = gtk.MessageDialog(
1448 self._window, gtk.DIALOG_MODAL,
1449 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO,
1450 _("File already exists.\nOverwrite?"))
1451 d.connect("response", lambda self, response: d.hide())
1452 if d.run() == gtk.RESPONSE_YES:
1453 file_exists = False
1454 else:
1455 file_exists = False
1456
1457 if not file_exists:
1458 try:
1459 f = open(name, 'w')
1460 f.write(conf_xml)
1461 f.close()
1462 chooser.destroy()
1463 except IOError, e:
1464 self._error(_("Could not open configuration file %s "
1465 "for writing (%s)" % (name, e[1])))
1466
1467 def response(d, response):
1468 if response == gtk.RESPONSE_ACCEPT:
1469 self._currentDir = d.get_current_folder_uri()
1470 deferred = self._adminModel.getConfiguration()
1471 name = d.get_filename()
1472 if settings.getValue('export_dir') != \
1473 d.get_current_folder_uri():
1474 settings.setValue('export_dir',
1475 d.get_current_folder_uri())
1476 settings.save()
1477 deferred.addCallback(getConfiguration, name, d)
1478 else:
1479 d.destroy()
1480
1481 d.connect('response', response)
1482 d.show()
1483
1485 if sys.version_info >= (2, 4):
1486 from flumotion.extern import code
1487 code
1488 else:
1489 import code
1490
1491 ns = {"admin": self._adminModel,
1492 "components": self._componentStates}
1493 message = """Flumotion Admin Debug Shell
1494
1495 Local variables are:
1496 admin (flumotion.admin.admin.AdminModel)
1497 components (dict: name -> flumotion.common.planet.AdminComponentState)
1498
1499 You can do remote component calls using:
1500 admin.componentCallRemote(components['component-name'],
1501 'methodName', arg1, arg2)
1502
1503 """
1504 code.interact(local=ns, banner=message)
1505
1507
1508 def gotConfiguration(xml):
1509 print xml
1510 d = self._adminModel.getConfiguration()
1511 d.addCallback(gotConfiguration)
1512
1514
1515 def setMarker(_, marker, level):
1516 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1517 debugMarkerDialog = DebugMarkerDialog()
1518 debugMarkerDialog.connect('set-marker', setMarker)
1519 debugMarkerDialog.show()
1520
1525
1527 for path in os.environ['PATH'].split(':'):
1528 executable = os.path.join(path, 'gnome-help')
1529 if os.path.exists(executable):
1530 break
1531 else:
1532 self._error(
1533 _("Cannot find a program to display the Flumotion manual."))
1534 return
1535 gobject.spawn_async([executable,
1536 'ghelp:%s' % (configure.PACKAGE, )])
1537
1539 d = gtk.MessageDialog(
1540 self._window, gtk.DIALOG_MODAL,
1541 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
1542 _("Do you want to shutdown manager and worker "
1543 "before exiting?"))
1544 response = d.run()
1545 d.destroy()
1546 return response == gtk.RESPONSE_YES
1547
1548
1549
1552
1555
1558
1559
1560
1563
1566
1569
1572
1575
1578
1581
1583 self._configurationAssistantIsRunning = False
1584
1587
1595
1598
1601
1604
1607
1610
1611
1612
1615
1618
1621
1624
1627
1630
1633
1636
1639
1643
1645 for c in self._componentStates.values():
1646 self._componentStop(c)
1647
1650
1653
1656
1659
1662
1665
1668
1669 - def _help_contents_cb(self, action):
1671
1674
1677
1678 gobject.type_register(AdminWindow)
1679