1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
43
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
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
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
106
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
121
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
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
196 if len(self.files) > 1:
197 return
198
199 return self.files[0].getFilename()
200
203
206
207 - def getDescription(self):
208 return self.description
209
210 - def getSource(self):
212
213 - def getEaters(self):
215
216 - def getFeeders(self):
218
220 return self.needs_sync
221
223 return self.clock_priority
224
225 - def getSockets(self):
227 pb.setUnjellyableForClass(RegistryEntryComponent, RegistryEntryComponent)
228
229
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
292
293 - def getDescription(self):
294 return self.description
295
296 - def getSocket(self):
298
299
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
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):
330
331 - def getUnder(self):
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
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
351
352 - def getFiles(self):
354
355
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):
365
366 - def getRelative(self):
368
369
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
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
389
392
393 - def getDescription(self):
394 return self.description
395
396 - def isRequired(self):
398
399 - def isMultiple(self):
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
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
439 return os.path.basename(self.filename)
440
443
444 - def getFilename(self):
446
447 - def isType(self, type):
448 return self.type == type
449
450
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
461
462 - def getLocation(self):
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):
474
475
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
486
487 - def getRequired(self):
489
490 - def getMultiple(self):
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
513 """
514 This class represents an <accept-format> or <provide-format>
515 entry in the registry
516 """
517
519 self.media_type = media_type
520
521
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
538
540 self._components = {}
541 self._directories = {}
542 self._bundles = {}
543 self._plugs = {}
544 self._scenarios = {}
545
547 return self._components.values()
548
555
557 return self._scenarios.values()
558
560 if type in self._scenarios:
561 return self._scenarios[type]
562 return None
563
565 return self._plugs.values()
566
573
575
576
577
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
590
591
592
593
594
595
596
597
598
599
600
601
602 componentType, baseDir, description, _description = \
603 self.parseAttributes(node,
604 required=('type', 'base'),
605 optional=('description', '_description'))
606
607
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
628
629
630
631
632
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
659
660
661
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
674
675
676
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
698
700
701
702
703
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
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
727
728
729
730
731
732
733
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
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
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
788
800
807
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
826
827
828
829
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
855
860
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
878 return {'default': self._parsePlugEntry(node)}
879
881
882
883
884
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
901
902
903
904
905
906
907
908
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
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
954
955 parsers = {'plug': (self._parsePlug, addPlug)}
956 self.parseFromTable(node, parsers)
957
958 return plugs
959
960
961
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
977
978 self.debug('%s does not have registry as root tag' % self.filename)
979 return
980
981
982 self._parseRoot(node, disallowed=['directories'])
983 root.unlink()
984
994
995 parsers = {'bundle': (self._parseBundle, addBundle)}
996 self.parseFromTable(node, parsers)
997
998 return bundles
999
1001
1002
1003
1004
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
1026
1038
1050
1059
1070
1071 parsers = {'filename': (parseFilename, filenames.append)}
1072 self.parseFromTable(node, parsers)
1073
1074 return RegistryEntryBundleDirectory(name, filenames)
1075
1076
1077
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
1093 return self._directories.values()
1094
1096 return self._directories[name]
1097
1099 """
1100 Add a registry path object to the parser.
1101
1102 @type directory: {RegistryDirectory}
1103 """
1104 self._directories[directory.getPath()] = directory
1105
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
1115
1116
1117
1118
1119
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
1135
1136
1137
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
1153
1156
1159
1161
1162
1163
1164
1165
1166
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
1197 if accepts and (isProducer or isEncoder):
1198 err = ('<wizard type="%s"> does not allow an accepted '
1199 'media-type.') % (parent_type, )
1200
1201 elif not accepts and (isMuxer or isConsumer):
1202 err = ('<wizard type="%s"> requires at least one accepted '
1203 'media-type.') % (parent_type, )
1204
1205 elif provides and (isProducer or isConsumer):
1206 err = ('<wizard type="%s"> does not allow a provided '
1207 'media-type.') % (parent_type, )
1208
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
1223
1228
1229
1230
1231
1232
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
1241 self._path = path
1242 self._prefix = prefix
1243 scanPath = os.path.join(path, prefix)
1244 self._files, self._dirs = self._getFileLists(scanPath)
1245
1247 return "<RegistryDirectory %s>" % self._path
1248
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
1275 if not os.path.isdir(path):
1276 if path.endswith('.xml'):
1277 files.append(path)
1278
1279
1280 elif entry != '.svn':
1281 newFiles, newDirs = self._getFileLists(path)
1282 files.extend(newFiles)
1283 dirs.extend(newDirs)
1284
1285 return files, dirs
1286
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
1309 """
1310 Return a list of all .xml registry files underneath this registry
1311 path.
1312 """
1313 return self._files
1314
1317
1318
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
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
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
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
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
1510 """Registry, this is normally not instantiated."""
1511
1512 logCategory = 'registry'
1513 filename = os.path.join(configure.registrydir, 'registry.xml')
1514
1533
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
1547 f = StringIO(string)
1548 self.addFile(f)
1549 f.close()
1550
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
1573
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
1584
1585
1587 return len(self._parser._components) == 0
1588
1590 """
1591 @rtype: L{RegistryEntryComponent}
1592 """
1593 return self._parser.getComponent(name)
1594
1596 return name in self._parser._components
1597
1600
1602 """
1603 @rtype: L{RegistryEntryPlug}
1604 """
1605 return self._parser.getPlug(type)
1606
1608 return name in self._parser._plugs
1609
1612
1615
1618
1620 return self._parser._bundles.values()
1621
1624
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):
1685
1687 """
1688 Clean the cache of components.
1689 """
1690 self._parser.clean()
1691
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
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
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
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
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):
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 = []
1815
1816 regwriter = RegistryWriter(components, plugs, bundles, directories)
1817 regwriter.dump(fd)
1818
1819 __registry = None
1820
1821
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
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
1924