Package flumotion :: Package common :: Module registry
[hide private]

Source Code for Module flumotion.common.registry

   1  # -*- Mode: Python; test-case-name: flumotion.test.test_registry -*- 
   2  # vi:si:et:sw=4:sts=4:ts=4 
   3  # 
   4  # Flumotion - a streaming media server 
   5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
   6  # All rights reserved. 
   7   
   8  # This file may be distributed and/or modified under the terms of 
   9  # the GNU General Public License version 2 as published by 
  10  # the Free Software Foundation. 
  11  # This file is distributed without any warranty; without even the implied 
  12  # warranty of merchantability or fitness for a particular purpose. 
  13  # See "LICENSE.GPL" in the source distribution for more information. 
  14   
  15  # Licensees having purchased or holding a valid Flumotion Advanced 
  16  # Streaming Server license may use this file in accordance with the 
  17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
  18  # See "LICENSE.Flumotion" in the source distribution for more information. 
  19   
  20  # Headers in this file shall remain intact. 
  21   
  22  """parsing of registry, which holds component and bundle information 
  23  """ 
  24   
  25  import os 
  26  import sets 
  27  import stat 
  28  import errno 
  29  import sys 
  30  from StringIO import StringIO 
  31   
  32  from xml.sax import saxutils 
  33  from twisted.spread import pb 
  34   
  35  from flumotion.common import common, log, errors, fxml 
  36  from flumotion.common.python import makedirs 
  37  from flumotion.common.bundle import BundlerBasket, MergedBundler 
  38  from flumotion.configure import configure 
  39   
  40  __all__ = ['ComponentRegistry', 'registry'] 
  41  __version__ = "$Rev: 6982 $" 
  42  # Re-enable when reading the registry cache is lighter-weight, or we 
  43  # decide that it's a good idea, or something. See #799. 
  44  READ_CACHE = False 
  45   
  46  _VALID_WIZARD_COMPONENT_TYPES = [ 
  47      'audio-producer', 
  48      'video-producer', 
  49      'muxer', 
  50      'audio-encoder', 
  51      'video-encoder', 
  52      ] 
  53   
  54  _VALID_WIZARD_PLUG_TYPES = [ 
  55      'http-consumer', 
  56      ] 
  57   
