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