Package elisa :: Package core :: Module plugin
[hide private]
[frames] | no frames]

Source Code for Module elisa.core.plugin

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 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   
32 -class Plugin(log.Loggable):
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
100 - def __init__(self):
101 # configure the log category based on my name 102 self.log_category = self.name 103 104 log.Loggable.__init__(self) 105 self.debug("Creating")
106 107 @classmethod
108 - def initialize(cls):
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
128 - def check_dependencies(cls):
129 """ 130 Check plugin's python dependencies 131 132 This check is performed at most once. 133 134 @raises: L{elisa.core.component.UnMetDependency} 135 """ 136 if cls.deps_error: 137 raise cls.deps_error 138 139 if not cls.checked: 140 cls.checked = True 141 try: 142 component.check_python_dependencies(cls.name, 143 cls.external_dependencies) 144 except component.ComponentError, error: 145 cls.deps_error = error 146 raise
147 148 @classmethod
149 - def check_component_dependencies(cls, component_name):
150 """ 151 Check the supported platforms and external dependencies of the 152 Component identified by its name. 153 154 This check is performed at most once. 155 156 @raises: L{elisa.core.component.UnMetDependency} 157 """ 158 informations = cls.components.get(component_name) 159 160 if not informations.get('checked', False): 161 platforms = informations.get('platforms',[]) 162 deps = informations.get('external_dependencies',[]) 163 component_path = "%s:%s" % (cls.name, component_name) 164 165 try: 166 component.check_platforms(component_path, platforms) 167 component.check_python_dependencies(component_path, deps) 168 finally: 169 cls.components[component_name]['checked'] = True
170 171 @classmethod
172 - def load_config(cls):
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 # scan components 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
237 - def load_translations(cls, translator):
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
256 - def get_resource_file(cls, path):
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