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