zaheer - in flumotion/branches/platform-3: . flumotion/common
flumotion/component flumotion/component/base
flumotion/component/consumers/disker
flumotion/component/converters/overlay
flumotion/component/muxers flumotion/manager flumotion/test
flumotion/wizard
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Tue Jun 12 12:48:56 CEST 2007
Author: zaheer
Date: Tue Jun 12 12:48:51 2007
New Revision: 5152
Modified:
flumotion/branches/platform-3/ChangeLog
flumotion/branches/platform-3/TODO
flumotion/branches/platform-3/configure.ac
flumotion/branches/platform-3/flumotion/common/config.py
flumotion/branches/platform-3/flumotion/component/Makefile.am
flumotion/branches/platform-3/flumotion/component/base/Makefile.am
flumotion/branches/platform-3/flumotion/component/base/base.xml
flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.py
flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.xml
flumotion/branches/platform-3/flumotion/component/converters/overlay/overlay.py
flumotion/branches/platform-3/flumotion/component/feedcomponent.py
flumotion/branches/platform-3/flumotion/component/feedcomponent010.py
flumotion/branches/platform-3/flumotion/component/muxers/multipart.py
flumotion/branches/platform-3/flumotion/manager/depgraph.py
flumotion/branches/platform-3/flumotion/manager/manager.py
flumotion/branches/platform-3/flumotion/test/test.xml
flumotion/branches/platform-3/flumotion/test/test_component.py
flumotion/branches/platform-3/flumotion/test/test_component_httpstreamer.py
flumotion/branches/platform-3/flumotion/test/test_config.py
flumotion/branches/platform-3/flumotion/test/test_manager_depgraph.py
flumotion/branches/platform-3/flumotion/wizard/save.py
Log:
* TODO:
* configure.ac:
* flumotion/common/config.py (buildEatersDict, dictDiff,
dictDiffMessageString, ref, FlumotionConfigXML._parseComponent,
FlumotionConfigXML._parseFeedId, FlumotionConfigXML._parseEaters,
FlumotionConfigXML.parseFeed, FlumotionConfigXML.addFeed,
FlumotionConfigXML._parseSources):
* flumotion/component/Makefile.am:
* flumotion/component/base/Makefile.am:
* flumotion/component/base/base.xml:
* flumotion/component/consumers/disker/disker.py
(DiskerMedium.remote_scheduleRecordings, Disker.change_filename,
Disker._notify_caps_cb, Disker.configure_pipeline,
Disker.eventStarted, Disker.eventStopped):
* flumotion/component/consumers/disker/disker.xml:
* flumotion/component/converters/overlay/overlay.py
(Overlay.get_pipeline_string):
* flumotion/component/feedcomponent.py
(MultiInputParseLaunchComponent.get_pipeline_string):
* flumotion/component/feedcomponent010.py (FeedComponent.init,
FeedComponent.do_setup, FeedComponent.parseEaterConfig,
FeedComponent.get_eater_name_for_feed_id):
* flumotion/component/muxers/multipart.py:
* flumotion/manager/depgraph.py (DepGraph.mapEatersToFeeders):
* flumotion/manager/manager.py (Vishnu.parseFeedId,
Vishnu.fixOldEaterConfig, Vishnu.verifyExistingComponentState,
Vishnu.makeNewComponentState):
* flumotion/test/test.xml:
* flumotion/test/test_component.py (PipelineTest.__init__,
PipelineTest.config, TestParser.testOneSource,
TestParser.testOneSourceWithout, TestParser.testTwoSources,
TestParser.testTwoBoth):
* flumotion/test/test_component_httpstreamer.py
(TestOldProperties.setUp):
* flumotion/test/test_config.py
(TestConfig.testParseComponentsWithEaters,
TestConfig.testParseComponentsWithEatersNotSpecified,
TestConfig.testParseComponentsWithEatersDeprecatedWay,
TestConfig.testParseComponentsWithTwoEaters,
TestConfig.testParseComponentsWithTwoEatersDeprecatedWay,
TestConfig.testParseComponentsWithMultipleEater,
TestConfig.testParseComponentsWithMultipleEaterDeprecatedWay,
AdminConfigTest.testUnknownSocket, TestDictDiff,
TestDictDiff.assertOND, TestDictDiff.testSimple,
TestDictDiff.testRecursive, TestDictDiff.testHumanReadable,
TestDictDiff.test):
* flumotion/test/test_manager_depgraph.py
(testDepGraph._createComponent):
* flumotion/wizard/save.py (Component.toXML):
Merge platform-3-a3.
Modified: flumotion/branches/platform-3/ChangeLog
==============================================================================
--- flumotion/branches/platform-3/ChangeLog (original)
+++ flumotion/branches/platform-3/ChangeLog Tue Jun 12 12:48:51 2007
@@ -1,3 +1,56 @@
+2007-06-12 Zaheer Abbas Merali <zaheerabbas at merali dot org>
+
+ * TODO:
+ * configure.ac:
+ * flumotion/common/config.py (buildEatersDict, dictDiff,
+ dictDiffMessageString, ref, FlumotionConfigXML._parseComponent,
+ FlumotionConfigXML._parseFeedId, FlumotionConfigXML._parseEaters,
+ FlumotionConfigXML.parseFeed, FlumotionConfigXML.addFeed,
+ FlumotionConfigXML._parseSources):
+ * flumotion/component/Makefile.am:
+ * flumotion/component/base/Makefile.am:
+ * flumotion/component/base/base.xml:
+ * flumotion/component/consumers/disker/disker.py
+ (DiskerMedium.remote_scheduleRecordings, Disker.change_filename,
+ Disker._notify_caps_cb, Disker.configure_pipeline,
+ Disker.eventStarted, Disker.eventStopped):
+ * flumotion/component/consumers/disker/disker.xml:
+ * flumotion/component/converters/overlay/overlay.py
+ (Overlay.get_pipeline_string):
+ * flumotion/component/feedcomponent.py
+ (MultiInputParseLaunchComponent.get_pipeline_string):
+ * flumotion/component/feedcomponent010.py (FeedComponent.init,
+ FeedComponent.do_setup, FeedComponent.parseEaterConfig,
+ FeedComponent.get_eater_name_for_feed_id):
+ * flumotion/component/muxers/multipart.py:
+ * flumotion/manager/depgraph.py (DepGraph.mapEatersToFeeders):
+ * flumotion/manager/manager.py (Vishnu.parseFeedId,
+ Vishnu.fixOldEaterConfig, Vishnu.verifyExistingComponentState,
+ Vishnu.makeNewComponentState):
+ * flumotion/test/test.xml:
+ * flumotion/test/test_component.py (PipelineTest.__init__,
+ PipelineTest.config, TestParser.testOneSource,
+ TestParser.testOneSourceWithout, TestParser.testTwoSources,
+ TestParser.testTwoBoth):
+ * flumotion/test/test_component_httpstreamer.py
+ (TestOldProperties.setUp):
+ * flumotion/test/test_config.py
+ (TestConfig.testParseComponentsWithEaters,
+ TestConfig.testParseComponentsWithEatersNotSpecified,
+ TestConfig.testParseComponentsWithEatersDeprecatedWay,
+ TestConfig.testParseComponentsWithTwoEaters,
+ TestConfig.testParseComponentsWithTwoEatersDeprecatedWay,
+ TestConfig.testParseComponentsWithMultipleEater,
+ TestConfig.testParseComponentsWithMultipleEaterDeprecatedWay,
+ AdminConfigTest.testUnknownSocket, TestDictDiff,
+ TestDictDiff.assertOND, TestDictDiff.testSimple,
+ TestDictDiff.testRecursive, TestDictDiff.testHumanReadable,
+ TestDictDiff.test):
+ * flumotion/test/test_manager_depgraph.py
+ (testDepGraph._createComponent):
+ * flumotion/wizard/save.py (Component.toXML):
+ Merge platform-3-a3.
+
2007-06-11 Andy Wingo <wingo at pobox.com>
* flumotion/worker/checks/package.xml:
Modified: flumotion/branches/platform-3/TODO
==============================================================================
--- flumotion/branches/platform-3/TODO (original)
+++ flumotion/branches/platform-3/TODO Tue Jun 12 12:48:51 2007
@@ -16,7 +16,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 rename components, files, ... to be uniform
(e.g. web-camera -> webcam, ...)
Modified: flumotion/branches/platform-3/configure.ac
==============================================================================
--- flumotion/branches/platform-3/configure.ac (original)
+++ flumotion/branches/platform-3/configure.ac Tue Jun 12 12:48:51 2007
@@ -174,6 +174,8 @@
flumotion/component/effects/colorbalance/Makefile
flumotion/component/effects/volume/Makefile
flumotion/component/encoders/Makefile
+flumotion/component/combiners/Makefile
+flumotion/component/combiners/switch/Makefile
flumotion/component/consumers/Makefile
flumotion/component/consumers/disker/Makefile
flumotion/component/consumers/httpstreamer/Makefile
Modified: flumotion/branches/platform-3/flumotion/common/config.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/common/config.py (original)
+++ flumotion/branches/platform-3/flumotion/common/config.py Tue Jun 12 12:48:51 2007
@@ -41,6 +41,101 @@
# all these string values should result in True
BOOL_TRUE_VALUES = ['True', 'true', '1', 'Yes', 'yes']
+def buildEatersDict(eatersList, eaterDefs):
+ """Build a eaters dict suitable for forming part of a component
+ config.
+
+ @param eatersList: List of eaters. For example,
+ [('default', 'othercomp:feeder')] says that our
+ eater 'default' will be fed by the feed
+ identified by the feedId 'othercomp:feeder'.
+ @type eatersList: List of (eaterName, feedId)
+ @param eaterDefs: The set of allowed and required eaters
+ @type eaterDefs: List of
+ L{flumotion.common.registry.RegistryEntryEater}
+ """
+ eaters = {}
+ for eater, feedId in eatersList:
+ if eater is None:
+ # cope with old <source> entries
+ eater = eaterDefs[0].getName()
+ feeders = eaters.get(eater, [])
+ if feedId in feeders:
+ raise ConfigError("Already have a feedId %s eating "
+ "from %s", feedId, eater)
+ feeders.append(feedId)
+ eaters[eater] = feeders
+ for e in eaterDefs:
+ eater = e.getName()
+ if e.getRequired() and not eater in eaters:
+ raise ConfigError("Component wants to eat on %s,"
+ " but no feeders specified."
+ % (e.getName(),))
+ if not e.getMultiple() and len(eaters.get(eater, [])) > 1:
+ raise ConfigError("Component does not support multiple "
+ "sources feeding %s (%r)"
+ % (eater, eaters[eater]))
+ return eaters
+
+def dictDiff(old, new, onlyOld=None, onlyNew=None, diff=None,
+ keyBase=None):
+ """Compute the difference between two config dicts.
+
+ @returns: 3 tuple: (onlyOld, onlyNew, diff) where:
+ onlyOld is a list of (key, value), representing key-value
+ pairs that are only in old;
+ onlyNew is a list of (key, value), representing key-value
+ pairs that are only in new;
+ diff is a list of (key, oldValue, newValue), representing
+ keys with different values in old and new; and
+ key is a tuple of strings representing the recursive key
+ to get to a value. For example, ('foo', 'bar') represents
+ the value d['foo']['bar'] on a dict d.
+ """
+ # key := tuple of strings
+
+ if onlyOld is None:
+ onlyOld = [] # key, value
+ onlyNew = [] # key, value
+ diff = [] # key, oldvalue, newvalue
+ keyBase = ()
+
+ for k in old:
+ key = (keyBase + (k,))
+ if k not in new:
+ onlyOld.append((key, old[k]))
+ elif old[k] != new[k]:
+ if isinstance(old[k], dict) and isinstance(new[k], dict):
+ dictDiff(old[k], new[k], onlyOld, onlyNew, diff, key)
+ else:
+ diff.append((key, old[k], new[k]))
+
+ for k in new:
+ key = (keyBase + (k,))
+ if k not in old:
+ onlyNew.append((key, new[k]))
+
+ return onlyOld, onlyNew, diff
+
+def dictDiffMessageString((old, new, diff), oldLabel='old',
+ newLabel='new'):
+ def ref(label, k):
+ return "%s%s: '%s'" % (label,
+ ''.join(["[%r]" % (subk,)
+ for subk in k[:-1]]),
+ k[-1])
+
+ out = []
+ for k, v in old:
+ out.append('Only in %s = %r' % (ref(oldLabel, k), v))
+ for k, v in new:
+ out.append('Only in %s = %r' % (ref(newLabel, k), v))
+ for k, oldv, newv in diff:
+ out.append('Value mismatch:')
+ out.append(' %s = %r' % (ref(oldLabel, k), oldv))
+ out.append(' %s = %r' % (ref(newLabel, k), newv))
+ return '\n'.join(out)
+
class ConfigEntryComponent(log.Loggable):
"I represent a <component> entry in a planet config file"
nice = 0
@@ -391,7 +486,10 @@
@rtype: L{ConfigEntryComponent}
"""
# <component name="..." type="..." worker="...">
- # <source>*
+ # <source>...</source>* DEPRECATED
+ # <eater name="...">
+ # <feed>...</feed>
+ # </eater>*
# <property name="name">value</property>*
# </component>
@@ -451,7 +549,7 @@
"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
@@ -466,10 +564,23 @@
# let the component know what its feeds should be called
config['feed'] = defs.getFeeders()
- sources = self._parseSources(node, defs)
- if sources:
- config['source'] = sources
-
+ eaters = self._parseEaters(node, defs)
+ if not eaters:
+ sources = self._parseSources(node, defs)
+ if sources:
+ config['source'] = sources
+ # assign sources up to first (and only eater)
+ # if more than one eater, parseSources would have raised
+ # a ConfigError
+ config['eater'] = {defs.getEaters()[0].getName():sources}
+ else:
+ # get sources as a list of strings from the dict of eaters
+ sources = []
+ for e in eaters:
+ sources.extend(eaters[e])
+ if sources:
+ config['source'] = sources
+ config['eater'] = eaters
config['clock-master'] = self._parseClockMaster(node)
config['plugs'] = self._parsePlugs(node, defs.getSockets())
@@ -627,19 +738,75 @@
raise ConfigError("<%s> value not specified" % name)
return value
+ def _parseFeedId(self, feedId):
+ if feedId.find(':') == -1:
+ return "%s:default" % feedId
+ else:
+ return feedId
+
+ 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>
+ svs = self.get_string_values([feedNode])
+ feeds = []
+ for sv in svs:
+ feeds.append(self._parseFeedId(sv))
+ return feeds
+ def addFeed(feeds):
+ feedNodes.extend(feeds)
+ 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()]
@@ -647,12 +814,17 @@
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))
-
- return strings
+ if len(strings) > 0:
+ self.warning("The <source> tag is deprecated. Please use the "
+ "<eater> tag now")
+ ret = []
+ for s in strings:
+ ret.append(self._parseFeedId(s))
+ return ret
def _parseClockMaster(self, node):
nodes = []
Modified: flumotion/branches/platform-3/flumotion/component/Makefile.am
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/Makefile.am (original)
+++ flumotion/branches/platform-3/flumotion/component/Makefile.am Tue Jun 12 12:48:51 2007
@@ -16,6 +16,7 @@
SUBDIRS = \
base \
bouncers \
+ combiners \
consumers \
converters \
effects \
Modified: flumotion/branches/platform-3/flumotion/component/base/Makefile.am
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/base/Makefile.am (original)
+++ flumotion/branches/platform-3/flumotion/component/base/Makefile.am Tue Jun 12 12:48:51 2007
@@ -1,6 +1,12 @@
include $(top_srcdir)/common/python.mk
-
-component_PYTHON = __init__.py admin_gtk.py admin_text.py http.py
+
+component_PYTHON = __init__.py \
+ admin_gtk.py \
+ admin_text.py \
+ http.py \
+ scheduler.py \
+ watcher.py
+
componentdir = $(libdir)/flumotion/python/flumotion/component/base
component_DATA = base.xml feeders.glade eaters.glade
Modified: flumotion/branches/platform-3/flumotion/component/base/base.xml
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/base/base.xml (original)
+++ flumotion/branches/platform-3/flumotion/component/base/base.xml Tue Jun 12 12:48:51 2007
@@ -35,5 +35,26 @@
</directory>
</directories>
</bundle>
+ <bundle name="base-scheduler">
+ <dependencies>
+ <dependency name="flumotion" />
+ <dependency name="base-watcher" />
+ </dependencies>
+ <directories>
+ <directory name="flumotion/component/base">
+ <filename location="scheduler.py" />
+ </directory>
+ </directories>
+ </bundle>
+ <bundle name="base-watcher">
+ <dependencies>
+ <dependency name="flumotion" />
+ </dependencies>
+ <directories>
+ <directory name="flumotion/component/base">
+ <filename location="watcher.py" />
+ </directory>
+ </directories>
+ </bundle>
</bundles>
</registry>
Modified: flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.py (original)
+++ flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.py Tue Jun 12 12:48:51 2007
@@ -42,6 +42,23 @@
__all__ = ['Disker']
+
+"""
+Disker has a property 'ical-schedule'. This allows an ical file to be
+specified in the config and have recordings scheduled based on events.
+This file will be monitored for changes and events reloaded if this
+happens.
+
+The filename of a recording started from an ical file will be produced
+via passing the ical event summary through strftime, so that an archive
+can encode the date and time that it was begun.
+
+The time that will be given to strftime will be given in the timezone of
+the ical event. In practice this will either be UTC or the local time of
+the machine running the disker, as the ical scheduler does not
+understand arbitrary timezones.
+"""
+
try:
# icalendar and dateutil modules needed for scheduling recordings
from icalendar import Calendar
@@ -62,7 +79,9 @@
self.comp.change_filename(filenameTemplate)
def remote_scheduleRecordings(self, ical):
- self.comp.parse_ical(ical)
+ if HAS_ICAL:
+ cal = Calendar.from_string(ical)
+ self.addEvents(self.comp.icalScheduler.parseCalendar(cal))
# called when admin ui wants updated state (current filename info)
def remote_notifyState(self):
@@ -158,11 +177,12 @@
mime += ";boundary=ThisRandomString"
return mime
- def change_filename(self, filenameTemplate=None):
+ def change_filename(self, filenameTemplate=None, timeOrTuple=None):
"""
@param filenameTemplate: strftime formatted string to decide filename
+ @param timeOrTuple: a valid time to pass to strftime, defaulting
+ to time.localtime(). A 9-tuple may be passed instead.
"""
- self.debug("change_filename()")
mime = self.get_mime()
if mime == 'application/ogg':
ext = 'ogg'
@@ -201,9 +221,9 @@
if not filenameTemplate:
filenameTemplate = self._defaultFilenameTemplate
filename = "%s.%s" % (time.strftime(filenameTemplate,
- time.localtime()), ext)
+ timeOrTuple or time.localtime()), ext)
self.location = os.path.join(self.directory, filename)
-
+ self.info("Changing filename to %s", self.location)
try:
self.file = open(self.location, 'a')
except IOError, e:
@@ -276,7 +296,8 @@
self.caps = caps
if new and self._recordAtStart:
- reactor.callLater(0, self.change_filename)
+ reactor.callLater(0, self.change_filename,
+ self._startFilenameTemplate)
# callback for when a client is removed so we can figure out
# errors
@@ -306,90 +327,35 @@
self._recordAtStart = properties.get('start-recording', True)
self._defaultFilenameTemplate = properties.get('filename',
'%s.%%Y%%m%%d-%%H%%M%%S' % self.getName())
+ self._startFilenameTemplate = self._defaultFilenameTemplate
icalfn = properties.get('ical-schedule')
- if icalfn:
- ical = open(icalfn, "rb").read()
- self.parse_ical(ical)
- self._recordAtStart = False
+ if HAS_ICAL and icalfn:
+ from flumotion.component.base import scheduler
+ self.icalScheduler = scheduler.ICalScheduler(open(
+ icalfn, 'r'))
+ self.icalScheduler.subscribe(self.eventStarted,
+ self.eventStopped)
+ currentEvents = self.icalScheduler.getCurrentEvents()
+ if currentEvents:
+ self._startFilenameTemplate = currentEvents[0]
+ self._recordAtStart = True
+ else:
+ self._recordAtStart = False
+ else:
+ self.warning("An ical file has been specified for "
+ "scheduling but the necessary modules "
+ "dateutil and/or icalendar are not installed")
sink = self.get_element('fdsink')
sink.get_pad('sink').connect('notify::caps', self._notify_caps_cb)
# connect to client-removed so we can detect errors in file writing
sink.connect('client-removed', self._client_removed_cb)
- # add code that lets recordings be schedules
- # TODO: resolve overlapping events
- def schedule_recording(self, whenStart, whenEnd, recur=None,
- filenameTemplate=None):
- """
- Sets a recording to start at a time in the future for a specified
- duration.
- @param whenStart time of when to start recording
- @type whenStart datetime
- @param whenEnd time of when to end recording
- @type whenEnd datetime
- @param recur recurrence rule
- @type recur icalendar.props.vRecur
- @param filenameTemplate strftime formatted string to decide filename
- @type filenameTemplate string
- """
- now = datetime.now()
-
- startRecurRule = None
- endRecurRule = None
-
- if recur:
- self.debug("Have a recurrence rule, parsing")
- # create dateutil.rrule from the recurrence rules
- startRecurRule = rrule.rrulestr(recur.ical(), dtstart=whenStart)
- endRecurRule = rrule.rrulestr(recur.ical(), dtstart=whenEnd)
- if now >= whenStart:
- self.debug("Initial start before now (%r), finding new starts",
- whenStart)
- whenStart = startRecurRule.after(now)
- whenEnd = endRecurRule.after(now)
- self.debug("New start is now %r", whenStart)
-
- if now < whenStart:
- start = whenStart - now
- startSecs = start.days * 86400 + start.seconds
- self.debug("scheduling a recording %d seconds away", startSecs)
- reactor.callLater(startSecs,
- self.start_scheduled_recording, startRecurRule, whenStart,
- filenameTemplate)
- end = whenEnd - now
- endSecs = end.days * 86400 + end.seconds
- reactor.callLater(endSecs,
- self.stop_scheduled_recording, endRecurRule, whenEnd)
- else:
- self.warning("attempt to schedule in the past!")
-
- def start_scheduled_recording(self, recurRule, when, filenameTemplate):
- self.change_filename(filenameTemplate)
- if recurRule:
- now = datetime.now()
- nextTime = recurRule.after(when)
- recurInterval = nextTime - now
- self.debug("recurring start interval: %r", recurInterval)
- recurIntervalSeconds = recurInterval.days * 86400 + \
- recurInterval.seconds
- self.debug("recurring start in %d seconds", recurIntervalSeconds)
- reactor.callLater(recurIntervalSeconds,
- self.start_scheduled_recording,
- recurRule, nextTime, filenameTemplate)
+ def eventStarted(self, event):
+ self.change_filename(event.content, event.start.timetuple())
- def stop_scheduled_recording(self, recurRule, when):
+ def eventStopped(self, event):
self.stop_recording()
- if recurRule:
- now = datetime.now()
- nextTime = recurRule.after(when)
- recurInterval = nextTime - now
- recurIntervalSeconds = recurInterval.days * 86400 + \
- recurInterval.seconds
- self.debug("recurring stop in %d seconds", recurIntervalSeconds)
- reactor.callLater(recurIntervalSeconds,
- self.stop_scheduled_recording,
- recurRule, nextTime)
def parse_ical(self, icsStr):
if HAS_ICAL:
Modified: flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.xml
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.xml (original)
+++ flumotion/branches/platform-3/flumotion/component/consumers/disker/disker.xml Tue Jun 12 12:48:51 2007
@@ -77,6 +77,7 @@
<dependencies>
<dependency name="component"/>
<dependency name="disker-base"/>
+ <dependency name="base-scheduler"/>
</dependencies>
<directories>
Modified: flumotion/branches/platform-3/flumotion/component/converters/overlay/overlay.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/converters/overlay/overlay.py (original)
+++ flumotion/branches/platform-3/flumotion/component/converters/overlay/overlay.py Tue Jun 12 12:48:51 2007
@@ -44,7 +44,7 @@
# this got added to ffmpegcolorspace in 0.8.5
addalpha = 'ffmpegcolorspace'
- source = self.config['source'][0]
+ source = self.config['eater']['default'][0]
eater = '@ eater:%s @' % source
# the order here is important; to have our eater be the reference
Modified: flumotion/branches/platform-3/flumotion/component/feedcomponent.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/feedcomponent.py (original)
+++ flumotion/branches/platform-3/flumotion/component/feedcomponent.py Tue Jun 12 12:48:51 2007
@@ -557,12 +557,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/platform-3/flumotion/component/feedcomponent010.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/feedcomponent010.py (original)
+++ flumotion/branches/platform-3/flumotion/component/feedcomponent010.py Tue Jun 12 12:48: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')
@@ -425,7 +426,7 @@
"""
Sets up component.
"""
- eater_config = self.config.get('source', [])
+ eater_config = self.config.get('eater', {})
feeder_config = self.config.get('feed', [])
self.debug("feedcomponent.setup(): eater_config %r" % eater_config)
@@ -554,16 +555,17 @@
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_ids.append(feed)
+ self._eaterMapping[feed] = 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):
@@ -1278,4 +1280,20 @@
self._gotFirstNewSegment[feedId] = True
return True
+ def get_eater_name_for_feed_id(self, feedId):
+ """
+ Given a feedId, it will return the eater name that it is in.
+ Unfortunately feedcomponent keys eaters on the feedId so
+ it is impossible to have the same feedId feeding multiple
+ eaters in the same component.
+
+ @param feedId the feedId to get the eater name for
+ @returns the eater name
+ @rtype: string
+ """
+
+ if self._eaterMapping.has_key(feedId):
+ return self._eaterMapping[feedId]
+ return None
+
pygobject.type_register(FeedComponent)
Modified: flumotion/branches/platform-3/flumotion/component/muxers/multipart.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/muxers/multipart.py (original)
+++ flumotion/branches/platform-3/flumotion/component/muxers/multipart.py Tue Jun 12 12:48:51 2007
@@ -23,15 +23,6 @@
from flumotion.component import feedcomponent
-class Multipart(feedcomponent.ParseLaunchComponent):
- def get_pipeline_string(self, properties):
- sources = self.config['source']
-
- pipeline = 'multipartmux name=muxer '
- for eater in sources:
- tmpl = '@ eater:%s @ ! queue ! muxer. '
- pipeline += tmpl % eater
-
- pipeline += 'muxer.'
-
- return pipeline
+class Multipart(feedcomponent.MultiInputParseLaunchComponent):
+ def get_muxer_string(self, properties):
+ return 'multipartmux name=muxer'
Modified: flumotion/branches/platform-3/flumotion/manager/depgraph.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/manager/depgraph.py (original)
+++ flumotion/branches/platform-3/flumotion/manager/depgraph.py Tue Jun 12 12:48:51 2007
@@ -200,46 +200,41 @@
# for this component setup, go through all the feeders in it
config = eatingComponent.get('config')
- if not config.has_key('source'):
+ if not config.has_key('eater'):
# no eaters
self.debug("Component %r has no eaters" % eatingComponent)
else:
- # source is a list of componentName[:feedName]
+ # eater is a dict of eaterName -> list of componentName[:feedName]
# with feedName defaulting to default
- # FIXME: maybe source should really be eaters and contain
- # a list of feedId
- list = config['source']
-
- # FIXME: there's a bug in config parsing - sometimes this gives
- # us one string, and sometimes a list of one string, and
- # sometimes a list
- if isinstance(list, str):
- list = [list, ]
-
- for source in list:
- feederFound = False
- feederComponentName = source.split(':')[0]
- # find the feeder
- for feedingComponent in toSetup:
- if feedingComponent.get("name") == feederComponentName:
- feederFound = True
- try:
- self._addEdge(feedingComponent, eatingComponent,
- "COMPONENTSETUP", "COMPONENTSETUP")
- except KeyError:
- # it is possible for a component to have
- # two eaters, each eating from feeders on
- # one other component
- pass
- try:
- self._addEdge(feedingComponent, eatingComponent,
- "COMPONENTSTART", "COMPONENTSTART")
- except KeyError:
- pass
-
- if not feederFound:
- raise errors.ComponentConfigError(eatingComponent,
- "No feeder exists for eater %s" % source)
+ eaters = config['eater']
+
+ for eater in eaters:
+ for feed in eaters[eater]:
+ feederFound = False
+ feederComponentName = feed.split(':')[0]
+ # find the feeder
+ for feedingComponent in toSetup:
+ if feedingComponent.get("name") == feederComponentName:
+ feederFound = True
+ try:
+ self._addEdge(feedingComponent, eatingComponent,
+ "COMPONENTSETUP", "COMPONENTSETUP")
+ except KeyError:
+ # it is possible for a component to have
+ # two eaters, each eating from feeders on
+ # one other component
+ pass
+ try:
+ self._addEdge(feedingComponent, eatingComponent,
+ "COMPONENTSTART", "COMPONENTSTART")
+ except KeyError:
+ pass
+
+ if not feederFound:
+ raise errors.ComponentConfigError(eatingComponent,
+ "No feeder exists for eater %s on component %s"
+ " feeding from %s" % (eater, eatingComponent,
+ feed))
def whatShouldBeStarted(self):
"""
Modified: flumotion/branches/platform-3/flumotion/manager/manager.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/manager/manager.py (original)
+++ flumotion/branches/platform-3/flumotion/manager/manager.py Tue Jun 12 12:48:51 2007
@@ -839,24 +839,62 @@
# (2) we don't know anything about this component, but since it
# logged in, we will deal with it, at least allowing the
# admin to control it.
+ def parseFeedId(feedId):
+ if feedId.find(':') == -1:
+ return "%s:default" % feedId
+ else:
+ return feedId
+
+ def fixOldEaterConfig(state):
+ # check for components that have no eater dict but a
+ # non-empty source list, and file all these under
+ # eater default
+ eaterConfig = conf.get('eater', {})
+ sourceConfig = conf.get('source', [])
+ if eaterConfig == {} and sourceConfig != []:
+ eaters = registry.getRegistry().getComponent(
+ conf.get('type')).getEaters()
+ eatersDict = {}
+ try:
+ eatersTuple = [(None, parseFeedId(s)) for s in sourceConfig]
+ eatersDict = config.buildEatersDict(eatersTuple, eaters)
+ except errors.ConfigError:
+ message = messages.Warning(T_(
+ N_("Component logged in with old deprecated "
+ "configuration and creating an eaters config "
+ "caused an error. Restarting this component "
+ "will result in bad things. Best thing to do "
+ "is stop the component, restart manager and "
+ "start it again.")))
+ state.append('messages', message)
+ conf['eater'] = eatersDict
+ if sourceConfig:
+ sources = []
+ for s in sourceConfig:
+ sources.append(parseFeedId(s))
+ conf['source'] = sources
def verifyExistingComponentState(jobState, state):
# condition (1)
state.setJobState(jobState)
-
- if conf and state.get('config') != conf:
- message = messages.Warning(T_(
- N_("Component logged in with stale configuration. "
- "Consider stopping this component and restarting "
- "the manager.")),
- debug=("Expected\n%r\n, but got\n%r;\n"
- "updating internal state accordingly." %
- (state.get('config'), conf)))
- self.warning('updating internal component state for %r '
- '(changing config from %r to %r)', state,
- state.get('config'), conf)
- state.set('config', conf)
- state.append('messages', message)
+ if conf:
+ fixOldEaterConfig(state)
+ if state.get('config') != conf:
+ diff = config.dictDiff(state.get('config'), conf)
+ diffMsg = config.dictDiffMessageString(diff,
+ 'internal conf',
+ 'running conf')
+ message = messages.Warning(T_(
+ N_("Component logged in with stale configuration. "
+ "Consider stopping this component and restarting "
+ "the manager.")),
+ debug=("Updating internal conf from running conf:\n"
+ + diffMsg))
+ self.warning('updating internal component state for %r')
+ self.debug('changes to conf: %s',
+ config.dictDiffMessageString(diff))
+ state.set('config', conf)
+ state.append('messages', message)
# if conf is None, then we just created the component and
# it's not set up yet
@@ -867,6 +905,7 @@
if conf:
flowName, compName = conf['parent'], conf['name']
+ fixOldEaterConfig(state)
else:
# unfortunately there is a window in which a component does
# not have a config. accept that so that an admin can stop
@@ -878,6 +917,7 @@
'avatarId': avatar.avatarId,
'properties': {}}
+
state.set('name', compName)
state.set('type', conf['type'])
state.set('workerRequested', jobState.get('workerName'))
Modified: flumotion/branches/platform-3/flumotion/test/test.xml
==============================================================================
--- flumotion/branches/platform-3/flumotion/test/test.xml (original)
+++ flumotion/branches/platform-3/flumotion/test/test.xml Tue Jun 12 12:48: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/platform-3/flumotion/test/test_component.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/test/test_component.py (original)
+++ flumotion/branches/platform-3/flumotion/test/test_component.py Tue Jun 12 12:48:51 2007
@@ -38,6 +38,10 @@
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)
@@ -45,6 +49,7 @@
def config(self):
config = {'name': 'fake',
'source': self._source,
+ 'eater': self._eater,
'feed': self._feed,
'plugs': {},
'properties': {}}
@@ -210,7 +215,7 @@
return d
def testOneSource(self):
- d, pipeline = pipelineFactory('@eater:foo@ ! bar', ['foo'])
+ d, pipeline = pipelineFactory('@eater:foo@ ! bar', ['foo:default'])
if weHaveAnOldTwisted():
res = unittest.deferredResult(d)
self.assertEquals(res, '%s ! bar' % self._eater('foo:default'))
@@ -220,7 +225,7 @@
return d
def testOneSourceWithout(self):
- d, pipeline = pipelineFactory('bar', ['foo'])
+ d, pipeline = pipelineFactory('bar', ['foo:default'])
if weHaveAnOldTwisted():
res = unittest.deferredResult(d)
self.assertEquals(res, '%s ! bar' % self._eater('foo:default'))
@@ -253,7 +258,7 @@
def testTwoSources(self):
d, pipeline = pipelineFactory('@eater:foo@ ! @eater:bar@ ! baz',
- ['foo', 'bar'])
+ ['foo:default', 'bar:default'])
if weHaveAnOldTwisted():
res = unittest.deferredResult(d)
self.assertEquals(res, '%s ! %s ! baz' % (
@@ -282,7 +287,7 @@
def testTwoBoth(self):
d, pipeline = pipelineFactory(
'@eater:comp1@ ! @eater:comp2@ ! @feeder::feed1@ ! @feeder::feed2@',
- ['comp1', 'comp2',],
+ ['comp1:default', 'comp2:default',],
['feed1', 'feed2'])
if weHaveAnOldTwisted():
res = unittest.deferredResult(d)
Modified: flumotion/branches/platform-3/flumotion/test/test_component_httpstreamer.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/test/test_component_httpstreamer.py (original)
+++ flumotion/branches/platform-3/flumotion/test/test_component_httpstreamer.py Tue Jun 12 12:48:51 2007
@@ -44,7 +44,8 @@
'feed': [],
'name': 'http-video',
'parent': 'default',
- 'source': ['muxer-video'],
+ 'eater': {'default': ['muxer-video:default']},
+ 'source': ['muxer-video:default'],
'avatarId': '/default/http-video',
'clock-master': None,
'plugs': {
Modified: flumotion/branches/platform-3/flumotion/test/test_config.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/test/test_config.py (original)
+++ flumotion/branches/platform-3/flumotion/test/test_config.py Tue Jun 12 12:48: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>
@@ -555,3 +743,89 @@
'</admin>')
self.assertRaises(config.ConfigError,
lambda: AdminConfig(('foo.bar',), doc))
+
+class TestDictDiff(unittest.TestCase):
+ def assertOND(self, d1, d2, old, new, diff):
+ o, n, d = config.dictDiff(d1, d2)
+ self.assertEquals(old, o)
+ self.assertEquals(new, n)
+ self.assertEquals(diff, d)
+
+ def testSimple(self):
+ ass = self.assertOND
+ ass({}, {}, [], [], [])
+
+ ass({'foo': 'bar'}, {}, [(('foo',), 'bar')], [], [])
+
+ ass({}, {'foo': 'bar'}, [], [(('foo',), 'bar')], [])
+
+ ass({'foo': 'bar'}, {'foo': 'baz'}, [], [], [(('foo',), 'bar', 'baz')])
+
+ def testRecursive(self):
+ ass = self.assertOND
+ ass({}, {}, [], [], [])
+
+ ass({'foo': {'bar': 'baz'}},
+ {},
+ [(('foo',), {'bar':'baz'})],
+ [],
+ [])
+
+ ass({'foo': {'bar': 'baz'}},
+ {'foo': {}},
+ [(('foo','bar'), 'baz')],
+ [],
+ [])
+
+ ass({'foo': {}},
+ {'foo': {'bar': 'baz'}},
+ [],
+ [(('foo','bar'), 'baz')],
+ [])
+
+ ass({},
+ {'foo': {'bar': 'baz'}},
+ [],
+ [(('foo',), {'bar':'baz'})],
+ [])
+
+ ass({'foo': {'bar': 'baz'}},
+ {'foo': {'bar': 'qux'}},
+ [],
+ [],
+ [(('foo','bar'), 'baz', 'qux')])
+
+ def testHumanReadable(self):
+ def test(d1, d2, s):
+ msg = config.dictDiffMessageString(config.dictDiff(d1, d2))
+ self.assertEquals(msg, s)
+
+ test({}, {}, '')
+ test({'foo': 42}, {}, "Only in old: 'foo' = 42")
+ test({}, {'foo': 42}, "Only in new: 'foo' = 42")
+ test({'foo': 17}, {'foo': 42},
+ "Value mismatch:\n"
+ " old: 'foo' = 17\n"
+ " new: 'foo' = 42")
+
+ test({'foo': {'bar': 'baz'}},
+ {},
+ "Only in old: 'foo' = {'bar': 'baz'}")
+
+ test({'foo': {'bar': 'baz'}},
+ {'foo': {}},
+ "Only in old['foo']: 'bar' = 'baz'")
+
+ test({'foo': {}},
+ {'foo': {'bar': 'baz'}},
+ "Only in new['foo']: 'bar' = 'baz'")
+
+ test({},
+ {'foo': {'bar': 'baz'}},
+ "Only in new: 'foo' = {'bar': 'baz'}")
+
+ test({'foo': {'bar': 'baz'}},
+ {'foo': {'bar': 'qux'}},
+ "Value mismatch:\n"
+ " old['foo']: 'bar' = 'baz'\n"
+ " new['foo']: 'bar' = 'qux'")
Modified: flumotion/branches/platform-3/flumotion/test/test_manager_depgraph.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/test/test_manager_depgraph.py (original)
+++ flumotion/branches/platform-3/flumotion/test/test_manager_depgraph.py Tue Jun 12 12:48:51 2007
@@ -40,8 +40,9 @@
source = []
for eater in defs[4]:
source.append(eater)
-
+
conf["source"] = source
+ conf["eater"] = {"default":source}
ret.set("config", conf)
return ret
Modified: flumotion/branches/platform-3/flumotion/wizard/save.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/wizard/save.py (original)
+++ flumotion/branches/platform-3/flumotion/wizard/save.py Tue Jun 12 12:48:51 2007
@@ -78,9 +78,12 @@
s = ' <component name="%s" type="%s" ' \
'project="flumotion" version="%s"%s>\n' % (
self.name, self.type, configure.version, extra)
-
- for sourceName in self.getFeeders():
- s += " <source>%s</source>\n" % sourceName
+ whoIsFeedingUs = self.getFeeders()
+ if len(whoIsFeedingUs) > 0:
+ s += ' <eater name="default">\n'
+ for sourceName in whoIsFeedingUs:
+ s += " <feed>%s</feed>\n" % sourceName
+ s += ' </eater>\n'
if self.props:
s += "\n"
More information about the flumotion-commit
mailing list