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

Source Code for Module elisa.core.application

  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  Module responsible for starting the Application 
 19  """ 
 20   
 21  __maintainer__ = 'Philippe Normand <philippe@fluendo.com>' 
 22   
 23  import pkg_resources 
 24  import sys, time 
 25  import os 
 26  import datetime 
 27  import locale 
 28  import gobject 
 29   
 30  from elisa.core import common 
 31  common.boot() 
 32   
 33  from elisa.core import __version__, version_info, config, log 
 34  from elisa.core.utils import exception_hook 
 35  from elisa.core.bus import bus, bus_message 
 36  from elisa.core.utils import i18n 
 37  from elisa.core.utils import classinit 
 38  from elisa.core import thumbnailer 
 39  from elisa.core import media_uri 
 40  from elisa.core.utils.mime_getter import MimeGetter 
 41   
 42  from elisa.core import plugin_registry, config_upgrader 
 43  from elisa.core import input_manager, media_manager, service_manager 
 44  from elisa.core import metadata_manager 
 45  from elisa.core import interface_controller, player_registry 
 46   
 47  from twisted.internet import reactor, defer, threads 
 48  from twisted.python import usage 
 49  from twisted.web import client 
 50   
 51  from elisa.extern.translation import Translator 
 52   
 53  UPDATES_URL = "http://elisa.fluendo.com/updates/update.php?install_date=%(date)s&version=%(version)s" 
 54  CONFIG_DIR = os.path.join(os.path.expanduser('~'), ".elisa") 
 55  CONFIG_FILE = "elisa.conf" 
 56  TRANS_FILE = "data/translations.lst" 
 57   
 58  DEFAULT_CONFIG = """\ 
 59  [general] 
 60  version = '%(version)s' 
 61  install_date = '%(install_date)s' 
 62  media_providers = ['daap:daap_media', 'youtube:youtube_media', 'shoutcast:shoutcast_media', 'fspot:fspot_media', 'coherence:upnp_media', 'ipod:ipod_media', 'base:local_media', 'media_db:elisa_media', 'gvfs:gnomevfs_media', 'audiocd:audiocd_media', 'flickr:flickr_media', 'stage6:stage_media'] 
 63  metadata_providers = ['gstreamer:gst_metadata_client', 'amazon:amazon_covers'] 
 64  service_providers = ['gnome:gnome_screensaver_service', 'hal:hal_service', 'coherence:coherence_service'] 
 65  player_engines = ['base:playbin_engine', 'audiocd:cdda_engine'] 
 66  backends = ['backend1'] 
 67  frontends = ['frontend1'] 
 68   
 69  [media_scanner] 
 70  enabled = '1' 
 71  db_backend = 'sqlite' 
 72  database = 'elisa.db' 
 73  fivemin_location_updates = [] 
 74  hourly_location_updates = [] 
 75  daily_location_updates = [] 
 76  weekly_location_updates = [] 
 77  unmonitored_locations = [] 
 78   
 79  [backend1] 
 80  activity = 'raval:elisa_activity' 
 81  mvc_mappings = 'raval:data/raval_mvc_mappings.conf' 
 82  input_providers = ['lirc:lirc_input'] 
 83   
 84  [frontend1] 
 85  backend = 'backend1' 
 86  theme = 'raval:tango_theme' 
 87  input_providers = ['pigment:pigment_input'] 
 88   
 89  [xmlmenu:locations_builder] 
 90  locations = [] 
 91  auto_locations = 1 
 92   
 93  [lirc:lirc_input] 
 94  # filename of the LIRC config map to use 
 95  lirc_rc = 'streamzap.lirc' 
 96  delay = '4' 
 97  repeat = '1' 
 98   
 99  [coherence:coherence_service] 
