1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Plugin base class, the mother of all Elisa plugins
19 """
20
21 __maintainer__ = 'Philippe Normand <philippe@fluendo.com>'
22
23 from elisa.core.utils import classinit, misc, locale_helper
24 from elisa.core import log
25 from elisa.core.config import Config
26 from elisa.core import component
27
28 import os
29 import inspect
30 import pkg_resources
31
33 """
34 Plugin Base Class
35
36 This class acts as a L{elisa.core.component.Component} factory. All the Plugin's class
37 variables can be stored in a plugin.conf file which has the
38 following syntax::
39
40 [general]
41 name="base"
42 version="0.1"
43 plugin_dependencies=["foo"]
44 external_dependencies=["pgm"]
45 description="base elisa components"
46
47 # that's a component!
48 [media_providers.local_media:LocalMedia]
49 description="To access file:// media"
50 platforms=["linux", "posix"]
51 external_dependencies=["gnomevfs",]
52 component_dependencies = ["base:coherence_service"]
53
54 Each option of the "general" section will be mapped to it's corresponding
55 class variable in the Plugin.
56
57 @cvar config_file: Plugin's directory relative path to plugin.conf
58 @type config_file: string
59 @cvar config: Config generated by reading L{config_file}
60 @type config: L{elisa.core.config.Config}
61 @cvar components: Component implementations provided by the
62 Plugin
63 @type components: dict mapping L{elisa.core.component.Component}
64 names to L{elisa.core.component.Component}
65 instances
66 @cvar name: plugin's name. Should be unique
67 @type name: string
68 @cvar external_dependencies: external Python dependencies specified using
69 dot-syntax
70 @type external_dependencies: list
71 @cvar plugin_dependencies: Elisa plugins dependencies specified using
72 colon-syntax
73 @type plugin_dependencies: list
74 @cvar version: Plugin's version as a dot-syntax string
75 @type version: string
76 @cvar description: one line Plugin's description
77 @type description: string
78 @cvar directory: absolute path to the directory storing the
79 Plugin's source code file (or Egg)
80 @type directory: string
81 """
82
83 __metaclass__ = classinit.ClassInitMeta
84 __classinit__ = classinit.build_properties
85
86 config = None
87 config_file = None
88
89 directory = ""
90 components = {}
91 name = ""
92 plugin_dependencies = []
93 external_dependencies = []
94 version = ""
95 description = ""
96
97 checked = False
98 deps_error = None
99
106
107 @classmethod
109 """
110 Check the components class attribute is a dictionary and that
111 none of the components of the plugin have the same name as the
112 plugin itself.
113
114 @raise InitializeFailure: if the Plugin is not well structured
115 """
116 log.debug(cls.name, "Initializing")
117
118 if not isinstance(cls.components, dict):
119 reason = "%s.components must be a dictionnary" % cls.__name__
120 raise component.InitializeFailure(cls.name, reason)
121
122 if cls.name in cls.components.keys():
123 reason = "Component %s has the same name as its plugin. "\
124 "Please rename it." % cls.name
125 raise component.InitializeFailure(cls.name, reason)
126
127 @classmethod
147
148 @classmethod
170
171 @classmethod
173 """ Load the L{config_file} if defined
174
175 Fill L{config} class variable if L{config_file} points to a
176 valid config filename. Also fill L{name}, L{version},
177 L{description}, L{plugin_dependencies},
178 L{external_dependencies} and L{components}.
179 """
180 components = {}
181
182 if cls.config:
183 log.debug(cls.name, "Config already loaded")
184 elif cls.config_file:
185 config_file = cls.config_file
186 if not os.path.exists(config_file):
187 plugin_dir = os.path.dirname(inspect.getsourcefile(cls))
188 config_file = os.path.join(plugin_dir, cls.config_file)
189
190 log.debug(cls.name, "Loading config from file %r", config_file)
191 cls.config = Config(config_file)
192 general_section = cls.config.get_section('general',{})
193
194 name = general_section.get('name','')
195 if name:
196 cls.name = name
197
198 version = general_section.get('version','')
199 if version:
200 cls.version = version
201
202 description = general_section.get('description','')
203 if description:
204 cls.description = description
205
206 deps = general_section.get('plugin_dependencies',[])
207 if deps:
208 cls.plugin_dependencies = deps
209
210 ext_deps = general_section.get('external_dependencies',[])
211 cls.external_dependencies = ext_deps
212
213 sections = cls.config.as_dict()
214 if 'general' in sections:
215 del sections['general']
216
217
218 for component_path, section in sections.iteritems():
219 if 'name' not in section:
220 path = component_path.split(':')
221 if len(path) < 2:
222 continue
223 path = path[1]
224 un_camel = misc.un_camelify(path)
225 section['name'] = un_camel
226 component_name = section['name']
227 section['path'] = component_path
228
229 components.update({component_name:section})
230
231 if components:
232 cls.components = components
233 else:
234 cls.config = Config()
235
236 @classmethod
238 """ Load the translation files supported by the Plugin into a
239 translator, usually the Application's translator. This method
240 uses the config class attribute, so be sure to have called
241 L{load_config} before calling this method. Translation files
242 are loaded from the i18n plugin.conf section (inside general
243 section).
244
245 @param translator: The translator to load i18n files into
246 @type translator: L{elisa.extern.translation.Translator}
247 """
248 i18n = cls.config.get_option('i18n', section='general', default={})
249
250 for key, value in i18n.iteritems():
251 trans_path = os.path.join(cls.directory, value)
252 translator.addLocaleDir(key, trans_path)
253 log.debug(cls.name, "Adding %s to domain %s", key, trans_path)
254
255 @classmethod
257 """ Retrieve a data file stored and managed by the plugin.
258
259 If the plugin is stored in a flat directory we build an
260 absolute filesystem path of the data file relative to the
261 plugin's directory. If the plugin is an egg (most of the time
262 zipped), we use pkg_resources which extract the file to a
263 temporary directory and use that temporary file.
264
265 @param path: relative path (to the plugin namespace) of the data file
266 @type path: string
267 @returns: absolute path to the data file
268 @rtype: string
269 """
270 resource = os.path.join(cls.directory, path)
271 if not os.path.exists(resource):
272 resource = pkg_resources.resource_filename(cls.__module__, path)
273 charset = locale_helper.system_encoding()
274 resource = resource.decode(charset)
275 return resource
276