1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import gettext
23 import os
24 import sets
25
26 import gtk
27 from gtk import gdk
28 from twisted.internet import defer
29
30 from flumotion.common import errors, messages
31 from flumotion.common.common import pathToModuleName
32 from flumotion.common.i18n import N_, ngettext, gettexter
33 from flumotion.common.pygobject import gsignal
34 from flumotion.ui.wizard import SectionWizard, WizardStep
35 from flumotion.wizard.scenarios import LiveScenario, OnDemandScenario
36 from flumotion.wizard.worker import WorkerList
37 from flumotion.wizard.workerstep import WorkerWizardStep
38
39
40
41
42 __pychecker__ = 'no-classattr no-argsused'
43 __version__ = "$Rev: 7088 $"
44 T_ = gettexter()
45 _ = gettext.gettext
46
47
48
49
50
52 """
53 Return a string to be used in serializing to XML.
54 """
55 return "%d/%d" % (number * denominator, denominator)
56
57
67
68
70 name = "Scenario"
71 title = _('Scenario')
72 section = _('Scenario')
73 icon = 'wizard.png'
74 gladeFile = 'scenario-wizard.glade'
75
76
78 self._currentScenarioClass = None
79 self._radioGroup = None
80 self._scenarioRadioButtons = []
81 super(ScenarioStep, self).__init__(wizard)
82
90
96
97
98
109
110
111
112
113
116
120
121
123 gsignal('finished', str)
124
126 SectionWizard.__init__(self, parent)
127
128
129 self.window1.set_name('ConfigurationWizard')
130 self._cursorWatch = gdk.Cursor(gdk.WATCH)
131 self._tasks = []
132 self._adminModel = None
133 self._workerHeavenState = None
134 self._lastWorker = 0
135 self._httpPorter = None
136 self._stepWorkers = {}
137 self._scenario = None
138 self._existingComponentNames = []
139
140 self._workerList = WorkerList()
141 self.top_vbox.pack_start(self._workerList, False, False)
142 self._workerList.connect('worker-selected',
143 self.on_combobox_worker_changed)
144 self.addSteps()
145
146
147
149 saver = self._scenario.save()
150 xml = saver.getXML()
151 self.emit('finished', xml)
152
154 SectionWizard.destroy(self)
155 self._adminModel = None
156
165
169
171
172 if self._tasks:
173 return
174 SectionWizard.blockNext(self, block)
175
176
177
178
180 """Add the step sections of the wizard, can be
181 overridden in a subclass
182 """
183
184
185 self.addStepSection(WelcomeStep)
186 self.addStepSection(ScenarioStep)
187
189 """Sets the current scenario of the wizard.
190 Normally called by ScenarioStep to tell the wizard the current scenario
191 just after creating it.
192 @param scenario: the scenario of the wizard
193 @type scenario: a L{flumotion.wizard.scenarios.Scenario} subclass
194 """
195 scenario.setExistingComponentNames(self._existingComponentNames)
196 self._scenario = scenario
197
199 """Fetches the currently set scenario of the wizard.
200 @returns scenario: the scenario of the wizard
201 @rtype: a L{flumotion.wizard.scenarios.Scenario} subclass
202 """
203 return self._scenario
204
206 """
207 Sets the worker heaven state of the wizard
208 @param workerHeavenState: the worker heaven state
209 @type workerHeavenState: L{WorkerComponentUIState}
210 """
211 self._workerHeavenState = workerHeavenState
212 self._workerList.setWorkerHeavenState(workerHeavenState)
213
215 """
216 Sets the admin model of the wizard
217 @param adminModel: the admin model
218 @type adminModel: L{AdminModel}
219 """
220 self._adminModel = adminModel
221
223 """Gets the admin model of the wizard
224 @returns adminModel: the admin model
225 @rtype adminModel: L{AdminModel}
226 """
227 return self._adminModel
228
230 """Instruct the wizard that we're waiting for a task
231 to be finished. This changes the cursor and prevents
232 the user from continuing moving forward.
233 Each call to this method should have another call
234 to taskFinished() when the task is actually done.
235 @param taskName: name of the name
236 @type taskName: string
237 """
238 self.info("waiting for task %s" % (taskName,))
239 if not self._tasks:
240 if self.window1.window is not None:
241 self.window1.window.set_cursor(self._cursorWatch)
242 self.blockNext(True)
243 self._tasks.append(taskName)
244
246 """Instruct the wizard that a task was finished.
247 @param blockNext: if we should still next when done
248 @type blockNext: boolean
249 """
250 if not self._tasks:
251 raise AssertionError(
252 "Stray call to taskFinished(), forgot to call waitForTask()?")
253
254 taskName = self._tasks.pop()
255 self.info("task %s has now finished" % (taskName,))
256 if not self._tasks:
257 self.window1.window.set_cursor(None)
258 self.blockNext(blockNext)
259
261 """Returns true if there are any pending tasks
262 @returns: if there are pending tasks
263 @rtype: bool
264 """
265 return bool(self._tasks)
266
267
269 """If the configured feed has a audio stream
270 @return: if we have audio
271 @rtype: bool
272 """
273 productionStep = self.getStep('Production')
274 return productionStep.hasAudio()
275
277 """If the configured feed has a video stream
278 @return: if we have video
279 @rtype: bool
280 """
281 productionStep = self.getStep('Production')
282 return productionStep.hasVideo()
283
285 """Returns the selected audio producer or None
286 @returns: producer or None
287 @rtype: L{flumotion.wizard.models.AudioProducer}
288 """
289 productionStep = self.getStep('Production')
290 return productionStep.getAudioProducer()
291
293 """Returns the selected video producer or None
294 @returns: producer or None
295 @rtype: L{flumotion.wizard.models.VideoProducer}
296 """
297 productionStep = self.getStep('Production')
298 return productionStep.getVideoProducer()
299
301 """Sets the HTTP porter of the wizard.
302 If the http port set in the new wizard is identical to the old one,
303 this porter will be reused
304 @param httpPorter: the http porter
305 @type httpPorter: L{flumotion.wizard.models.Porter} instance
306 """
307 self._httpPorter = httpPorter
308
310 return self._httpPorter
311
313 """Check if the given list of GStreamer elements exist on the
314 given worker.
315 @param workerName: name of the worker to check on
316 @type workerName: string
317 @param elementNames: names of the elements to check
318 @type elementNames: list of strings
319 @returns: a deferred returning a tuple of the missing elements
320 @rtype: L{twisted.internet.defer.Deferred}
321 """
322 if not self._adminModel:
323 self.debug('No admin connected, not checking presence of elements')
324 return
325
326 asked = sets.Set(elementNames)
327 def _checkElementsCallback(existing, workerName):
328 existing = sets.Set(existing)
329 self.taskFinished()
330 return tuple(asked.difference(existing))
331
332 self.waitForTask('check elements %r' % (elementNames,))
333 d = self._adminModel.checkElements(workerName, elementNames)
334 d.addCallback(_checkElementsCallback, workerName)
335 return d
336
338 """Require that the given list of GStreamer elements exists on the
339 given worker. If the elements do not exist, an error message is
340 posted and the next button remains blocked.
341 @param workerName: name of the worker to check on
342 @type workerName: string
343 @param elementNames: names of the elements to check
344 @type elementNames: list of strings
345 @returns: element name
346 @rtype: deferred -> list of strings
347 """
348 if not self._adminModel:
349 self.debug('No admin connected, not checking presence of elements')
350 return
351
352 self.debug('requiring elements %r' % (elementNames,))
353 def gotMissingElements(elements, workerName):
354 if elements:
355 self.warning('elements %r do not exist' % (elements,))
356 f = ngettext("Worker '%s' is missing GStreamer element '%s'.",
357 "Worker '%s' is missing GStreamer elements '%s'.",
358 len(elements))
359 message = messages.Error(T_(f, workerName,
360 "', '".join(elements)))
361 message.add(T_(N_("\n"
362 "Please install the necessary GStreamer plug-ins that "
363 "provide these elements and restart the worker.")))
364 message.add(T_(N_("\n\n"
365 "You will not be able to go forward using this worker.")))
366 message.id = 'element' + '-'.join(elementNames)
367 self.add_msg(message)
368 self.taskFinished(bool(elements))
369 return elements
370
371 self.waitForTask('require elements %r' % (elementNames,))
372 d = self.checkElements(workerName, *elementNames)
373 d.addCallback(gotMissingElements, workerName)
374
375 return d
376
378 """Check if the given module can be imported.
379 @param workerName: name of the worker to check on
380 @type workerName: string
381 @param moduleName: name of the module to import
382 @type moduleName: string
383 @returns: a deferred firing None or Failure.
384 @rtype: L{twisted.internet.defer.Deferred}
385 """
386 if not self._adminModel:
387 self.debug('No admin connected, not checking presence of elements')
388 return
389
390 d = self._adminModel.checkImport(workerName, moduleName)
391 return d
392
393 - def requireImport(self, workerName, moduleName, projectName=None,
394 projectURL=None):
395 """Require that the given module can be imported on the given worker.
396 If the module cannot be imported, an error message is
397 posted and the next button remains blocked.
398 @param workerName: name of the worker to check on
399 @type workerName: string
400 @param moduleName: name of the module to import
401 @type moduleName: string
402 @param projectName: name of the module to import
403 @type projectName: string
404 @param projectURL: URL of the project
405 @type projectURL: string
406 @returns: a deferred firing None or Failure
407 @rtype: L{twisted.internet.defer.Deferred}
408 """
409 if not self._adminModel:
410 self.debug('No admin connected, not checking presence of elements')
411 return
412
413 self.debug('requiring module %s' % moduleName)
414 def _checkImportErrback(failure):
415 self.warning('could not import %s', moduleName)
416 message = messages.Error(T_(N_(
417 "Worker '%s' cannot import module '%s'."),
418 workerName, moduleName))
419 if projectName:
420 message.add(T_(N_("\n"
421 "This module is part of '%s'."), projectName))
422 if projectURL:
423 message.add(T_(N_("\n"
424 "The project's homepage is %s"), projectURL))
425 message.add(T_(N_("\n\n"
426 "You will not be able to go forward using this worker.")))
427 message.id = 'module-%s' % moduleName
428 self.add_msg(message)
429 self.taskFinished(True)
430
431 d = self.checkImport(workerName, moduleName)
432 d.addErrback(_checkImportErrback)
433 return d
434
435
436 - def runInWorker(self, workerName, moduleName, functionName,
437 *args, **kwargs):
438 """
439 Run the given function and arguments on the selected worker.
440 The given function should return a L{messages.Result}.
441
442 @param workerName: name of the worker to run the function in
443 @type workerName: string
444 @param moduleName: name of the module where the function is found
445 @type moduleName: string
446 @param functionName: name of the function to run
447 @type functionName: string
448
449 @returns: a deferred firing the Result's value.
450 @rtype: L{twisted.internet.defer.Deferred}
451 """
452 self.debug('runInWorker(moduleName=%r, functionName=%r)' % (
453 moduleName, functionName))
454 admin = self._adminModel
455 if not admin:
456 self.warning('skipping runInWorker, no admin')
457 return defer.fail(errors.FlumotionError('no admin'))
458
459 if not workerName:
460 self.warning('skipping runInWorker, no worker')
461 return defer.fail(errors.FlumotionError('no worker'))
462
463 def callback(result):
464 self.debug('runInWorker callbacked a result')
465 self.clear_msg(functionName)
466
467 if not isinstance(result, messages.Result):
468 msg = messages.Error(T_(
469 N_("Internal error: could not run check code on worker.")),
470 debug=('function %r returned a non-Result %r'
471 % (functionName, result)))
472 self.add_msg(msg)
473 self.taskFinished(True)
474 raise errors.RemoteRunError(functionName, 'Internal error.')
475
476 for m in result.messages:
477 self.debug('showing msg %r' % m)
478 self.add_msg(m)
479
480 if result.failed:
481 self.debug('... that failed')
482 self.taskFinished(True)
483 raise errors.RemoteRunFailure(functionName, 'Result failed')
484 self.debug('... that succeeded')
485 self.taskFinished()
486 return result.value
487
488 def errback(failure):
489 self.debug('runInWorker errbacked, showing error msg')
490 if failure.check(errors.RemoteRunError):
491 debug = failure.value
492 else:
493 debug = "Failure while running %s.%s:\n%s" % (
494 moduleName, functionName, failure.getTraceback())
495
496 msg = messages.Error(T_(
497 N_("Internal error: could not run check code on worker.")),
498 debug=debug)
499 self.add_msg(msg)
500 self.taskFinished(True)
501 raise errors.RemoteRunError(functionName, 'Internal error.')
502
503 self.waitForTask('run in worker: %s.%s(%r, %r)' % (
504 moduleName, functionName, args, kwargs))
505 d = admin.workerRun(workerName, moduleName,
506 functionName, *args, **kwargs)
507 d.addErrback(errback)
508 d.addCallback(callback)
509 return d
510
511 - def getWizardEntry(self, componentType):
512 """Fetches a wizard bundle from a specific kind of component
513 @param componentType: the component type to get the wizard entry
514 bundle from.
515 @type componentType: string
516 @returns: a deferred returning either::
517 - factory of the component
518 - noBundle error: if the component lacks a wizard bundle
519 @rtype: L{twisted.internet.defer.Deferred}
520 """
521 self.waitForTask('get wizard entry %s' % (componentType,))
522 self.clear_msg('wizard-bundle')
523 d = self._adminModel.callRemote(
524 'getEntryByType', componentType, 'wizard')
525 d.addCallback(self._gotEntryPoint)
526 return d
527
528 - def getWizardPlugEntry(self, plugType):
529 """Fetches a wizard bundle from a specific kind of plug
530 @param plugType: the plug type to get the wizard entry
531 bundle from.
532 @type plugType: string
533 @returns: a deferred returning either::
534 - factory of the plug
535 - noBundle error: if the plug lacks a wizard bundle
536 @rtype: L{twisted.internet.defer.Deferred}
537 """
538 self.waitForTask('get wizard plug %s' % (plugType,))
539 self.clear_msg('wizard-bundle')
540 d = self._adminModel.callRemote(
541 'getPlugEntry', plugType, 'wizard')
542 d.addCallback(self._gotEntryPoint)
543 return d
544
546 """Queries the manager for a list of wizard entries matching the
547 query.
548 @param wizardTypes: list of component types to fetch, is usually
549 something like ['video-producer'] or
550 ['audio-encoder']
551 @type wizardTypes: list of str
552 @param provides: formats provided, eg ['jpeg', 'speex']
553 @type provides: list of str
554 @param accepts: formats accepted, eg ['theora']
555 @type accepts: list of str
556
557 @returns: a deferred firing a list
558 of L{flumotion.common.componentui.WizardEntryState}
559 @rtype: L{twisted.internet.defer.Deferred}
560 """
561 self.debug('querying wizard entries (wizardTypes=%r,provides=%r'
562 ',accepts=%r)'% (wizardTypes, provides, accepts))
563 return self._adminModel.getWizardEntries(wizardTypes=wizardTypes,
564 provides=provides,
565 accepts=accepts)
566
568 """Tells the wizard about the existing components available, so
569 we can resolve naming conflicts when saving the configuration
570 @param componentNames: existing component names
571 @type componentNames: list of strings
572 """
573 self._existingComponentNames = componentNames
574 if self._scenario is not None:
575 self._scenario.setExistingComponentNames(componentNames)
576
578 """Tell a step that its worker changed.
579 @param step: step which worker changed for
580 @type step: a L{WorkerWizardStep} subclass
581 @param workerName: name of the worker
582 @type workerName: string
583 """
584 if self._stepWorkers.get(step) == workerName:
585 return
586
587 self.debug('calling %r.workerChanged' % step)
588 step.workerChanged(workerName)
589 self._stepWorkers[step] = workerName
590
591
592
593 - def _gotEntryPoint(self, (filename, procname)):
594
595
596 filename = filename.replace('/', os.path.sep)
597 modname = pathToModuleName(filename)
598 d = self._adminModel.getBundledFunction(modname, procname)
599 self.clear_msg('wizard-bundle')
600 self.taskFinished()
601 return d
602
607
608
609
611 self.debug('combobox_workerChanged, worker %r' % worker)
612 if worker:
613 self.clear_msg('worker-error')
614 self._lastWorker = worker
615 step = self.getCurrentStep()
616 if step and isinstance(step, WorkerWizardStep):
617 self._setupWorker(step, worker)
618 self.workerChangedForStep(step, worker)
619 else:
620 msg = messages.Error(T_(
621 N_('All workers have logged out.\n'
622 'Make sure your Flumotion network is running '
623 'properly and try again.')),
624 mid='worker-error')
625 self.add_msg(msg)
626