1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 4
18 """main interface for the cursor admin client"""
19
20 import curses
21 import os
22 import string
23
24 import gobject
25 from twisted.internet import reactor
26 from twisted.python import rebuild
27 from zope.interface import implements
28
29 from flumotion.common import log, errors, common
30 from flumotion.twisted import flavors, reflect
31 from flumotion.common.planet import moods
32
33 from flumotion.admin.text import misc_curses
34
35 __version__ = "$Rev$"
36
37
38 -class AdminTextView(log.Loggable, gobject.GObject, misc_curses.CursesStdIO):
39
40 implements(flavors.IStateListener)
41
42 logCategory = 'admintextview'
43
44 global_commands = ['startall', 'stopall', 'clearall', 'quit']
45
46 LINES_BEFORE_COMPONENTS = 5
47 LINES_AFTER_COMPONENTS = 6
48
49 - def __init__(self, model, stdscr):
50 self.initialised = False
51 self.stdscr = stdscr
52 self.inputText = ''
53 self.command_result = ""
54 self.lastcommands = []
55 self.nextcommands = []
56 self.rows, self.cols = self.stdscr.getmaxyx()
57 self.max_components_per_page = self.rows - \
58 self.LINES_BEFORE_COMPONENTS - \
59 self.LINES_AFTER_COMPONENTS
60 self._first_onscreen_component = 0
61
62 self._components = {}
63 self._comptextui = {}
64 self._setAdminModel(model)
65
66 self.setPlanetState(self.admin.planet)
67
68 - def _setAdminModel(self, model):
69 self.admin = model
70
71 self.admin.connect('connected', self.admin_connected_cb)
72 self.admin.connect('disconnected', self.admin_disconnected_cb)
73 self.admin.connect('connection-refused',
74 self.admin_connection_refused_cb)
75 self.admin.connect('connection-failed',
76 self.admin_connection_failed_cb)
77
78
79 self.admin.connect('update', self.admin_update_cb)
80
81
82
84 self.initialised = True
85 self.stdscr.addstr(0, 0, "Main Menu")
86 self.show_components()
87 self.display_status()
88 self.stdscr.move(self.lasty, 0)
89 self.stdscr.clrtoeol()
90 self.stdscr.move(self.lasty+1, 0)
91 self.stdscr.clrtoeol()
92 self.stdscr.addstr(self.lasty+1, 0, "Prompt: %s" % self.inputText)
93 self.stdscr.refresh()
94
95
96
97
98
100 if self.initialised:
101 self.stdscr.addstr(2, 0, "Components:")
102
103 names = self._components.keys()
104 names.sort()
105
106 cury = 4
107
108
109
110
111 if len(names) > self.max_components_per_page:
112 if self._first_onscreen_component > 0:
113 self.stdscr.move(cury, 0)
114 self.stdscr.clrtoeol()
115 self.stdscr.addstr(cury, 0,
116 "Press page up to scroll up components list")
117 cury=cury+1
118 cur_component = self._first_onscreen_component
119 for name in names[self._first_onscreen_component:len(names)]:
120
121 if cury - self.LINES_BEFORE_COMPONENTS >= \
122 self.max_components_per_page:
123 self.stdscr.move(cury, 0)
124 self.stdscr.clrtoeol()
125 self.stdscr.addstr(cury, 0,
126 "Press page down to scroll down components list")
127 cury = cury + 1
128 break
129
130 component = self._components[name]
131 mood = component.get('mood')
132
133 self.stdscr.move(cury, 0)
134 self.stdscr.clrtoeol()
135
136 self.stdscr.addstr(cury, 0, "%s: %s" % (
137 name, moods[mood].name))
138 cury = cury + 1
139 cur_component = cur_component + 1
140
141 self.lasty = cury
142
143
144 - def gotEntryCallback(self, result, name):
145 entryPath, filename, methodName = result
146 filepath = os.path.join(entryPath, filename)
147 self.debug('Got the UI for %s and it lives in %s' % (name, filepath))
148 self.uidir = os.path.split(filepath)[0]
149
150
151
152
153
154 moduleName = common.pathToModuleName(filename)
155 statement = 'import %s' % moduleName
156 self.debug('running %s' % statement)
157 try:
158 exec(statement)
159 except SyntaxError, e:
160
161 where = getattr(e, 'filename', "<entry file>")
162 lineno = getattr(e, 'lineno', 0)
163 msg = "Syntax Error at %s:%d while executing %s" % (
164 where, lineno, filename)
165 self.warning(msg)
166 raise errors.EntrySyntaxError(msg)
167 except NameError, e:
168
169 msg = "NameError while executing %s: %s" % (filename,
170 " ".join(e.args))
171 self.warning(msg)
172 raise errors.EntrySyntaxError(msg)
173 except ImportError, e:
174 msg = "ImportError while executing %s: %s" % (filename,
175 " ".join(e.args))
176 self.warning(msg)
177 raise errors.EntrySyntaxError(msg)
178
179
180 module = reflect.namedAny(moduleName)
181 rebuild.rebuild(module)
182
183
184 if not hasattr(module, methodName):
185 self.warning('method %s not found in file %s' % (
186 methodName, filename))
187 raise
188 klass = getattr(module, methodName)
189
190
191
192
193 instance = klass(self._components[name], self.admin)
194 self.debug("Created entry instance %r" % instance)
195
196
197
198 self._comptextui[name] = instance
199
200 - def gotEntryNoBundleErrback(self, failure, name):
201 failure.trap(errors.NoBundleError)
202 self.debug("No admin ui for component %s" % name)
203
206
207 - def getEntry(self, componentState, type):
208 """
209 Do everything needed to set up the entry point for the given
210 component and type, including transferring and setting up bundles.
211
212 Caller is responsible for adding errbacks to the deferred.
213
214 @returns: a deferred returning (entryPath, filename, methodName) with
215 entryPath: the full local path to the bundle's base
216 fileName: the relative location of the bundled file
217 methodName: the method to instantiate with
218 """
219 lexicalVariableHack = []
220
221 def gotEntry(res):
222 fileName, methodName = res
223 lexicalVariableHack.append(res)
224 self.debug("entry for %r of type %s is in file %s and method %s",
225 componentState, type, fileName, methodName)
226 return self.admin.bundleLoader.getBundles(fileName=fileName)
227
228 def gotBundles(res):
229 name, bundlePath = res[-1]
230 fileName, methodName = lexicalVariableHack[0]
231 return (bundlePath, fileName, methodName)
232
233 d = self.admin.callRemote('getEntryByType',
234 componentState.get('type'), type)
235 d.addCallback(gotEntry)
236 d.addCallback(gotBundles)
237 return d
238
239 - def update_components(self, components):
240 for name in self._components.keys():
241 component = self._components[name]
242 try:
243 component.removeListener(self)
244 except KeyError:
245
246 self.debug("silly")
247
248 def compStateSet(state, key, value):
249 self.log('stateSet: state %r, key %s, value %r' % (
250 state, key, value))
251
252 if key == 'mood':
253
254
255 d = self.getEntry(state, 'admin/text')
256 d.addCallback(self.gotEntryCallback, state.get('name'))
257 d.addErrback(self.gotEntryNoBundleErrback, state.get('name'))
258 d.addErrback(self.gotEntrySleepingComponentErrback)
259
260 self.show()
261 elif key == 'name':
262 if value:
263 self.show()
264
265 self._components = components
266 for name in self._components.keys():
267 component = self._components[name]
268 component.addListener(self, set_=compStateSet)
269
270
271 d = self.getEntry(component, 'admin/text')
272 d.addCallback(self.gotEntryCallback, name)
273 d.addErrback(self.gotEntryNoBundleErrback, name)
274 d.addErrback(self.gotEntrySleepingComponentErrback)
275
276 self.show()
277
278 - def setPlanetState(self, planetState):
279
280 def flowStateAppend(state, key, value):
281 self.debug('flow state append: key %s, value %r' % (key, value))
282 if state.get('name') != 'default':
283 return
284 if key == 'components':
285 self._components[value.get('name')] = value
286
287 self.update_components(self._components)
288
289 def flowStateRemove(state, key, value):
290 if state.get('name') != 'default':
291 return
292 if key == 'components':
293 name = value.get('name')
294 self.debug('removing component %s' % name)
295 del self._components[name]
296
297 self.update_components(self._components)
298
299 def atmosphereStateAppend(state, key, value):
300 if key == 'components':
301 self._components[value.get('name')] = value
302
303 self.update_components(self._components)
304
305 def atmosphereStateRemove(state, key, value):
306 if key == 'components':
307 name = value.get('name')
308 self.debug('removing component %s' % name)
309 del self._components[name]
310
311 self.update_components(self._components)
312
313 def planetStateAppend(state, key, value):
314 if key == 'flows':
315 if value.get('name') != 'default':
316 return
317
318 value.addListener(self, append=flowStateAppend,
319 remove=flowStateRemove)
320 for c in value.get('components'):
321 flowStateAppend(value, 'components', c)
322
323 def planetStateRemove(state, key, value):
324 self.debug('something got removed from the planet')
325
326 self.debug('parsing planetState %r' % planetState)
327 self._planetState = planetState
328
329
330 self._components = {}
331
332 planetState.addListener(self, append=planetStateAppend,
333 remove=planetStateRemove)
334
335 a = planetState.get('atmosphere')
336 a.addListener(self, append=atmosphereStateAppend,
337 remove=atmosphereStateRemove)
338 for c in a.get('components'):
339 atmosphereStateAppend(a, 'components', c)
340
341 for f in planetState.get('flows'):
342 planetStateAppend(f, 'flows', f)
343
344 - def _component_stop(self, state):
345 return self._component_do(state, 'Stop', 'Stopping', 'Stopped')
346
347 - def _component_start(self, state):
348 return self._component_do(state, 'Start', 'Starting', 'Started')
349
350 - def _component_do(self, state, action, doing, done):
351 name = state.get('name')
352 if not name:
353 return None
354
355 self.admin.callRemote('component'+action, state)
356
357 - def run_command(self, command):
358
359 can_stop = True
360 can_start = True
361 for x in self._components.values():
362 mood = moods.get(x.get('mood'))
363 can_stop = can_stop and (mood != moods.lost and
364 mood != moods.sleeping)
365 can_start = can_start and (mood == moods.sleeping)
366 can_clear = can_start and not can_stop
367
368 if string.lower(command) == 'quit':
369 reactor.stop()
370 elif string.lower(command) == 'startall':
371 if can_start:
372 for c in self._components.values():
373 self._component_start(c)
374 self.command_result = 'Attempting to start all components'
375 else:
376 self.command_result = (
377 'Components not all in state to be started')
378
379
380 elif string.lower(command) == 'stopall':
381 if can_stop:
382 for c in self._components.values():
383 self._component_stop(c)
384 self.command_result = 'Attempting to stop all components'
385 else:
386 self.command_result = (
387 'Components not all in state to be stopped')
388 elif string.lower(command) == 'clearall':
389 if can_clear:
390 self.admin.cleanComponents()
391 self.command_result = 'Attempting to clear all components'
392 else:
393 self.command_result = (
394 'Components not all in state to be cleared')
395 else:
396 command_split = command.split()
397
398 if len(command_split)>1:
399
400 for c in self._components.values():
401 if string.lower(c.get('name')) == (
402 string.lower(command_split[0])):
403
404 if string.lower(command_split[1]) == 'start':
405
406 self._component_start(c)
407 elif string.lower(command_split[1]) == 'stop':
408
409 self._component_stop(c)
410 else:
411
412 try:
413 textui = self._comptextui[c.get('name')]
414
415 if textui:
416 d = textui.runCommand(
417 ' '.join(command_split[1:]))
418 self.debug(
419 "textui runcommand defer: %r" % d)
420
421 d.addCallback(self._runCommand_cb)
422
423 except KeyError:
424 pass
425
426 - def _runCommand_cb(self, result):
427 self.command_result = result
428 self.debug("Result received: %s" % result)
429 self.show()
430
431 - def get_available_commands(self, input):
432 input_split = input.split()
433 last_input=''
434 if len(input_split) >0:
435 last_input = input_split[len(input_split)-1]
436 available_commands = []
437 if len(input_split) <= 1 and not input.endswith(' '):
438
439 can_stop = True
440 can_start = True
441 for x in self._components.values():
442 mood = moods.get(x.get('mood'))
443 can_stop = can_stop and (mood != moods.lost and
444 mood != moods.sleeping)
445 can_start = can_start and (mood == moods.sleeping)
446 can_clear = can_start and not can_stop
447
448 for command in self.global_commands:
449 command_ok = (command != 'startall' and
450 command != 'stopall' and
451 command != 'clearall')
452 command_ok = command_ok or (command == 'startall' and
453 can_start)
454 command_ok = command_ok or (command == 'stopall' and
455 can_stop)
456 command_ok = command_ok or (command == 'clearall' and
457 can_clear)
458
459 if (command_ok and string.lower(command).startswith(
460 string.lower(last_input))):
461 available_commands.append(command)
462 else:
463 available_commands = (available_commands +
464 self.get_available_commands_for_component(
465 input_split[0], input))
466
467 return available_commands
468
470 self.debug("getting commands for component %s" % comp)
471 commands = []
472 for c in self._components:
473 if c == comp:
474 component_commands = ['start', 'stop']
475 textui = None
476 try:
477 textui = self._comptextui[comp]
478 except KeyError:
479 self.debug("no text ui for component %s" % comp)
480
481 input_split = input.split()
482
483 if len(input_split) >= 2 or input.endswith(' '):
484 for command in component_commands:
485 if len(input_split) == 2:
486 if command.startswith(input_split[1]):
487 commands.append(command)
488 elif len(input_split) == 1:
489 commands.append(command)
490 if textui:
491 self.debug(
492 "getting component commands from ui of %s" % comp)
493 comp_input = ' '.join(input_split[1:])
494 if input.endswith(' '):
495 comp_input = comp_input + ' '
496 commands = commands + textui.getCompletions(comp_input)
497
498 return commands
499
501 completions = self.get_available_commands(input)
502
503
504
505 if len(input.split()) <= 1:
506 for c in self._components:
507 if c.startswith(input):
508 completions.append(c)
509
510 return completions
511
512 - def display_status(self):
513 availablecommands = self.get_available_commands(self.inputText)
514 available_commands = ' '.join(availablecommands)
515
516
517 self.stdscr.move(self.lasty+2, 0)
518 self.stdscr.clrtoeol()
519
520 self.stdscr.addstr(self.lasty+2, 0,
521 "Available Commands: %s" % available_commands)
522
523 self.stdscr.move(self.lasty+3, 0)
524 self.stdscr.clrtoeol()
525 self.stdscr.move(self.lasty+4, 0)
526 self.stdscr.clrtoeol()
527
528 if self.command_result != "":
529 self.stdscr.addstr(self.lasty+4,
530 0, "Result: %s" % self.command_result)
531 self.stdscr.clrtobot()
532
533
534
535 - def admin_connected_cb(self, admin):
536 self.info('Connected to manager')
537
538
539 self.setPlanetState(self.admin.planet)
540
541 if not self._components:
542 self.debug('no components detected, running wizard')
543
544 self.show()
545
546 - def admin_disconnected_cb(self, admin):
547 message = "Lost connection to manager, reconnecting ..."
548 print message
549
551 log.debug('textadminclient', "handling connection-refused")
552
553 log.debug('textadminclient', "handled connection-refused")
554
556 log.debug('textadminclient', "handling connection-failed")
557
558 log.debug('textadminclient', "handled connection-failed")
559
560 - def admin_update_cb(self, admin):
561 self.update_components(self._components)
562
563 - def connectionLost(self, why):
566
567 - def whsStateAppend(self, state, key, value):
568 if key == 'names':
569 self.debug('Worker %s logged in.' % value)
570
571 - def whsStateRemove(self, state, key, value):
572 if key == 'names':
573 self.debug('Worker %s logged out.' % value)
574
575
576
578 """ Input is ready! """
579 c = self.stdscr.getch()
580
581 if c == curses.KEY_BACKSPACE or c == 127:
582 self.inputText = self.inputText[:-1]
583 elif c == curses.KEY_STAB or c == 9:
584 available_commands = self.get_available_completions(self.inputText)
585 if len(available_commands) == 1:
586 input_split = self.inputText.split()
587 if len(input_split) > 1:
588 if not self.inputText.endswith(' '):
589 input_split.pop()
590 self.inputText = (
591 ' '.join(input_split) + ' ' + available_commands[0])
592 else:
593 self.inputText = available_commands[0]
594
595 elif c == curses.KEY_ENTER or c == 10:
596
597 self.run_command(self.inputText)
598
599 self.display_status()
600
601 self.stdscr.move(self.lasty+1, 0)
602 self.stdscr.clrtoeol()
603 self.stdscr.addstr(self.lasty+1, 0, 'Prompt: ')
604 self.stdscr.refresh()
605 if len(self.nextcommands) > 0:
606 self.lastcommands = self.lastcommands + self.nextcommands
607 self.nextcommands = []
608 self.lastcommands.append(self.inputText)
609 self.inputText = ''
610 self.command_result = ''
611 elif c == curses.KEY_UP:
612 lastcommand = ""
613 if len(self.lastcommands) > 0:
614 lastcommand = self.lastcommands.pop()
615 if self.inputText != "":
616 self.nextcommands.append(self.inputText)
617 self.inputText = lastcommand
618 elif c == curses.KEY_DOWN:
619 nextcommand = ""
620 if len(self.nextcommands) > 0:
621 nextcommand = self.nextcommands.pop()
622 if self.inputText != "":
623 self.lastcommands.append(self.inputText)
624 self.inputText = nextcommand
625 elif c == curses.KEY_PPAGE:
626 if self._first_onscreen_component > 0:
627 self._first_onscreen_component = \
628 self._first_onscreen_component - 1
629 self.show()
630 elif c == curses.KEY_NPAGE:
631 if self._first_onscreen_component < len(self._components) - \
632 self.max_components_per_page:
633 self._first_onscreen_component = \
634 self._first_onscreen_component + 1
635 self.show()
636
637 else:
638
639 if len(self.inputText) == self.cols-2:
640 return
641
642 if c<=256:
643 self.inputText = self.inputText + chr(c)
644
645
646 self.display_status()
647
648 self.stdscr.move(self.lasty+1, 0)
649 self.stdscr.clrtoeol()
650
651 self.stdscr.addstr(self.lasty+1, 0, 'Prompt: %s' % self.inputText)
652 self.stdscr.refresh()
653
654
655
656
657
658 - def componentCall(self, componentState, methodName, *args, **kwargs):
659
660
661
662
663 self.log("componentCall received for %r.%s ..." % (
664 componentState, methodName))
665 localMethodName = "component_%s" % methodName
666 name = componentState.get('name')
667
668 try:
669 textui = self._comptextui[name]
670 except KeyError:
671 return
672
673 if not hasattr(textui, localMethodName):
674 self.log("... but does not have method %s" % localMethodName)
675 self.warning("Component view %s does not implement %s" % (
676 name, localMethodName))
677 return
678 self.log("... and executing")
679 method = getattr(textui, localMethodName)
680
681
682 try:
683 result = method(*args, **kwargs)
684 except TypeError:
685 msg = ("component method %s did not"
686 " accept *a %s and **kwa %s (or TypeError)") % (
687 methodName, args, kwargs)
688 self.debug(msg)
689 raise errors.RemoteRunError(msg)
690 self.log("component: returning result: %r to caller" % result)
691 return result
692