100  logmode = 'none' 
101  controlpoint = 'yes' 
102   
103  [[plugins]] 
104   
105  [base:service_activity] 
106  # a list of activites, which should beappear in the service_menu 
107  service_activities = ['service:about_activity'] 
108   
109  [base:local_media] 
110  hidden_file_chars = '!.' 
111   
112  [dvd:dvd_activity] 
113  # uri of the dvd. must be file:///* or dvd:// 
114  dvd_uri = 'dvd://' 
115   
116  [player] 
117  audiosink = 'autoaudiosink' 
118   
119  [theme_switcher:theme_switcher_activity] 
120  # a list of themes 'plugin:component' like 'raval:tango_theme' 
121  themes = ['raval:tango_theme', 'raval:poblenou_theme', 'raval:chris_theme']""" 
122   
123                       
124 -class Application(log.Loggable):
125 """ Application is the entry point of Elisa. It groups all the necessary 126 elements needed for Elisa to run. It is in charge of instantiating a 127 Config and a PluginRegistry. Application also provides access to 128 input events and data, and holds the user interfaces. It creates 129 various managers (InputManager, MediaManager...), 130 an InterfaceController and a DBBackend. 131 132 @ivar plugin_registry: loads and manages the plugins 133 @type plugin_registry: L{elisa.core.plugin_registry.PluginRegistry} 134 @ivar config: Application's configuration file, storing options 135 @type config: L{elisa.core.config.Config} 136 @ivar bus: DOCME 137 @type bus: L{elisa.core.bus.bus.Bus} 138 @ivar translator: DOCME 139 @type translator: L{elisa.extern.translator.Translator} 140 @ivar metadata_manager: DOCME 141 @type metadata_manager: L{elisa.core.metadata_manager.MetadataManager} 142 @ivar mime_getter: DOCME 143 @type mime_getter: L{elisa.core.utils.mime_getter.MimeGetter} 144 @ivar service_manager: DOCME 145 @type service_manager: L{elisa.core.service_manager.ServiceManager} 146 @ivar player_registry: DOCME 147 @type player_registry: L{elisa.core.player_registry.PlayerRegistry} 148 @ivar interface_controller: DOCME 149 @type interface_controller: L{elisa.core.interface_controller.InterfaceController} 150 @ivar input_manager: DOCME 151 @type input_manager: L{elisa.core.input_manager.InputManager} 152 @ivar media_manager: DOCME 153 @type media_manager: L{elisa.core.media_manager.MediaManager} 154 @ivar thumbnailer: DOCME 155 @type thumbnailer: L{elisa.core.thumbnailer.Thumbnailer} 156 """ 157 158 # Allows property fget/fset/fdel/doc overriding 159 __metaclass__ = classinit.ClassInitMeta 160 __classinit__ = classinit.build_properties 161 162 log_category = "application" 163
164 - def __init__(self, config_filename=None, show_tracebacks=False):
165 """ 166 Application constructor. It: 167 168 - loads global localization 169 - loads the config file 170 - loads the profiler 171 172 @param config_filename: the config filename to use. Can be absolute 173 or relative path 174 @type config_filename: string or None to use default config file 175 """ 176 log.Loggable.__init__(self) 177 self.debug("Creating") 178 179 self.show_tracebacks = show_tracebacks 180 self.running = False 181 self._plugin_registry = None 182 183 self._config = None 184 185 self._load_config(config_filename) 186 self._load_exception_hook() 187 self._load_profiler() 188 self._compile_po_files() 189 190 common.set_application(self) 191 192 self._load_translator() 193 194 self._plugin_registry = plugin_registry.PluginRegistry(self.config) 195 self._plugin_registry.load_plugins() 196 197 self._bus = bus.Bus() 198 199 self._service_manager = service_manager.ServiceManager() 200 self._metadata_manager = metadata_manager.MetadataManager() 201 self._media_manager = media_manager.MediaManager(self.metadata_manager) 202 self._input_manager = input_manager.InputManager() 203 self._player_registry = player_registry.PlayerRegistry() 204 self._interface_controller = interface_controller.InterfaceController() 205 self._thumbnailer = thumbnailer.Thumbnailer() 206 self._mime_getter = MimeGetter()
207
208 - def _compile_po_files(self):
211
212 - def _load_translator(self):
213 214 # check the locale is supported 215 try: 216 locale.getpreferredencoding() 217 except locale.Error, error: 218 self.warning(error) 219 self.warning("Falling back to system locale") 220 #locale.setlocale(locale.LC_ALL, '') 221 os.environ['LANG'] = 'C' 222 223 self._translator = Translator()
224
225 - def _load_config(self, config_filename):
226 if not config_filename: 227 if not os.path.exists(CONFIG_DIR): 228 try: 229 os.makedirs(CONFIG_DIR) 230 except OSError, e: 231 self.warning("Could not create '%s': %s" % (CONFIG_DIR, e)) 232 raise 233 234 config_filename = os.path.join(CONFIG_DIR, CONFIG_FILE) 235 236 self.info("Using config file: %r", config_filename) 237 self._config_filename = config_filename 238 today = datetime.date.today().isoformat() 239 default_config = DEFAULT_CONFIG % {'version': __version__, 240 'install_date': today} 241 242 try: 243 cfg = config.Config(config_filename, default_config=default_config) 244 except config.ConfigError, error: 245 self.warning(error) 246 raise 247 248 if not cfg.first_load: 249 # ok we might have an old config format here 250 upgrader = config_upgrader.ConfigUpgrader(cfg, default_config) 251 cfg = upgrader.update_for(version_info) 252 253 self._install_date = cfg.get_option('install_date', section='general', 254 default=today) 255 self._config = cfg
256
257 - def _load_profiler(self):
258 """ 259 This imports profiling modules (TODO) 260 """ 261 enable = int(self.config.get_option('enable_profiling', default='0')) 262 if enable: 263 # TODO: import profiling modules and set things ready 264 pass
265
266 - def _check_updates(self):
267 url = UPDATES_URL % {'date': self._install_date, 'version': __version__} 268 dfr = client.getPage(url) 269 270 def got_result(result): 271 # TODO: notify user about new version 272 pass
273 274 dfr.addCallback(got_result)
275
276 - def _load_exception_hook(self):
277 """ Override the default system exception hook with our own 278 """ 279 # FIXME: should this be in config? 280 logdir = None 281 282 params = {'format': 'text', 'logdir': logdir, 283 'file': sys.stderr, 284 'display': self.show_tracebacks} 285 self._except_hook = exception_hook.ExceptionHook(**params) 286 sys.excepthook = self._except_hook
287 288 289 # FIXME: given the current use of this method, it should probably be called 290 # something like log error to file
291 - def handle_traceback(self):
292 """ Call this to force the exception hook to handle the exception on top 293 of the stack. This can be handy to use from and except: block. 294 """ 295 self._except_hook.handle()
296
297 - def plugin_registry__get(self):
298 return self._plugin_registry
299
300 - def bus__get(self):
301 return self._bus
302
303 - def translator__get(self):
304 return self._translator
305
306 - def metadata_manager__get(self):
307 return self._metadata_manager
308
309 - def mime_getter__get(self):
310 return self._mime_getter
311
312 - def service_manager__get(self):
313 return self._service_manager
314 315
316 - def player_registry__get(self):
317 return self._player_registry
318
319 - def interface_controller__get(self):
320 return self._interface_controller
321
322 - def input_manager__get(self):
323 return self._input_manager
324
325 - def media_manager__get(self):
326 return self._media_manager
327
328 - def thumbnailer__get(self):
329 return self._thumbnailer
330
331 - def config__get(self):
332 return self._config
333
334 - def initialize(self):
335 def initialize_managers_done(result, managers): 336 # see if any of the managers failed 337 failed = False 338 first_failure = None 339 initialized = [] 340 for i, item in enumerate(result): 341 # item is (True, manager) if manager.initialize() succeeded, 342 # otherwise (False, failure.Failure) 343 res, obj = item 344 manager = managers[i] 345 if res == False: 346 if first_failure is None: 347 # we log everything and return the first failure to 348 # main() 349 first_failure = obj 350 351 self.warning("Manager %s failed to initialize: %s", 352 manager, result) 353 failed = True 354 else: 355 initialized.append(manager) 356 357 if failed: 358 deferreds = [] 359 # HMPF, clean initialized managers 360 for manager in initialized: 361 deferreds.append(defer.maybeDeferred(manager.stop)) 362 363 dfr = defer.DeferredList(deferreds) 364 # if a stop() call fails here we can't do anything, at least we 365 # tried to clean 366 dfr.addCallback(lambda result: defer.fail(first_failure)) 367 return dfr 368 369 self._player_registry.initialize() 370 self._interface_controller.initialize() 371 self.bus.send_message(bus_message.ComponentsLoaded())
372 373 # call initialize() on all the managers, if any of the managers fail to 374 # initialize, stop() the initialized ones and return a failure to the 375 # caller; otherwise go on and initialize the player registry and the 376 # interface controller and send the damn ComponentsLoaded Message 377 managers = (self._service_manager, self._metadata_manager, 378 self._media_manager, self._input_manager) 379 deferreds = [] 380 for manager in managers: 381 self.debug("Initializing manager %s" % manager) 382 deferreds.append(defer.maybeDeferred(manager.initialize)) 383 384 dfr = defer.DeferredList(deferreds) 385 dfr.addCallback(initialize_managers_done, managers) 386 return dfr 387
388 - def start(self):
389 """ Execute the application. Start the Managers and the 390 InterfaceController. 391 """ 392 if hasattr(gobject, 'set_prgname') == True: 393 gobject.set_prgname('elisa') 394 395 threads.deferToThread(self._check_updates) 396 397 self.running = True 398 self.info("Starting") 399 self.bus.start() 400 self.input_manager.start() 401 self.metadata_manager.start() 402 self.media_manager.start(seconds=5) 403 self.service_manager.start() 404 self.interface_controller.start()
405
406 - def restart(self):
407 return self.stop(restart=True)
408
409 - def stop(self, stop_reactor=True):
410 """Stop the application. 411 412 @param stop_reactor: stop the reactor after stopping the application 413 @type stop_reactor: bool 414 @rtype: L{twisted.internet.defer.Deferred} 415 """ 416 417 def interface_controller_stopped(result): 418 self.info("Stopping managers") 419 self.player_registry.deinitialize() 420 self.thumbnailer.stop() 421 422 manager_deferreds = [] 423 for manager in (self.service_manager, self.metadata_manager, 424 self.media_manager, self.input_manager): 425 manager_deferreds.append(defer.maybeDeferred(manager.stop)) 426 427 dfr = defer.DeferredList(manager_deferreds) 428 dfr.addCallback(managers_stopped) 429 return dfr
430 431 def managers_stopped(managers): 432 self.info("Stopping reactor") 433 self.bus.stop() 434 435 self.running = False 436 437 if self.config: 438 self.config.write() 439 440 if stop_reactor and reactor.running: 441 reactor.stop() 442 443 if self.running: 444 # stop the interface controller, then the player registry, the 445 # thumbnailer and the managers 446 self.info("Stopping interface controller") 447 dfr = self.interface_controller.stop() 448 dfr.addCallback(interface_controller_stopped) 449 else: 450 dfr = defer.succeed(None) 451 452 return dfr 453
454 -class Options(usage.Options):
455 """ 456 Application's command-line options definitions 457 """ 458 459 optFlags = [['version', '', 'show elisa version'], 460 ['twisted-version', '', 'show twisted version'], 461 ['tracebacks', 't', 'display tracebacks'], 462 ['log', 'l', 'log output in elisa.log files'], 463 ] 464 465 tracebacks = False 466
467 - def parseArgs(self, config_file=None):
468 self['config_file'] = config_file
469
470 - def opt_twisted_version(self):
471 return usage.Options.opt_version(self)
472
473 - def opt_version(self):
474 print 'Elisa version %s' % __version__ 475 sys.exit(0)
476 477
478 -def main(args=None):
479 """ Parse command-line options and start a new Application in the 480 Twisted's reactor. 481 """ 482 if not args: 483 args = sys.argv 484 485 options = Options() 486 try: 487 options.parseOptions(args[1:]) 488 except usage.UsageError, errortext: 489 print '%s: %s' % (args[0], errortext) 490 print '%s: Try --help for usage details.' % (args[0]) 491 sys.exit(1) 492 493 try: 494 app = Application(options['config_file'], 495 show_tracebacks=options['tracebacks']) 496 except Exception, exc: 497 print 'Error', exc 498 if options['tracebacks']: 499 raise 500 else: 501 def initialize_done(result): 502 return app.start()
503 504 def initialize_failure(failure): 505 print 'Elisa failed to initialize', failure 506 reactor.stop() 507 508 # if initialize() succeeds go ahead and call start(), otherwise stop the 509 # reactor 510 dfr = app.initialize() 511 dfr.addCallbacks(initialize_done, initialize_failure) 512 reactor.run() 513 dfr = app.stop(stop_reactor=False) 514