58 -def _getMTime(file):
59 return os.stat(file)[stat.ST_MTIME]
60
61 -class RegistryEntryComponent:
62 """ 63 I represent a <component> entry in the registry 64 """ 65 # RegistryEntryComponent has a constructor with a lot of arguments, 66 # but that's ok here. Allow it through pychecker. 67 __pychecker__ = 'maxargs=15' 68
69 - def __init__(self, filename, type, 70 source, description, base, properties, files, 71 entries, eaters, feeders, needs_sync, clock_priority, 72 sockets, wizards):
73 """ 74 @param filename: name of the XML file this component is parsed from 75 @type filename: str 76 @param properties: dict of name -> property 77 @type properties: dict of str -> L{RegistryEntryProperty} 78 @param files: list of files 79 @type files: list of L{RegistryEntryFile} 80 @param entries: dict of entry point type -> entry 81 @type entries: dict of str -> L{RegistryEntryEntry} 82 @param sockets: list of sockets supported by the element 83 @type sockets: list of str 84 @param wizards: list of wizard entries 85 @type wizards: list of L{RegistryEntryWizard} 86 """ 87 self.filename = filename 88 self.type = type 89 self.source = source 90 self.description = description 91 # we don't want to end up with the string "None" 92 if not self.description: 93 self.description = "" 94 self.base = base 95 self.properties = properties 96 self.files = files 97 self.entries = entries 98 self.eaters = eaters 99 self.feeders = feeders 100 self.needs_sync = needs_sync 101 self.clock_priority = clock_priority 102 self.sockets = sockets 103 self.wizards = wizards
104
105 - def getProperties(self):
106 """ 107 Get a list of all properties. 108 109 @rtype: list of L{RegistryEntryProperty} 110 """ 111 return self.properties.values()
112
113 - def hasProperty(self, name):
114 """ 115 Check if the component has a property with the given name. 116 """ 117 return name in self.properties.keys()
118
119 - def getFiles(self):
120 """ 121 @rtype: list of L{RegistryEntryFile} 122 """ 123 return self.files
124
125 - def getEntries(self):
126 return self.entries.values()
127
128 - def getEntryByType(self, type):
129 """ 130 Get the entry point for the given type of entry. 131 132 @type type: string 133 """ 134 return self.entries[type]
135
136 - def getGUIEntry(self):
137 if not self.files: 138 return 139 140 # FIXME: Handle multiple files 141 if len(self.files) > 1: 142 return 143 144 return self.files[0].getFilename()
145
146 - def getType(self):
147 return self.type
148
149 - def getBase(self):
150 return self.base
151
152 - def getDescription(self):
153 return self.description
154
155 - def getSource(self):
156 return self.source
157
158 - def getEaters(self):
159 return self.eaters
160
161 - def getFeeders(self):
162 return self.feeders
163
164 - def getNeedsSynchronization(self):
165 return self.needs_sync
166
167 - def getClockPriority(self):
168 return self.clock_priority
169
170 - def getSockets(self):
171 return self.sockets
172
173 -class RegistryEntryPlug:
174 """ 175 I represent a <plug> entry in the registry 176 """ 177
178 - def __init__(self, filename, type, socket, entries, properties, wizards):
179 """ 180 @param filename: name of the XML file this plug is parsed from 181 @type filename: str 182 @param type: the type of plug 183 @type type: str 184 @param socket: the fully qualified class name of the socket this 185 plug can be plugged in to 186 @type socket: str 187 @param entries: entry pointes for instantiating the plug 188 @type entries: list of L{RegistryEntryEntry} 189 @param properties: properties of the plug 190 @type properties: dict of str -> L{RegistryEntryProperty} 191 @param wizards: list of wizard entries 192 @type wizards: list of L{RegistryEntryWizard} 193 """ 194 self.filename = filename 195 self.type = type 196 self.socket = socket 197 self.entries = entries 198 self.properties = properties 199 self.wizards = wizards
200
201 - def getProperties(self):
202 """ 203 Get a list of all properties. 204 205 @rtype: list of L{RegistryEntryProperty} 206 """ 207 return self.properties.values()
208
209 - def hasProperty(self, name):
210 """ 211 Check if the component has a property with the given name. 212 """ 213 return name in self.properties.keys()
214
215 - def getEntryByType(self, type):
216 """ 217 Get the entry point for the given type of entry. 218 219 @type type: string 220 """ 221 return self.entries[type]
222
223 - def getEntry(self):
224 return self.entries['default']
225
226 - def getEntries(self):
227 return self.entries.values()
228
229 - def getType(self):
230 return self.type
231
232 - def getSocket(self):
233 return self.socket
234
235 -class RegistryEntryBundle:
236 "This class represents a <bundle> entry in the registry"
237 - def __init__(self, name, project, under, dependencies, directories):
238 self.name = name 239 self.project = project 240 self.under = under 241 self.dependencies = dependencies 242 self.directories = directories
243
244 - def __repr__(self):
245 return '<Bundle name=%s>' % self.name
246
247 - def getName(self):
248 return self.name
249
250 - def getDependencies(self):
251 """ 252 @rtype: list of str 253 """ 254 return self.dependencies
255
256 - def getDirectories(self):
257 """ 258 @rtype: list of L{RegistryEntryBundleDirectory} 259 """ 260 return self.directories
261
262 - def getProject(self):
263 return self.project
264
265 - def getUnder(self):
266 return self.under
267
268 - def getBaseDir(self):
269 if self.project == configure.PACKAGE: 270 return getattr(configure, self.under) 271 272 from flumotion.project import project 273 return project.get(self.project, self.under)
274
275 -class RegistryEntryBundleDirectory:
276 "This class represents a <directory> entry in the registry"
277 - def __init__(self, name, files):
278 self.name = name 279 self.files = files
280
281 - def getName(self):
282 return self.name
283
284 - def getFiles(self):
285 return self.files
286
287 -class RegistryEntryBundleFilename:
288 "This class represents a <filename> entry in the registry"
289 - def __init__(self, location, relative):
290 self.location = location 291 self.relative = relative
292
293 - def getLocation(self):
294 return self.location
295
296 - def getRelative(self):
297 return self.relative
298
299 -class RegistryEntryProperty:
300 "This class represents a <property> entry in the registry"
301 - def __init__(self, name, type, description, required=False, multiple=False):
302 self.name = name 303 self.type = type 304 self.description = description 305 # we don't want to end up with the string "None" 306 if not self.description: 307 self.description = "" 308 self.required = required 309 self.multiple = multiple
310
311 - def __repr__(self):
312 return '<Property name=%s>' % self.name
313
314 - def getName(self):
315 return self.name
316
317 - def getType(self):
318 return self.type
319
320 - def getDescription(self):
321 return self.description
322
323 - def isRequired(self):
324 return self.required
325
326 - def isMultiple(self):
327 return self.multiple
328
329 -class RegistryEntryCompoundProperty(RegistryEntryProperty):
330 "This class represents a <compound-property> entry in the registry"
331 - def __init__(self, name, description, properties, required=False, 332 multiple=False):
333 RegistryEntryProperty.__init__(self, name, 'compound', description, 334 required, multiple) 335 self.properties = properties
336
337 - def __repr__(self):
338 return '<Compound-property name=%s>' % self.name
339
340 - def getProperties(self):
341 """ 342 Get a list of all sub-properties. 343 344 @rtype: list of L{RegistryEntryProperty} 345 """ 346 return self.properties.values()
347
348 - def hasProperty(self, name):
349 """ 350 Check if the compound-property has a sub-property with the 351 given name. 352 """ 353 return name in self.properties
354
355 -class RegistryEntryFile:
356 "This class represents a <file> entry in the registry"
357 - def __init__(self, filename, type):
358 self.filename = filename 359 self.type = type
360
361 - def getName(self):
362 return os.path.basename(self.filename)
363
364 - def getType(self):
365 return self.type
366
367 - def getFilename(self):
368 return self.filename
369
370 - def isType(self, type):
371 return self.type == type
372
373 -class RegistryEntryEntry:
374 "This class represents a <entry> entry in the registry"
375 - def __init__(self, type, location, function):
376 self.type = type 377 self.location = location 378 self.function = function
379
380 - def getType(self):
381 return self.type
382
383 - def getLocation(self):
384 return self.location
385
386 - def getModuleName(self, base=None):
387 if base: 388 path = os.path.join(base, self.getLocation()) 389 else: 390 path = self.getLocation() 391 return common.pathToModuleName(path)
392
393 - def getFunction(self):
394 return self.function
395
396 -class RegistryEntryEater:
397 "This class represents a <eater> entry in the registry"
398 - def __init__(self, name, required=True, multiple=False):
399 self.name = name 400 self.required = required 401 self.multiple = multiple
402
403 - def getName(self):
404 return self.name
405
406 - def getRequired(self):
407 return self.required
408
409 - def getMultiple(self):
410 return self.multiple
411
412 -class RegistryEntryWizard(pb.Copyable):
413 "This class represents a <wizard> entry in the registry"
414 - def __init__(self, componentType, type, description, feeder, 415 eater, accepts, provides):
416 self.componentType = componentType 417 self.type = type 418 self.description = description 419 self.feeder = feeder 420 self.eater = eater 421 self.accepts = accepts 422 self.provides = provides
423
424 - def __repr__(self):
425 return '<wizard %s type=%s, feeder=%s>' % (self.componentType, 426 self.type, self.feeder)
427 428
429 -class RegistryEntryWizardFormat(pb.Copyable):
430 """ 431 This class represents an <accept-format> or <provide-format> 432 entry in the registry 433 """
434 - def __init__(self, media_type):
435 self.media_type = media_type
436 437
438 -class RegistryParser(fxml.Parser):
439 """ 440 Registry parser 441 442 I have two modes, one to parse registries and another one to parse 443 standalone component files. 444 445 For parsing registries use the parseRegistry function and for components 446 use parseRegistryFile. 447 448 I also have a list of all components and directories which the 449 registry uses (instead of saving its own copy) 450 """ 451
452 - def __init__(self):
453 self.clean()
454
455 - def clean(self):
456 self._components = {} 457 self._directories = {} # path -> RegistryDirectory 458 self._bundles = {} 459 self._plugs = {}
460
461 - def getComponents(self):
462 return self._components.values()
463
464 - def getComponent(self, name):
465 try: 466 return self._components[name] 467 except KeyError: 468 raise errors.UnknownComponentError("unknown component type:" 469 " %s" % (name,))
470
471 - def getPlugs(self):
472 return self._plugs.values()
473
474 - def getPlug(self, name):
475 try: 476 return self._plugs[name] 477 except KeyError: 478 raise errors.UnknownPlugError("unknown plug type: %s" 479 % (name,))
480
481 - def _parseComponents(self, node):
482 # <components> 483 # <component> 484 # </components> 485 486 components = {} 487 def addComponent(comp): 488 components[comp.getType()] = comp
489 490 parsers = {'component': (self._parseComponent, addComponent)} 491 self.parseFromTable(node, parsers) 492 493 return components
494
495 - def _parseComponent(self, node):
496 # <component type="..." base="..." description="..."> 497 # <source> 498 # <eater> 499 # <feeder> 500 # <properties> 501 # <entries> 502 # <synchronization> 503 # <sockets> 504 # <wizard> 505 # </component> 506 507 componentType, baseDir, description = self.parseAttributes(node, 508 required=('type', 'base'), optional=('description',)) 509 510 files = [] 511 source = fxml.Box(None) 512 entries = {} 513 eaters = [] 514 feeders = [] 515 synchronization = fxml.Box((False, 100)) 516 sockets = [] 517 properties = {} 518 wizards = [] 519 520 # Merge in options for inherit 521 #if node.hasAttribute('inherit'): 522 # base_type = str(node.getAttribute('inherit')) 523 # base = self.getComponent(base_type) 524 # for prop in base.getProperties(): 525 # properties[prop.getName()] = prop 526 527 parsers = { 528 'source': (self._parseSource, source.set), 529 'properties': (self._parseProperties, properties.update), 530 'files': (self._parseFiles, files.extend), 531 'entries': (self._parseEntries, entries.update), 532 'eater': (self._parseEater, eaters.append), 533 'feeder': (self._parseFeeder, feeders.append), 534 'synchronization': (self._parseSynchronization, 535 synchronization.set), 536 'sockets': (self._parseSockets, sockets.extend), 537 'wizard': (self._parseComponentWizard, wizards.append), 538 } 539 self.parseFromTable(node, parsers) 540 541 source = source.unbox() 542 needs_sync, clock_priority = synchronization.unbox() 543 544 return RegistryEntryComponent(self.filename, 545 componentType, source, description, 546 baseDir, properties, files, 547 entries, eaters, feeders, 548 needs_sync, clock_priority, 549 sockets, wizards)
550
551 - def _parseSource(self, node):
552 # <source location="..."/> 553 location, = self.parseAttributes(node, ('location',)) 554 return location
555
556 - def _parseProperty(self, node):
557 # <property name="..." type="" required="yes/no" multiple="yes/no"/> 558 # returns: RegistryEntryProperty 559 560 attrs = self.parseAttributes(node, required=('name', 'type'), 561 optional=('required', 'multiple', 'description')) 562 name, propertyType, required, multiple, description = attrs 563 required = common.strToBool(required) 564 multiple = common.strToBool(multiple) 565 return RegistryEntryProperty(name, propertyType, description, 566 required=required, multiple=multiple)
567
568 - def _parseCompoundProperty(self, node):
569 # <compound-property name="..." required="yes/no" multiple="yes/no"> 570 # <property ... />* 571 # <compound-property ... >...</compound-property>* 572 # </compound-property> 573 # returns: RegistryEntryCompoundProperty 574 575 attrs = self.parseAttributes(node, required=('name',), 576 optional=('required', 'multiple', 'description')) 577 name, required, multiple, description = attrs 578 required = common.strToBool(required) 579 multiple = common.strToBool(multiple) 580 581 properties = {} 582 def addProperty(prop): 583 properties[prop.getName()] = prop
584 585 parsers = {'property': (self._parseProperty, addProperty), 586 'compound-property': (self._parseCompoundProperty, 587 addProperty)} 588 self.parseFromTable(node, parsers) 589 590 return RegistryEntryCompoundProperty(name, description, properties, 591 required=required, multiple=multiple) 592
593 - def _parseProperties(self, node):
594 # <properties> 595 # <property>* 596 # <compound-property>* 597 # </properties> 598 599 properties = {} 600 def addProperty(prop): 601 properties[prop.getName()] = prop
602 603 parsers = {'property': (self._parseProperty, addProperty), 604 'compound-property': (self._parseCompoundProperty, 605 addProperty)} 606 607 self.parseFromTable(node, parsers) 608 609 return properties 610
611 - def _parseFile(self, node):
612 # <file name="..." type=""/> 613 # returns: RegistryEntryFile 614 615 name, fileType = self.parseAttributes(node, ('name', 'type')) 616 directory = os.path.split(self.filename)[0] 617 filename = os.path.join(directory, name) 618 return RegistryEntryFile(filename, fileType)
619
620 - def _parseFiles(self, node):
621 # <files> 622 # <file> 623 # </files> 624 625 files = [] 626 parsers = {'file': (self._parseFile, files.append)} 627 628 self.parseFromTable(node, parsers) 629 630 return files
631
632 - def _parseSocket(self, node):
633 # <socket type=""/> 634 # returns: str of the type 635 636 socketType, = self.parseAttributes(node, ('type',)) 637 return socketType
638
639 - def _parseSockets(self, node):
640 # <sockets> 641 # <socket> 642 # </sockets> 643 644 sockets = [] 645 parsers = {'socket': (self._parseSocket, sockets.append)} 646 647 self.parseFromTable(node, parsers) 648 649 return sockets
650
651 - def _parseEntry(self, node):
652 attrs = self.parseAttributes(node, ('type', 'location', 'function')) 653 entryType, location, function = attrs 654 return RegistryEntryEntry(entryType, location, function)
655
656 - def _parseEntries(self, node):
657 # <entries> 658 # <entry> 659 # </entries> 660 # returns: dict of type -> entry 661 662 entries = {} 663 def addEntry(entry): 664 if entry.getType() in entries: 665 raise fxml.ParserError("entry %s already specified" 666 % entry.getType()) 667 entries[entry.getType()] = entry
668 669 parsers = {'entry': (self._parseEntry, addEntry)} 670 671 self.parseFromTable(node, parsers) 672 673 return entries 674
675 - def _parseEater(self, node):
676 # <eater name="..." [required="yes/no"] [multiple="yes/no"]/> 677 attrs = self.parseAttributes(node, ('name',), ('required', 'multiple')) 678 name, required, multiple = attrs 679 # only required defaults to True 680 required = common.strToBool(required or 'True') 681 multiple = common.strToBool(multiple) 682 683 return RegistryEntryEater(name, required, multiple)
684
685 - def _parseFeeder(self, node):
686 # <feeder name="..."/> 687 name, = self.parseAttributes(node, ('name',)) 688 return name
689
690 - def _parseSynchronization(self, node):
691 # <synchronization [required="yes/no"] [clock-priority="100"]/> 692 attrs = self.parseAttributes(node, (), ('required', 'clock-priority')) 693 required, clock_priority = attrs 694 required = common.strToBool(required) 695 clock_priority = int(clock_priority or '100') 696 return required, clock_priority
697
698 - def _parsePlugEntry(self, node):
699 attrs = self.parseAttributes(node, ('location', 'function'), ('type',)) 700 location, function, entryType = attrs 701 if not entryType: 702 entryType = 'default' 703 return RegistryEntryEntry(entryType, location, function)
704
705 - def _parseDefaultPlugEntry(self, node):
706 return {'default': self._parsePlugEntry(node)}
707
708 - def _parsePlugEntries(self, node):
709 # <entries> 710 # <entry> 711 # </entries> 712 # returns: dict of type -> entry 713 714 entries = {} 715 def addEntry(entry): 716 if entry.getType() in entries: 717 raise fxml.ParserError("entry %s already specified" 718 % entry.getType()) 719 entries[entry.getType()] = entry
720 721 parsers = {'entry': (self._parsePlugEntry, addEntry)} 722 723 self.parseFromTable(node, parsers) 724 725 return entries 726
727 - def _parsePlug(self, node):
728 # <plug socket="..." type="..."> 729 # <entries> 730 # <entry> 731 # <properties> 732 # <wizard> 733 # </plug> 734 735 plugType, socket = self.parseAttributes(node, ('type', 'socket')) 736 737 entries = {} 738 properties = {} 739 wizards = [] 740 741 parsers = { 742 'entries': (self._parsePlugEntries, entries.update), 743 # backwards compatibility 744 'entry': (self._parseDefaultPlugEntry, entries.update), 745 'properties': (self._parseProperties, properties.update), 746 'wizard': (self._parsePlugWizard, wizards.append), 747 } 748 749 self.parseFromTable(node, parsers) 750 751 if not 'default' in entries: 752 raise fxml.ParserError( 753 "<plug> %s needs a default <entry>" % plugType) 754 755 return RegistryEntryPlug(self.filename, plugType, 756 socket, entries, properties, 757 wizards)
758
759 - def _parsePlugs(self, node):
760 # <plugs> 761 # <plug> 762 # </plugs> 763 764 self.checkAttributes(node) 765 766 plugs = {} 767 def addPlug(plug): 768 plugs[plug.getType()] = plug
769 770 parsers = {'plug': (self._parsePlug, addPlug)} 771 self.parseFromTable(node, parsers) 772 773 return plugs 774 775 ## Component registry specific functions
776 - def parseRegistryFile(self, file):
777 """ 778 @param file: The file to parse, either as an open file object, 779 or as the name of a file to open. 780 @type file: str or file. 781 """ 782 if isinstance(file, basestring): 783 self.filename = file 784 else: 785 self.filename = getattr(file, 'name', '<string>') 786 root = self.getRoot(file) 787 node = root.documentElement 788 789 if node.nodeName != 'registry': 790 # ignore silently, since this function is used to parse all 791 # .xml files encountered 792 self.debug('%s does not have registry as root tag' % self.filename) 793 return 794 795 # shouldn't have <directories> elements in registry fragments 796 self._parseRoot(node, disallowed=['directories']) 797 root.unlink()
798
799 - def _parseBundles(self, node):
800 # <bundles> 801 # <bundle> 802 # </bundles> 803 804 bundles = {} 805 def addBundle(bundle): 806 bundles[bundle.getName()] = bundle
807 808 parsers = {'bundle': (self._parseBundle, addBundle)} 809 self.parseFromTable(node, parsers) 810 811 return bundles 812
813 - def _parseBundle(self, node):
814 # <bundle name="..."> 815 # <dependencies> 816 # <directories> 817 # </bundle> 818 819 attrs = self.parseAttributes(node, ('name',), ('project', 'under')) 820 name, project, under = attrs 821 project = project or configure.PACKAGE 822 under = under or 'pythondir' 823 824 dependencies = [] 825 directories = [] 826 827 parsers = {'dependencies': (self._parseBundleDependencies, 828 dependencies.extend), 829 'directories': (self._parseBundleDirectories, 830 directories.extend)} 831 self.parseFromTable(node, parsers) 832 833 return RegistryEntryBundle(name, project, under, dependencies, directories)
834
835 - def _parseBundleDependency(self, node):
836 name, = self.parseAttributes(node, ('name',)) 837 return name
838
839 - def _parseBundleDependencies(self, node):
840 # <dependencies> 841 # <dependency name=""> 842 # </dependencies> 843 dependencies = [] 844 845 parsers = {'dependency': (self._parseBundleDependency, 846 dependencies.append)} 847 self.parseFromTable(node, parsers) 848 849 return dependencies
850
851 - def _parseBundleDirectories(self, node):
852 # <directories> 853 # <directory> 854 # </directories> 855 directories = [] 856 857 parsers = {'directory': (self._parseBundleDirectory, 858 directories.append)} 859 self.parseFromTable(node, parsers) 860 861 return directories
862
863 - def _parseBundleDirectoryFilename(self, node, name):
864 attrs = self.parseAttributes(node, ('location',), ('relative',)) 865 location, relative = attrs 866 867 if not relative: 868 relative = os.path.join(