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 kiwi.ui.delegates import GladeDelegate
71 from kiwi.ui.dialogs import yesno
72 from twisted.internet import defer, reactor
73 from zope.interface import implements
74
75 from flumotion.admin.admin import AdminModel
76 from flumotion.admin.connections import getRecentConnections, \
77 hasRecentConnections
78 from flumotion.admin.gtk.dialogs import AboutDialog, ErrorDialog, \
79 ProgressDialog, showConnectionErrorDialog
80 from flumotion.admin.gtk.connections import ConnectionsDialog
81 from flumotion.admin.gtk.componentlist import getComponentLabel, ComponentList
82 from flumotion.admin.gtk.debugmarkerview import DebugMarkerDialog
83 from flumotion.admin.gtk.statusbar import AdminStatusbar
84 from flumotion.common.common import componentId
85 from flumotion.common.connection import PBConnectionInfo
86 from flumotion.common.errors import ConnectionRefusedError, \
87 ConnectionFailedError, BusyComponentError
88 from flumotion.common.i18n import N_, gettexter
89 from flumotion.common.log import Loggable
90 from flumotion.common.planet import AdminComponentState, moods
91 from flumotion.common.pygobject import gsignal
92 from flumotion.configure import configure
93 from flumotion.manager import admin
94 from flumotion.twisted.flavors import IStateListener
95 from flumotion.ui.trayicon import FluTrayIcon
96 from flumotion.wizard.models import AudioProducer, Porter, VideoProducer
97
98 admin
99
100 __version__ = "$Rev: 7070 $"
101 _ = gettext.gettext
102 T_ = gettexter()
103
104 MAIN_UI = """
105 <ui>
106 <menubar name="Menubar">
107 <menu action="Connection">
108 <menuitem action="OpenRecent"/>
109 <menuitem action="OpenExisting"/>
110 <menuitem action="ImportConfig"/>
111 <menuitem action="ExportConfig"/>
112 <separator name="sep-conn1"/>
113 <placeholder name="Recent"/>
114 <separator name="sep-conn2"/>
115 <menuitem action="Quit"/>
116 </menu>
117 <menu action="Manage">
118 <menuitem action="StartComponent"/>
119 <menuitem action="StopComponent"/>
120 <menuitem action="DeleteComponent"/>
121 <separator name="sep-manage1"/>
122 <menuitem action="StartAll"/>
123 <menuitem action="StopAll"/>
124 <menuitem action="ClearAll"/>
125 <separator name="sep-manage2"/>
126 <menuitem action="AddFormat"/>
127 <separator name="sep-manage3"/>
128 <menuitem action="RunConfigurationWizard"/>
129 </menu>
130 <menu action="Debug">
131 <menuitem action="EnableDebugging"/>
132 <separator name="sep-debug1"/>
133 <menuitem action="StartShell"/>
134 <menuitem action="DumpConfiguration"/>
135 <menuitem action="WriteDebugMarker"/>
136 </menu>
137 <menu action="Help">
138 <menuitem action="About"/>
139 </menu>
140 </menubar>
141 <toolbar name="Toolbar">
142 <toolitem action="OpenRecent"/>
143 <separator name="sep-toolbar1"/>
144 <toolitem action="StartComponent"/>
145 <toolitem action="StopComponent"/>
146 <toolitem action="DeleteComponent"/>
147 <separator name="sep-toolbar2"/>
148 <toolitem action="RunConfigurationWizard"/>
149 </toolbar>
150 <popup name="ComponentContextMenu">
151 <menuitem action="StartComponent"/>
152 <menuitem action="StopComponent"/>
153 <menuitem action="DeleteComponent"/>
154 <menuitem action="KillComponent"/>
155 </popup>
156 </ui>
157 """
158
159 RECENT_UI_TEMPLATE = '''<ui>
160 <menubar name="Menubar">
161 <menu action="Connection">
162 <placeholder name="Recent">
163 %s
164 </placeholder>
165 </menu>
166 </menubar>
167 </ui>'''
168
169 MAX_RECENT_ITEMS = 4
170
171
173 '''Creates the GtkWindow for the user interface.
174 Also connects to the manager on the given host and port.
175 '''
176
177
178 gladefile = 'admin.glade'
179 toplevel_name = 'main_window'
180
181
182 logCategory = 'adminwindow'
183
184
185 implements(IStateListener)
186
187
188 gsignal('connected')
189
191 GladeDelegate.__init__(self)
192
193 self._adminModel = None
194 self._currentComponentStates = None
195 self._componentContextMenu = None
196 self._componentList = None
197 self._componentStates = None
198 self._componentView = None
199 self._debugEnabled = False
200 self._debugActions = None
201 self._debugEnableAction = None
202 self._disconnectedDialog = None
203 self._planetState = None
204 self._recentMenuID = None
205 self._trayicon = None
206 self._configurationWizardIsRunning = False
207
208 self._createUI()
209 self._appendRecentConnections()
210 self.setDebugEnabled(False)
211
212
213
214
215
216
217
218
232
233
234
235
236
237
240
241 def cb(result, self, mid):
242 if mid:
243 self.statusbar.remove('main', mid)
244 if post:
245 self.statusbar.push('main', post % label)
246
247 def eb(failure, self, mid):
248 if mid:
249 self.statusbar.remove('main', mid)
250 self.warning("Failed to execute %s on component %s: %s"
251 % (methodName, label, failure))
252 if fail:
253 self.statusbar.push('main', fail % label)
254 if not state:
255 states = self.components_view.getSelectedStates()
256 if not states:
257 return
258 for state in states:
259 self.componentCallRemoteStatus(state, pre, post, fail,
260 methodName, args, kwargs)
261 else:
262 label = getComponentLabel(state)
263 if not label:
264 return
265
266 mid = None
267 if pre:
268 mid = self.statusbar.push('main', pre % label)
269 d = self._adminModel.componentCallRemote(
270 state, methodName, *args, **kwargs)
271 d.addCallback(cb, self, mid)
272 d.addErrback(eb, self, mid)
273
277
279 if key == 'names':
280 self.statusbar.set(
281 'main', _('Worker %s logged in.') % value)
282
284 if key == 'names':
285 self.statusbar.set(
286 'main', _('Worker %s logged out.') % value)
287
290
292 """Set if debug should be enabled for the admin client window
293 @param enable: if debug should be enabled
294 """
295 self._debugEnabled = enabled
296 self._debugActions.set_sensitive(enabled)
297 self._debugEnableAction.set_active(enabled)
298 self._componentView.setDebugEnabled(enabled)
299 self._killComponentAction.set_property('visible', enabled)
300
302 """Get the gtk window for the admin interface
303 @returns: window
304 @rtype: gtk.Window
305 """
306 return self._window
307
309 """Connects to a manager given a connection info
310 @param info: connection info
311 @type info: L{PBConnectionInfo}
312 """
313 assert isinstance(info, PBConnectionInfo), info
314 return self._openConnection(info)
315
316
317
319 self.debug('creating UI')
320
321
322 self._window = self.toplevel
323 self._componentList = ComponentList(self.component_list)
324 del self.component_list
325 self._componentView = self.component_view
326 del self.component_view
327 self._statusbar = AdminStatusbar(self.statusbar)
328 del self.statusbar
329 self._messageView = self.messages_view
330 del self.messages_view
331
332 self._window.set_name("AdminWindow")
333 self._window.connect('delete-event', self._window_delete_event_cb)
334
335 uimgr = gtk.UIManager()
336 uimgr.connect('connect-proxy',
337 self._on_uimanager__connect_proxy)
338 uimgr.connect('disconnect-proxy',
339 self._on_uimanager__disconnect_proxy)
340
341
342 group = gtk.ActionGroup('Actions')
343 group.add_actions([
344
345 ('Connection', None, _("_Connection")),
346 ('OpenRecent', gtk.STOCK_OPEN, _('_Open Recent Connection...'),
347 None, _('Connect to a recently used connection'),
348 self._connection_open_recent_cb),
349 ('OpenExisting', None, _('Connect to _running manager...'), None,
350 _('Connect to an previously used connection'),
351 self._connection_open_existing_cb),
352 ('ImportConfig', None, _('_Import Configuration...'), None,
353 _('Import configuration from a file'),
354 self._connection_import_configuration_cb),
355 ('ExportConfig', None, _('_Export Configuration...'), None,
356 _('Export current configuration to a file'),
357 self._connection_export_configuration_cb),
358 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None,
359 _('Quit the application and disconnect from the manager'),
360 self._connection_quit_cb),
361
362
363 ('Manage', None, _('_Manage')),
364 ('StartComponent', 'flumotion-play', _('_Start Component(s)'),
365 None, _('Start the selected component(s)'),
366 self._manage_start_component_cb),
367 ('StopComponent', 'flumotion-stop', _('St_op Component(s)'),
368 None, _('Stop the selected component(s)'),
369 self._manage_stop_component_cb),
370 ('DeleteComponent', gtk.STOCK_DELETE, _('_Delete Component(s)'),
371 None, _('Delete the selected component(s)'),
372 self._manage_delete_component_cb),
373 ('StartAll', None, _('Start _All'), None,
374 _('Start all components'),
375 self._manage_start_all_cb),
376 ('StopAll', None, _('Stop A_ll'), None,
377 _('Stop all components'),
378 self._manage_stop_all_cb),
379 ('ClearAll', gtk.STOCK_CLEAR, _('_Clear All'), None,
380 _('Remove all components'),
381 self._manage_clear_all_cb),
382 ('AddFormat', gtk.STOCK_ADD, _('Add new encoding _format...'), None,
383 _('Add a new format to the current stream'),
384 self._manage_add_format_cb),
385 ('RunConfigurationWizard', 'flumotion-wizard', _('Run _Wizard'), None,
386 _('Run the configuration wizard'),
387 self._manage_run_wizard_cb),
388
389
390 ('Debug', None, _('_Debug')),
391
392
393 ('Help', None, _('_Help')),
394 ('About', gtk.STOCK_ABOUT, _('_About'), None,
395 _('Displays an about dialog'),
396 self._help_about_cb),
397
398
399 ('KillComponent', None, _('_Kill Component'), None,
400 _('Kills the currently selected component'),
401 self._kill_component_cb),
402
403 ])
404 group.add_toggle_actions([
405 ('EnableDebugging', None, _('Enable _Debugging'), None,
406 _('Enable debugging in the admin interface'),
407 self._debug_enable_cb),
408 ])
409 self._debugEnableAction = group.get_action('EnableDebugging')
410 uimgr.insert_action_group(group, 0)
411
412
413 self._debugActions = gtk.ActionGroup('Actions')
414 self._debugActions.add_actions([
415
416 ('StartShell', gtk.STOCK_EXECUTE, _('Start _Shell'), None,
417 _('Start an interactive debugging shell'),
418 self._debug_start_shell_cb),
419 ('DumpConfiguration', gtk.STOCK_EXECUTE,
420 _('Dump configuration'), None,
421 _('Dumps the current manager configuration'),
422 self._debug_dump_configuration_cb),
423 ('WriteDebugMarker', gtk.STOCK_EXECUTE,
424 _('Write debug marker...'), None,
425 _('Writes a debug marker to all the logs'),
426 self._debug_write_debug_marker_cb)
427 ])
428 uimgr.insert_action_group(self._debugActions, 0)
429 self._debugActions.set_sensitive(False)
430
431 uimgr.add_ui_from_string(MAIN_UI)
432 self._window.add_accel_group(uimgr.get_accel_group())
433
434 menubar = uimgr.get_widget('/Menubar')
435 self.main_vbox.pack_start(menubar, expand=False)
436 self.main_vbox.reorder_child(menubar, 0)
437
438 toolbar = uimgr.get_widget('/Toolbar')
439 toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
440 toolbar.set_style(gtk.TOOLBAR_ICONS)
441 self.main_vbox.pack_start(toolbar, expand=False)
442 self.main_vbox.reorder_child(toolbar, 1)
443
444 self._componentContextMenu = uimgr.get_widget('/ComponentContextMenu')
445 self._componentContextMenu.show()
446
447 menubar.show_all()
448
449 self._actiongroup = group
450 self._uimgr = uimgr
451 self._openRecentAction = group.get_action("OpenRecent")
452 self._startComponentAction = group.get_action("StartComponent")
453 self._stopComponentAction = group.get_action("StopComponent")
454 self._deleteComponentAction = group.get_action("DeleteComponent")
455 self._stopAllAction = group.get_action("StopAll")
456 assert self._stopAllAction
457 self._startAllAction = group.get_action("StartAll")
458 assert self._startAllAction
459 self._clearAllAction = group.get_action("ClearAll")
460 assert self._clearAllAction
461 self._addFormatAction = group.get_action("AddFormat")
462 assert self._addFormatAction
463 self._killComponentAction = group.get_action("KillComponent")
464 assert self._killComponentAction
465
466 self._trayicon = FluTrayIcon(self._window)
467 self._trayicon.connect("quit", self._trayicon_quit_cb)
468 self._trayicon.set_tooltip(_('Not connected'))
469
470 self._componentList.connect('selection_changed',
471 self._components_selection_changed_cb)
472 self._componentList.connect('show-popup-menu',
473 self._components_show_popup_menu_cb)
474
475 self._updateComponentActions()
476 self._componentList.connect(
477 'notify::can-start-any',
478 self._components_start_stop_notify_cb)
479 self._componentList.connect(
480 'notify::can-stop-any',
481 self._components_start_stop_notify_cb)
482 self._updateComponentActions()
483
484 self._messageView.hide()
485
487 tooltip = action.get_property('tooltip')
488 if not tooltip:
489 return
490
491 if isinstance(widget, gtk.MenuItem):
492 cid = widget.connect('select', self._on_menu_item__select,
493 tooltip)
494 cid2 = widget.connect('deselect', self._on_menu_item__deselect)
495 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
496 elif isinstance(widget, gtk.ToolButton):
497 cid = widget.child.connect('enter', self._on_tool_button__enter,
498 tooltip)
499 cid2 = widget.child.connect('leave', self._on_tool_button__leave)
500 widget.set_data('pygtk-app::proxy-signal-ids', (cid, cid2))
501
503 cids = widget.get_data('pygtk-app::proxy-signal-ids')
504 if not cids:
505 return
506
507 if isinstance(widget, gtk.ToolButton):
508 widget = widget.child
509
510 for name, cid in cids:
511 widget.disconnect(cid)
512
537
545
546 model = AdminModel()
547 d = model.connectToManager(info)
548 d.addCallback(connected)
549 return d
550
552 d = self._openConnection(info)
553
554 def errorMessageDisplayed(unused):
555 self._window.set_sensitive(True)
556
557 def connected(model):
558 self._window.set_sensitive(True)
559
560 def errbackConnectionRefusedError(failure):
561 failure.trap(ConnectionRefusedError)
562 d = showConnectionErrorDialog(failure, info, parent=self._window)
563 d.addCallback(errorMessageDisplayed)
564
565 def errbackConnectionFailedError(failure):
566 failure.trap(ConnectionFailedError)
567 d = showConnectionErrorDialog(failure, info, parent=self._window)
568 d.addCallback(errorMessageDisplayed)
569 return d
570
571 d.addCallback(connected)
572 d.addErrback(errbackConnectionRefusedError)
573 d.addErrback(errbackConnectionFailedError)
574 self._window.set_sensitive(False)
575 return d
576
596
598 """Quitting the application in a controlled manner"""
599 self._clearAdmin()
600 self._close()
601
604
606 import pprint
607 import cStringIO
608 fd = cStringIO.StringIO()
609 pprint.pprint(configation, fd)
610 fd.seek(0)
611 self.debug('Configuration=%s' % fd.read())
612
617
626
627 - def _setStatusbarText(self, text):
628 return self._statusbar.push('main', text)
629
631 self._statusbar.pop('main')
632
642
644 if componentType is None:
645 raise ValueError
646 componentStates = []
647
648 for state in self._componentStates.values():
649 config = state.get('config')
650 if componentType and config['type'] == componentType:
651 componentStates.append(state)
652
653 if not componentStates:
654 return None
655 elif len(componentStates) == 1:
656 return componentStates[0]
657 else:
658 raise AssertionError(
659 "Attempted to fetch a component state by type %r, "
660 "expected one, but got %r" % (
661 componentType, componentStates))
662
675
677 def _getComponents():
678 for componentState in self._componentStates.values():
679 componentType = componentState.get('config')['type']
680 for entry in entries:
681 if entry.componentType == componentType:
682 yield (componentState, entry)
683
684
685 for componentState, entry in _getComponents():
686 component = componentClass()
687 component.componentType = entry.componentType
688 component.description = entry.description
689 component.exists = True
690 config = componentState.get('config')
691 for key, value in config['properties'].items():
692 component.properties[key] = value
693 yield component
694
710
711 d = self._adminModel.getWizardEntries(
712 wizardTypes=['audio-producer', 'video-producer'])
713 d.addCallback(cb)
714
722
723 if not self._componentStates:
724 runWizard()
725 return
726
727 if yesno(_("Running the Configuration Wizard again will remove "
728 "all components from the current stream and create "
729 "a new one."),
730 parent=self._window,
731 buttons=((_("Keep the current stream"), gtk.RESPONSE_NO),
732 (_("Run the Wizard anyway"), gtk.RESPONSE_YES))
733 ) != gtk.RESPONSE_YES:
734 return
735
736 d = self._clearAllComponents()
737 d.addCallback(lambda unused: runWizard())
738
756
767
770
772 canStart = self._componentList.canStart()
773 canStop = self._componentList.canStop()
774 canDelete = bool(self._currentComponentStates and canStart)
775 self._startComponentAction.set_sensitive(canStart)
776 self._stopComponentAction.set_sensitive(canStop)
777 self._deleteComponentAction.set_sensitive(canDelete)
778 self.debug('can start %r, can stop %r' % (canStart, canStop))
779 canStartAll = self._componentList.get_property('can-start-any')
780 canStopAll = self._componentList.get_property('can-stop-any')
781
782
783 canClearAll = canStartAll and not canStopAll
784 self._stopAllAction.set_sensitive(canStopAll)
785 self._startAllAction.set_sensitive(canStartAll)
786 self._clearAllAction.set_sensitive(canClearAll)
787 self._killComponentAction.set_sensitive(canStop)
788
789 hasProducer = self._hasProducerComponent()
790 self._addFormatAction.set_sensitive(hasProducer)
791
793 self._componentList.clearAndRebuild(self._componentStates)
794 self._trayicon.update(self._componentStates)
795
806
808 self._messageView.clear()
809 pstate = self._planetState
810 if pstate and pstate.hasKey('messages'):
811 for message in pstate.get('messages').values():
812 self._messageView.addMessage(message)
813
815
816 def flowStateAppend(state, key, value):
817 self.debug('flow state append: key %s, value %r' % (key, value))
818 if key == 'components':
819 self._componentStates[value.get('name')] = value
820
821 self._updateComponents()
822
823 def flowStateRemove(state, key, value):
824 if key == 'components':
825 self._removeComponent(value)
826
827 def atmosphereStateAppend(state, key, value):
828 if key == 'components':
829 self._componentStates[value.get('name')] = value
830
831 self._updateComponents()
832
833 def atmosphereStateRemove(state, key, value):
834 if key == 'components':
835 self._removeComponent(value)
836
837 def planetStateAppend(state, key, value):
838 if key == 'flows':
839 if value != state.get('flows')[0]:
840 self.warning('flumotion-admin can only handle one '
841 'flow, ignoring /%s', value.get('name'))
842 return
843 self.debug('%s flow started', value.get('name'))
844 value.addListener(self, append=flowStateAppend,
845 remove=flowStateRemove)
846 for c in value.get('components'):
847 flowStateAppend(value, 'components', c)
848 self._updateComponents()
849
850 def planetStateRemove(state, key, value):
851 self.debug('something got removed from the planet')
852
853 def planetStateSetitem(state, key, subkey, value):
854 if key == 'messages':
855 self._messageView.addMessage(value)
856
857 def planetStateDelitem(state, key, subkey, value):
858 if key == 'messages':
859 self._messageView.clearMessage(value.id)
860
861 self.debug('parsing planetState %r' % planetState)
862 self._planetState = planetState
863
864
865 self._componentStates = {}
866
867 planetState.addListener(self, append=planetStateAppend,
868 remove=planetStateRemove,
869 setitem=planetStateSetitem,
870 delitem=planetStateDelitem)
871
872 self._clearMessages()
873
874 a = planetState.get('atmosphere')
875 a.addListener(self, append=atmosphereStateAppend,
876 remove=atmosphereStateRemove)
877 for c in a.get('components'):
878 atmosphereStateAppend(a, 'components', c)
879
880 for f in planetState.get('flows'):
881 planetStateAppend(planetState, 'flows', f)
882
884 d = self._adminModel.cleanComponents()
885 def busyComponentError(failure):
886 failure.trap(BusyComponentError)
887 self._error(
888 _("Some component(s) are still busy and cannot be removed.\n"
889 "Try again later."))
890 d.addErrback(busyComponentError)
891 return d
892
893
894
909
911 """
912 @returns: a L{twisted.internet.defer.Deferred}
913 """
914 return self._componentDo(state, 'componentStop',
915 'Stop', 'Stopping', 'Stopped')
916
918 """
919 @returns: a L{twisted.internet.defer.Deferred}
920 """
921 return self._componentDo(state, 'componentStart',
922 'Start', 'Starting', 'Started')
923
925 """
926 @returns: a L{twisted.internet.defer.Deferred}
927 """
928 return self._componentDo(state, 'deleteComponent',
929 'Delete', 'Deleting', 'Deleted')
930
931 - def _componentDo(self, state, methodName, action, doing, done):
932 """Do something with a component and update the statusbar
933 @param state: componentState
934 @type state: L{AdminComponentState}
935 @param methodName: name of the method to call
936 @type methodName: str
937 @param action: string used to explain that to do
938 @type action: str
939 @param doing: string used to explain that the action started
940 @type doing: str
941 @param done: string used to explain that the action was completed
942 @type done: str
943 """
944 if state is None:
945 states = self._componentList.getSelectedStates()
946 else:
947 states = [state]
948
949 if not states:
950 return
951
952 def callbackSingle(result, self, mid, name):
953 self._statusbar.remove('main', mid)
954 self._setStatusbarText(
955 _("%s component %s") % (done, name))
956
957 def errbackSingle(failure, self, mid, name):
958 self._statusbar.remove('main', mid)
959 self.warning("Failed to %s component %s: %s" % (
960 action.lower(), name, failure))
961 self._setStatusbarText(
962 _("Failed to %(action)s component %(name)s.") % {
963 'action': action.lower(),
964 'name': name,
965 })
966
967 def callbackMultiple(results, self, mid):
968 self._statusbar.remove('main', mid)
969 self._setStatusbarText(
970 _("%s components.") % (done,))
971
972 def errbackMultiple(failure, self, mid):
973 self._statusbar.remove('main', mid)
974 self.warning("Failed to %s some components: %s." % (
975 action.lower(), failure))
976 self._setStatusbarText(
977 _("Failed to %s some components.") % (action,))
978
979 f = gettext.dngettext(
980 configure.PACKAGE,
981
982
983 N_("%s component %s"),
984
985
986
987 N_("%s components %s"), len(states))
988 statusText = f % (doing,
989 ', '.join([getComponentLabel(s) for s in states]))
990 mid = self._setStatusbarText(statusText)
991
992 if len(states) == 1:
993 state = states[0]
994 name = getComponentLabel(state)
995 d = self._adminModel.callRemote(methodName, state)
996 d.addCallback(callbackSingle, self, mid, name)
997 d.addErrback(errbackSingle, self, mid, name)
998 else:
999 deferreds = []
1000 for state in states:
1001 d = self._adminModel.callRemote(methodName, state)
1002 deferreds.append(d)
1003 d = defer.DeferredList(deferreds)
1004 d.addCallback(callbackMultiple, self, mid)
1005 d.addErrback(errbackMultiple, self, mid)
1006 return d
1007
1015
1017 self.debug('component %s has selection', states)
1018
1019 def compSet(state, key, value):
1020 if key == 'mood':
1021 self._updateComponentActions()
1022
1023 def compAppend(state, key, value):
1024 name = state.get('name')
1025 self.debug('stateAppend on component state of %s' % name)
1026 if key == 'messages':
1027 current = self._componentList.getSelectedNames()
1028 if name in current:
1029 self._messageView.addMessage(value)
1030
1031 def compRemove(state, key, value):
1032 name = state.get('name')
1033 self.debug('stateRemove on component state of %s' % name)
1034 if key == 'messages':
1035 current = self._componentList.getSelectedNames()
1036 if name in current:
1037 self._messageView.clearMessage(value.id)
1038
1039 if self._currentComponentStates:
1040 for currentComponentState in self._currentComponentStates:
1041 currentComponentState.removeListener(self)
1042 self._currentComponentStates = states
1043 if self._currentComponentStates:
1044 for currentComponentState in self._currentComponentStates:
1045 currentComponentState.addListener(
1046 self, set=compSet, append=compAppend, remove=compRemove)
1047
1048 self._updateComponentActions()
1049 self._clearMessages()
1050 state = None
1051 if states:
1052 if len(states) == 1:
1053 self.debug(
1054 "only one component is selected on the components view")
1055 state = states[0]
1056 elif states:
1057 self.debug("more than one components are selected in the "
1058 "components view")
1059 self._componentView.activateComponent(state)
1060
1061 statusbarMessage = " "
1062 for state in states:
1063 name = getComponentLabel(state)
1064 messages = state.get('messages')
1065 if messages:
1066 for m in messages:
1067 self.debug('have message %r' % m)
1068 self.debug('message id %s' % m.id)
1069 self._messageView.addMessage(m)
1070
1071 if state.get('mood') == moods.sad.value:
1072 self.debug('component %s is sad' % name)
1073 statusbarMessage = statusbarMessage + \
1074 _("Component %s is sad. ") % name
1075 if statusbarMessage != " ":
1076 self._setStatusbarText(statusbarMessage)
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1091 self._componentContextMenu.popup(None, None, None,
1092 event_button, event_time)
1093
1121
1123 RESPONSE_REFRESH = 1
1124
1125 def response(dialog, response_id):
1126 if response_id == RESPONSE_REFRESH:
1127 self._adminModel.reconnect()
1128 else:
1129
1130 dialog.stop()
1131 dialog.destroy()
1132 return
1133
1134 dialog = ProgressDialog(
1135 _("Reconnecting ..."),
1136 _("Lost connection to manager %s, reconnecting ...")
1137 % (self._adminModel.adminInfoStr(), ), self._window)
1138
1139 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
1140 dialog.add_button(gtk.STOCK_REFRESH, RESPONSE_REFRESH)
1141 dialog.connect("response", response)
1142 dialog.start()
1143 self._disconnectedDialog = dialog
1144
1154
1156 def refusedLater():
1157 self._fatalError(
1158 _("Connection to manager on %s was refused.") % (
1159 self._adminModel.connectionInfoStr()),
1160 _("Connection to %s was refused") %
1161 (self._adminModel.adminInfoStr(),))
1162
1163 self.debug("handling connection-refused")
1164 reactor.callLater(0, refusedLater)
1165 self.debug("handled connection-refused")
1166
1168 return self._fatalError(
1169 _("Connection to manager on %s failed (%s).") % (
1170 self._adminModel.connectionInfoStr(), reason),
1171 _("Connection to %s failed") %
1172 (self._adminModel.adminInfoStr(),))
1173
1183
1184 d.connect('have-connection', on_have_connection)
1185 d.show()
1186
1196
1197 def cancel(failure):
1198 failure.trap(WizardCancelled)
1199 wiz.stop()
1200
1201 d = wiz.runAsync()
1202 d.addCallback(got_state, wiz)
1203 d.addErrback(cancel)
1204
1206 dialog = gtk.FileChooserDialog(
1207 _("Import Configuration..."), self._window,
1208 gtk.FILE_CHOOSER_ACTION_OPEN,
1209 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1210 _('Import'), gtk.RESPONSE_ACCEPT))
1211 dialog.set_modal(True)
1212 dialog.set_default_response(gtk.RESPONSE_ACCEPT)
1213 ffilter = gtk.FileFilter()
1214 ffilter.set_name(_("Flumotion XML Configuration files"))
1215 ffilter.add_pattern("*.xml")
1216 dialog.add_filter(ffilter)
1217 ffilter = gtk.FileFilter()
1218 ffilter.set_name(_("All files"))
1219 ffilter.add_pattern("*")
1220 dialog.add_filter(ffilter)
1221
1222 def response(dialog, response):
1223 if response == gtk.RESPONSE_ACCEPT:
1224 name = dialog.get_filename()
1225 conf_xml = open(name, 'r').read()
1226 self._adminModel.loadConfiguration(conf_xml)
1227 dialog.destroy()
1228
1229 dialog.connect('response', response)
1230 dialog.show()
1231
1233 d = gtk.FileChooserDialog(
1234 _("Export Configuration..."), self._window,
1235 gtk.FILE_CHOOSER_ACTION_SAVE,
1236 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1237 _('Export'), gtk.RESPONSE_ACCEPT))
1238 d.set_modal(True)
1239 d.set_default_response(gtk.RESPONSE_ACCEPT)
1240 d.set_current_name("configuration.xml")
1241
1242 def getConfiguration(conf_xml, name, chooser):
1243 if not name.endswith('.xml'):
1244 name += '.xml'
1245
1246 file_exists = True
1247 if os.path.exists(name):
1248 d = gtk.MessageDialog(
1249 self._window, gtk.DIALOG_MODAL,
1250 gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO,
1251 _("File already exists.\nOverwrite?"))
1252 d.connect("response", lambda self, response: d.hide())
1253 if d.run() == gtk.RESPONSE_YES:
1254 file_exists = False
1255 else:
1256 file_exists = False
1257
1258 if not file_exists:
1259 f = open(name, 'w')
1260 f.write(conf_xml)
1261 f.close()
1262 chooser.destroy()
1263
1264 def response(d, response):
1265 if response == gtk.RESPONSE_ACCEPT:
1266 deferred = self._adminModel.getConfiguration()
1267 name = d.get_filename()
1268 deferred.addCallback(getConfiguration, name, d)
1269 else:
1270 d.destroy()
1271
1272 d.connect('response', response)
1273 d.show()
1274
1276 if sys.version_info >= (2, 4):
1277 from flumotion.extern import code
1278 code
1279 else:
1280 import code
1281
1282 ns = { "admin": self._adminModel,
1283 "components": self._componentStates }
1284 message = """Flumotion Admin Debug Shell
1285
1286 Local variables are:
1287 admin (flumotion.admin.admin.AdminModel)
1288 components (dict: name -> flumotion.common.planet.AdminComponentState)
1289
1290 You can do remote component calls using:
1291 admin.componentCallRemote(components['component-name'],
1292 'methodName', arg1, arg2)
1293
1294 """
1295 code.interact(local=ns, banner=message)
1296
1298 def gotConfiguration(xml):
1299 print xml
1300 d = self._adminModel.getConfiguration()
1301 d.addCallback(gotConfiguration)
1302
1304 def setMarker(_, marker, level):
1305 self._adminModel.callRemote('writeFluDebugMarker', level, marker)
1306 debugMarkerDialog = DebugMarkerDialog()
1307 debugMarkerDialog.connect('set-marker', setMarker)
1308 debugMarkerDialog.show()
1309
1314
1315
1316
1319
1322
1325
1328
1331
1332
1333
1336
1339
1342
1345
1348
1351
1354
1357
1360
1363
1366
1369
1372
1373
1374
1377
1380
1383
1386
1389
1392
1395
1398
1401
1405
1407 for c in self._componentStates.values():
1408 self._componentStop(c)
1409
1412
1415
1418
1421
1424
1427
1430
1433
1434 gobject.type_register(AdminWindow)
1435