sebastien - in flumotion/branches/transcoder-1: . flumotion/admin
flumotion/admin/command flumotion/common flumotion/component
flumotion/component/misc/httpfile flumotion/component/producers
flumotion/component/producers/playlist flumotion/extern
flumotion/manager flumotion/test flumotion/twisted
flumotion/wizard flumotion/worker
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Wed May 9 16:46:03 CEST 2007
Author: sebastien
Date: Wed May 9 16:45:51 2007
New Revision: 4911
Added:
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/Makefile.am
- copied unchanged from r4904, flumotion/trunk/flumotion/component/producers/playlist/Makefile.am
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/__init__.py
- copied unchanged from r4904, flumotion/trunk/flumotion/component/producers/playlist/__init__.py
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlist.py
- copied, changed from r4904, flumotion/trunk/flumotion/component/producers/playlist/playlist.py
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlist.xml
- copied, changed from r4904, flumotion/trunk/flumotion/component/producers/playlist/playlist.xml
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlistparser.py
- copied, changed from r4904, flumotion/trunk/flumotion/component/producers/playlist/playlistparser.py
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/singledecodebin.py
- copied unchanged from r4904, flumotion/trunk/flumotion/component/producers/playlist/singledecodebin.py
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/smartscale.py
- copied unchanged from r4904, flumotion/trunk/flumotion/component/producers/playlist/smartscale.py
flumotion/branches/transcoder-1/flumotion/test/test_component_httpserver.py
- copied unchanged from r4904, flumotion/trunk/flumotion/test/test_component_httpserver.py
Modified:
flumotion/branches/transcoder-1/ChangeLog
flumotion/branches/transcoder-1/TODO
flumotion/branches/transcoder-1/configure.ac
flumotion/branches/transcoder-1/flumotion/admin/command/commands.py
flumotion/branches/transcoder-1/flumotion/admin/multi.py
flumotion/branches/transcoder-1/flumotion/common/common.py
flumotion/branches/transcoder-1/flumotion/common/config.py
flumotion/branches/transcoder-1/flumotion/component/feedcomponent.py
flumotion/branches/transcoder-1/flumotion/component/feedcomponent010.py
flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/file.py
flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/httpfile.py
flumotion/branches/transcoder-1/flumotion/component/producers/Makefile.am
flumotion/branches/transcoder-1/flumotion/component/producers/playlist/ (props changed)
flumotion/branches/transcoder-1/flumotion/extern/Makefile.am
flumotion/branches/transcoder-1/flumotion/manager/manager.py
flumotion/branches/transcoder-1/flumotion/test/Makefile.am
flumotion/branches/transcoder-1/flumotion/test/test.xml
flumotion/branches/transcoder-1/flumotion/test/test_component.py
flumotion/branches/transcoder-1/flumotion/test/test_component_httpstreamer.py
flumotion/branches/transcoder-1/flumotion/test/test_config.py
flumotion/branches/transcoder-1/flumotion/test/test_misc_httpfile.py
flumotion/branches/transcoder-1/flumotion/twisted/fdserver.py
flumotion/branches/transcoder-1/flumotion/wizard/save.py
flumotion/branches/transcoder-1/flumotion/wizard/steps.py
flumotion/branches/transcoder-1/flumotion/worker/job.py
flumotion/branches/transcoder-1/flumotion/worker/medium.py
flumotion/branches/transcoder-1/flumotion/worker/worker.py
Log:
2007-05-09 Sebastien Merle <sebastien at fluendo.com>
* Merged flumotion trunk changesets up to [4910].
Modified: flumotion/branches/transcoder-1/ChangeLog
==============================================================================
--- flumotion/branches/transcoder-1/ChangeLog (original)
+++ flumotion/branches/transcoder-1/ChangeLog Wed May 9 16:45:51 2007
@@ -1,5 +1,9 @@
2007-05-09 Sebastien Merle <sebastien at fluendo.com>
+ * Merged flumotion trunk changesets up to [4910].
+
+2007-05-09 Sebastien Merle <sebastien at fluendo.com>
+
* Merged flumotion trunk changesets from [4853] to [4867].
2007-05-09 Sebastien Merle <sebastien at fluendo.com>
@@ -34,8 +38,225 @@
be jellyed and unjellyed. Now an enum value can be directly
used with spread without converting to int.
+2007-05-09 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/playlist.py:
+ * flumotion/component/producers/playlist/playlist.xml:
+ * flumotion/component/producers/playlist/playlistparser.py:
+ Allow audio-only or video-only compositions to be configured.
+
+2007-05-09 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/playlistparser.py:
+ Remove unused local to satisfy pychecker.
+
+2007-05-09 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/playlistparser.py:
+ Use the discoverer on files before adding them.
+
+2007-05-09 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
+
+ * flumotion/common/config.py (FlumotionConfigXML.parseFeed,
+ FlumotionConfigXML.addFeed):
+ Used table parser to parse subnodes of eater. This guarantees
+ that only feed nodes are children of the eater node.
+
+2007-05-09 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
+
+ * flumotion/wizard/steps.py (Soundcard.update_inputs):
+ Commit forgotten when moving audio checks to audio.py.
+
+2007-05-09 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
+
+ * flumotion/component/feedcomponent.py
+ (MultiInputParseLaunchComponent.get_pipeline_string):
+ * flumotion/component/feedcomponent010.py (FeedComponent.init,
+ FeedComponent.do_setup, FeedComponent.parseEaterConfig):
+ Use the eater key not the source key for building up the
+ eaters list. Build a mapping for feedId -> name of eater so
+ we can quickly look it up in elements needing to find the eater
+ name that a feedId (hence eater's element name) corresponds to.
+ * flumotion/test/test_component.py (PipelineTest.__init__,
+ PipelineTest.config):
+ * flumotion/test/test_component_httpstreamer.py
+ (TestOldProperties.setUp):
+ Fix up tests to use eater key not source key.
+ * flumotion/wizard/save.py (Component.toXML):
+ Output non-deprecated config form for wizard.
+
+2007-05-09 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/Makefile.am:
+ Add playlistparser.py to Makefile.am
+
+2007-05-09 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/admin/command/commands.py:
+ Allow flumotion-command invoke arguments to include the contents of
+ a file as a string argument using the 'F' format character.
+
+2007-05-09 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/playlist.py:
+ Fix remote method to use proper variable.
+
+ * flumotion/component/producers/playlist/playlistparser.py:
+ When truncating an existing gnlsource duration, also change the
+ media-duration, to avoid unwanted speedups.
+
+2007-05-09 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ * flumotion/component/misc/httpfile/file.py (loadMimeTypes):
+ Add .flv mime type in the code.
+
+2007-05-09 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
+
+ * TODO:
+ * flumotion/common/config.py (FlumotionConfigXML._parseEaters):
+ Thanks Andy, using the helper method parseAttributes.
+
+2007-05-08 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
+
+ * flumotion/test/test.xml:
+ Update xml flow used in tests to use new way of configuring
+ eaters.
+
+2007-05-08 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
+
+ * flumotion/common/config.py (FlumotionConfigXML._parseComponent,
+ FlumotionConfigXML._parseEaters, FlumotionConfigXML._parseSources):
+ * flumotion/test/test_config.py
+ (TestConfig.testParseComponentsWithEaters,
+ TestConfig.testParseComponentsWithEatersNotSpecified,
+ TestConfig.testParseComponentsWithEatersDeprecatedWay,
+ TestConfig.testParseComponentsWithTwoEaters,
+ TestConfig.testParseComponentsWithTwoEatersDeprecatedWay,
+ TestConfig.testParseComponentsWithMultipleEater,
+ TestConfig.testParseComponentsWithMultipleEaterDeprecatedWay,
+ TestConfig.testGetComponentEntriesWrong):
+ Add new way of configuring eaters in the XML along with tests.
+ Fixes #200.
+
+2007-05-08 Michael Smith <msmith at fluendo.com>
+
+ * configure.ac:
+ * flumotion/component/feedcomponent.py:
+ * flumotion/component/feedcomponent010.py:
+ * flumotion/component/producers/Makefile.am:
+ * flumotion/component/producers/playlist/Makefile.am:
+ * flumotion/component/producers/playlist/__init__.py:
+ * flumotion/component/producers/playlist/playlist.py:
+ * flumotion/component/producers/playlist/playlist.xml:
+ * flumotion/component/producers/playlist/playlistparser.py:
+ * flumotion/component/producers/playlist/singledecodebin.py:
+ * flumotion/component/producers/playlist/smartscale.py:
+ Merge playlist component back to trunk now that the basics are
+ functional.
+
+2007-05-08 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ * flumotion/component/misc/httpfile/file.py (File, File.__init__,
+ File.getChild, File.renderAuthenticated, MimedFileFactory,
+ MimedFileFactory.__init__, MimedFileFactory.create, FLVFile,
+ FLVFile.renderAuthenticated):
+ Add a subclass for handling FLV files.
+ Implement handling of the start= GET parameter, just like in
+ Apache and lighthttpd. Fixes #618.
+ Add a MimedFileFactory that allows us to create even the root
+ resource as a mime-type-dependent subclass.
+ * flumotion/component/misc/httpfile/httpfile.py (HTTPFileStreamer.init,
+ HTTPFileStreamer.do_start):
+ * flumotion/test/test_misc_httpfile.py (TestDirectory,
+ TestDirectory.setUp, TestDirectory.tearDown,
+ TestDirectory.testGetChild, TestDirectory.testFLV,
+ TestDirectory.finish, TestDirectory.testFLVStart,
+ TestDirectory.finish, TestDirectory.testFLVStartZero,
+ TestDirectory.finish):
+ Add tests for this.
+
+2007-05-07 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ * flumotion/component/misc/httpfile/file.py (File.getChild):
+ * flumotion/component/misc/httpfile/httpfile.py
+ (HTTPFileStreamer.do_setup, HTTPFileStreamer.do_start):
+ We still need to distinguish between the root resource being the
+ File resource directly, and a tree. Apparently client.getPage()
+ in the tests does not mimic correctly what something like wget would
+ do, making it hard to test for the Root resource tree being set up
+ correctly.
+
+2007-05-07 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ * flumotion/component/misc/httpfile/httpfile.py (HTTPFileStreamer.init,
+ HTTPFileStreamer.getDescription, HTTPFileStreamer.do_setup,
+ HTTPFileStreamer.do_stop, HTTPFileStreamer.do_start,
+ HTTPFileStreamer.requestFinished, HTTPFileStreamer.getStreamData):
+ Rearrange imports.
+ Privatize some variables.
+ Store some variables for resources we need to clean up.
+ Set self.port from what we're actually listening to, so we can
+ listen to port 0 then figure out which port it chose.
+ Make sure we can work without any loggers.
+ Fix handling of empty and / mount points, as well as getting
+ empty or / resources - fixes #567
+ * flumotion/test/Makefile.am:
+ * flumotion/test/test_component_httpserver.py (MountTest,
+ MountTest.setUp, MountTest.tearDown, MountTest.start,
+ MountTest.getURL, MountTest.testDirMountEmpty,
+ MountTest.testDirMountRoot, MountTest.testDirMountOnDemand,
+ MountTest.testFileMountEmpty, MountTest.testFileMountOnDemand):
+ Add tests for http-server component with various mount points
+ and serving either a directory or a file.
+ * flumotion/component/misc/httpfile/file.py (File.getChild,
+ File.renderAuthenticated):
+ Restat the file on every request, to make sure we handle changed
+ lengths of files.
+ Handle requests ending with /
+
+2007-05-07 Andy Wingo <wingo at pobox.com>
+
+ * flumotion/worker/medium.py (WorkerMedium.remote_killJob): New
+ remote method, kills a job by avatarId, defaulting to SIGKILL.
+ Callable via flumotion-command invoke sss workerCallRemote
+ WORKERNAME killJob AVATARID. Fixes #499.
+
+ * flumotion/worker/worker.py (WorkerBrain.killJob): New function,
+ proxies to JobHeaven.killJob.
+
+ * flumotion/worker/job.py (JobHeaven.killJob): New function, kills
+ a job by avatarId.
+ (JobHeaven.kill): Use killJob.
+
+ * flumotion/common/common.py (signalPid): New function.
+ (termPid, killPid, checkPidRunning): Use signalPid.
+
+ * flumotion/manager/manager.py (Vishnu.__init__): Remove a FIXME
+ made irrelevant by [3299].
+
+ * flumotion/twisted/fdserver.py (PassableServerPort.transport):
+ Remove the passable client things, they are in feed.py now.
+
+ * flumotion/extern/Makefile.am (clean-local): rm -rf _trial_temp,
+ not -r.
+
2007-05-02 Andy Wingo <wingo at pobox.com>
+ * flumotion/worker/worker.py (WorkerBrain.getFeedServerPort):
+ Whoops, fix the case when we don't listen with a feedserver.
+
+ * flumotion/admin/multi.py (MultiAdminModel.__init__): Use a
+ StartSet instead of our own ghetto homebrew.
+ (MultiAdminModel.addManager): API change: take a PBConnectionInfo.
+ Use the StartSet to arbitrate making only one connection per
+ managerId. Returns a deferred that will fire with the admin, or
+ error on error.
+ (MultiAdminModel.removeManager): API change: renamed from
+ close_admin. Uses the start set to cancel any connection, existing
+ or in progress.
+
+ * flumotion/common/errors.py (AlreadyConnectingError): New error.
+
* flumotion/admin/gtk/main.py (startAdminFromGreeter.failed):
* flumotion/admin/gtk/dialogs.py (connection_failed_message):
* flumotion/admin/gtk/client.py
Modified: flumotion/branches/transcoder-1/TODO
==============================================================================
--- flumotion/branches/transcoder-1/TODO (original)
+++ flumotion/branches/transcoder-1/TODO Wed May 9 16:45:51 2007
@@ -12,7 +12,7 @@
* MEDIUM add api for admins to "subscribe" to admin methods in the
manager, so only relevant commands get sent
-* EASY deprecate <source> tag and require base= attribute to <comp>
+* EASY require base= attribute to <comp>
* EASY remove all old deprecated bundling code everywhere
Modified: flumotion/branches/transcoder-1/configure.ac
==============================================================================
--- flumotion/branches/transcoder-1/configure.ac (original)
+++ flumotion/branches/transcoder-1/configure.ac Wed May 9 16:45:51 2007
@@ -195,6 +195,7 @@
flumotion/component/producers/icecast/Makefile
flumotion/component/producers/ivtv/Makefile
flumotion/component/producers/pipeline/Makefile
+flumotion/component/producers/playlist/Makefile
flumotion/component/producers/rtsp/Makefile
flumotion/component/producers/soundcard/Makefile
flumotion/component/producers/videotest/Makefile
Modified: flumotion/branches/transcoder-1/flumotion/admin/command/commands.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/admin/command/commands.py (original)
+++ flumotion/branches/transcoder-1/flumotion/admin/command/commands.py Wed May 9 16:45:51 2007
@@ -169,11 +169,21 @@
pass
def _parse_typed_args(spec, args):
+ def _readFile(filename):
+ try:
+ f = open(filename)
+ contents = f.read()
+ f.close()
+ return contents
+ except:
+ raise ParseException("Failed to read file %s" % (filename,))
+
def _do_parse_typed_args(spec, args):
accum = []
while spec:
argtype = spec.pop(0)
- parsers = {'i': int, 's': str, 'b': common.strToBool}
+ parsers = {'i': int, 's': str, 'b': common.strToBool,
+ 'F': _readFile}
if argtype == ')':
return tuple(accum)
elif argtype == '(':
Modified: flumotion/branches/transcoder-1/flumotion/admin/multi.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/admin/multi.py (original)
+++ flumotion/branches/transcoder-1/flumotion/admin/multi.py Wed May 9 16:45:51 2007
@@ -20,8 +20,10 @@
# Headers in this file shall remain intact.
+from twisted.internet import defer
+
from flumotion.twisted import pb as fpb
-from flumotion.common import log, planet, connection, errors
+from flumotion.common import log, planet, connection, errors, startset
from flumotion.admin import admin
@@ -75,7 +77,9 @@
self.admins = WatchedDict() # {managerId: AdminModel}
# private
self.listeners = []
- self._pending = {}
+ self._startSet = startset.StartSet(self.admins.has_key,
+ errors.AlreadyConnectingError,
+ errors.AlreadyConnectedError)
# Listener implementation
def emit(self, signal_name, *args, **kwargs):
@@ -94,28 +98,9 @@
assert not obj in self.listeners
self.listeners.append(obj)
- def _pushPendingConnection(self, admin):
- assert admin.managerId not in self._pending
- self._pending[admin.managerId] = admin
-
- def _popPendingConnection(self, admin):
- if admin.managerId in self._pending:
- self._pending.pop(admin.managerId)
- else:
- self.warning('tried to pop nonpending manager %r; please '
- 'report bug', admin.managerId)
-
- # FIXME: should take a connectioninfo rather than separate args
- # Public
- def addManager(self, host, port, use_insecure, authenticator,
- tenacious=False):
+ def addManager(self, connectionInfo, tenacious=False):
def connected_cb(admin):
- planet = admin.planet
- self.info('Connected to manager %s (planet %s)',
- admin.managerId, planet.get('name'))
- self._popPendingConnection(admin)
- self.admins[admin.managerId] = admin
- self.emit('addPlanet', admin, planet)
+ self._startSet.avatarStarted(managerId)
def disconnected_cb(admin):
self.info('Disconnected from manager')
@@ -124,46 +109,90 @@
del self.admins[admin.managerId]
else:
self.warning('Could not find admin model %r', admin)
+ if self._startSet.shutdownRegistered(managerId):
+ self._startSet.shutdownSuccess(managerId)
def connection_refused_cb(admin):
+ msg = 'Connection to %s:%d refused.' % (i.host, i.port)
+ self.info('%s', msg)
if not tenacious:
- self._popPendingConnection(admin)
- self.info('Connection to %s:%d refused.', host, port)
+ self._startSet.avatarStopped(managerId,
+ errors.ConnectionRefusedError(msg))
def connection_failed_cb(admin, string):
+ msg = 'Connection to %s:%d failed: %s' % (i.host, i.port,
+ string)
+ self.info('%s', msg)
if not tenacious:
- self._popPendingConnection(admin)
- self.info('Connection to %s:%d failed: %s', host, port,
- string)
+ self._startSet.avatarStopped(managerId,
+ lambda _: errors.ConnectionFailedError(msg))
def connection_error_cb(admin, obj):
+ msg = 'Error connecting to %s:%d: %r' % (i.host, i.port,
+ obj)
+ self.warning('%s', msg)
if not tenacious:
- self._popPendingConnection(admin)
- self.warning('Error connecting to %s:%d: %r', host, port, obj)
+ self._startSet.avatarStopped(managerId,
+ lambda _: errors.ConnectionFailedError(msg))
- info = connection.PBConnectionInfo(host, port, not use_insecure,
- authenticator)
+ i = connectionInfo
+ managerId = str(i)
- # a bit of a hack: we know that str(info) is the managerId.
- if str(info) in self.admins or str(info) in self._pending:
- raise errors.AlreadyConnectedError('Already connected to %s'
- % info)
+ # can raise errors.AlreadyConnectingError or
+ # errors.AlreadyConnectedError
+ try:
+ d = self._startSet.createStart(managerId)
+ except Exception, e:
+ return defer.fail(e)
a = admin.AdminModel()
- a.connectToManager(info, tenacious)
- self._pushPendingConnection(a)
+ a.connectToManager(i, tenacious)
+ assert a.managerId == managerId
+
a.connect('connected', connected_cb)
a.connect('disconnected', disconnected_cb)
a.connect('connection-refused', connection_refused_cb)
a.connect('connection-failed', connection_failed_cb)
a.connect('connection-error', connection_error_cb)
- return a
- def close_admin(self, admin):
- if admin.managerId not in self.admins:
- self.debug('removing admin %s from pending', admin.managerId)
- self._popPendingConnection(admin)
- admin.shutdown()
+ # the admin should offer a decent deferred-connect interface;
+ # instead here we conflate the startset and the
+ # signal->deferred adaptations in one function
+
+ def emit_add_planet(_):
+ planet = a.planet
+ self.info('Connected to manager %s (planet %s)',
+ a.managerId, planet.get('name'))
+ self.admins[a.managerId] = a
+ self.emit('addPlanet', a, planet)
+ return a
+
+ def disconnect_on_error(failure):
+ a.shutdown()
+ return failure
+
+ d.addCallbacks(emit_add_planet, disconnect_on_error)
+
+ return d
+
+ def removeManager(self, managerId):
+ self.info('disconnecting from %s', managerId)
+ if managerId in self.admins:
+ self.admins[managerId].shutdown()
+ return self._startSet.shutdownStart(managerId)
+ elif self._startSet.createRegistered(managerId):
+ # this admin has not yet connected; let us assume that in
+ # this window, it will not connect. Firing this makes the
+ # admin shutdown, see disconnect_on_error above.
+ self._startSet.shutdownSuccess(admin.managerId)
+ return defer.succeed(managerId)
+ elif self._startSet.shutdownRegistered(managerId):
+ # some caller is overzealous?
+ return self._startSet.shutdownStart(managerId)
+ else:
+ self.warning('told to remove an unknown manager: %s',
+ managerId)
+ return defer.succeed(managerId)
def for_each_component(self, object, proc):
'''Call a procedure on each component that is a child of OBJECT'''
Modified: flumotion/branches/transcoder-1/flumotion/common/common.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/common/common.py (original)
+++ flumotion/branches/transcoder-1/flumotion/common/common.py Wed May 9 16:45:51 2007
@@ -393,14 +393,14 @@
return int(pid)
-def termPid(pid):
+def signalPid(pid, signum):
"""
- Send the given process a TERM signal.
+ Send the given process a signal.
@returns: whether or not the process with the given pid was running
"""
try:
- os.kill(pid, signal.SIGTERM)
+ os.kill(pid, signum)
return True
except OSError, e:
if not e.errno == errno.ESRCH:
@@ -408,20 +408,21 @@
raise
return False
+def termPid(pid):
+ """
+ Send the given process a TERM signal.
+
+ @returns: whether or not the process with the given pid was running
+ """
+ return signalPid(pid, signal.SIGTERM)
+
def killPid(pid):
"""
Send the given process a KILL signal.
@returns: whether or not the process with the given pid was running
"""
- try:
- os.kill(pid, signal.SIGKILL)
- return True
- except OSError, e:
- if not e.errno == errno.ESRCH:
- # FIXME: unhandled error, maybe give some better info ?
- raise
- return False
+ return signalPid(pid, signal.SIGKILL)
def checkPidRunning(pid):
"""
@@ -429,13 +430,7 @@
@returns: whether or not a process with that pid is active.
"""
- try:
- os.kill(pid, 0)
- return True
- except OSError, e:
- if e.errno is not errno.ESRCH:
- raise
- return False
+ return signalPid(pid, 0)
def waitPidFile(type, name=None):
"""
Modified: flumotion/branches/transcoder-1/flumotion/common/config.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/common/config.py (original)
+++ flumotion/branches/transcoder-1/flumotion/common/config.py Wed May 9 16:45:51 2007
@@ -62,7 +62,7 @@
def _buildComponentConfig(parentName, componentType, componentName,
componentProperties, componentPlugs=None,
- sources=None, isClockMaster=None,
+ eaters=None, isClockMaster=None,
flumotionVersion=None):
"""
Build a componenet configuration dictionary.
@@ -91,25 +91,29 @@
# let the component know what its feeds should be called
config['feed'] = defs.getFeeders()
- eaters = dict([(x.getName(), x) for x in defs.getEaters()])
- # at this point we don't support assigning certain sources to
- # certain eaters -- a problem to fix later. for now take the
- # union of the properties.
- required = [x for x in eaters.values() if x.getRequired()]
- multiple = [x for x in eaters.values() if x.getMultiple()]
-
- if sources == None:
- sources = list()
- if len(sources) == 0 and required:
- raise ConfigError("Component %s wants to eat on %s, but no "
- "source specified"
- % (componentName, eaters.keys()[0]))
- elif len(sources) > 1 and not multiple:
- raise ConfigError("Component %s does not support multiple "
- "sources feeding %s (%r)"
- % (componentName, eaters.keys()[0], sources))
- if sources:
- config['source'] = sources
+ eatersDef = dict([(x.getName(), x) for x in defs.getEaters()])
+
+ if eaters == None:
+ eaters = dict()
+
+ for e in eatersDef:
+ if eatersDef[e].getRequired() and not (e in eaters):
+ raise ConfigError("Component %s wants to eat on %s, but no "
+ "eater with name: %s specified." % (
+ componentName, e, e))
+ if not eatersDef[e].getMultiple() and (e in eaters):
+ if len(eaters[e]) > 1:
+ raise ConfigError("Component %s does not support multiple "
+ "sources feeding %s (%r)"
+ % (componenetName, e, eaters[e]))
+
+ if eaters:
+ config['eater'] = eaters
+ sources = []
+ for e in eaters:
+ sources.extend(eaters[e])
+ if sources:
+ config['source'] = sources
sockets = defs.getSockets()
if componentPlugs == None:
@@ -118,11 +122,11 @@
for socket in sockets:
plugs[socket] = []
for plug in componentPlugs:
- if not plug.socket in sockets:
+ if not plug['socket'] in sockets:
raise ConfigError("Component %s does not support "
"sockets of type %s"
% (componentName, plug['socket']))
- plugs[plug.socket].append(plug)
+ plugs[plug['socket']].append(plug)
config['plugs'] = plugs
properties = dict()
@@ -135,8 +139,10 @@
raise ConfigError("%s: unknown type '%s' for property %s"
% (componentName, definition.type, name))
checker = _property_checkers.get(definition.type)
- if value and (isinstance(value, tuple) or isinstance(value, list)):
- if not definition.multiple and len(nodes) > 1:
+ #Don't check for tuple because fraction are tuple but are values
+ #if value and (isinstance(value, tuple) or isinstance(value, list)):
+ if value and isinstance(value, list):
+ if not definition.multiple and len(value) > 1:
raise ConfigError("%s: multiple value specified but not "
"allowed for property %s"
% (componentName, name))
@@ -161,11 +167,11 @@
def buildComponentConfig(parentName, componentType, componentName,
componentProperties, componentPlugs=None,
- workerName=None, sources=None, isClockMaster=None,
+ workerName=None, eaters=None, isClockMaster=None,
flumotionVersion=None):
config = _buildComponentConfig(parentName, componentType, componentName,
componentProperties, componentPlugs,
- sources, isClockMaster, flumotionVersion)
+ eaters, isClockMaster, flumotionVersion)
defs = registry.getRegistry().getComponent(componentType)
return ConfigEntryComponent(componentName, parentName, componentType,
config, defs, workerName)
@@ -514,7 +520,7 @@
ret[component.name] = component
return ret
-
+
def _parseComponent(self, node, parent, forManager=False):
"""
Parse a <component></component> block.
@@ -522,10 +528,13 @@
@rtype: L{ConfigEntryComponent}
"""
# <component name="..." type="..." worker="...">
- # <source>*
+ # <source>...</source>* DEPRECATED
+ # <eater name="...">
+ # <feed>...</feed>
+ # </eater>*
# <property name="name">value</property>*
# </component>
-
+
if not node.hasAttribute('name'):
raise ConfigError("<component> must have a name attribute")
if not node.hasAttribute('type'):
@@ -565,9 +574,9 @@
except KeyError:
raise errors.UnknownComponentError(
"unknown component type: %s" % type)
-
+
possible_node_names = ['source', 'clock-master', 'property',
- 'plugs']
+ 'plugs', 'eater']
for subnode in node.childNodes:
if subnode.nodeType == Node.COMMENT_NODE:
continue
@@ -579,10 +588,19 @@
raise ConfigError("Invalid subnode of <component>: %s"
% subnode.nodeName)
- sources = self._parseSources(node, defs)
+ eaters = self._parseEaters(node, defs)
+ if not eaters:
+ sources = self._parseSources(node, defs)
+ if sources:
+ # assign sources up to first (and only eater)
+ # if more than one eater, parseSources would have raised
+ # a ConfigError
+ eaters = {defs.getEaters()[0].getName():sources}
+
isClockMaster = self._parseClockMaster(node)
+
plugs = []
- plugParsers = {'plug': (self.parsePlug, plugs.append)}
+ plugParser = {'plug': (self.parsePlug, plugs.append)}
for subnode in node.childNodes:
if subnode.nodeName == 'plugs':
self.parseFromTable(subnode, plugParser)
@@ -591,7 +609,7 @@
lambda msg: ConfigError('%s: %s' % (name, str)))
config = _buildComponentConfig(parent, type, name, properties, plugs,
- sources, isClockMaster, version)
+ eaters, isClockMaster, version)
# fixme: all of the information except the worker is in the
# config dict: why?
@@ -740,19 +758,65 @@
raise ConfigError("<%s> value not specified" % name)
return value
+ def _parseEaters(self, node, defs):
+ # <eater name="eater-name">
+ # <feed>feeding-component:feed-name</feed>*
+ # </eater>
+ eaters = dict([(x.getName(), x) for x in defs.getEaters()])
+
+ nodes = {}
+ hasSourceNodes = False
+ for subnode in node.childNodes:
+ if subnode.nodeName == 'eater':
+ name, = self.parseAttributes(subnode, ('name',))
+ if nodes.has_key(name):
+ raise ConfigError("Component %s should not have "
+ "multiple eater nodes configured with same name:"
+ " %s" % (node.nodeName, name))
+ feedNodes = []
+ def parseFeed(feedNode):
+ # <feed>feeding-component:feed-name</feed>
+ return self.get_string_values([feedNode])
+ def addFeed(feedNode):
+ feedNodes.extend(feedNode)
+ self.parseFromTable(subnode, {'feed': (parseFeed, addFeed)})
+ nodes[name] = feedNodes
+ elif subnode.nodeName == 'source':
+ hasSourceNodes = True
+
+ # for backwards compatibility
+ if len(nodes) == 0 and hasSourceNodes:
+ return nodes
+
+ for e in eaters:
+ if eaters[e].getRequired() and not nodes.has_key(e):
+ raise ConfigError("Component %s wants to eat on %s, but no "
+ "eater with name: %s specified." % (
+ node.nodeName, e, e))
+ if not eaters[e].getMultiple() and nodes.has_key(e):
+ if len(nodes[e]) > 1:
+ raise ConfigError("Component %s does not support multiple "
+ "sources feeding %s (%r)"
+ % (node.nodeName, e, nodes[e]))
+ return nodes
+
def _parseSources(self, node, defs):
+ # deprecated in favour of eater tag
# <source>feeding-component:feed-name</source>
eaters = dict([(x.getName(), x) for x in defs.getEaters()])
+ if len(eaters) > 1:
+ raise ConfigError("Component %s has many eater names specified "
+ "in the registry, the <source> tag cannot be used for this "
+ "and is now deprecated. Use the <eater> tag." % (node.nodeName))
nodes = []
for subnode in node.childNodes:
if subnode.nodeName == 'source':
nodes.append(subnode)
strings = self.get_string_values(nodes)
- # at this point we don't support assigning certain sources to
- # certain eaters -- a problem to fix later. for now take the
- # union of the properties.
+ # assigning certain sources to certain eaters can be done
+ # with the <eater> tag, the <source> tag is now deprecated
required = [x for x in eaters.values() if x.getRequired()]
multiple = [x for x in eaters.values() if x.getMultiple()]
@@ -760,11 +824,13 @@
raise ConfigError("Component %s wants to eat on %s, but no "
"source specified"
% (node.nodeName, eaters.keys()[0]))
- elif len(strings) > 1 and not multiple:
+ if len(strings) > 1 and not multiple:
raise ConfigError("Component %s does not support multiple "
"sources feeding %s (%r)"
% (node.nodeName, eaters.keys()[0], strings))
-
+ if len(strings) > 0:
+ self.warning("The <source> tag is deprecated. Please use the "
+ "<eater> tag now")
return strings
def _parseClockMaster(self, node):
Modified: flumotion/branches/transcoder-1/flumotion/component/feedcomponent.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/component/feedcomponent.py (original)
+++ flumotion/branches/transcoder-1/flumotion/component/feedcomponent.py Wed May 9 16:45:51 2007
@@ -275,17 +275,6 @@
try:
pipeline = gst.parse_launch(self.pipeline_string)
-
- # Connect to the client-fd-removed signals on each feeder, so we
- # can clean up properly on removal.
- feeder_element_names = map(lambda n: "feeder:" + n,
- self.feeder_names)
- for feeder in feeder_element_names:
- element = pipeline.get_by_name(feeder)
- element.connect('client-fd-removed', self.removeClientCallback)
- self.debug("Connected %s to removeClientCallback", feeder)
-
- return pipeline
except gobject.GError, e:
self.warning('Could not parse pipeline: %s' % e.message)
m = messages.Error(T_(N_(
@@ -294,6 +283,10 @@
self.state.append('messages', m)
raise errors.PipelineParseError(e.message)
+ self.connect_feeders(pipeline)
+
+ return pipeline
+
def set_pipeline(self, pipeline):
FeedComponent.set_pipeline(self, pipeline)
self.configure_pipeline(self.pipeline, self.config['properties'])
@@ -556,12 +549,13 @@
self.QUEUE_SIZE_BUFFERS)
def get_pipeline_string(self, properties):
- sources = self.config['source']
+ eaters = self.config['eater']
pipeline = self.get_muxer_string(properties) + ' '
- for eater in sources:
- tmpl = '@ eater:%s @ ! muxer. '
- pipeline += tmpl % eater
+ for e in eaters:
+ for feed in eaters[e]:
+ tmpl = '@ eater:%s @ ! muxer. '
+ pipeline += tmpl % feed
pipeline += 'muxer.'
Modified: flumotion/branches/transcoder-1/flumotion/component/feedcomponent010.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/component/feedcomponent010.py (original)
+++ flumotion/branches/transcoder-1/flumotion/component/feedcomponent010.py Wed May 9 16:45:51 2007
@@ -368,7 +368,6 @@
# add extra keys to state
self.state.addKey('eaterNames') # feedId of eaters
self.state.addKey('feederNames') # feedId of feeders
-
# add keys for eaters and feeders uiState
self._feeders = {} # feeder feedId -> Feeder
self._eaters = {} # eater feedId -> Eater
@@ -401,6 +400,8 @@
self._stateChangeDeferreds = {}
self._gotFirstNewSegment = {}
+ # feedId of eater -> eater name as specified in config
+ self._eaterMapping = {}
# multifdsink's get-stats signal had critical bugs before this version
tcppluginversion = gstreamer.get_plugin_version('tcp')
@@ -428,7 +429,7 @@
Invokes the L{create_pipeline} and L{set_pipeline} vmethods,
which subclasses can provide.
"""
- eater_config = self.config.get('source', [])
+ eater_config = self.config.get('eater', {})
feeder_config = self.config.get('feed', [])
self.debug("FeedComponent.do_setup(): eater_config %r" % eater_config)
@@ -558,16 +559,20 @@
def parseEaterConfig(self, eater_config):
# the source feeder names come from the config
- # they are specified under <component> as <source> elements in XML
+ # they are specified under <eater> as <feed> elements in XML
# so if they don't specify a feed name, use "default" as the feed name
- eater_names = []
- for block in eater_config:
- eater_name = block
- if block.find(':') == -1:
- eater_name = block + ':default'
- eater_names.append(eater_name)
- self.debug('parsed eater config, eater feedIds %r' % eater_names)
- self.eater_names = eater_names
+ # there is also a deprecated way by specifying them under <component>
+ # as <source> elements in XML
+ feed_ids = []
+ for eater in eater_config:
+ for feed in eater_config[eater]:
+ feed_id = feed
+ if feed.find(':') == -1:
+ feed_id = feed + ':default'
+ feed_ids.append(feed_id)
+ self._eaterMapping[feed_id] = eater
+ self.debug('parsed eater config, eater feedIds %r' % feed_ids)
+ self.eater_names = feed_ids
self.state.set('eaterNames', self.eater_names)
def parseFeederConfig(self, feeder_config):
@@ -584,6 +589,16 @@
self.debug('parsed feeder config, feeders %r' % self.feeder_names)
self.state.set('feederNames', self.feeder_names)
+ def connect_feeders(self, pipeline):
+ # Connect to the client-fd-removed signals on each feeder, so we
+ # can clean up properly on removal.
+ feeder_element_names = map(lambda n: "feeder:" + n,
+ self.feeder_names)
+ for feeder in feeder_element_names:
+ element = pipeline.get_by_name(feeder)
+ element.connect('client-fd-removed', self.removeClientCallback)
+ self.debug("Connected %s to removeClientCallback", feeder)
+
def get_eater_names(self):
"""
Return the list of feeder names this component eats from.
Modified: flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/file.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/file.py (original)
+++ flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/file.py Wed May 9 16:45:51 2007
@@ -28,24 +28,27 @@
from flumotion.component.misc.porter import porterclient
from flumotion.component.base import http as httpbase
from twisted.web import resource, server, http
-from twisted.web import error as weberror
+from twisted.web import error as weberror, static
from twisted.internet import defer, reactor, error, abstract
from twisted.python import filepath
from flumotion.twisted import fdserver
from twisted.cred import credentials
-from twisted.web.static import loadMimeTypes, getTypeAndEncoding
+# add our own mime types to the ones parsed from /etc/mime.types
+def loadMimeTypes():
+ d = static.loadMimeTypes()
+ d['.flv'] = 'video/x-flv'
+ return d
# this file is inspired by/adapted from twisted.web.static
class File(resource.Resource, filepath.FilePath, log.Loggable):
contentTypes = loadMimeTypes()
-
defaultType = "application/octet-stream"
childNotFound = weberror.NoResource("File not found.")
- def __init__(self, path, component):
+ def __init__(self, path, component, mimeToResource=None):
"""
@param component: L{flumotion.component.component.BaseComponent}
"""
@@ -53,8 +56,16 @@
filepath.FilePath.__init__(self, path)
self._component = component
+ # mapping of mime type -> File subclass
+ self._mimeToResource = mimeToResource or {}
+ self._factory = MimedFileFactory(component, self._mimeToResource)
def getChild(self, path, request):
+ self.log('getChild: self %r, path %r', self, path)
+ # we handle a request ending in '/' as well; this is how those come in
+ if path == '':
+ return self
+
self.restat()
if not self.isdir():
@@ -68,7 +79,7 @@
if not fpath.exists():
return self.childNotFound
- return self.createSimilarFile(fpath.path)
+ return self._factory.create(fpath.path)
def openForReading(self):
"""Open a file and return it."""
@@ -92,7 +103,11 @@
return server.NOT_DONE_YET
- def renderAuthenticated(self, _, request):
+ def renderAuthenticated(self, _, request, first=0):
+ """
+ @type first: int
+ @param first: starting byte to send from
+ """
# Now that we're authenticated (or authentication wasn't requested),
# write the file (or appropriate other response) to the client.
# We override static.File to implement Range requests, and to get access
@@ -101,6 +116,9 @@
# self.restat()
self.debug('renderAuthenticated request %r' % request)
+ # make sure we notice changes in the file
+ self.restat()
+
ext = os.path.splitext(self.basename())[1].lower()
type = self.contentTypes.get(ext, self.defaultType)
@@ -109,6 +127,7 @@
return self.childNotFound.render(request)
if self.isdir():
+ self.debug("%s is a directory, can't be GET", self.path)
return self.childNotFound.render(request)
# Different headers not normally set in static.File...
@@ -137,7 +156,6 @@
fileSize = self.getFileSize()
# first and last byte offset we will write
- first = 0
last = fileSize - 1
range = request.getHeader('range')
@@ -180,15 +198,16 @@
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
return ''
- # Start sending from the requested position in the file
- f.seek(first)
-
# FIXME: is it still partial if the request was for the complete
# file ? Couldn't find a conclusive answer in the spec.
request.setResponseCode(http.PARTIAL_CONTENT)
request.setHeader('Content-Range', "bytes %d-%d/%d" %
(first, last, fileSize))
+ # Start sending from the requested position in the file
+ if first:
+ f.seek(first)
+
request.setHeader("Content-Length", str(last - first + 1))
if request.method == 'HEAD':
@@ -198,10 +217,49 @@
return server.NOT_DONE_YET
- def createSimilarFile(self, path):
- self.debug("createSimilarFile at %r", path)
- f = self.__class__(path, self._component)
- return f
+class MimedFileFactory(log.Loggable):
+ """
+ I create File subclasses based on the mime type of the given path.
+ """
+ contentTypes = loadMimeTypes()
+ defaultType = "application/octet-stream"
+
+ def __init__(self, component, mimeToResource=None):
+ self._component = component
+ self._mimeToResource = mimeToResource or {}
+
+ def create(self, path):
+ """
+ Creates and returns an instance of a File subclass based on the mime
+ type/extension of the given path.
+ """
+
+ self.debug("createMimedFile at %r", path)
+ ext = os.path.splitext(path)[1].lower()
+ mimeType = self.contentTypes.get(ext, self.defaultType)
+ klazz = self._mimeToResource.get(mimeType, File)
+ self.debug("mimetype %s, class %r" % (mimeType, klazz))
+ return klazz(path, self._component, mimeToResource=self._mimeToResource)
+
+class FLVFile(File):
+ """
+ I am a File resource for FLV files.
+ I can handle requests with a 'start' GET parameter.
+ This parameter represents the byte offset from where to start.
+ If it is non-zero, I will output an FLV header so the result is
+ playable.
+ """
+ header = 'FLV\x01\x01\000\000\000\x09\000\000\000\x09'
+ def renderAuthenticated(self, _, request):
+ self.debug('rendering FLV')
+ first = 0
+ # each value is a list
+ start = int(request.args.get('start', ['0'])[0])
+ if start:
+ first = start
+ request.write(self.header)
+
+ return File.renderAuthenticated(self, _, request, first=first)
class FileTransfer:
"""
Modified: flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/httpfile.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/httpfile.py (original)
+++ flumotion/branches/transcoder-1/flumotion/component/misc/httpfile/httpfile.py Wed May 9 16:45:51 2007
@@ -1,4 +1,4 @@
-# -*- Mode: Python -*-
+# -*- Mode: Python; test-case-name: flumotion.test.test_component_httpserver -*-
# vi:si:et:sw=4:sts=4:ts=4
#
# Flumotion - a streaming media server
@@ -22,17 +22,19 @@
import time
import string
+from twisted.web import resource, static, server, http
+from twisted.web import error as weberror
+from twisted.internet import defer, reactor, error
+from twisted.cred import credentials
+
from flumotion.component import component
from flumotion.common import log, messages, errors, netutils, interfaces
from flumotion.component.component import moods
from flumotion.component.misc.porter import porterclient
from flumotion.component.base import http as httpbase
-from twisted.web import resource, static, server, http
-from twisted.web import error as weberror
-from twisted.internet import defer, reactor, error
+
from flumotion.twisted import fdserver
from flumotion.twisted.compat import implements
-from twisted.cred import credentials
from flumotion.component.misc.httpfile import file
@@ -134,10 +136,10 @@
self.type = None
self.port = None
self.hostname = None
- self.loggers = []
- self.logfilter = None
+ self._loggers = []
+ self._logfilter = None
- self.description = 'On-Demand Flumotion Stream',
+ self._description = 'On-Demand Flumotion Stream',
self._singleFile = False
self._connected_clients = []
@@ -145,17 +147,27 @@
self._pbclient = None
+ self._twistedPort = None
+ self._timeoutRequestsCallLater = None
+
+ # FIXME: maybe we want to allow the configuration to specify
+ # additional mime -> File class mapping ?
+ self._mimeToResource = {
+ 'video/x-flv': file.FLVFile,
+ }
+
# store number of connected clients
self.uiState.addKey("connected-clients", 0)
self.uiState.addKey("bytes-transferred", 0)
def getDescription(self):
- return self.description
+ return self._description
def do_setup(self):
props = self.config['properties']
- mountPoint = props.get('mount-point', '')
+ # always make sure the mount point starts with /
+ mountPoint = props.get('mount-point', '/')
if not mountPoint.startswith('/'):
mountPoint = '/' + mountPoint
self.mountPoint = mountPoint
@@ -171,8 +183,8 @@
self._porterPath = props['porter-socket-path']
self._porterUsername = props['porter-username']
self._porterPassword = props['porter-password']
- self.loggers = \
- self.plugs['flumotion.component.plugs.loggers.Logger']
+ self._loggers = \
+ self.plugs.get('flumotion.component.plugs.loggers.Logger', [])
if 'bouncer' in props:
self.setBouncerName(props['bouncer'])
@@ -182,9 +194,15 @@
filter = http.LogFilter()
for f in props['ip-filter']:
filter.addIPFilter(f)
- self.logfilter = filter
-
+ self._logfilter = filter
+
def do_stop(self):
+ if self._timeoutRequestsCallLater:
+ self._timeoutRequestsCallLater.cancel()
+ self._timeoutRequestsCallLater = None
+ if self._twistedPort:
+ self._twistedPort.stopListening()
+
if self.type == 'slave' and self._pbclient:
return self._pbclient.deregisterPath(self.mountPoint)
@@ -220,22 +238,33 @@
"Can't specify porter details in master mode")
def do_start(self, *args, **kwargs):
- #root = HTTPRoot()
- root = resource.Resource()
- # TwistedWeb wants the child path to not include the leading /
- mount = self.mountPoint[1:]
- # split path on / and add iteratively twisted.web resources
- children = string.split(mount, '/')
- current_resource = root
- for child in children[:-1]:
- res = resource.Resource()
- current_resource.putChild(child, res)
- current_resource = res
- fileResource = file.File(self.filePath, self)
- self.debug("Putting File resource at %r", children[-1:][0])
- current_resource.putChild(children[-1:][0], fileResource)
+ self.debug('Starting with mount point "%s"' % self.mountPoint)
+ factory = file.MimedFileFactory(self,
+ mimeToResource=self._mimeToResource)
+ if self.mountPoint == '/':
+ self.debug('mount point / - create File resource as root')
+ # directly create a File resource for the path
+ root = factory.create(self.filePath)
+ else:
+ # split path on / and add iteratively twisted.web resources
+ # Asking for '' or '/' will retrieve the root Resource's '' child,
+ # so the split on / returning a first list value '' is correct
+ self.debug('mount point %s - creating root Resource and children',
+ self.mountPoint)
+ root = resource.Resource()
+ children = string.split(self.mountPoint[1:], '/')
+ parent = root
+ for child in children[:-1]:
+ res = resource.Resource()
+ self.debug("Putting Resource at %s", child)
+ parent.putChild(child, res)
+ parent = res
+ fileResource = factory.create(self.filePath)
+ self.debug("Putting resource %r at %r", fileResource, children[-1])
+ parent.putChild(children[-1], fileResource)
- reactor.callLater(self.REQUEST_TIMEOUT, self._timeoutRequests)
+ self._timeoutRequestsCallLater = reactor.callLater(
+ self.REQUEST_TIMEOUT, self._timeoutRequests)
d = defer.Deferred()
if self.type == 'slave':
@@ -257,10 +286,14 @@
else:
# File Streamer is standalone.
try:
- self.debug('Listening on %s' % self.port)
+ self.debug('Going to listen on port %d' % self.port)
iface = ""
- reactor.listenTCP(self.port, Site(root, self),
- interface=iface)
+ # we could be listening on port 0, in which case we need
+ # to figure out the actual port we listen on
+ self._twistedPort = reactor.listenTCP(self.port,
+ Site(root, self), interface=iface)
+ self.port = self._twistedPort.getHost().port
+ self.debug('Listening on port %d' % self.port)
except error.CannotListenError:
t = 'Port %d is not available.' % self.port
self.warning(t)
@@ -294,11 +327,6 @@
msg = 'slave mode, missing required property porter-%s' % k
return defer.fail(errors.ConfigError(msg))
- if props.get('mount-point', None) is not None:
- if props['mount-point'] == '/':
- return defer.fail(errors.ConfigError(
- "A mount-point of / is not supported in this release"))
-
path = props.get('path', None)
if path is None:
msg = "missing required property 'path'"
@@ -334,7 +362,7 @@
headers = request.getAllHeaders()
ip = request.getClientIP()
- if not self.logfilter or not self.logfilter.isInRange(ip):
+ if not self._logfilter or not self._logfilter.isInRange(ip):
args = {'ip': ip,
'time': time.gmtime(),
'method': request.method,
@@ -348,7 +376,7 @@
'user-agent': headers.get('user-agent', None),
'time-connected': timeConnected}
- for logger in self.loggers:
+ for logger in self._loggers:
logger.event('http_session_completed', args)
self._connected_clients.remove(request)
@@ -369,7 +397,7 @@
else:
return {
'protocol': 'HTTP',
- 'description': self.description,
+ 'description': self._description,
'url' : self.getUrl()
}
@@ -403,7 +431,6 @@
"""
Close the logfile, then reopen using the previous logfilename
"""
- for logger in self.loggers:
+ for logger in self._loggers:
self.debug('rotating logger %r' % logger)
logger.rotate()
-
Modified: flumotion/branches/transcoder-1/flumotion/component/producers/Makefile.am
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/component/producers/Makefile.am (original)
+++ flumotion/branches/transcoder-1/flumotion/component/producers/Makefile.am Wed May 9 16:45:51 2007
@@ -17,6 +17,7 @@
icecast \
ivtv \
pipeline \
+ playlist \
rtsp \
soundcard \
videotest \
Copied: flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlist.py (from r4904, flumotion/trunk/flumotion/component/producers/playlist/playlist.py)
==============================================================================
--- flumotion/trunk/flumotion/component/producers/playlist/playlist.py (original)
+++ flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlist.py Wed May 9 16:45:51 2007
@@ -91,6 +91,9 @@
self.basetime = -1
self.pipeline = None
+ self._hasAudio = True
+ self._hasVideo = True
+
# The gnlcompositions for audio and video
self.videocomp = None
self.audiocomp = None
@@ -142,6 +145,10 @@
pipeline = gst.Pipeline()
for mediatype in ['audio', 'video']:
+ if (mediatype == 'audio' and not self._hasAudio) or (
+ mediatype == 'video' and not self._hasVideo):
+ continue
+
composition = gst.element_factory_make("gnlcomposition",
mediatype + "-composition")
@@ -183,13 +190,15 @@
return pipeline
def _createDefaultSources(self):
- vsrc = videotest_gnl_src("videotestdefault", 0, 2**63 - 1,
- 2**31 - 1)
- self.videocomp.add(vsrc)
-
- asrc = audiotest_gnl_src("videotestdefault", 0, 2**63 - 1,
- 2**31 - 1)
- self.audiocomp.add(asrc)
+ if self._hasVideo:
+ vsrc = videotest_gnl_src("videotestdefault", 0, 2**63 - 1,
+ 2**31 - 1)
+ self.videocomp.add(vsrc)
+
+ if self._hasAudio:
+ asrc = audiotest_gnl_src("videotestdefault", 0, 2**63 - 1,
+ 2**31 - 1)
+ self.audiocomp.add(asrc)
def _setupClock(self, pipeline):
# Configure our pipeline to use a known basetime and clock.
@@ -226,20 +235,20 @@
item.duration = item.duration + start
start = 0
- if item.hasVideo:
+ if self._hasVideo and item.hasVideo:
item.vsrc = file_gnl_src(None, item.uri, self.videocaps,
start, item.duration, item.offset, 0)
self.videocomp.add(item.vsrc)
- if item.hasAudio:
+ if self._hasAudio and item.hasAudio:
item.asrc = file_gnl_src(None, item.uri, self.audiocaps,
start, item.duration, item.offset, 0)
self.audiocomp.add(item.asrc)
def unscheduleItem(self, item):
self.debug("Unscheduling item at uri %s", item.uri)
- if item.hasVideo:
+ if self._hasVideo and item.hasVideo:
self.videocomp.remove(item.vsrc)
- if item.hasAudio:
+ if self._hasAudio and item.hasAudio:
self.audiocomp.remove(item.asrc)
def addPlaylist(self, data):
@@ -256,6 +265,9 @@
self._samplerate = props.get('samplerate', 44100)
self._channels = props.get('channels', 2)
+ self._hasAudio = props.get('audio', True)
+ self._hasVideo = props.get('video', True)
+
pipeline = self._buildPipeline()
self._setupClock(pipeline)
Copied: flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlist.xml (from r4904, flumotion/trunk/flumotion/component/producers/playlist/playlist.xml)
==============================================================================
--- flumotion/trunk/flumotion/component/producers/playlist/playlist.xml (original)
+++ flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlist.xml Wed May 9 16:45:51 2007
@@ -14,6 +14,11 @@
<synchronization required="yes" clock-priority="110" />
<properties>
+ <property name="audio" type="boolean"
+ description="Output audio"/>
+ <property name="video" type="boolean"
+ description="Output video"/>
+
<property name="height" type="int"
description="Scaled output height of video" />
<property name="width" type="int"
Copied: flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlistparser.py (from r4904, flumotion/trunk/flumotion/component/producers/playlist/playlistparser.py)
==============================================================================
--- flumotion/trunk/flumotion/component/producers/playlist/playlistparser.py (original)
+++ flumotion/branches/transcoder-1/flumotion/component/producers/playlist/playlistparser.py Wed May 9 16:45:51 2007
@@ -20,11 +20,15 @@
# Headers in this file shall remain intact.
import gst
+from gst.extend import discoverer
+
import time
from StringIO import StringIO
from xml.dom import Node
+from twisted.internet import reactor
+
from flumotion.common import log, fxml
import singledecodebin
@@ -68,12 +72,18 @@
self.producer = producer
- def addItem(self, timestamp, uri, offset, duration):
+ self._pending_items = []
+ self._discovering = False
+
+ def addItem(self, timestamp, uri, offset, duration, hasAudio, hasVideo):
"""
Add an item to the playlist.
The duration of previous and this entry may be adjusted to make it fit.
"""
newitem = PlaylistItem(timestamp, uri, offset, duration)
+ newitem.hasAudio = hasAudio
+ newitem.hasVideo = hasVideo
+
prev = next = None
item = self.items
while item:
@@ -108,10 +118,12 @@
# Duration adjustments -> Reflect into gnonlin timeline
if prev and prev.timestamp + prev.duration > newitem.timestamp:
prev.duration = newitem.timestamp - prev.timestamp
- prev.asrc.props.duration = prev.duration
- prev.vsrc.props.duration = prev.duration
- prev.asrc.props.media_duration = prev.duration
- prev.vsrc.props.media_duration = prev.duration
+ if prev.asrc:
+ prev.asrc.props.duration = prev.duration
+ prev.asrc.props.media_duration = prev.duration
+ if prev.vsrc:
+ prev.vsrc.props.duration = prev.duration
+ prev.vsrc.props.media_duration = prev.duration
if next and timestamp + newitem.duration > next.timestamp:
newitem.duration = next.timestamp - newitem.timestamp
@@ -153,25 +165,74 @@
self.debug("Parsing entry")
self._parsePlaylistEntry(parser, child)
+ # Now launch the discoverer for any pending items
+ if not self._discovering:
+ self._discoverPending()
+
+ def _discoverPending(self):
+ def _discovered(disc, is_media):
+ self.debug("Discovered!")
+ reactor.callFromThread(_discoverer_done, disc, is_media)
+
+ def _discoverer_done(disc, is_media):
+ if is_media:
+ self.debug("Discovery complete, media found")
+ uri = "file://" + item[0]
+ timestamp = item[1]
+ duration = item[2]
+ offset = item[3]
+
+ hasA = disc.is_audio
+ hasV = disc.is_video
+ durationDiscovered = min(disc.audiolength,
+ disc.videolength)
+ if not duration or duration > durationDiscovered:
+ duration = durationDiscovered
+
+ if duration + offset > durationDiscovered:
+ offset = 0
+
+ if duration > 0:
+ self.addItem(timestamp, uri, offset, duration, hasA, hasV)
+ else:
+ self.warning("Duration of item is zero, not adding")
+ else:
+ self.warning("Discover failed to find media in %s", item[0])
+
+ self.debug("Continuing on to next file")
+ self._discoverPending()
+
+ if not self._pending_items:
+ self.debug("No more files to discover")
+ self._discovering = False
+ return
+
+ self._discovering = True
+
+ item = self._pending_items.pop(0)
+
+ self.debug("Discovering file %s", item[0])
+ disc = discoverer.Discoverer(item[0])
+
+ disc.connect('discovered', _discovered)
+ disc.discover()
+
def _parsePlaylistEntry(self, parser, entry):
- # TODO: Once we use the discoverer, we should move duration to optional
- mandatory = ['filename', 'time', 'duration']
- optional = ['offset']
+ mandatory = ['filename', 'time']
+ optional = ['duration', 'offset']
(filename, timestamp, duration, offset) = parser.parseAttributes(
entry, mandatory, optional)
- duration = int(float(duration) * gst.SECOND)
+ if duration is not None:
+ duration = int(float(duration) * gst.SECOND)
if offset is None:
offset = 0
- offset = int(offset)
+ offset = int(offset) * gst.SECOND
timestamp = self._parseTimestamp(timestamp)
- uri = 'file://'+filename
-
- self.debug("Adding item")
- self.addItem(timestamp, uri, offset, duration)
+ self._pending_items.append((filename, timestamp, duration, offset))
def _parseTimestamp(self, ts):
# Take TS in YYYY-MM-DDThh:mm:ssZ format, return timestamp in
Modified: flumotion/branches/transcoder-1/flumotion/extern/Makefile.am
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/extern/Makefile.am (original)
+++ flumotion/branches/transcoder-1/flumotion/extern/Makefile.am Wed May 9 16:45:51 2007
@@ -24,7 +24,7 @@
PYTHONPATH=$(srcdir):$$PYTHONPATH trial log.test_log
clean-local:
- rm -r _trial_temp
+ rm -rf _trial_temp
SUBDIRS = fdpass \
$(PTI_DIR)
Modified: flumotion/branches/transcoder-1/flumotion/manager/manager.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/manager/manager.py (original)
+++ flumotion/branches/transcoder-1/flumotion/manager/manager.py Wed May 9 16:45:51 2007
@@ -259,7 +259,6 @@
# create a portal so that I can be connected to, through our dispatcher
# implementing the IRealm and a bouncer
- # FIXME: decide if we allow anonymous login in this small (?) window
self.portal = fportal.BouncerPortal(self.dispatcher, None)
#unsafeTracebacks = 1 # for debugging tracebacks to clients
self.factory = pb.PBServerFactory(self.portal,
Modified: flumotion/branches/transcoder-1/flumotion/test/Makefile.am
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/test/Makefile.am (original)
+++ flumotion/branches/transcoder-1/flumotion/test/Makefile.am Wed May 9 16:45:51 2007
@@ -29,6 +29,7 @@
test_component.py \
test_component_init.py \
test_component_feed.py \
+ test_component_httpserver.py \
test_component_httpstreamer.py \
test_config.py \
test_credentials.py \
Modified: flumotion/branches/transcoder-1/flumotion/test/test.xml
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/test/test.xml (original)
+++ flumotion/branches/transcoder-1/flumotion/test/test.xml Wed May 9 16:45:51 2007
@@ -8,14 +8,18 @@
</component>
<component name="converter-ogg-theora" type="pipeline-converter" worker="worker">
- <source>producer-video-test</source>
+ <eater name="default">
+ <feed>producer-video-test</feed>
+ </eater>
<property name="pipeline">
ffmpegcolorspace ! theoraenc keyframe-force=5 ! oggmux
</property>
</component>
<component name="streamer-ogg-theora" type="http-streamer" worker="streamer">
- <source>converter-ogg-theora</source>
+ <eater name="default">
+ <feed>converter-ogg-theora</feed>
+ </eater>
<property name="port">8800</property>
<plugs>
<plug socket="flumotion.component.plugs.loggers.Logger"
Modified: flumotion/branches/transcoder-1/flumotion/test/test_component.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/test/test_component.py (original)
+++ flumotion/branches/transcoder-1/flumotion/test/test_component.py Wed May 9 16:45:51 2007
@@ -37,14 +37,17 @@
class PipelineTest(ParseLaunchComponent):
def __init__(self, eaters=None, feeders=None, pipeline='test-pipeline'):
self.__pipeline = pipeline
- self._source = eaters or []
+ if eaters:
+ self._eater = {'default':eaters}
+ else:
+ self._eater = {}
self._feed = feeders or []
ParseLaunchComponent.__init__(self)
def config(self):
config = {'name': 'fake',
- 'source': self._source,
+ 'eater': self._eater,
'feed': self._feed,
'plugs': {},
'properties': {}}
Modified: flumotion/branches/transcoder-1/flumotion/test/test_component_httpstreamer.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/test/test_component_httpstreamer.py (original)
+++ flumotion/branches/transcoder-1/flumotion/test/test_component_httpstreamer.py Wed May 9 16:45:51 2007
@@ -44,7 +44,7 @@
'feed': [],
'name': 'http-video',
'parent': 'default',
- 'source': ['muxer-video'],
+ 'eater': {'default': ['muxer-video']},
'avatarId': '/default/http-video',
'clock-master': None,
'plugs': {
Modified: flumotion/branches/transcoder-1/flumotion/test/test_config.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/test/test_config.py (original)
+++ flumotion/branches/transcoder-1/flumotion/test/test_config.py Wed May 9 16:45:51 2007
@@ -51,6 +51,19 @@
<component type="test-component-sync-provider">
<synchronization required="true" clock-priority="130"/>
</component>
+ <component type="test-component-with-feeder">
+ <feeder name="default" />
+ </component>
+ <component type="test-component-with-one-eater">
+ <eater name="default" required="true" />
+ </component>
+ <component type="test-component-with-two-eaters">
+ <eater name="video" required="true" />
+ <eater name="audio" required="true" />
+ </component>
+ <component type="test-component-with-multiple-eater">
+ <eater name="default" multiple="true" />
+ </component>
</components>
<plugs>
<plug socket="foo.bar" type="frobulator">
@@ -493,6 +506,181 @@
self.failUnless(entries.has_key('/atmosphere/atmocomp'))
self.failUnless(entries.has_key('/default/flowcomp'))
+ def testParseComponentsWithEaters(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="prod" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="cons" type="test-component-with-one-eater"
+ worker="foo">
+ <eater name="default">
+ <feed>prod:default</feed>
+ </eater>
+ </component>
+ </flow>
+ </planet>
+ """)
+ conf.parse()
+ entries = conf.getComponentEntries()
+ self.failUnless(entries.has_key('/default/prod'))
+ self.failUnless(entries.has_key('/default/cons'))
+ cons = entries['/default/cons'].getConfigDict()
+ self.failUnless(cons.has_key('eater'))
+ self.failUnless(cons['eater'].has_key('default'))
+ self.failUnless(cons['eater']['default'] == ["prod:default"])
+ self.failUnless(cons['source'] == ["prod:default"])
+
+ def testParseComponentsWithEatersNotSpecified(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="cons" type="test-component-with-one-eater"
+ worker="foo">
+ </component>
+ </flow>
+ </planet>
+ """)
+ self.assertRaises(config.ConfigError, conf.parse)
+
+ def testParseComponentsWithEatersDeprecatedWay(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="prod" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="cons" type="test-component-with-one-eater"
+ worker="foo">
+ <source>prod:default</source>
+ </component>
+ </flow>
+ </planet>
+ """)
+ conf.parse()
+ entries = conf.getComponentEntries()
+ self.failUnless(entries.has_key('/default/prod'))
+ self.failUnless(entries.has_key('/default/cons'))
+ cons = entries['/default/cons'].getConfigDict()
+ self.failUnless(cons.has_key('source'))
+ self.failUnless(cons['source'] == ["prod:default"])
+ self.failUnless(cons['eater']['default'] == ["prod:default"])
+
+ def testParseComponentsWithTwoEaters(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="prod" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="prod2" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="cons" type="test-component-with-two-eaters"
+ worker="foo">
+ <eater name="video">
+ <feed>prod:default</feed>
+ </eater>
+ <eater name="audio">
+ <feed>prod2:default</feed>
+ </eater>
+ </component>
+ </flow>
+ </planet>
+ """)
+ conf.parse()
+ entries = conf.getComponentEntries()
+ self.failUnless(entries.has_key('/default/prod'))
+ self.failUnless(entries.has_key('/default/cons'))
+ cons = entries['/default/cons'].getConfigDict()
+ self.failUnless(cons.has_key('eater'))
+ self.failUnless(cons['eater'].has_key('video'))
+ self.failUnless(cons['eater']['video'] == ["prod:default"])
+ self.failUnless(cons['eater'].has_key('audio'))
+ self.failUnless(cons['eater']['audio'] == ['prod2:default'])
+
+ def testParseComponentsWithTwoEatersDeprecatedWay(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="prod" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="prod2" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="cons" type="test-component-with-two-eaters"
+ worker="foo">
+ <source>prod:default</source>
+ <source>prod2:default</source>
+ </component>
+ </flow>
+ </planet>
+ """)
+ self.assertRaises(config.ConfigError, conf.parse)
+
+ def testParseComponentsWithMultipleEater(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="prod" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="prod2" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="cons" type="test-component-with-multiple-eater"
+ worker="foo">
+ <eater name="default">
+ <feed>prod:default</feed>
+ <feed>prod2:default</feed>
+ </eater>
+ </component>
+ </flow>
+ </planet>
+ """)
+ conf.parse()
+ entries = conf.getComponentEntries()
+ self.failUnless(entries.has_key('/default/prod'))
+ self.failUnless(entries.has_key('/default/cons'))
+ cons = entries['/default/cons'].getConfigDict()
+ self.failUnless(cons.has_key('eater'))
+ self.failUnless(cons['eater'].has_key('default'))
+ self.failUnless(cons['eater']['default'] == [
+ "prod:default", "prod2:default"])
+ self.failUnless(cons.has_key('source'))
+ self.failUnless(cons['source'] == [
+ "prod:default", "prod2:default"])
+
+ def testParseComponentsWithMultipleEaterDeprecatedWay(self):
+ conf = ConfigXML(
+ """
+ <planet>
+ <flow name="default">
+ <component name="prod" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="prod2" type="test-component-with-feeder"
+ worker="foo"/>
+ <component name="cons" type="test-component-with-multiple-eater"
+ worker="foo">
+ <source>prod:default</source>
+ <source>prod2:default</source>
+ </component>
+ </flow>
+ </planet>
+ """)
+ conf.parse()
+ entries = conf.getComponentEntries()
+ self.failUnless(entries.has_key('/default/prod'))
+ self.failUnless(entries.has_key('/default/cons'))
+ cons = entries['/default/cons'].getConfigDict()
+ self.failUnless(cons.has_key('eater'))
+ self.failUnless(cons['eater'].has_key('default'))
+ self.failUnless(cons['eater']['default'] == [
+ "prod:default", "prod2:default"])
+ self.failUnless(cons.has_key('source'))
+ self.failUnless(cons['source'] == [
+ "prod:default", "prod2:default"])
+
def testGetComponentEntriesWrong(self):
xml = """
<planet>
Modified: flumotion/branches/transcoder-1/flumotion/test/test_misc_httpfile.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/test/test_misc_httpfile.py (original)
+++ flumotion/branches/transcoder-1/flumotion/test/test_misc_httpfile.py Wed May 9 16:45:51 2007
@@ -168,7 +168,6 @@
'a text file', 0, 10)
return fr.finishDeferred
-
def testRangeHead(self):
fr = FakeRequest(method='HEAD', headers={'range': 'bytes=2-5'})
self.assertEquals(self.resource.render(fr), server.NOT_DONE_YET)
@@ -176,4 +175,51 @@
http.PARTIAL_CONTENT, '', 4)
return fr.finishDeferred
+class TestDirectory(unittest.TestCase):
+ def setUp(self):
+ self.path = tempfile.mkdtemp()
+ h = open(os.path.join(self.path, 'test.flv'), 'w')
+ h.write('a fake FLV file')
+ h.close()
+ self.component = FakeComponent()
+ # a directory resource
+ self.resource = file.File(self.path, self.component,
+ { 'video/x-flv': file.FLVFile } )
+
+ def tearDown(self):
+ os.system('rm -r %s' % self.path)
+
+ def testGetChild(self):
+ fr = FakeRequest()
+ r = self.resource.getChild('test.flv', fr)
+ self.assertEquals(r.__class__, file.FLVFile)
+
+ def testFLV(self):
+ fr = FakeRequest()
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.data, 'a fake FLV file')
+ fr.finishDeferred.addCallback(finish)
+
+ return fr.finishDeferred
+
+ def testFLVStart(self):
+ fr = FakeRequest(args={'start': [2]})
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.data, file.FLVFile.header + 'fake FLV file')
+ fr.finishDeferred.addCallback(finish)
+ return fr.finishDeferred
+
+ def testFLVStartZero(self):
+ fr = FakeRequest(args={'start': [0]})
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.data, 'a fake FLV file')
+ fr.finishDeferred.addCallback(finish)
+
+ return fr.finishDeferred
Modified: flumotion/branches/transcoder-1/flumotion/twisted/fdserver.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/twisted/fdserver.py (original)
+++ flumotion/branches/transcoder-1/flumotion/twisted/fdserver.py Wed May 9 16:45:51 2007
@@ -176,14 +176,3 @@
class PassableServerPort(tcp.Port):
transport = PassableServerConnection
-class PassableClientConnection(_SocketMaybeCloser, tcp.Client):
- pass
-
-class PassableClientConnector(tcp.Connector):
- # It is unfortunate, but it seems that either we override this
- # private-ish method or reimplement BaseConnector.connect(). This is
- # the path that tcp.py takes, so we take it too.
- def _makeTransport(self):
- return PassableClientConnection(self.host, self.port,
- self.bindAddress, self,
- self.reactor)
Modified: flumotion/branches/transcoder-1/flumotion/wizard/save.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/wizard/save.py (original)
+++ flumotion/branches/transcoder-1/flumotion/wizard/save.py Wed May 9 16:45:51 2007
@@ -78,9 +78,10 @@
s = ' <component name="%s" type="%s" ' \
'project="flumotion" version="%s"%s>\n' % (
self.name, self.type, configure.version, extra)
-
+ s += ' <eater name="default">\n'
for sourceName in self.getFeeders():
- s += " <source>%s</source>\n" % sourceName
+ s += " <feed>%s</feed>\n" % sourceName
+ s+= ' </eater>\n'
if self.props:
s += "\n"
Modified: flumotion/branches/transcoder-1/flumotion/wizard/steps.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/wizard/steps.py (original)
+++ flumotion/branches/transcoder-1/flumotion/wizard/steps.py Wed May 9 16:45:51 2007
@@ -769,7 +769,7 @@
e = self.combobox_channels.get_enum()
channels = 2
if e: channels = e.intvalue
- d = self.workerRun('flumotion.worker.checks.video', 'checkMixerTracks',
+ d = self.workerRun('flumotion.worker.checks.audio', 'checkMixerTracks',
enum.element, device, channels, id='soundcard-check')
def soundcardCheckComplete((deviceName, tracks)):
self.clear_msg('soundcard-check')
Modified: flumotion/branches/transcoder-1/flumotion/worker/job.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/worker/job.py (original)
+++ flumotion/branches/transcoder-1/flumotion/worker/job.py Wed May 9 16:45:51 2007
@@ -25,6 +25,7 @@
import os
import sys
+import signal
from twisted.cred import portal
from twisted.internet import defer, reactor
@@ -456,11 +457,18 @@
ret.addCallback(stopListening)
return ret
- def kill(self):
+ def kill(self, signum=signal.SIGKILL):
self.warning("Killing all children immediately")
- for jobInfo in self.getJobInfos():
- self.debug("Sending SIGKILL to pid %d", jobInfo.pid)
- common.killPid(jobInfo.pid)
+ for avatarId in self.getJobAvatarIds():
+ self.killJob(avatarId, signum)
+
+ def killJob(self, avatarId, signum):
+ if avatarId not in self._jobInfos:
+ raise errors.UnknownComponentError(avatarId)
+ jobInfo = self._jobInfos[avatarId]
+ self.debug("Sending signal %d to job %s at pid %d", signum,
+ avatarId, jobInfo.pid)
+ common.signalPid(jobInfo.pid, signum)
class JobAvatar(fpb.Avatar, log.Loggable):
"""
Modified: flumotion/branches/transcoder-1/flumotion/worker/medium.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/worker/medium.py (original)
+++ flumotion/branches/transcoder-1/flumotion/worker/medium.py Wed May 9 16:45:51 2007
@@ -23,6 +23,8 @@
worker-side objects to handle worker clients
"""
+import signal
+
from twisted.internet import reactor
import twisted.cred.error
from twisted.internet import error
@@ -242,3 +244,21 @@
@returns: a list of componentAvatarIds
"""
return self.brain.getComponents()
+
+ def remote_killJob(self, avatarId, signum=signal.SIGKILL):
+ """Kill one of the worker's jobs.
+
+ This method is intended for exceptional purposes only; a normal
+ component shutdown is performed by the manager via calling
+ remote_stop() on the component avatar.
+
+ Raises L{flumotion.common.errors.UnknownComponentError} if the
+ job is unknown.
+
+ @param avatarId: the avatar Id of the component, e.g.
+ '/default/audio-encoder'
+ @type avatarId: string
+ @param signum: Signal to send, optional. Defaults to SIGKILL.
+ @type signum: int
+ """
+ self.brain.killJob(avatarId, signum)
Modified: flumotion/branches/transcoder-1/flumotion/worker/worker.py
==============================================================================
--- flumotion/branches/transcoder-1/flumotion/worker/worker.py (original)
+++ flumotion/branches/transcoder-1/flumotion/worker/worker.py Wed May 9 16:45:51 2007
@@ -223,7 +223,10 @@
return self.ports
def getFeedServerPort(self):
- return self.feedServer.getPortNum()
+ if self.feedServer:
+ return self.feedServer.getPortNum()
+ else:
+ return None
def create(self, avatarId, type, moduleName, methodName, nice=0):
def getBundles():
@@ -278,3 +281,6 @@
def getComponents(self):
return self.jobHeaven.getJobAvatarIds()
+
+ def killJob(self, avatarId, signum):
+ self.jobHeaven.killJob(avatarId, signum)
More information about the flumotion-commit
mailing list