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 stat 
  27  import errno 
  28  import sys 
  29  from StringIO import StringIO 
  30   
  31  from xml.sax import saxutils 
  32  from twisted.spread import pb 
  33   
  34  from flumotion.common import common, log, errors, fxml, python 
  35  from flumotion.common.python import makedirs 
  36  from flumotion.common.bundle import BundlerBasket, MergedBundler 
  37  from flumotion.configure import configure 
  38   
  39  __all__ = ['ComponentRegistry', 'registry'] 
  40  __version__ = "$Rev: 7991 $" 
  41   
  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   
59 -def _getMTime(file):
60 return os.stat(file)[stat.ST_MTIME]
61 62
63 -class RegistryEntryScenario(pb.Copyable, pb.RemoteCopy):
64 """ 65 I represent a <scenario> entry in the registry 66 """ 67
68 - def __init__(self, type, description, base, entries):
69 """ 70 @param type: the type of this scenario 71 @type type: str 72 @param description: description of this scenario 73 @type description: str 74 @param base: base directory where this scenario is placed 75 @type base: str 76 @param entries: dict of entry point type -> entry 77 @type entries: dict of str -> L{RegistryEntryEntry} 78 """ 79 self.type = type 80 # we don't want to end up with the string "None" 81 self.description = description or "" 82 self.base = base 83 self.entries = entries
84
85 - def getEntries(self):
86 """ 87 Get the entries asociated with this scenario 88 89 @rtype: list of L{RegistryEntryEntry} 90 """ 91 return self.entries.values()
92
93 - def getEntryByType(self, type):
94 """ 95 Get the entry point for the given type of entry. 96 97 @param type: The type of the wanted entry. 98 @type type: string 99 100 @rtype: L{RegistryEntryEntry} 101 """ 102 return self.entries[type]
103
104 - def getType(self):
105 return self.type
106
107 - def getBase(self):
108 return self.base
109
110 - def getDescription(self):
111 return self.description
112 113 pb.setUnjellyableForClass(RegistryEntryScenario, RegistryEntryScenario) 114 115
116 -class RegistryEntryComponent(pb.Copyable, pb.RemoteCopy):
117 """ 118 I represent a <component> entry in the registry 119 """ 120 # RegistryEntryComponent has a constructor with a lot of arguments, 121 # but that's ok here. Allow it through pychecker. 122 __pychecker__ = 'maxargs=15' 123
124 - def __init__(self, filename, type, 125 source, description, base, properties, files, 126 entries, eaters, feeders, needs_sync, clock_priority, 127 sockets, wizards):
128 """ 129 @param filename: name of the XML file this component is parsed from 130 @type filename: str 131 @param properties: dict of name -> property 132 @type properties: dict of str -> L{RegistryEntryProperty} 133 @param files: list of files 134 @type files: list of L{RegistryEntryFile} 135 @param entries: dict of entry point type -> entry 136 @type entries: dict of str -> L{RegistryEntryEntry} 137 @param sockets: list of sockets supported by the component 138 @type sockets: list of str 139 @param wizards: list of wizard entries 140 @type wizards: list of L{RegistryEntryWizard} 141 """ 142 self.filename = filename 143 self.type = type 144 self.source = source 145 self.description = description 146 # we don't want to end up with the string "None" 147 if not self.description: 148 self.description = "" 149 self.base = base 150 self.properties = properties 151 self.files = files 152 self.entries = entries 153 self.eaters = eaters 154 self.feeders = feeders 155 self.needs_sync = needs_sync 156 self.clock_priority = clock_priority 157 self.sockets = sockets 158 self.wizards = wizards
159
160 - def getProperties(self):
161 """ 162 Get a list of all properties. 163 164 @rtype: list of L{RegistryEntryProperty} 165 """ 166 return self.properties.values()
167
168 - def hasProperty(self, name):
169 """ 170 Check if the component has a property with the given name. 171 """ 172 return name in self.properties.keys()
173
174 - def getFiles(self):
175 """ 176 @rtype: list of L{RegistryEntryFile} 177 """ 178 return self.files
179
180 - def getEntries(self):
181 return self.entries.values()
182
183 - def getEntryByType(self, type):
184 """ 185 Get the entry point for the given type of entry. 186 187 @type type: string 188 """ 189 return self.entries[type]
190
191 - def getGUIEntry(self):
192 if not self.files: 193 return 194 195 # FIXME: Handle multiple files 196 if len(self.files) > 1: 197 return 198 199 return self.files[0].getFilename()
200
201 - def getType(self):
202 return self.type
203
204 - def getBase(self):
205 return self.base
206
207 - def getDescription(self):
208 return self.description
209
210 - def getSource(self):
211 return self.source
212
213 - def getEaters(self):
214 return self.eaters
215
216 - def getFeeders(self):
217 return self.feeders
218
219 - def getNeedsSynchronization(self):
220 return self.needs_sync
221
222 - def getClockPriority(self):
223 return self.clock_priority
224
225 - def getSockets(self):
226 return self.sockets
227 pb.setUnjellyableForClass(RegistryEntryComponent, RegistryEntryComponent) 228 229
230 -class RegistryEntryPlug:
231 """ 232 I represent a <plug> entry in the registry 233 """ 234
235 - def __init__(self, filename, type, 236 description, socket, entries, properties, wizards):
237 """ 238 @param filename: name of the XML file this plug is parsed from 239 @type filename: str 240 @param type: the type of plug 241 @type type: str 242 @param description: the translatable description of the plug 243 @type description: str 244 @param socket: the fully qualified class name of the socket this 245 plug can be plugged in to 246 @type socket: str 247 @param entries: entry points for instantiating the plug 248 @type entries: list of L{RegistryEntryEntry} 249 @param properties: properties of the plug 250 @type properties: dict of str -> L{RegistryEntryProperty} 251 @param wizards: list of wizard entries 252 @type wizards: list of L{RegistryEntryWizard} 253 """ 254 self.filename = filename 255 self.type = type 256 self.description = description 257 self.socket = socket 258 self.entries = entries 259 self.properties = properties 260 self.wizards = wizards
261
262 - def getProperties(self):
263 """ 264 Get a list of all properties. 265 266 @rtype: list of L{RegistryEntryProperty} 267 """ 268 return self.properties.values()
269
270 - def hasProperty(self, name):
271 """ 272 Check if the component has a property with the given name. 273 """ 274 return name in self.properties.keys()
275
276 - def getEntryByType(self, type):
277 """ 278 Get the entry point for the given type of entry. 279 280 @type type: string 281 """ 282 return self.entries[type]
283
284 - def getEntry(self):
285 return self.entries['default']
286
287 - def getEntries(self):
288 return self.entries.values()
289
290 - def getType(self):
291 return self.type
292
293 - def getDescription(self):
294 return self.description
295
296 - def getSocket(self):
297 return self.socket
298 299
300 -class RegistryEntryBundle:
301 "This class represents a <bundle> entry in the registry" 302
303 - def __init__(self, name, project, under, dependencies, directories):
304 self.name = name 305 self.project = project 306 self.under = under 307 self.dependencies = dependencies 308 self.directories = directories
309
310 - def __repr__(self):
311 return '<Bundle name=%s>' % self.name
312
313 - def getName(self):
314 return self.name
315
316 - def getDependencies(self):
317 """ 318 @rtype: list of str 319 """ 320 return self.dependencies
321
322 - def getDirectories(self):
323 """ 324 @rtype: list of L{RegistryEntryBundleDirectory} 325 """ 326 return self.directories
327
328 - def getProject(self):
329 return self.project
330
331 - def getUnder(self):
332 return self.under
333
334 - def getBaseDir(self):
335 if self.project == configure.PACKAGE: 336 return getattr(configure, self.under) 337 338 from flumotion.project import project 339 return project.get(self.project, self.under)
340 341
342 -class RegistryEntryBundleDirectory:
343 "This class represents a <directory> entry in the registry" 344
345 - def __init__(self, name, files):
346 self.name = name 347 self.files = files
348
349 - def getName(self):
350 return self.name
351
352 - def getFiles(self):
353 return self.files
354 355
356 -class RegistryEntryBundleFilename:
357 "This class represents a <filename> entry in the registry" 358
359 - def __init__(self, location, relative):
360 self.location = location 361 self.relative = relative
362
363 - def getLocation(self):
364 return self.location
365
366 - def getRelative(self):
367 return self.relative
368 369
370 -class RegistryEntryProperty:
371 "This class represents a <property> entry in the registry" 372
373 - def __init__(self, name, type, description, 374 required=False, multiple=False):
375 self.name = name 376 self.type = type 377 self.description = description 378 # we don't want to end up with the string "None" 379 if not self.description: 380 self.description = "" 381 self.required = required 382 self.multiple = multiple
383
384 - def __repr__(self):
385 return '<Property name=%s>' % self.name
386
387 - def getName(self):
388 return self.name
389
390 - def getType(self):
391 return self.type
392
393 - def getDescription(self):
394 return self.description
395
396 - def isRequired(self):
397 return self.required
398
399 - def isMultiple(self):
400 return self.multiple
401 402
403 -class RegistryEntryCompoundProperty(RegistryEntryProperty):
404 "This class represents a <compound-property> entry in the registry" 405
406 - def __init__(self, name, description, properties, required=False, 407 multiple=False):
408 RegistryEntryProperty.__init__(self, name, 'compound', description, 409 required, multiple) 410 self.properties = properties
411
412 - def __repr__(self):
413 return '<Compound-property name=%s>' % self.name
414
415 - def getProperties(self):
416 """ 417 Get a list of all sub-properties. 418 419 @rtype: list of L{RegistryEntryProperty} 420 """ 421 return self.properties.values()
422
423 - def hasProperty(self, name):
424 """ 425 Check if the compound-property has a sub-property with the 426 given name. 427 """ 428 return name in self.properties
429 430
431 -class RegistryEntryFile:
432 "This class represents a <file> entry in the registry" 433
434 - def __init__(self, filename, type):
435 self.filename = filename 436 self.type = type
437
438 - def getName(self):
439 return os.path.basename(self.filename)
440
441 - def getType(self):
442 return self.type
443
444 - def getFilename(self):
445 return self.filename
446
447 - def isType(self, type):
448 return self.type == type
449 450
451 -class RegistryEntryEntry:
452 "This class represents a <entry> entry in the registry" 453
454 - def __init__(self, type, location, function):
455 self.type = type 456 self.location = location 457 self.function = function
458
459 - def getType(self):
460 return self.type
461
462 - def getLocation(self):
463 return self.location
464
465 - def getModuleName(self, base=None):
466 if base: 467 path = os.path.join(base, self.getLocation()) 468 else: 469 path = self.getLocation() 470 return common.pathToModuleName(path)
471
472 - def getFunction(self):
473 return self.function
474 475
476 -class RegistryEntryEater:
477 "This class represents a <eater> entry in the registry" 478
479 - def __init__(self, name, required=True, multiple=False):
480 self.name = name 481 self.required = required 482 self.multiple = multiple
483
484 - def getName(self):
485 return self.name
486
487 - def getRequired(self):
488 return self.required
489
490 - def getMultiple(self):
491 return self.multiple
492 493
494 -class RegistryEntryWizard(pb.Copyable):
495 "This class represents a <wizard> entry in the registry" 496
497 - def __init__(self, componentType, type, description, feeder, 498 eater, accepts, provides):
499 self.componentType = componentType 500 self.type = type 501 self.description = description 502 self.feeder = feeder 503 self.eater = eater 504 self.accepts = accepts 505 self.provides = provides
506
507 - def __repr__(self):
508 return '<wizard %s type=%s, feeder=%s>' % (self.componentType, 509 self.type, self.feeder)
510 511
512 -class RegistryEntryWizardFormat(pb.Copyable):
513 """ 514 This class represents an <accept-format> or <provide-format> 515 entry in the registry 516 """ 517
518 - def __init__(self, media_type):
519 self.media_type = media_type
520 521
522 -class RegistryParser(fxml.Parser):
523 """ 524 Registry parser 525 526 I have two modes, one to parse registries and another one to parse 527 standalone component files. 528 529 For parsing registries use the parseRegistry function and for components 530 use parseRegistryFile. 531 532 I also have a list of all components and directories which the 533 registry uses (instead of saving its own copy) 534 """ 535
536 - def __init__(self):
537 self.clean()
538
539 - def clean(self):
540 self._components = {} 541 self._directories = {} # path -> RegistryDirectory 542 self._bundles = {} 543 self._plugs = {} 544 self._scenarios = {}
545
546 - def getComponents(self):
547 return self._components.values()
548
549 - def getComponent(self, name):
550 try: 551 return self._components[name] 552 except KeyError: 553 raise errors.UnknownComponentError("unknown component type:" 554 " %s" % (name, ))
555
556 - def getScenarios(self):
557 return self._scenarios.values()
558
559 - def getScenarioByType(self, type):
560 if type in self._scenarios: 561 return self._scenarios[type] 562 return None
563
564 - def getPlugs(self):
565 return self._plugs.values()
566
567 - def getPlug(self, name):
568 try: 569 return self._plugs[name] 570 except KeyError: 571 raise errors.UnknownPlugError("unknown plug type: %s" 572 % (name, ))
573
574 - def _parseComponents(self, node):
575 # <components> 576 # <component> 577 # </components> 578 579 components = {} 580 581 def addComponent(comp): 582 components[comp.getType()] = comp
583 584 parsers = {'component': (self._parseComponent, addComponent)} 585 self.parseFromTable(node, parsers) 586 587 return components
588
589 - def _parseComponent(self, node):
590 # <component type="..." base="..." _description="..."> 591 # <source> 592 # <eater> 593 # <feeder> 594 # <properties> 595 # <entries> 596 # <synchronization> 597 # <sockets> 598 # <wizard> 599 # </component> 600 601 # F0.8: remove description, require _description 602 componentType, baseDir, description, _description = \ 603 self.parseAttributes(node, 604 required=('type', 'base'), 605 optional=('description', '_description')) 606 607 # intltool-extract only translates attributes starting with _ 608 if description: 609 import warnings 610 warnings.warn( 611 "Please change '<component description=...'" 612 " to '<component _description=...' for %s" % componentType, 613 DeprecationWarning) 614 if _description: 615 description = _description 616 617 files = [] 618 source = fxml.Box(None) 619 entries = {} 620 eaters = [] 621 feeders = [] 622 synchronization = fxml.Box((False, 100)) 623 sockets = [] 624 properties = {} 625 wizards = [] 626 627 # Merge in options for inherit 628 #if node.hasAttribute('inherit'): 629 # base_type = str(node.getAttribute('inherit')) 630 # base = self.getComponent(base_type) 631 # for prop in base.getProperties(): 632 # properties[prop.getName()] = prop 633 634 parsers = { 635 'source': (self._parseSource, source.set), 636 'properties': (self._parseProperties, properties.update), 637 'files': (self._parseFiles, files.extend), 638 'entries': (self._parseEntries, entries.update), 639 'eater': (self._parseEater, eaters.append), 640 'feeder': (self._parseFeeder, feeders.append), 641 'synchronization': (self._parseSynchronization, 642 synchronization.set), 643 'sockets': (self._parseSockets, sockets.extend), 644 'wizard': (self._parseComponentWizard, wizards.append), 645 } 646 self.parseFromTable(node, parsers) 647 648 source = source.unbox() 649 needs_sync, clock_priority = synchronization.unbox() 650 651 return RegistryEntryComponent(self.filename, 652 componentType, source, description, 653 baseDir, properties, files, 654 entries, eaters, feeders, 655 needs_sync, clock_priority, 656 sockets, wizards)
657
658 - def _parseScenarios(self, node):
659 # <scenarios> 660 # <scenario> 661 # </scenarios> 662 663 scenarios = {} 664 665 def addScenario(scenario): 666 scenarios[scenario.getType()] = scenario
667 668 parsers = {'scenario': (self._parseScenario, addScenario)} 669 self.parseFromTable(node, parsers) 670 671 return scenarios 672
673 - def _parseScenario(self, node):
674 # <scenario type="..." base="..." _description="..."> 675 # <entries> 676 # </scenario> 677 678 scenarioType, baseDir, description = \ 679 self.parseAttributes(node, 680 required=('type', 'base'), 681 optional=('_description', )) 682 683 entries = {} 684 685 parsers = { 686 'entries': (self._parseEntries, entries.update), 687 } 688 689 self.parseFromTable(node, parsers) 690 691 return RegistryEntryScenario(scenarioType, description, 692 baseDir, entries)
693
694 - def _parseSource(self, node):
695 # <source location="..."/> 696 location, = self.parseAttributes(node, ('location', )) 697 return location
698
699 - def _parseProperty(self, node):
700 # <property name="..." type="" required="yes/no" multiple="yes/no"/> 701 # returns: RegistryEntryProperty 702 703 # F0.8: remove description, require _description 704 attrs = self.parseAttributes(node, required=('name', 'type'), 705 optional=('required', 'multiple', 'description', '_description')) 706 name, propertyType, required, multiple, description, _d = attrs 707 if description: 708 import warnings 709 warnings.warn("Please change '<property description=...'" 710 " to '<property _description=...' for %s" % name, 711 DeprecationWarning) 712 if _d: 713 description = _d 714 # see flumotion.common.config.parsePropertyValue 715 allowed = ('string', 'rawstring', 'int', 'long', 'bool', 716 'float', 'fraction') 717 if propertyType not in allowed: 718 raise fxml.ParserError( 719 "<property> %s's type is not one of %s" % ( 720 name, ", ".join(allowed))) 721 required = common.strToBool(required) 722 multiple = common.strToBool(multiple) 723 return RegistryEntryProperty(name, propertyType, description, 724 required=required, multiple=multiple)
725
726 - def _parseCompoundProperty(self, node):
727 # <compound-property name="..." required="yes/no" multiple="yes/no"> 728 # <property ... />* 729 # <compound-property ... >...</compound-property>* 730 # </compound-property> 731 # returns: RegistryEntryCompoundProperty 732 733 # F0.8: remove description, require _description 734 attrs = self.parseAttributes(node, required=('name', ), 735 optional=('required', 'multiple', 'description', '_description')) 736 name, required, multiple, description, _description = attrs 737 if description: 738 import warnings 739 warnings.warn("Please change '<compound-property description=...'" 740 " to '<compound-property _description=...' for %s" % name, 741 DeprecationWarning) 742 if _description: 743 description = _description 744 # see flumotion.common.config.parsePropertyValue 745 required = common.strToBool(required) 746 multiple = common.strToBool(multiple) 747 748 properties = {} 749 750 def addProperty(prop): 751 properties[prop.getName()] = prop
752 753 parsers = {'property': (self._parseProperty, addProperty), 754 'compound-property': (self._parseCompoundProperty, 755 addProperty)} 756 self.parseFromTable(node, parsers) 757 758 return RegistryEntryCompoundProperty(name, description, properties, 759 required=required, multiple=multiple) 760
761 - def _parseProperties(self, node):
762 # <properties> 763 # <property>* 764 # <compound-property>* 765 # </properties> 766 767 properties = {} 768 769 def addProperty(prop): 770 properties[prop.getName()] = prop
771 772 parsers = {'property': (self._parseProperty, addProperty), 773 'compound-property': (self._parseCompoundProperty, 774 addProperty)} 775 776 self.parseFromTable(node, parsers) 777 778 return properties 779
780 - def _parseFile(self, node):
781 # <file name="..." type=""/> 782 # returns: RegistryEntryFile 783 784 name, fileType = self.parseAttributes(node, ('name', 'type')) 785 directory = os.path.split(self.filename)[0] 786 filename = os.path.join(directory, name) 787 return RegistryEntryFile(filename, fileType)
788
789 - def _parseFiles(self, node):
790 # <files> 791 # <file> 792 # </files> 793 794 files = [] 795 parsers = {'file': (self._parseFile, files.append)} 796 797 self.parseFromTable(node, parsers) 798 799 return files
800
801 - def _parseSocket(self, node):
802 # <socket type=""/> 803 # returns: str of the type 804 805 socketType, = self.parseAttributes(node, ('type', )) 806 return socketType
807
808 - def _parseSockets(self, node):
809 # <sockets> 810 # <socket> 811 # </sockets> 812 813 sockets = [] 814 parsers = {'socket': (self._parseSocket, sockets.append)} 815 816 self.parseFromTable(node, parsers) 817 818 return sockets
819
820 - def _parseEntry(self, node):
821 attrs = self.parseAttributes(node, ('type', 'location', 'function')) 822 entryType, location, function = attrs 823 return RegistryEntryEntry(entryType, location, function)
824
825 - def _parseEntries(self, node):
826 # <entries> 827 # <entry> 828 # </entries> 829 # returns: dict of type -> entry 830 831 entries = {} 832 833 def addEntry(entry): 834 if entry.getType() in entries: 835 raise fxml.ParserError("entry %s already specified" 836 % entry.getType()) 837 entries[entry.getType()] = entry
838 839 parsers = {'entry': (self._parseEntry, addEntry)} 840 841 self.parseFromTable(node, parsers) 842 843 return entries 844
845 - def _parseEater(self, node):
846 # <eater name="..." [required="yes/no"] [multiple="yes/no"]/> 847 attrs = self.parseAttributes(node, ('name', ), 848 ('required', 'multiple')) 849 name, required, multiple = attrs 850 # only required defaults to True 851 required = common.strToBool(required or 'True') 852 multiple = common.strToBool(multiple) 853 854 return RegistryEntryEater(name, required, multiple)
855
856 - def _parseFeeder(self, node):
857 # <feeder name="..."/> 858 name, = self.parseAttributes(node, ('name', )) 859 return name
860
861 - def _parseSynchronization(self, node):
862 # <synchronization [required="yes/no"] [clock-priority="100"]/> 863 attrs = self.parseAttributes(node, (), ('required', 'clock-priority')) 864 required, clock_priority = attrs 865 required = common.strToBool(required) 866 clock_priority = int(clock_priority or '100') 867 return required, clock_priority
868
869 - def _parsePlugEntry(self, node):
870 attrs = self.parseAttributes(node, 871 ('location', 'function'), ('type', )) 872 location, function, entryType = attrs 873 if not entryType: 874 entryType = 'default' 875 return RegistryEntryEntry(entryType, location, function)
876
877 - def _parseDefaultPlugEntry(self, node):
878 return {'default': self._parsePlugEntry(node)}
879
880 - def _parsePlugEntries(self, node):
881 # <entries> 882 # <entry> 883 # </entries> 884 # returns: dict of type -> entry 885 886 entries = {} 887 888 def addEntry(entry): 889 if entry.getType() in entries: 890 raise fxml.ParserError("entry %s already specified" 891 % entry.getType()) 892 entries[entry.getType()] = entry
893 894 parsers = {'entry': (self._parsePlugEntry, addEntry)} 895 896 self.parseFromTable(node, parsers) 897 898 return entries 899
900 - def _parsePlug(self, node):
901 # <plug socket="..." type="..." _description="..."> 902 # <entries> 903 # <entry> 904 # <properties> 905 # <wizard> 906 # </plug> 907 908 # F0.8: make _description be required 909 plugType, socket, description = \ 910 self.parseAttributes(node, required=('type', 'socket'), 911 optional=('_description', )) 912 913 if not description: 914 import warnings 915 warnings.warn( 916 "Please add '_description=...' attribute to plug '%s'" % 917 plugType, 918 DeprecationWarning) 919 description = 'TODO' 920 921 entries = {} 922 properties = {} 923 wizards = [] 924 925 parsers = { 926 'entries': (self._parsePlugEntries, entries.update), 927 # backwards compatibility 928 'entry': (self._parseDefaultPlugEntry, entries.update), 929 'properties': (self._parseProperties, properties.update), 930 'wizard': (self._parsePlugWizard, wizards.append), 931 } 932 933 self.parseFromTable(node, parsers) 934 935 if not 'default' in entries: 936 raise fxml.ParserError( 937 "<plug> %s needs a default <entry>" % plugType) 938 939 return RegistryEntryPlug(self.filename, plugType, description, 940 socket, entries, properties, 941 wizards)
942
943 - def _parsePlugs(self, node):
944 # <plugs> 945 # <plug> 946 # </plugs> 947 948 self.checkAttributes(node) 949 950 plugs = {} 951 952 def addPlug(plug): 953 plugs[plug.getType()] = plug
954 955 parsers = {'plug': (self._parsePlug, addPlug)} 956 self.parseFromTable(node, parsers) 957 958 return plugs 959 960 ## Component registry specific functions 961
962 - def parseRegistryFile(self, file):
963 """ 964 @param file: The file to parse, either as an open file object, 965 or as the name of a file to open. 966 @type file: str or file. 967 """ 968 if isinstance(file, basestring): 969 self.filename = file 970 else: 971 self.filename = getattr(file, 'name', '<string>') 972 root = self.getRoot(file) 973 node = root.documentElement 974 975 if node.nodeName != 'registry': 976 # ignore silently, since this function is used to parse all 977 # .xml files encountered 978 self.debug('%s does not have registry as root tag' % self.filename) 979 return 980 981 # shouldn't have <directories> elements in registry fragments 982 self._parseRoot(node, disallowed=['directories']) 983 root.unlink()
984
985 - def _parseBundles(self, node):
986 # <bundles> 987 # <bundle> 988 # </bundles> 989 990 bundles = {} 991 992 def addBundle(bundle): 993 bundles[bundle.getName()] = bundle
994 995 parsers = {'bundle': (self._parseBundle, addBundle)} 996 self.parseFromTable(node, parsers) 997 998 return bundles 999
1000 - def _parseBundle(self, node):
1001 # <bundle name="..."> 1002 # <dependencies> 1003 # <directories> 1004 # </bundle> 1005 1006 attrs = self.parseAttributes(node, ('name', ), ('project', 'under')) 1007 name, project, under = attrs 1008 project = project or configure.PACKAGE 1009 under = under or 'pythondir' 1010 1011 dependencies = [] 1012 directories = [] 1013 1014 parsers = {'dependencies': (self._parseBundleDependencies, 1015 dependencies.extend), 1016 'directories': (self._parseBundleDirectories, 1017 directories.extend)} 1018 self.parseFromTable(node, parsers) 1019 1020 return RegistryEntryBundle(name, project, under, 1021 dependencies, directories)
1022
1023 - def _parseBundleDependency(self, node):
1024 name, = self.parseAttributes(node, ('name', )) 1025 return name
1026
1027 - def _parseBundleDependencies(self, node):
1028 # <dependencies> 1029 # <dependency name=""> 1030 # </dependencies> 1031 dependencies = [] 1032 1033 parsers = {'dependency': (self._parseBundleDependency, 1034 dependencies.append)} 1035 self.parseFromTable(node, parsers) 1036 1037 return dependencies
1038
1039 - def _parseBundleDirectories(self, node):
1040 # <directories> 1041 # <directory> 1042 # </directories> 1043 directories = [] 1044 1045 parsers = {'directory': (self._parseBundleDirectory, 1046 directories.append)} 1047 self.parseFromTable(node, parsers) 1048 1049 return directories
1050
1051 - def _parseBundleDirectoryFilename(self, node, name):
1052 attrs = self.parseAttributes(node, ('location', ), ('relative', )) 1053 location, relative = attrs 1054 1055 if not relative: 1056 relative = os.path.join(name, location) 1057 1058 return RegistryEntryBundleFilename(location, relative)
1059
1060 - def _parseBundleDirectory(self, node):
1061 # <directory name=""> 1062 # <filename location="" [ relative="" ] > 1063 # </directory> 1064 name, = self.parseAttributes(node, ('name', )) 1065 1066 filenames = [] 1067 1068 def parseFilename(node): 1069 return self._parseBundleDirectoryFilename(node, name)
1070 1071 parsers = {'filename': (parseFilename, filenames.append)} 1072 self.parseFromTable(node, parsers) 1073 1074 return RegistryEntryBundleDirectory(name, filenames) 1075 1076 ## Base registry specific functions 1077
1078 - def parseRegistry(self, file):
1079 """ 1080 @param file: The file to parse, either as an open file object, 1081 or as the name of a file to open. 1082 @type file: str or file. 1083 """ 1084 if isinstance(file, basestring): 1085 self.filename = file 1086 else: 1087 self.filename = getattr(file, 'name', '<string>') 1088 root = self.getRoot(file) 1089 self._parseRoot(root.documentElement) 1090 root.unlink()
1091
1092 - def getDirectories(self):
1093 return self._directories.values()
1094
1095 - def getDirectory(self, name):
1096 return self._directories[name]
1097
1098 - def addDirectory(self, directory):
1099 """ 1100 Add a registry path object to the parser. 1101 1102 @type directory: {RegistryDirectory} 1103 """ 1104 self._directories[directory.getPath()] = directory
1105
1106 - def removeDirectoryByPath(self, path):
1107 """ 1108 Remove a directory from the parser given the path. 1109 Used when the path does not actually contain any registry information. 1110 """ 1111 if path in self._directories.keys(): 1112 del self._directories[path]
1113
1114 - def _parseRoot(self, node, disallowed=None):
1115 # <components>...</components>* 1116 # <plugs>...</plugs>* 1117 # <directories>...</directories>* 1118 # <bundles>...</bundles>* 1119 # <scenarios>...</scenarios>* 1120 parsers = {'components': (self._parseComponents, 1121 self._components.update), 1122 'directories': (self._parseDirectories, 1123 self._directories.update), 1124 'bundles': (self._parseBundles, self._bundles.update), 1125 'plugs': (self._parsePlugs, self._plugs.update), 1126 'scenarios': (self._parseScenarios, self._scenarios.update)} 1127 1128 if disallowed: 1129 for k in disallowed: 1130 del parsers[k] 1131 1132 self.parseFromTable(node, parsers)
1133
1134 - def _parseDirectories(self, node):
1135 # <directories> 1136 # <directory> 1137 # </directories> 1138 1139 directories = {} 1140 1141 def addDirectory(d): 1142 directories[d.getPath()] = d
1143 1144 parsers = {'directory': (self._parseDirectory, addDirectory)} 1145 self.parseFromTable(node, parsers) 1146 1147 return directories 1148
1149 - def _parseDirectory(self, node):
1150 # <directory filename="..."/> 1151 filename, = self.parseAttributes(node, ('filename', )) 1152 return RegistryDirectory(filename)
1153
1154 - def _parseComponentWizard(self, node):
1155 return self._parseWizard(node, _VALID_WIZARD_COMPONENT_TYPES)
1156
1157 - def _parsePlugWizard(self, node):
1158 return self._parseWizard(node, _VALID_WIZARD_PLUG_TYPES)
1159
1160 - def _parseWizard(self, node, validTypes):
1161 # <wizard type="..." _description=" " feeder="..." eater="..."]/> 1162 # 1163 # NOTE: We are using _description with the leading underscore for 1164 # the case of intltool, it is not possible for it to pickup 1165 # translated attributes otherwise. Ideally we would use another 1166 # tool so we can avoid underscores in our xml schema. 1167 attrs = self.parseAttributes(node, 1168 ('type', '_description'), 1169 ('feeder', 'eater')) 1170 wizardType, description, feeder, eater = attrs 1171 1172 accepts = [] 1173 provides = [] 1174 parsers = { 1175 'accept-format': (self._parseAcceptFormat, 1176 lambda n: accepts.append(n)), 1177 'provide-format': (self._parseProvideFormat, 1178 lambda n: provides.append(n)), 1179 } 1180 self.parseFromTable(node, parsers) 1181 1182 parent_type = node.parentNode.getAttribute('type') 1183 1184 if not wizardType in validTypes: 1185 raise fxml.ParserError( 1186 "<wizard>'s type attribute is %s must be one of %s" % ( 1187 parent_type, 1188 ', '.join(validTypes))) 1189 1190 isProducer = wizardType.endswith('-producer') 1191 isEncoder = wizardType.endswith('-encoder') 1192 isMuxer = (wizardType == 'muxer') 1193 isConsumer = wizardType.endswith('-consumer') 1194 1195 err = None 1196 # Producers and Encoders cannot have provided 1197 if accepts and (isProducer or isEncoder): 1198 err = ('<wizard type="%s"> does not allow an accepted ' 1199 'media-type.') % (parent_type, ) 1200 # Encoders, Muxers and Consumers must have an accepted 1201 elif not accepts and (isMuxer or isConsumer): 1202 err = ('<wizard type="%s"> requires at least one accepted ' 1203 'media-type.') % (parent_type, ) 1204 # Producers and Consumers cannot have provided 1205 elif provides and (isProducer or isConsumer): 1206 err = ('<wizard type="%s"> does not allow a provided ' 1207 'media-type.') % (parent_type, ) 1208 # Producers, Encoders and Muxers must have exactly one provided 1209 if len(provides) != 1 and (isEncoder or isMuxer): 1210 err = ('<wizard type="%s"> requires exactly one provided ' 1211 'media-type.') % (parent_type, ) 1212 1213 if err: 1214 raise fxml.ParserError(err) 1215 1216 return RegistryEntryWizard(parent_type, wizardType, description, 1217 feeder, eater, accepts, provides)
1218
1219 - def _parseAcceptFormat(self, node):
1220 # <accept-format media-type="..."/> 1221 media_type, = self.parseAttributes(node, ('media-type', )) 1222 return RegistryEntryWizardFormat(media_type)
1223
1224 - def _parseProvideFormat(self, node):
1225 # <provide-format media-type="..."/> 1226 media_type, = self.parseAttributes(node, ('media-type', )) 1227 return RegistryEntryWizardFormat(media_type)
1228 1229 1230 # FIXME: filename -> path 1231 1232
1233 -class RegistryDirectory(log.Loggable):
1234 """ 1235 I represent a directory under a path managed by the registry. 1236 I can be queried for a list of partial registry .xml files underneath 1237 the given path, under the given prefix. 1238 """ 1239
1240 - def __init__(self, path, prefix=configure.PACKAGE):
1241 self._path = path 1242 self._prefix = prefix 1243 scanPath = os.path.join(path, prefix) 1244 self._files, self._dirs = self._getFileLists(scanPath)
1245
1246 - def __repr__(self):
1247 return "<RegistryDirectory %s>" % self._path
1248
1249 - def _getFileLists(self, root):
1250 """ 1251 Get all files ending in .xml from all directories under the given root. 1252 1253 @type root: string 1254 @param root: the root directory under which to search 1255 1256 @returns: a list of .xml files, relative to the given root directory 1257 """ 1258 files = [] 1259 dirs = [] 1260 1261 if os.path.exists(root): 1262 try: 1263 directory_files = os.listdir(root) 1264 except OSError, e: 1265 if e.errno == errno.EACCES: 1266 return files, dirs 1267 else: 1268 raise 1269 1270 dirs.append(root) 1271 1272 for entry in directory_files: 1273 path = os.path.join(root, entry) 1274 # if it's a .xml file, then add it to the list 1275 if not os.path.isdir(path): 1276 if path.endswith('.xml'): 1277 files.append(path) 1278 # if it's a directory and not an svn directory, then get 1279 # its files and add them 1280 elif entry != '.svn': 1281 newFiles, newDirs = self._getFileLists(path) 1282 files.extend(newFiles) 1283 dirs.extend(newDirs) 1284 1285 return files, dirs
1286
1287 - def rebuildNeeded(self, mtime):
1288 1289 def _rebuildNeeded(file): 1290 try: 1291 if _getMTime(file) > mtime: 1292 self.debug("Path %s changed since registry last " 1293 "scanned", f) 1294 return True 1295 return False 1296 except OSError: 1297 self.debug("Failed to stat file %s, need to rescan", f) 1298 return True
1299 1300 for f in self._files: 1301 if _rebuildNeeded(f): 1302 return True 1303 for f in self._dirs: 1304 if _rebuildNeeded(f): 1305 return True 1306 return False
1307
1308 - def getFiles(self):
1309 """ 1310 Return a list of all .xml registry files underneath this registry 1311 path. 1312 """ 1313 return self._files
1314
1315 - def getPath(self):
1316 return self._path
1317 1318
1319 -class RegistryWriter(log.Loggable):
1320
1321 - def __init__(self, components, plugs, bundles, directories):
1322 """ 1323 @param components: components to write 1324 @type components: list of L{RegistryEntryComponent} 1325 @param plugs: plugs to write 1326 @type plugs: list of L{RegistryEntryPlug} 1327 @param bundles: bundles to write 1328 @type bundles: list of L{RegistryEntryBundle} 1329 @param directories: directories to write 1330 @type directories: list of L{RegistryEntryBundleDirectory} 1331 """ 1332 self.components = components 1333 self.plugs = plugs 1334 self.bundles = bundles 1335 self.directories = directories
1336
1337 - def dump(self, fd):
1338 """ 1339 Dump the cache of components to the given opened file descriptor. 1340 1341 @type fd: integer 1342 @param fd: open file descriptor to write to 1343 """ 1344 1345 def w(i, msg): 1346 print >> fd, ' '*i + msg
1347 1348 def e(attr): 1349 return saxutils.quoteattr(attr)
1350 1351 def _dump_proplist(i, proplist, ioff=2): 1352 for prop in proplist: 1353 if isinstance(prop, RegistryEntryCompoundProperty): 1354 _dump_compound(i, prop) 1355 else: 1356 w(i, ('<property name="%s" type="%s"' 1357 % (prop.getName(), prop.getType()))) 1358 w(i, (' _description=%s' 1359 % (e(prop.getDescription()), ))) 1360 w(i, (' required="%s" multiple="%s"/>' 1361 % (prop.isRequired(), prop.isMultiple()))) 1362 1363 def _dump_compound(i, cprop, ioff=2): 1364 w(i, ('<compound-property name="%s"' % (cprop.getName(), ))) 1365 w(i, (' _description=%s' 1366 % (e(cprop.getDescription()), ))) 1367 w(i, (' required="%s" multiple="%s">' 1368 % (cprop.isRequired(), cprop.isMultiple()))) 1369 _dump_proplist(i + ioff, cprop.getProperties()) 1370 w(i, ('</compound-property>')) 1371 1372 def _dump_entries(i, entries): 1373 if not entries: 1374 return 1375 1376 w(i, '<entries>') 1377 for entry in entries: 1378 w(i+2, '<entry type="%s" location="%s" function="%s"/>' % ( 1379 entry.getType(), 1380 entry.getLocation(), 1381 entry.getFunction())) 1382 w(i, '</entries>') 1383 1384 w(0, '<registry>') 1385 w(0, '') 1386 1387 # Write components 1388 w(2, '<components>') 1389 w(0, '') 1390 for component in self.components: 1391 w(4, '<component type="%s" base="%s"' % ( 1392 component.getType(), component.getBase())) 1393 w(4, ' _description=%s>' 1394 % (e(component.getDescription()), )) 1395 1396 w(6, '<source location="%s"/>' % component.getSource()) 1397 for x in component.getEaters(): 1398 w(6, '<eater name="%s" required="%s" multiple="%s"/>' 1399 % (x.getName(), x.getRequired() and "yes" or "no", 1400 x.getMultiple() and "yes" or "no")) 1401 for x in component.getFeeders(): 1402 w(6, '<feeder name="%s"/>' % x) 1403 w(6, '<synchronization required="%s" clock-priority="%d"/>' 1404 % (component.getNeedsSynchronization() and "yes" or "no", 1405 component.getClockPriority())) 1406 1407 sockets = component.getSockets() 1408 if sockets: 1409 w(6, '<sockets>') 1410 for socket in sockets: 1411 w(8, '<socket type="%s"/>' % socket) 1412 w(6, '</sockets>') 1413 1414 w(6, '<properties>') 1415 _dump_proplist(8, component.getProperties()) 1416 w(6, '</properties>') 1417 1418 for wizard in component.wizards: 1419 w(6, '<wizard type="%s" _description="%s" feeder="%s">' % ( 1420 wizard.type, 1421 e(wizard.description), 1422 wizard.feeder)) 1423 for accept in wizard.accepts: 1424 w(8, '<accept-format media-type="%s"/>' % ( 1425 accept.media_type)) 1426 for provide in wizard.provides: 1427 w(8, '<provide-format media-type="%s"/>' % ( 1428 provide.media_type)) 1429 w(6, '</wizard>') 1430 1431 registryEntryFiles = component.getFiles() 1432 if registryEntryFiles: 1433 w(6, '<files>') 1434 for entryFile in registryEntryFiles: 1435 w(8, '<file name="%s" type="%s"/>' % ( 1436 entryFile.getName(), 1437 entryFile.getType())) 1438 w(6, '</files>') 1439 1440 _dump_entries(6, component.getEntries()) 1441 1442 w(4, '</component>') 1443 w(0, '') 1444 1445 w(2, '</components>') 1446 w(0, '') 1447 1448 # Write plugs 1449 w(2, '<plugs>') 1450 w(0, '') 1451 for plug in self.plugs: 1452 w(4, '<plug type="%s" socket="%s" _description="%s">' 1453 % (plug.getType(), plug.getSocket(), plug.getDescription())) 1454 1455 _dump_entries(6, plug.getEntries()) 1456 1457 w(6, '<properties>') 1458 _dump_proplist(8, plug.getProperties()) 1459 w(6, '</properties>') 1460 1461 w(4, '</plug>') 1462 w(0, '') 1463 1464 w(2, '</plugs>') 1465 w(0, '') 1466 1467 # bundles 1468 w(2, '<bundles>') 1469 for bundle in self.bundles: 1470 w(4, '<bundle name="%s" under="%s" project="%s">' % ( 1471 bundle.getName(), bundle.getUnder(), bundle.getProject())) 1472 1473 dependencies = bundle.getDependencies() 1474 if dependencies: 1475 w(6, '<dependencies>') 1476 for dependency in dependencies: 1477 w(8, '<dependency name="%s"/>' % dependency) 1478 w(6, '</dependencies>') 1479 1480 bundleDirectories = bundle.getDirectories() 1481 if bundleDirectories: 1482 w(6, '<directories>') 1483 for directory in bundleDirectories: 1484 w(8, '<directory name="%s">' % directory.getName()) 1485 for filename in directory.getFiles(): 1486 w(10, '<filename location="%s" relative="%s"/>' % ( 1487 filename.getLocation(), filename.getRelative())) 1488 w(8, '</directory>') 1489 w(6, '</directories>') 1490 1491 w(4, '</bundle>') 1492 w(0, '') 1493 w(2, '</bundles>') 1494 1495 1496 # Directories 1497 directories = self.directories 1498 if directories: 1499 w(2, '<directories>') 1500 w(0, '') 1501 for d in directories: 1502 w(4, '<directory filename="%s"/>' % d.getPath()) 1503 w(2, '</directories>') 1504 w(0, '') 1505 1506 w(0, '</registry>') 1507 1508
1509 -class ComponentRegistry(log.Loggable):
1510 """Registry, this is normally not instantiated.""" 1511 1512 logCategory = 'registry' 1513 filename = os.path.join(configure.registrydir, 'registry.xml') 1514
1515 - def __init__(self):
1516 self._parser = RegistryParser() 1517 1518 if (READ_CACHE and 1519 os.path.exists(self.filename) and 1520 os.access(self.filename, os.R_OK)): 1521 self.info('Parsing registry: %s' % self.filename) 1522 try: 1523 self._parser.parseRegistry(self.filename) 1524 except fxml.ParserError, e: 1525 # this can happen for example if we upgraded to a new version, 1526 # ran, then downgraded again; the registry can then contain 1527 # XML keys that are not understood by this version. 1528 # This is non-fatal, and gets fixed due to a re-scan 1529 self.warning('Could not parse registry %s.' % self.filename) 1530 self.debug('fxml.ParserError: %s' % log.getExceptionMessage(e)) 1531 1532 self.verify(force=not READ_CACHE)
1533
1534 - def addFile(self, file):
1535 """ 1536 @param file: The file to add, either as an open file object, or 1537 as the name of a file to open. 1538 @type file: str or file. 1539 """ 1540 if isinstance(file, str) and file.endswith('registry.xml'): 1541 self.warning('%s seems to be an old registry in your tree, ' 1542 'please remove it', file) 1543 self.debug('Adding file: %r', file) 1544 self._parser.parseRegistryFile(file)
1545
1546 - def addFromString(self, string):
1547 f = StringIO(string) 1548 self.addFile(f) 1549 f.close()
1550
1551 - def addRegistryPath(self, path, prefix=configure.PACKAGE):
1552 """ 1553 Add a registry path to this registry, scanning it for registry 1554 snippets. 1555 1556 @param path: a full path containing a 'flumotion' directory, 1557 which will be scanned for registry files. 1558 1559 @rtype: bool 1560 @returns: whether the path could be added 1561 """ 1562 self.debug('path %s, prefix %s' % (path, prefix)) 1563 if not os.path.exists(path): 1564 self.warning( 1565 "Cannot add non-existent path '%s' to registry" % path) 1566 return False 1567 if not os.path.exists(os.path.join(path, prefix)): 1568 self.warning("Cannot add path '%s' to registry " 1569 "since it does not contain prefix '%s'" % (path, prefix)) 1570 return False 1571 1572 # registry path was either not watched or updated, or a force was 1573 # asked, so reparse 1574 self.info('Scanning registry path %s' % path) 1575 registryPath = RegistryDirectory(path, prefix=prefix) 1576 files = registryPath.getFiles() 1577 self.debug('Found %d possible registry files' % len(files)) 1578 map(self.addFile, files) 1579 1580 self._parser.addDirectory(registryPath) 1581 return True
1582 1583 # fixme: these methods inconsistenly molest and duplicate those of 1584 # the parser. 1585
1586 - def isEmpty(self):
1587 return len(self._parser._components) == 0
1588
1589 - def getComponent(self, name):
1590 """ 1591 @rtype: L{RegistryEntryComponent} 1592 """ 1593 return self._parser.getComponent(name)
1594
1595 - def hasComponent(self, name):
1596 return name in self._parser._components
1597
1598 - def getComponents(self):
1599 return self._parser.getComponents()
1600
1601 - def getPlug(self, type):
1602 """ 1603 @rtype: L{RegistryEntryPlug} 1604 """ 1605 return self._parser.getPlug(type)
1606
1607 - def hasPlug(self, name):
1608 return name in self._parser._plugs
1609
1610 - def getPlugs(self):
1611 return self._parser.getPlugs()
1612
1613 - def getScenarios(self):
1614 return self._parser.getScenarios()
1615
1616 - def getScenarioByType(self, type):
1617 return self._parser.getScenarioByType(type)
1618
1619 - def getBundles(self):
1620 return self._parser._bundles.values()
1621
1622 - def getDirectories(self):
1623 return self._parser.getDirectories()
1624
1625 - def makeBundlerBasket(self):
1626 """ 1627 @rtype: L{flumotion.common.bundle.BundlerBasket} 1628 """ 1629 1630 def load(): 1631 ret = BundlerBasket() 1632 for b in self.getBundles(): 1633 bundleName = b.getName() 1634 self.debug('Adding bundle %s' % bundleName) 1635 for d in b.getDirectories(): 1636 directory = d.getName() 1637 for bundleFilename in d.getFiles(): 1638 try: 1639 basedir = b.getBaseDir() 1640 except errors.NoProjectError, e: 1641 self.warning("Could not load project %s" % e.args) 1642 raise 1643 fullpath = os.path.join(basedir, directory, 1644 bundleFilename.getLocation()) 1645 relative = bundleFilename.getRelative() 1646 self.log('Adding path %s as %s to bundle %s' % ( 1647 fullpath, relative, bundleName)) 1648 try: 1649 ret.add(bundleName, fullpath, relative) 1650 except Exception, e: 1651 self.debug("Reason: %r" % e) 1652 raise RuntimeError( 1653 'Could not add %s to bundle %s (%s)' 1654 % (fullpath, bundleName, e)) 1655 for d in b.getDependencies(): 1656 self.log('Adding dependency of %s on %s' % (bundleName, d)) 1657 ret.depend(bundleName, d) 1658 return ret
1659 1660 try: 1661 return load() 1662 except Exception, e: 1663 self.debug("Could not register bundles the first time: %s" % 1664 log.getExceptionMessage(e)) 1665 self.warning("Bundle problem, rebuilding registry") 1666 self.verify(force=True) 1667 try: 1668 return load() 1669 except Exception, e: 1670 self.debug("Could not register bundles the second time: %s" % 1671 log.getExceptionMessage(e)) 1672 self.error("Could not not register bundles (%s)" % 1673 log.getExceptionMessage(e))
1674
1675 - def dump(self, fd):
1676 """ 1677 Dump the cache of components to the given opened file descriptor. 1678 1679 @type fd: integer 1680 @param fd: open file descriptor to write to 1681 """ 1682 writer = RegistryWriter(self.getComponents(), self.getPlugs(), 1683 self.getBundles(), self.getDirectories()) 1684 writer.dump(fd)
1685
1686 - def clean(self):
1687 """ 1688 Clean the cache of components. 1689 """ 1690 self._parser.clean()
1691
1692 - def rebuildNeeded(self):
1693 if not os.path.exists(self.filename): 1694 return True 1695 1696 # A bit complicated because we want to allow FLU_PROJECT_PATH to 1697 # point to nonexistent directories 1698 registryPaths = python.set(self._getRegistryPathsFromEnviron()) 1699 oldRegistryPaths = python.set([directory.getPath() 1700 for directory in self.getDirectories()]) 1701 if registryPaths != oldRegistryPaths: 1702 if oldRegistryPaths - registryPaths: 1703 return True 1704 if filter(os.path.exists, registryPaths - oldRegistryPaths): 1705 return True 1706 1707 registry_modified = _getMTime(self.filename) 1708 for d in self._parser.getDirectories(): 1709 if d.rebuildNeeded(registry_modified): 1710 return True 1711 1712 return False
1713
1714 - def save(self, force=False):
1715 if not force and not self.rebuildNeeded(): 1716 return 1717 1718 self.info('Saving registry to %s' % self.filename) 1719 1720 # create parent directory 1721 directory = os.path.split(self.filename)[0] 1722 if not os.path.exists(directory): 1723 try: 1724 makedirs(directory) 1725 except OSError, e: 1726 if e.errno == errno.EACCES: 1727 self.error('Registry directory %s could not be created !' % 1728 directory) 1729 else: 1730 raise 1731 1732 if not os.path.isdir(directory): 1733 self.error('Registry directory %s is not a directory !') 1734 try: 1735 fd = open(self.filename, 'w') 1736 self.dump(fd) 1737 except IOError, e: 1738 if e.errno == errno.EACCES: 1739 self.error('Registry file %s could not be created !' % 1740 self.filename) 1741 else: 1742 raise
1743
1744 - def _getRegistryPathsFromEnviron(self):
1745 registryPaths = [configure.pythondir, ] 1746 if 'FLU_PROJECT_PATH' in os.environ: 1747 paths = os.environ['FLU_PROJECT_PATH'] 1748 registryPaths += paths.split(':') 1749 return registryPaths
1750
1751 - def verify(self, force=False):
1752 """ 1753 Verify if the registry is uptodate and rebuild if it is not. 1754 1755 @param force: True if the registry needs rebuilding for sure. 1756 """ 1757 # construct a list of all paths to scan for registry .xml files 1758 if force or self.rebuildNeeded(): 1759 self.info("Rebuilding registry") 1760 if force: 1761 self.info("Rebuild of registry is forced") 1762 if self.rebuildNeeded(): 1763 self.info("Rebuild of registry is needed") 1764 self.clean() 1765 for path in self._getRegistryPathsFromEnviron(): 1766 if not self.addRegistryPath(path): 1767 self._parser.removeDirectoryByPath(path) 1768 self.save(True)
1769 1770
1771 -class RegistrySubsetWriter(RegistryWriter):
1772
1773 - def __init__(self, fromRegistry=None, onlyBundles=None):
1774 """ 1775 @param fromRegistry: The registry to subset, or the default. 1776 @type fromRegistry: L{ComponentRegistry} 1777 @param onlyBundles: If given, only include the subset of the 1778 registry that is provided by bundles whose names are in this 1779 list. 1780 @type onlyBundles: list of str 1781 """ 1782 self.fromRegistry = fromRegistry 1783 self.onlyBundles = onlyBundles
1784
1785 - def dump(self, fd):
1786 reg = self.fromRegistry or getRegistry() 1787 pred = None 1788 bundles = reg.getBundles() 1789 if self.onlyBundles is not None: 1790 bundles = [b for b in bundles 1791 if b.name in self.onlyBundles] 1792 1793 bundledfiles = {} 1794 for b in bundles: 1795 for d in b.getDirectories(): 1796 for f in d.getFiles(): 1797 filename = os.path.join(d.getName(), f.getLocation()) 1798 bundledfiles[filename] = b 1799 1800 def fileIsBundled(basedir, filename): 1801 return os.path.join(basedir, filename) in bundledfiles
1802 1803 pred = lambda c: (filter(lambda f: fileIsBundled(c.getBase(), 1804 f.getFilename()), 1805 c.getFiles()) 1806 or filter(lambda e: fileIsBundled(c.getBase(), 1807 e.getLocation()), 1808 c.getEntries())) 1809 components = filter(pred, reg.getComponents()) 1810 1811 pred = lambda p: p.getEntry().getLocation() in bundledfiles 1812 plugs = filter(pred, reg.getPlugs()) 1813 1814 directories = [] # no need for this 1815 1816 regwriter = RegistryWriter(components, plugs, bundles, directories) 1817 regwriter.dump(fd)
1818 1819 __registry = None 1820 1821
1822 -def makeBundleFromLoadedModules(outfile, outreg, *prefixes):
1823 """ 1824 Make a bundle from a subset of all loaded modules, also writing out 1825 a registry file that can apply to that subset of the global 1826 registry. Suitable for use as a FLU_ATEXIT handler. 1827 1828 @param outfile: The path to which a zip file will be written. 1829 @type outfile: str 1830 @param outreg: The path to which a registry file will be written. 1831 @type outreg: str 1832 @param prefixes: A list of prefixes to which to limit the export. If 1833 not given, package up all modules. For example, "flumotion" would 1834 limit the output to modules that start with "flumotion". 1835 @type prefixes: list of str 1836 """ 1837 from twisted.python import reflect 1838 1839 def getUsedModules(prefixes): 1840 ret = {} 1841 for modname in sys.modules: 1842 if prefixes and not filter(modname.startswith, prefixes): 1843 continue 1844 try: 1845 module = reflect.namedModule(modname) 1846 if hasattr(module, '__file__'): 1847 ret[modname] = module 1848 else: 1849 log.info('makebundle', 'Module %s has no file', module) 1850 except ImportError: 1851 log.info('makebundle', 'Could not import %s', modname) 1852 return ret
1853 1854 def calculateModuleBundleMap(): 1855 allbundles = getRegistry().getBundles() 1856 ret = {} 1857 for bundle in allbundles: 1858 for directory in bundle.getDirectories(): 1859 for bundleFile in directory.getFiles(): 1860 path = os.path.join(directory.getName(), 1861 bundleFile.getLocation()) 1862 parts = path.split(os.path.sep) 1863 if parts[-1].startswith('__init__.py'): 1864 parts.pop() 1865 elif parts[-1].endswith('.py'): 1866 parts[-1] = parts[-1][:-3] 1867 else: 1868 # not a bundled module 1869 continue 1870 modname = '.'.join(parts) 1871 ret[modname] = bundle 1872 return ret 1873 1874 def makeMergedBundler(modules, modulebundlemap): 1875 ret = MergedBundler() 1876 basket = getRegistry().makeBundlerBasket() 1877 for modname in modules: 1878 modfilename = modules[modname].__file__ 1879 if modname in modulebundlemap: 1880 bundleName = modulebundlemap[modname].getName() 1881 for depBundleName in basket.getDependencies(bundleName): 1882 ret.addBundler(basket.getBundlerByName(depBundleName)) 1883 else: 1884 if modfilename.endswith('.pyc'): 1885 modfilename = modfilename[:-1] 1886 if os.path.isdir(modfilename): 1887 with_init = os.path.join(modfilename, '__init__.py') 1888 if os.path.exists(with_init): 1889 modfilename = with_init 1890 nparts = len(modname.split('.')) 1891 if '__init__' in modfilename: 1892 nparts += 1 1893 relpath = os.path.join(*modfilename.split( 1894 os.path.sep)[-nparts:]) 1895 ret.add(modfilename, relpath) 1896 return ret 1897 1898 modules = getUsedModules(prefixes) 1899 modulebundlemap = calculateModuleBundleMap() 1900 bundler = makeMergedBundler(modules, modulebundlemap) 1901 1902 print 'Writing bundle to', outfile 1903 open(outfile, 'w').write(bundler.bundle().getZip()) 1904 1905 print 'Writing registry to', outreg 1906 bundlers_used = [b.name for b in bundler.getSubBundlers()] 1907 regwriter = RegistrySubsetWriter(onlyBundles=bundlers_used) 1908 regwriter.dump(open(outreg, 'w')) 1909 1910
1911 -def getRegistry():
1912 """ 1913 Return the registry. Only one registry will ever be created. 1914 1915 @rtype: L{ComponentRegistry} 1916 """ 1917 global __registry 1918 1919 if not __registry: 1920 log.debug('registry', 'instantiating registry') 1921 __registry = ComponentRegistry() 1922 1923 return __registry
1924