1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """HTTP wizard integration
23
24 This provides a step which you can chose:
25 - http port
26 - bandwidth/client limit
27 - mount point (eg, the url it will be accessed as)
28 - burst on connect
29 - cortado java applet
30
31 A component of type 'http-streamer' will always be created.
32 In addition, if you include the java applet, a 'porter' and
33 'http-server' will be included to share the port between the streamer
34 and the server and to serve an html file plus the java applet itself.
35 On the http-server the applet will be provided with help of a plug.
36 """
37
38 import gettext
39 import re
40 import os
41
42 import gobject
43 from kiwi.utils import gsignal
44 import gtk
45 from twisted.internet import defer
46 from zope.interface import implements
47
48 from flumotion.admin.assistant.interfaces import IConsumerPlugin
49 from flumotion.admin.assistant.models import Consumer, Porter
50 from flumotion.admin.gtk.basesteps import ConsumerStep
51 from flumotion.configure import configure
52 from flumotion.common import errors, log, messages
53 from flumotion.common.i18n import N_, gettexter, ngettext
54
55 __version__ = "$Rev$"
56 _ = gettext.gettext
57 T_ = gettexter()
58
59
61 """I am a model representing the configuration file for a
62 HTTP streamer component.
63 @ivar has_client_limit: If a client limit was set
64 @ivar client_limit: The client limit
65 @ivar has_bandwidth_limit: If a bandwidth limit was set
66 @ivar bandwidth_limit: The bandwidth limit
67 @ivar set_hostname: If a hostname was set
68 @ivar hostname: the hostname this will be streamed on
69 @ivar port: The port this server will be listening to
70 """
71 componentType = 'http-streamer'
72 requiresPorter = True
73 prefix = 'http'
74
90
91
92
101
103 """Fetch the hostname this stream will be published on
104 @returns: the hostname
105 """
106 return self.hostname
107
109 """
110 Sets the data from another model so we can reuse it.
111
112 @param model : model to get the data from
113 @type model : L{HTTPStreamer}
114 """
115 self.has_client_limit = model.has_client_limit
116 self.has_bandwidth_limit = model.has_bandwidth_limit
117 self.client_limit = model.client_limit
118 self.bandwidth_limit = model.bandwidth_limit
119 self.set_hostname = model.set_hostname
120 self.hostname = model.hostname
121 self.properties.burst_on_connect = model.properties.burst_on_connect
122 self.port = model.port
123
124
125
135
156
157
159 """I am a step of the configuration wizard which allows you
160 to configure a stream to be served over HTTP.
161 """
162 section = _('Consumption')
163 gladeFile = os.path.join(os.path.dirname(os.path.abspath(__file__)),
164 'wizard.glade')
165
169
171 """
172 There is a previous httpstreamer step from where the data can be copied
173 It will be copied to the actual model and the advanced
174 tab would be hidden.
175
176 @param model: The previous model we are going to copy.
177 @type model: L{HTTPStreamer}
178 """
179 self.model.setData(model)
180 self.expander.set_expanded(False)
181 self._proxy2.set_model(self.model)
182
183
184
187
189 for line in self.plugarea.getEnabledLines():
190 yield line.getConsumer(self.model,
191 self.wizard.getScenario().getAudioProducer(self.wizard),
192 self.wizard.getScenario().getVideoProducer(self.wizard))
193
194
195
197 self.mount_point.data_type = str
198 self.bandwidth_limit.data_type = float
199 self.burst_on_connect.data_type = bool
200 self.client_limit.data_type = int
201 self.port.data_type = int
202 self.hostname.data_type = str
203
204 self.model.properties.mount_point = self._getDefaultMountPath()
205 self._proxy1 = self.add_proxy(self.model.properties,
206 ['mount_point', 'burst_on_connect'])
207 self._proxy2 = self.add_proxy(
208 self.model, ['has_client_limit',
209 'has_bandwidth_limit',
210 'client_limit',
211 'bandwidth_limit',
212 'set_hostname',
213 'hostname',
214 'port'])
215
216 self.client_limit.set_sensitive(self.model.has_client_limit)
217 self.bandwidth_limit.set_sensitive(self.model.has_bandwidth_limit)
218 self.hostname.set_sensitive(self.model.set_hostname)
219
220 self.port.connect('changed', self.on_port_changed)
221 self.mount_point.connect('changed', self.on_mount_point_changed)
222
228
235 d = defer.maybeDeferred(ConsumerStep.getNext, self)
236 d.addCallback(setModel)
237 return d
238
239
240
242 encodingStep = self.wizard.getStep('Encoding')
243 return '/%s-%s/' % (str(encodingStep.getMuxerFormat()),
244 self.getConsumerType(), )
245
247
248
249
250
251 mountPoint = mountPoint.rstrip('/')
252
253 pattern = re.compile('(\d*$)')
254 match = pattern.search(mountPoint)
255 trailingDigit = match.group()
256
257
258
259
260 if trailingDigit:
261 digit = int(trailingDigit) + 1
262 mountPoint = mountPoint[:-len(trailingDigit)]
263
264
265
266 else:
267 digit = 2
268 return mountPoint + str(digit) + '/'
269
271 if not canPopulate:
272 return
273
274 self.plugarea.clean()
275
276 def gotEntries(entries):
277 log.debug('httpwizard', 'got %r' % (entries, ))
278 for entry in entries:
279 if not self._canAddPlug(entry):
280 continue
281
282 def response(factory, entry):
283
284 plugin = factory(self.wizard)
285 if hasattr(plugin, 'workerChanged'):
286 d = plugin.workerChanged(self.worker)
287
288 def cb(found, plugin, entry):
289 self._addPlug(plugin.getPlugWizard(
290 N_(entry.description)), found)
291 d.addCallback(cb, plugin, entry)
292 else:
293 self._addPlug(plugin.getPlugWizard(
294 N_(entry.description)), True)
295 d = self.wizard.getWizardPlugEntry(entry.componentType)
296 d.addCallback(response, entry)
297
298 d = self.wizard.getWizardEntries(wizardTypes=['http-consumer'])
299 d.addCallbacks(gotEntries)
300
302
303
304 muxerTypes = []
305 audioTypes = []
306 videoTypes = []
307 for mediaType in entry.getAcceptedMediaTypes():
308 kind, name = mediaType.split(':', 1)
309 if kind == 'muxer':
310 muxerTypes.append(name)
311 elif kind == 'video':
312 videoTypes.append(name)
313 elif kind == 'audio':
314 audioTypes.append(name)
315 else:
316 raise AssertionError
317
318 encoding_step = self.wizard.getStep('Encoding')
319 if encoding_step.getMuxerFormat() not in muxerTypes:
320 return False
321
322 audioFormat = encoding_step.getAudioFormat()
323 videoFormat = encoding_step.getVideoFormat()
324 if ((audioFormat and audioFormat not in audioTypes) or
325 (videoFormat and videoFormat not in videoTypes)):
326 return False
327
328 return True
329
331 plugin.setEnabled(enabled)
332 self.plugarea.addLine(plugin)
333
341
342 def gotHostname(hostname):
343 self.model.hostname = hostname
344 self._proxy2.update('hostname')
345 self.wizard.taskFinished()
346 return True
347
348 def getHostname(result):
349 if not result:
350 return False
351
352 d = self.wizard.runInWorker(
353 self.worker, 'flumotion.worker.checks.http',
354 'runHTTPStreamerChecks')
355 d.addCallback(gotHostname)
356 d.addErrback(hostnameErrback)
357 return d
358
359 def checkImport(elements):
360 if elements:
361 self.wizard.taskFinished(blockNext=True)
362 return False
363
364 d = self.wizard.requireImport(
365 self.worker, 'twisted.web', projectName='Twisted project',
366 projectURL='http://www.twistedmatrix.com/')
367 d.addCallback(getHostname)
368 return d
369
370
371 d = self.wizard.requireElements(self.worker, 'multifdsink')
372 d.addCallback(checkImport)
373 return d
374
375 - def _checkMountPoint(self, port=None, worker=None,
376 mount_point=None, need_fix=False):
377 """
378 Checks whether the provided mount point is available with the
379 current configuration (port, worker). It can provide a valid
380 mountpoint if it is required with need_fix=True.
381
382 @param port : The port the streamer is going to be listening.
383 @type port : int
384 @param worker : The worker the streamer will be running.
385 @type worker : str
386 @param mount_point : The desired mount point.
387 @type mount_point : str
388 @param need_fix : Whether the method should search for a valid
389 mount_point if the provided one is not.
390 @type need_fix : bool
391
392 @returns : True if the mount_point can be used, False if it is in use.
393 @rtype : bool
394 """
395 self.wizard.clear_msg('http-streamer-mountpoint')
396
397 port = port or self.model.port
398 worker = worker or self.model.worker
399 mount_point = mount_point or self.model.properties.mount_point
400
401 self.wizard.waitForTask('http-streamer-mountpoint')
402
403 if self.wizard.addMountPoint(worker, port, mount_point,
404 self.getConsumerType()):
405 self.wizard.taskFinished()
406 return True
407 else:
408 if need_fix:
409 while not self.wizard.addMountPoint(worker, port,
410 mount_point,
411 self.getConsumerType()):
412 mount_point=self._suggestMountPoint(mount_point)
413
414 self.model.properties.mount_point = mount_point
415 self._proxy1.update('mount_point')
416 self.wizard.taskFinished()
417 return True
418
419 message = messages.Error(T_(N_(
420 "The mount point %s is already being used for worker %s and "
421 "port %s. Please correct this to be able to go forward."),
422 mount_point, worker, port))
423 message.id = 'http-streamer-mountpoint'
424 self.wizard.add_msg(message)
425 self.wizard.taskFinished(True)
426 return False
427
428
429
431 if not entry.get_text():
432 self.wizard.clear_msg('http-streamer-mountpoint')
433 message = messages.Error(T_(N_(
434 "Mountpoint cannot be left empty.\n"
435 "Fill the text field with a correct mount point to"
436 "be able to go forward.")))
437 message.id = 'http-streamer-mountpoint'
438 self.wizard.add_msg(message)
439 self.wizard.blockNext(True)
440 else:
441 self._checkMountPoint(mount_point=entry.get_text())
442
444 self.client_limit.set_sensitive(cb.get_active())
445
447 self.bandwidth_limit.set_sensitive(cb.get_active())
448
450 self.hostname.set_sensitive(cb.get_active())
451
455
456
469
470
483
484
497
498
500 name = 'HTTPStreamerGeneric'
501 title = _('HTTP Streamer (Generic)')
502 sidebarName = _('HTTP Generic')
503 docSection = 'help-configuration-assistant-http-streaming-generic'
504 docAnchor = ''
505 docVersion = 'local'
506
510
511
512
514 return self._consumertype
515
516
532