1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """admin window interface, the main interface of flumotion-admin.
19
20 Here is an overview of the different parts of the admin interface::
21
22 +--------------[ AdminWindow ]-------------+
23 | Menubar |
24 +------------------------------------------+
25 | Toolbar |
26 +--------------------+---------------------+
27 | | |
28 | | |
29 | | |
30 | | |
31 | ComponentList | ComponentView |
32 | | |
33 | | |
34 | | |
35 | | |
36 | | |
37 +--------------------+---------------------+
38 | AdminStatusbar |
39 +-------------------------------------------
40
41 The main class which builds everything together is a L{AdminWindow},
42 which is defined in this file:
43
44 - L{AdminWindow} creates the other UI parts internally, see the
45 L{AdminWindow._createUI}.
46 - Menubar and Toolbar are created by a GtkUIManager, see
47 L{AdminWindow._createUI} and L{MAIN_UI}.
48 - L{ComponentList<flumotion.admin.gtk.componentlist.ComponentList>}
49 is a list of all components, and is created in the
50 L{flumotion.admin.gtk.componentlist} module.
51 - L{ComponentView<flumotion.admin.gtk.componentview.ComponentView>}
52 contains a component specific view, usually a set of tabs, it is
53 created in the L{flumotion.admin.gtk.componentview} module.
54 - L{AdminStatus<flumotion.admin.gtk.statusbar.AdminStatus>} is a
55 statusbar displaying context specific hints and is defined in the
56 L{flumotion.admin.gtk.statusbar} module.
57
58 """
59
60 import gettext
61 import os
62 import sys
63
64 import gobject
65 import gtk
66 from gtk import gdk
67 from gtk import keysyms
68 from kiwi.ui.delegates import GladeDelegate
69 from kiwi.ui.dialogs import yesno
70 from twisted.internet import defer, reactor
71 from zope.interface import implements
72
73 from flumotion.admin.admin import AdminModel
74 from flumotion.admin.assistant.models import AudioProducer, Porter, \
75 VideoProducer, Muxer
76 from flumotion.admin.connections import getRecentConnections, \
77 hasRecentConnections
78 from flumotion.admin.settings import getSettings
79 from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \
80 ProgressDialog, showConnectionErrorDialog
81 from flumotion.admin.gtk.connections import ConnectionsDialog
82 from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList
83 from flumotion.admin.gtk.componentview import MultipleAdminComponentStates
84 from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog
85 from flumotion.admin.gtk.statusbar import AdminStatusbar
86 from flumotion.common.common import componentId
87 from flumotion.common.connection import PBConnectionInfo
88 from flumotion.common.errors import ConnectionCancelledError, \
89 ConnectionRefusedError, ConnectionFailedError, BusyComponentError
90 from flumotion.common.i18n import N_, gettexter
91 from flumotion.common.log import Loggable
92 from flumotion.common.planet import AdminComponentState, moods
93 from flumotion.common.pygobject import gsignal
94 from flumotion.configure import configure
95 from flumotion.manager import admin
96 from flumotion.twisted.flavors import IStateListener
97 from flumotion.ui.trayicon import FluTrayIcon
98
99 admin
100
101 __version__ = "$Rev$"
102 _ = gettext.gettext
103 T_ = gettexter()
104
105 MAIN_UI = """
106 <ui>
107 <menubar name="Menubar">
108 <menu action="Connection">
109 <menuitem action="OpenRecent"/>
110 <menuitem action="OpenExisting"/>
111 <menuitem action="ImportConfig"/>
112 <menuitem action="ExportConfig"/>
113 <separator name="sep-conn1"/>
114 <placeholder name="Recent"/>
115 <separator name="sep-conn2"/>
116 <menuitem action="Quit"/>
117 </menu>
118 <menu action="Manage">
119 <menuitem action="StartComponent"/>
120 <menuitem action="StopComponent"/>
121 <menuitem action="DeleteComponent"/>
122 <separator name="sep-manage1"/>
123 <menuitem action="StartAll"/>
124 <menuitem action="StopAll"/>
125 <menuitem action="ClearAll"/>
126 <separator name="sep-manage2"/>
127 <menuitem action="AddFormat"/>
128 <menuitem action="AddStreamer"/>
129 <separator name="sep-manage3"/>
130 <menuitem action="RunConfigurationAssistant"/>
131 </menu>
132 <menu action="Debug">
133 <menuitem action="EnableDebugging"/>
134 <separator name="sep-debug1"/>
135 <menuitem action="StartShell"/>
136 <menuitem action="DumpConfiguration"/>
137 <menuitem action="WriteDebugMarker"/>
138 </menu>
139 <menu action="Help">
140 <menuitem action="Contents"/>
141 <menuitem action="About"/>
142 </menu>
143 </menubar>
144 <toolbar name="Toolbar">
145 <toolitem action="OpenRecent"/>
146 <separator name="sep-toolbar1"/>
147 <toolitem action="StartComponent"/>
148 <toolitem action="StopComponent"/>
149 <toolitem action="DeleteComponent"/>
150 <separator name="sep-toolbar2"/>
151 <toolitem action="RunConfigurationAssistant"/>
152 </toolbar>
153 <popup name="ComponentContextMenu">
154 <menuitem action="StartComponent"/>
155 <menuitem action="StopComponent"/>
156 <menuitem action="DeleteComponent"/>
157 <menuitem action="KillComponent"/>
158 </popup>
159 </ui>
160 """
161
162 RECENT_UI_TEMPLATE = '''<ui>
163 <menubar name="Menubar">
164 <menu action="Connection">
165 <placeholder name="Recent">
166 %s
167 </placeholder>
168 </menu>
169 </menubar>
170 </ui>'''
171
172 MAX_RECENT_ITEMS = 4
173
174
176 '''Creates the GtkWindow for the user interface.
177 Also connects to the manager on the given host and port.
178 '''
179
180
181 gladefile = 'admin.glade'
182 toplevel_name = 'main_window'
183
184
185 logCategory = 'adminwindow'
186
187
188 implements(IStateListener)
189
190
191 gsignal('connected')
192
194 GladeDelegate.__init__(self)
195
196 self._adminModel = None
197 self._currentComponentStates = None
198 self._componentContextMenu = None
199 self._componentList = None
200 self._componentStates = None
201 self._componentView = None
202 self._componentNameToSelect = None
203 self._debugEnabled = False
204 self._debugActions = None
205 self._debugEnableAction = None
206 self._disconnectedDialog = None
207 self._planetState = None
208 self._recentMenuID = None
209 self._trayicon = None
210 self._configurationAssistantIsRunning = False
211 self._managerSpawner = None
212
213 self._createUI()
214 self._appendRecentConnections()
215 self.setDebugEnabled(False)
216
217
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 self._managerSpawner = managerSpawner
325 return self._openConnection(info)
326
327
328
330 if minimize:
331 self._eat_resize_id = self._vpaned.connect(
332 'button-press-event', self._eat_resize_vpaned_event)
333 self._vpaned.set_position(-1)
334 else:
335 self._vpaned.disconnect(self._eat_resize_id)
336
340
342 self.debug('creating UI')
343
344
345 self._window = self.toplevel
346 self._componentList = ComponentList(self.component_list)
347 del self.component_list
348 self._componentView = self.component_view
349 del self.component_view
350 self._statusbar = AdminStatusbar(self.statusbar)
351 del self.statusbar
352 self._messageView = self.messages_view
353 del self.messages_view
354
355 self._messageView.connect('resize-event', self._resize_vpaned)
356 self._vpaned = self.vpaned
357 del self.vpaned
358 self._eat_resize_id = self._vpaned.connect(
359 'button-press-event', self._eat_resize_vpaned_event)
360
361 self._window.set_name("AdminWindow")
362 self._window.connect('delete-event',
363 self._window_delete_event_cb)
364 self._window.connect('key-press-event',
365 self._window_key_press_event_cb)
366
367 uimgr = gtk.UIManager()
368 uimgr.connect('connect-proxy',
369 self._on_uimanager__connect_proxy)
370 uimgr.connect('disconnect-proxy',
371 self._on_uimanager__disconnect_proxy)
372
373
374 group = gtk.ActionGroup('Actions')
375 group.add_actions([
376
377 ('Connection', None, _("_Connection")),
378 ('OpenRecent', gtk.STOCK_OPEN, _('_Open Recent Connection...'),
379 None, _('Connect to a recently used connection'),
380 self._connection_open_recent_cb),
381 ('OpenExisting', None, _('Connect to _running manager...'), None,
382 _('Connect to a previously used connection'),
383 self._connection_open_existing_cb),
384 ('ImportConfig', None, _('_Import Configuration...'), None,
385 _('Import a configuration from a file'),
386 self._connection_import_configuration_cb),
387 ('ExportConfig', None, _('_Export Configuration...'), None,
388 _('Export the current configuration to a file'),
389 self._connection_export_configuration_cb),
390 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None,
391 _('Quit the application and disconnect from the manager'),
392 self._connection_quit_cb),
393
394
395 ('Manage', None, _('_Manage')),
396 ('StartComponent', gtk.STOCK_MEDIA_PLAY, _('_Start Component(s)'),
397 None, _('Start the selected component(s)'),
398 self._manage_start_component_cb),
399 ('StopComponent', gtk.STOCK_MEDIA_STOP, _('St_op Component(s)'),
400 None, _('Stop the selected component(s)'),
401 self._manage_stop_component_cb),
402 ('DeleteComponent', gtk.STOCK_DELETE, _('_Delete Component(s)'),
403 None, _('Delete the selected component(s)'),
404 self._manage_delete_component_cb),
405 ('StartAll', None, _('Start _All'), None,
406 _('Start all components'),
407 self._manage_start_all_cb),
408 ('StopAll', None, _('Stop A_ll'), None,
409 _('Stop all components'),
410 self._manage_stop_all_cb),
411 ('ClearAll', gtk.STOCK_CLEAR, _('_Clear All'), None,
412 _('Remove all components'),
413 self._manage_clear_all_cb),
414 ('AddFormat', gtk.STOCK_ADD, _('Add new encoding _format...'),
415 None,
416 _('Add a new format to the current stream'),
417 self._manage_add_format_cb),
418 ('AddStreamer', gtk.STOCK_ADD, _('Add new _streamer...'),
419 None,
420 _('Add a new streamer to the flow'),
421 self._manage_add_streamer_cb),
422 ('RunConfigurationAssistant', 'flumotion.admin.gtk',
423 _('Run _Assistant'), None,
424 _('Run the configuration assistant'),
425 self._manage_run_assistant_cb),
426
427
428 ('Debug', None, _('_Debug')),
429
430
431 ('Help', None, _('_Help')),
432 ('Contents', gtk.STOCK_HELP, _('_Contents'), 'F1',
433 _('Open the Flumotion manual'),
434 self._help_contents_cb),
435 ('About', gtk.STOCK_ABOUT, _('_About'), None,
436 _('About this software'),
437 self._help_about_cb),
438
439
440 ('KillComponent', None, _('_Kill Component'), None,
441 _('Kills the currently selected component'),
442 self._kill_component_cb),
443
444 ])
445 group.add_toggle_actions([
446 ('EnableDebugging', None, _('Enable _Debugging'), None,
447 _('Enable debugging in the admin interface'),
448 self._debug_enable_cb),
449 ])
450 self._debugEnableAction = group.get_action('EnableDebugging')
451 uimgr.insert_action_group(group, 0)
452
453
454 self._debugActions = gtk.ActionGroup('Actions')
455 self._debugActions.add_actions([
456
457 ('StartShell', gtk.STOCK_EXECUTE, _('Start _Shell'), None,
458 _('Start an interactive debugging shell'),
459 self._debug_start_shell_cb),
460 ('DumpConfiguration', gtk.STOCK_EXECUTE,
461 _('Dump configuration'), None,
462 _('Dumps the current manager configuration'),
463 self._debug_dump_configuration_cb),
464 ('WriteDebugMarker', gtk.STOCK_EXECUTE,
465 _('Write debug marker...'), None,
466 _('Writes a debug marker to all the logs'),
467 self._debug_write_debug_marker_cb)])
468 uimgr.insert_action_group(self._debugActions, 0)
469 self._debugActions.set_sensitive(False)
470
471 uimgr.add_ui_from_string(MAIN_UI)
472 self._window.add_accel_group(uimgr.get_accel_group())
473
474 menubar = uimgr.get_widget('/Menubar')
475 self.main_vbox.pack_start(menubar, expand=False)
476 self.main_vbox.reorder_child(menubar, 0)
477
478 toolbar = uimgr.get_widget('/Toolbar')
479 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
480 toolbar.set_style(gtk.TOOLBAR_ICONS)
481 self.main_vbox.pack_start(toolbar, expand=False)
482 self.main_vbox.reorder_child(toolbar, 1)
483
484 self._componentContextMenu = uimgr.get_widget('/ComponentContextMenu')
485 self._componentContextMenu.show()
486
487 menubar.show_all()
488
489 self._actiongroup = group
490 self._uimgr = uimgr
491 self._openRecentAction = group.get_action("OpenRecent")
492 self._startComponentAction = group.get_action("StartComponent")
493 self._stopComponentAction = group.get_action("StopComponent")
494 self._deleteComponentAction = group.get_action("DeleteComponent")
495 self._stopAllAction = group.get_action("StopAll")
496 assert self._stopAllAction
497 self._startAllAction = group.get_action("StartAll")
498 assert self._startAllAction
499 self._clearAllAction = group.get_action("ClearAll")
500 assert self._clearAllAction
501 self._addFormatAction = group.get_action("AddFormat")
502 self._addFormatAction.set_sensitive(False)
503 self._addStreamerAction = group.get_action("AddStreamer")
504 self._addStreamerAction.set_sensitive(False)
505 self._runConfigurationAssistantAction = (
506 group.get_action("RunConfigurationAssistant"))
507 self._runConfigurationAssistantAction.set_sensitive(False)
508 self._killComponentAction = group.get_action("KillComponent")
509 assert self._killComponentAction
510
511 self._trayicon = FluTrayIcon(self._window)
512 self._trayicon.connect("quit", self._trayicon_quit_cb)
513 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
514
515 self._componentList.connect('selection_changed',
516 self._components_selection_changed_cb)
517 self._componentList.connect('show-popup-menu',
518 self._components_show_popup_menu_cb)
519
520 self._updateComponentActions()
521 self._componentList.connect(
522 'notify::can-start-any',
523 self._components_start_stop_notify_cb)
524 self._componentList.connect(
525 'notify::can-stop-any',
526 self._components_start_stop_notify_cb)
527 self._updateComponentActions()
528
529 self._messageView.hide()
530
532 tooltip = action.get_property('tooltip')
533 if not tooltip:
534 return
535
536 if isinstance(widget, gtk.MenuItem):
537 cid = widget.connect('select', self._on_menu_item__select,
538 tooltip)
539 cid2 = widget.connect('deselect', self._on_menu_item__deselect)
540 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
541 elif isinstance(widget, gtk.ToolButton):
542 cid = widget.child.connect('enter', self._on_tool_button__enter,
543 tooltip)
544 cid2 = widget.child.connect('leave', self._on_tool_button__leave)
545 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
546
548 cids = widget.get_data('pygtk-app::proxy-signal-ids')
549 if not cids:
550 return
551
552 if isinstance(widget, gtk.ToolButton):
553 widget = widget.child
554
555 for cid in cids:
556 widget.disconnect(cid)
557
586
594
595 model = AdminModel()
596 d = model.connectToManager(info)
597 d.addCallback(connected)
598 return d
599
601 d = self._openConnection(info)
602
603 def errorMessageDisplayed(unused):
604 self._window.set_sensitive(True)
605
606 def connected(model):
607 self._window.set_sensitive(True)
608
609 def errbackConnectionRefusedError(failure):
610 failure.trap(ConnectionRefusedError)
611 d = showConnectionErrorDialog(failure, info, parent=self._window)
612 d.addCallback(errorMessageDisplayed)
613
614 def errbackConnectionFailedError(failure):
615 failure.trap(ConnectionFailedError)
616 d = showConnectionErrorDialog(failure, info, parent=self._window)
617 d.addCallback(errorMessageDisplayed)
618 return d
619
620 d.addCallback(connected)
621 d.addErrback(errbackConnectionRefusedError)
622 d.addErrback(errbackConnectionFailedError)
623 self._window.set_sensitive(False)
624 return d
625
645
647 """Quitting the application in a controlled manner"""
648 self._clearAdmin()
649
650 def clearAndClose(unused):
651 self._close()
652 if self._managerSpawner and self._promptForShutdown():
653 r = self._managerSpawner.stop(True)
654 r.addCallback(clearAndClose)
655 else:
656 clearAndClose('')
657
660
662 import pprint
663 import cStringIO
664 fd = cStringIO.StringIO()
665 pprint.pprint(configation, fd)
666 fd.seek(0)
667 self.debug('Configuration=%s' % fd.read())
668
673
674 - def _setStatusbarText(self, text):
675 return self._statusbar.push('main', text)
676
678 self._statusbar.pop('main')
679
691
693 """
694 Obtains the components according a given type.
695
696 @param componentType: The type of the components to get
697 @type componentType: str
698
699 @rtype : list of L{flumotion.common.component.AdminComponentState}
700 """
701 if componentType is None:
702 raise ValueError
703
704 componentStates = []
705
706 for state in self._componentStates.values():
707 config = state.get('config')
708 if componentType and config['type'] == componentType:
709 componentStates.append(state)
710
711 return componentStates
712
734
758
767
768 for componentState, entry in _getComponents():
769 component = componentClass()
770 component.componentType = entry.componentType
771 component.description = entry.description
772 component.exists = True
773 component.name = componentState.get('name')
774 config = componentState.get('config')
775 for key, value in config['properties'].items():
776 component.properties[key] = value
777 yield component
778
810
811 def gotBundledFunction(function):
812 scenario = function()
813 scenario.setMode('add%s' % addition)
814 scenario.addSteps(configurationAssistant)
815 configurationAssistant.setScenario(scenario)
816 httpPorters = self._getHTTPPorters()
817 self._setMountPoints(configurationAssistant)
818 if httpPorters:
819 configurationAssistant.setHTTPPorters(httpPorters)
820
821 if addition == 'format':
822 return self._adminModel.getWizardEntries(
823 wizardTypes=['audio-producer', 'video-producer'])
824 elif addition == 'streamer':
825 return self._adminModel.getWizardEntries(
826 wizardTypes=['muxer'])
827
828 d = self._adminModel.getBundledFunction(
829 'flumotion.scenario.live.wizard_gtk',
830 'LiveAssistantPlugin')
831
832 d.addCallback(gotBundledFunction)
833 d.addCallback(gotWizardEntries)
834
848
849 if not self._componentStates:
850 runAssistant()
851 return
852
853 for componentState in self._componentList.getComponentStates():
854 if componentState.get('mood') == moods.lost.value:
855 self._error(
856 _("Cannot run the configuration assistant since there "
857 "is at least one component in the lost state"))
858 return
859
860 if yesno(_("Running the Configuration Assistant again will remove "
861 "all components from the current stream and create "
862 "a new one."),
863 parent=self._window,
864 buttons=((_("Keep the current stream"),
865 gtk.RESPONSE_NO),
866 (_("Run the Assistant anyway"),
867 gtk.RESPONSE_YES))) != gtk.RESPONSE_YES:
868 return
869
870 d = self._clearAllComponents()
871
872
873 d.addCallback(lambda list: list and runAssistant())
874
898
911
913 self._window.set_sensitive(connected)
914 group = self._actiongroup
915 group.get_action('ImportConfig').set_sensitive(connected)
916 group.get_action('ExportConfig').set_sensitive(connected)
917 group.get_action('EnableDebugging').set_sensitive(connected)
918
919 self._clearLastStatusbarText()
920 if connected:
921 self._window.set_title(_('%s - Flumotion Administration') %
922 self._adminModel.adminInfoStr())
923 self._trayicon.set_tooltip(_('Flumotion: %s') % (
924 self._adminModel.adminInfoStr(), ))
925 else:
926 self._setStatusbarText(_('Not connected'))
927 self._trayicon.set_tooltip(_('Flumotion: Not connected'))
928
931
933 canStart = self._componentList.canStart()
934 canStop = self._componentList.canStop()
935 canDelete = self._componentList.canDelete()
936 self._startComponentAction.set_sensitive(canStart)
937 self._stopComponentAction.set_sensitive(canStop)
938 self._deleteComponentAction.set_sensitive(canDelete)
939 self.debug('can start %r, can stop %r, can delete %r' % (
940 canStart, canStop, canDelete))
941 canStartAll = self._componentList.get_property('can-start-any')
942 canStopAll = self._componentList.get_property('can-stop-any')
943
944
945 canClearAll = canStartAll and not canStopAll
946 self._stopAllAction.set_sensitive(canStopAll)
947 self._startAllAction.set_sensitive(canStartAll)
948 self._clearAllAction.set_sensitive(canClearAll)
949 self._killComponentAction.set_sensitive(canStop)
950
951 hasProducer = self._hasProducerComponent()
952 self._addFormatAction.set_sensitive(hasProducer)
953 self._addStreamerAction.set_sensitive(hasProducer)
954
956 self._componentList.clearAndRebuild(self._componentStates,
957 self._componentNameToSelect)
958 self._trayicon.update(self._componentStates)
959
965
976
978 self._messageView.clear()
979 pstate = self._planetState
980 if pstate and pstate.hasKey('messages'):
981 for message in pstate.get('messages').values():
982 self._messageView.addMessage(message)
983
985
986 def flowStateAppend(state, key, value):
987 self.debug('flow state append: key %s, value %r' % (key, value))
988 if key == 'components':
989 self._appendComponent(value)
990
991 def flowStateRemove(state, key, value):
992 if key == 'components':
993 self._removeComponent(value)
994
995 def atmosphereStateAppend(state, key, value):
996 if key == 'components':
997 self._appendComponent(value)
998
999 def atmosphereStateRemove(state, key, value):
1000 if key == 'components':
1001 self._removeComponent(value)
1002
1003 def planetStateAppend(state, key, value):
1004 if key == 'flows':
1005 if value != state.get('flows')[0]:
1006 self.warning('flumotion-admin can only handle one '
1007 'flow, ignoring /%s', value.get('name'))
1008 return
1009 self.debug('%s flow started', value.get('name'))
1010 value.addListener(self, append=flowStateAppend,
1011 remove=flowStateRemove)
1012
1013 self._componentStates.update(
1014 dict((c.get('name'), c) for c in value.get('components')))
1015 self._updateComponents()
1016
1017 def planetStateRemove(state, key, value):
1018 self.debug('something got removed from the planet')
1019
1020 def planetStateSetitem(state, key, subkey, value):
1021 if key == 'messages':
1022 self._messageView.addMessage(value)
1023
1024 def planetStateDelitem(state, key, subkey, value):
1025 if key == 'messages':
1026 self._messageView.clearMessage(value.id)
1027
1028 self.debug('parsing planetState %r' % planetState)
1029 self._planetState = planetState
1030
1031
1032 self._componentStates = {}
1033
1034 planetState.addListener(self, append=planetStateAppend,
1035 remove=planetStateRemove,
1036 setitem=planetStateSetitem,
1037 delitem=planetStateDelitem)
1038
1039 self._clearMessages()
1040
1041 a = planetState.get('atmosphere')
1042 a.addListener(self, append=atmosphereStateAppend,
1043 remove=atmosphereStateRemove)
1044
1045 self._componentStates.update(
1046 dict((c.get('name'), c) for c in a.get('components')))
1047
1048 for f in planetState.get('flows'):
1049 planetStateAppend(planetState, 'flows', f)
1050
1051 if not planetState.get('flows'):
1052 self._updateComponents()
1053
1055 if not self._adminModel.isConnected():
1056 return
1057
1058 d = self._adminModel.cleanComponents()
1059
1060 def busyComponentError(failure):
1061 failure.trap(BusyComponentError)
1062 self._error(
1063 _("Some component(s) are still busy and cannot be removed.\n"
1064 "Try again later."))
1065 d.addErrback(busyComponentError)
1066 return d
1067
1068
1069
1084
1086 """
1087 @returns: a L{twisted.internet.defer.Deferred}
1088 """
1089 self.debug('stopping component %r' % state)
1090 return self._componentDo(state, 'componentStop',
1091 'Stop', 'Stopping', 'Stopped')
1092
1094 """
1095 @returns: a L{twisted.internet.defer.Deferred}
1096 """
1097 return self._componentDo(state, 'componentStart',
1098 'Start', 'Starting', 'Started')
1099
1101 """
1102 @returns: a L{twisted.internet.defer.Deferred}
1103 """
1104 return self._componentDo(state, 'deleteComponent',
1105 'Delete', 'Deleting', 'Deleted')
1106
1117
1118 - def _componentDo(self, state, methodName, action, doing, done):
1119 """Do something with a component and update the statusbar.
1120
1121 @param state: componentState; if not specified, will use the
1122 currently selected component(s)
1123 @type state: L{AdminComponentState} or None
1124 @param methodName: name of the method to call
1125 @type methodName: str
1126 @param action: string used to explain that to do
1127 @type action: str
1128 @param doing: string used to explain that the action started
1129 @type doing: str
1130 @param done: string used to explain that the action was completed
1131 @type done: str
1132
1133 @rtype: L{twisted.internet.defer.Deferred}
1134 @returns: a deferred that will fire when the action is completed.
1135 """
1136 states = self._getStatesFromState(state)
1137
1138 if not states:
1139 return
1140
1141 def callbackSingle(result, self, mid, name):
1142 self._statusbar.remove('main', mid)
1143 self._setStatusbarText(
1144 _("%s component %s") % (done, name))
1145
1146 def errbackSingle(failure, self, mid, name):
1147 self._statusbar.remove('main', mid)
1148 self.warning("Failed to %s component %s: %s" % (
1149 action.lower(), name, failure))
1150 self._setStatusbarText(
1151 _("Failed to %(action)s component %(name)s.") % {
1152 'action': action.lower(),
1153 'name': name,
1154 })
1155
1156 def callbackMultiple(results, self, mid):
1157 self._statusbar.remove('main', mid)
1158 self._setStatusbarText(
1159 _("%s components.") % (done, ))
1160
1161 def errbackMultiple(failure, self, mid):
1162 self._statusbar.remove('main', mid)
1163 self.warning("Failed to %s some components: %s." % (
1164 action.lower(), failure))
1165 self._setStatusbarText(
1166 _("Failed to %s some components.") % (action, ))
1167
1168 f = gettext.dngettext(
1169 configure.PACKAGE,
1170
1171
1172 N_("%s component %s"),
1173
1174
1175
1176 N_("%s components %s"), len(states))
1177 statusText = f % (doing,
1178 ', '.join([getComponentLabel(s) for s in states]))
1179 mid = self._setStatusbarText(statusText)
1180
1181 if len(states) == 1:
1182 state = states[0]
1183 name = getComponentLabel(state)
1184 d = self._adminModel.callRemote(methodName, state)
1185 d.addCallback(callbackSingle, self, mid, name)
1186 d.addErrback(errbackSingle, self, mid, name)
1187 else:
1188 deferreds = []
1189 for state in states:
1190 d = self._adminModel.callRemote(methodName, state)
1191 deferreds.append(d)
1192 d = defer.DeferredList(deferreds)
1193 d.addCallback(callbackMultiple, self, mid)
1194 d.addErrback(errbackMultiple, self, mid)
1195 return d
1196
1204
1206 self.debug('component %s has selection', states)
1207
1208 def compSet(state, key, value):
1209 if key == 'mood':
1210 self.debug('state %r has mood set to %r' % (state, value))
1211 self._updateComponentActions()
1212
1213 def compAppend(state, key, value):
1214 name = state.get('name')
1215 self.debug('stateAppend on component state of %s' % name)
1216 if key == 'messages':
1217 current = self._componentList.getSelectedNames()
1218 if name in current:
1219 self._messageView.addMessage(value)
1220
1221 def compRemove(state, key, value):
1222 name = state.get('name')
1223 self.debug('stateRemove on component state of %s' % name)
1224 if key == 'messages':
1225 current = self._componentList.getSelectedNames()
1226 if name in current:
1227 self._messageView.clearMessage(value.id)
1228
1229 if self._currentComponentStates:
1230 for currentComponentState in self._currentComponentStates:
1231 currentComponentState.removeListener(self)
1232 self._currentComponentStates = states
1233 if self._currentComponentStates:
1234 for currentComponentState in self._currentComponentStates:
1235 currentComponentState.addListener(
1236 self, set_=compSet, append=compAppend, remove=compRemove)
1237
1238 self._updateComponentActions()
1239 self._clearMessages()
1240 state = None
1241 if states:
1242 if len(states) == 1:
1243 self.debug(
1244 "only one component is selected on the components view")
1245 state = states[0]
1246 elif states:
1247 self.debug("more than one components are selected in the "
1248 "components view")
1249 state = MultipleAdminComponentStates(states)
1250 self._componentView.activateComponent(state)
1251
1252 statusbarMessage = " "
1253 for state in states:
1254 name = getComponentLabel(state)
1255 messages = state.get('messages')
1256 if messages:
1257 for m in messages:
1258 self.debug('have message %r', m)
1259 self.debug('message id %s', m.id)
1260 self._messageView.addMessage(m)
1261
1262 if state.get('mood') == moods.sad.value:
1263 self.debug('component %s is sad' % name)
1264 statusbarMessage = statusbarMessage + \
1265 _("Component %s is sad. ") % name
1266 if statusbarMessage != " ":
1267 self._setStatusbarText(statusbarMessage)
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1282 self._componentContextMenu.popup(None, None, None,
1283 event_button, event_time)
1284
1310
1322
1323 d = self._adminModel.reconnect(keepTrying=True)
1324 d.addErrback(errback)
1325 else:
1326 self._disconnectedDialog.destroy()
1327 self._disconnectedDialog = None
1328 self._adminModel.disconnectFromManager()
1329 self._window.set_sensitive(True)
1330
1331 dialog = ProgressDialog(
1332 _("Reconnecting ..."),
1333 _("Lost connection to manager %s. Reconnecting ...")
1334 % (self._adminModel.adminInfoStr(), ), self._window)
1335
1336 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
1337 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH)
1338 dialog.connect("response", response)
1339 dialog.start()
1340 self._disconnectedDialog = dialog
1341 self._window.set_sensitive(False)
1342
1353
1363
1364 d.connect('have-connection', on_have_connection)
1365 d.show()
1366
1376
1377 def cancel(failure):
1378 failure.trap(WizardCancelled)
1379 wiz.stop()
1380
1381 d = wiz.runAsync()
1382 d.addCallback(got_state, wiz)
1383 d.addErrback(cancel)
1384
1386 dialog = gtk.FileChooserDialog(
1387 _("Import Configuration..."), self._window,
1388 gtk.FILE_CHOOSER_ACTION_OPEN,
1389 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1390 _('Import'), gtk.RESPONSE_ACCEPT))
1391 dialog.set_modal(True)
1392 dialog.set_default_response(gtk.RESPONSE_ACCEPT)
1393 dialog.set_select_multiple(True)
1394
1395 ffilter = gtk.FileFilter()
1396 ffilter.set_name(_("Flumotion XML configuration files"))
1397 ffilter.add_pattern("*.xml")
1398 dialog.add_filter(ffilter)
1399 ffilter = gtk.FileFilter()
1400 ffilter.set_name(_("All files"))
1401 ffilter.add_pattern("*")
1402 dialog.add_filter(ffilter)
1403
1404 settings = getSettings()
1405 if settings.hasValue('import_dir'):
1406 dialog.set_current_folder_uri(settings.getValue('import_dir'))
1407
1408 def response(dialog, response):
1409 if response == gtk.RESPONSE_ACCEPT:
1410 if settings.getValue('import_dir') != \
1411 dialog.get_current_folder_uri():
1412 settings.setValue('import_dir',
1413 dialog.get_current_folder_uri())
1414 settings.save()
1415 for name in dialog.get_filenames():
1416 conf_xml = open(name, 'r').read()
1417 self._adminModel.loadConfiguration(conf_xml)
1418 dialog.destroy()
1419
1420 dialog.connect('response', response)
1421 dialog.show()
1422
1424 d = gtk.FileChooserDialog(
1425 _("Export Configuration..."), self._window,
1426 gtk.FILE_CHOOSER_ACTION_SAVE,
1427 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1428 _('Export'), gtk.RESPONSE_ACCEPT))
1429 d.set_modal(True)
1430 d.set_default_response(gtk.RESPONSE_ACCEPT)
1431 d.set_current_name("configuration.xml")
1432
1433 settings = getSettings()
1434 if settings.hasValue('export_dir'):
1435 d.set_current_folder_uri(settings.getValue('export_dir'))
1436
1437 def getConfiguration(conf_xml, name, chooser):
1438 if not name.endswith('.xml'):
1439 name += '.xml'
1440
1441 file_exists = True
1442 if os.path.exists(name):
1443 d = gtk.MessageDialog(
1444 self._window, gtk.DIALOG_MODAL,
1445 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO,
1446 _("File already exists.\nOverwrite?"))
1447 d.connect("response", lambda self, response: d.hide())
1448 if d.run() == gtk.RESPONSE_YES:
1449 file_exists = False
1450 else:
1451 file_exists = False
1452
1453 if not file_exists:
1454 try:
1455 f = open(name, 'w')
1456 f.write(conf_xml)
1457 f.close()
1458 chooser.destroy()
1459 except IOError, e:
1460 self._error(_("Could not open configuration file %s "
1461 "for writing (%s)" % (name, e[1])))
1462
1463 def response(d, response):
1464 if response == gtk.RESPONSE_ACCEPT:
1465 self._currentDir = d.get_current_folder_uri()
1466 deferred = self._adminModel.getConfiguration()
1467 name = d.get_filename()
1468 if settings.getValue('export_dir') != \
1469 d.get_current_folder_uri():
1470 settings.setValue('export_dir',
1471 d.get_current_folder_uri())
1472 settings.save()
1473 deferred.addCallback(getConfiguration, name, d)
1474 else:
1475 d.destroy()
1476
1477 d.connect('response', response)
1478 d.show()
1479
1481 if sys.version_info >= (2, 4):
1482 from flumotion.extern import code
1483 code
1484 else:
1485 import code
1486
1487 ns = {"admin": self._adminModel,
1488 "components": self._componentStates}
1489 message = """Flumotion Admin Debug Shell
1490
1491 Local variables are:
1492 admin (flumotion.admin.admin.AdminModel)
1493 components (dict: name -> flumotion.common.planet.AdminComponentState)
1494
1495 You can do remote component calls using:
1496 admin.componentCallRemote(components['component-name'],
1497 'methodName', arg1, arg2)
1498
1499 """
1500 code.interact(local=ns, banner=message)
1501
1503
1504 def gotConfiguration(xml):
1505 print xml
1506 d = self._adminModel.getConfiguration()
1507 d.addCallback(gotConfiguration)
1508
1510
1511 def setMarker(_, marker, level):
1512 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1513 debugMarkerDialog = DebugMarkerDialog()
1514 debugMarkerDialog.connect('set-marker', setMarker)
1515 debugMarkerDialog.show()
1516
1521
1523 for path in os.environ['PATH'].split(':'):
1524 executable = os.path.join(path, 'gnome-help')
1525 if os.path.exists(executable):
1526 break
1527 else:
1528 self._error(
1529 _("Cannot find a program to display the Flumotion manual."))
1530 return
1531 gobject.spawn_async([executable,
1532 'ghelp:%s' % (configure.PACKAGE, )])
1533
1535 d = gtk.MessageDialog(
1536 self._window, gtk.DIALOG_MODAL,
1537 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
1538 _("Do you want to shutdown manager and worker "
1539 "before exiting?"))
1540 response = d.run()
1541 d.destroy()
1542 return response == gtk.RESPONSE_YES
1543
1544
1545
1548
1551
1554
1555
1556
1559
1562
1565
1568
1571
1574
1577
1579 self._configurationAssistantIsRunning = False
1580
1583
1591
1594
1597
1600
1603
1606
1607
1608
1611
1614
1617
1620
1623
1626
1629
1632
1635
1639
1641 for c in self._componentStates.values():
1642 self._componentStop(c)
1643
1646
1649
1652
1655
1658
1661
1664
1665 - def _help_contents_cb(self, action):
1667
1670
1673
1674 gobject.type_register(AdminWindow)
1675