Package elisa :: Package plugins :: Package good :: Package xmlmenu :: Package xmlmenu_components :: Module uri_node_builder
[hide private]
[frames] | no frames]

Source Code for Module elisa.plugins.good.xmlmenu.xmlmenu_components.uri_node_builder

  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  __maintainer__ = 'Benjamin Kampmann <benjamin@fluendo.com>' 
 19   
 20  from menu_entry_builder import MenuEntryBuilder 
 21  from elisa.core.observers.list import ListObserver, ListObservable 
 22  from elisa.core.observers.dict import DictObserver, DictObservable 
 23  from elisa.core.media_uri import MediaUri 
 24  from elisa.core.media_manager import MediaProviderNotFound 
 25  from elisa.core import common 
 26   
 27  from Queue import Queue 
 28   
 29  from weakref import ref 
 30   
 31  from twisted.internet import defer, reactor  
 32  from twisted.internet.error import AlreadyCalled 
 33   
34 -class DeferredCanceled(Exception):
35 pass
36
37 -class MetadataObserver(DictObserver):
38
39 - def __init__(self, model):
40 DictObserver.__init__(self) 41 self._model = ref(model)
42
43 - def modified(self, key, value):
44 if key == 'default_image': 45 model = self._model() 46 if model: 47 model.thumbnail_source = value
48
49 -class UriObserver(ListObserver):
50
51 - def __init__(self, model, config, first_element_dfr):
52 ListObserver.__init__(self) 53 self._model = ref(model) 54 self._config = config 55 self._queue = Queue() 56 self._observers = [] 57 self.running = False 58 self.check_on_empty = False 59 self._first_element_dfr = first_element_dfr 60 self._queue_delayed_call = None
61
62 - def stop(self):
63 """ 64 clean up all references and stop the process of running 65 """ 66 self.running = False 67 try: 68 self._first_element_dfr.errback(DeferredCanceled) 69 except defer.AlreadyCalledError: 70 pass 71 72 if self._queue_delayed_call and self._queue_delayed_call.active(): 73 self._queue_delayed_call.cancel() 74 model = self._model() 75 if model: 76 model.loading = False 77 self._observers[:] = []
78
79 - def inserted(self, elements, position):
80 model = self._model() 81 82 if not model: 83 return 84 85 for (counter, element) in enumerate(elements): 86 uri, metadata = element 87 88 new_model = model.activity.create_menu_for_uri(uri, self._config) 89 self._queue.put( (uri, metadata, new_model, position+counter) ) 90 91 if not self.running: 92 self.running = True 93 self._process_queue()
94
95 - def _process_queue(self):
96 # FIXME: Does that need to be threadsafe? 97 def delay_call(result): 98 self._queue_delayed_call = reactor.callLater(0.01, self._process_queue)
99 100 if not self._queue.empty(): 101 uri, metadata, model, index = self._queue.get() 102 if self.running: 103 dfr = self._process_model(uri, metadata, model, index) 104 dfr.addBoth(delay_call) 105 else: 106 self.running = False 107 model = self._model() 108 if model and self.check_on_empty and len(model.children) == 0: 109 try: 110 self._first_element_dfr.errback(Exception) 111 except defer.AlreadyCalledError: 112 pass 113 model = self._model() 114 if model: 115 model.loading = False
116
117 - def _process_model(self, uri, metadata, model, index):
118 registry = common.application.plugin_registry 119 metadata_manager = common.application.metadata_manager 120 media_manager = common.application.media_manager 121 122 def observe_metadata(metadata, model): 123 if not isinstance(metadata, DictObservable): 124 # metadata is pretty much never a DictObservable 125 metadata = DictObservable(metadata) 126 127 content_type = self._config['ContentType'] 128 129 # FIXME: resetting default_image works only when dealing with 130 # audio content_type 131 if content_type in ('audio','artist', 'album') and \ 132 not metadata.has_key('default_image'): 133 metadata['default_image'] = None 134 135 metadata['uri'] = uri 136 metadata['content-type'] = content_type 137 138 metadata_observer = MetadataObserver(model) 139 metadata.add_observer(metadata_observer) 140 self._observers.append(metadata_observer) 141 return metadata
142 143 def insert_model(new_model, index): 144 model = self._model() 145 if not model: 146 return 147 new_model.activity = model.activity 148 model.children.insert(index, new_model) 149 model.has_children = True 150 try: 151 model.loading = False 152 self._first_element_dfr.callback(model.children) 153 except AlreadyCalled: 154 # the callback has to be fired only for the very first 155 # insertion 156 pass 157 158 def set_play_action(uri, model, file_type): 159 action = registry.create_component('xmlmenu:play_action') 160 action.player_model = model.activity.activity.player_model 161 action.model = ref(model) 162 model.activate_action = action 163 164 def set_videoplay_action(uri, model): 165 action = registry.create_component('xmlmenu:videoplay_action') 166 action.player_model = model.activity.activity.player_model 167 action.model = ref(model) 168 model.activate_action = action 169 170 def set_view_image_action(uri, model): 171 registry = common.application.plugin_registry 172 action = registry.create_component('xmlmenu:viewimage_action') 173 action.uri = uri 174 action.parent_model = self._model 175 action.slideshow_model = model.activity.activity.slideshow_model 176 model.activate_action = action 177 178 def set_enqueue_action(model): 179 registry = common.application.plugin_registry 180 action = registry.create_component('xmlmenu:enqueue_action') 181 action.parent_model = model 182 #FIXME: pfui! 183 action.playlist_model = model.activity.activity.player_model.playlist 184 model.children = registry.create_component('base:list_model') 185 new_model = registry.create_component('base:menu_node_model') 186 new_model.text = 'Enqueue' 187 new_model.activate_action = action 188 model.children.append(new_model) 189 190 def set_thumbnail_source(uri, metadata, model, file_type): 191 if metadata.has_key('default_image'): 192 model.thumbnail_source = metadata['default_image'] 193 else: 194 # thumbnail source is the media itself for everything but 195 # directories and audio files 196 if file_type == "image" or file_type == "video": 197 model.thumbnail_source = uri 198 else: 199 model.thumbnail_source = None 200 201 def got_media_type(media_type): 202 file_type = media_type['file_type'] 203 if file_type == "directory": 204 model.has_children = True 205 206 # FIXME: hack to detect the URI represents an audio album. 207 # That totally suck, hardcoding a theme icon. 208 if uri.scheme == 'elisa' and uri.path.find('/albums/') != -1: 209 theme_icon = 'audio_album_icon' 210 else: 211 theme_icon = self._config['DefaultDirsIcon'] 212 213 model.theme_icon = theme_icon 214 # Disabled, because it is hakkish 215 #set_enqueue_action(model) 216 else: 217 # FIXME: action management should not be hardcorded! 218 model.has_children = False 219 model.theme_icon = self._config['DefaultFilesIcon'] 220 if file_type == "image": 221 set_view_image_action(uri, model) 222 elif file_type == "video": 223 set_videoplay_action(uri, model) 224 elif file_type == "audio": 225 set_play_action(uri, model, file_type) 226 227 file_filter = self._config['MediaFilter'] 228 229 if file_filter.lower() == 'any' or \ 230 file_type == 'directory' or \ 231 file_type == file_filter: 232 233 # FIXME: this is an ugly hack... 234 if metadata.has_key('artist'): 235 model.sub_text = metadata['artist'] 236 237 modified_metadata = observe_metadata(metadata, model) 238 metadata_manager.get_metadata(modified_metadata) 239 set_thumbnail_source(uri, modified_metadata, model, file_type) 240 insert_model(model, index) 241 242 dfr = media_manager.get_media_type(uri) 243 dfr.addCallback(got_media_type) 244 return dfr 245
246 - def removed(self, elements, position):
247 model = self._model() 248 249 if not model: 250 return 251 252 index = model.activity.files_index_start+position 253 model.children[index:index+len(elements)] = [] 254 255 if len(model.children) == model.activity.files_index_start: 256 model.children[:] = [] 257 model.has_children = False
258
259 -class UriNodeBuilder(MenuEntryBuilder):
260 """ 261 Build MenuEntries for the type 'menu_node' 262 """ 263
264 - def initialize(self):
265 MenuEntryBuilder.initialize(self) 266 self._observers = {}
267
268 - def menu_entry_identifiers__get(self):
269 return ['uri_node']
270
271 - def clean(self):
272 for observer in self._observers.itervalues(): 273 observer.stop()
274
275 - def build_menu_entry(self, parent, node):
276 # URI-Lookup 277 url = node.find('URI') 278 if url == None: 279 self.warning("URI-MenuNode needs a URI!") 280 return defer.succeed(parent.children) 281 uri = MediaUri(url.text) 282 283 # Label-Making 284 label_tag = node.find('Label') 285 label = self._make_label(label_tag) 286 287 default_config = self.model_configs[parent] 288 config = self._make_config(node.find('Configuration'), 289 default_config) 290 291 registry = common.application.plugin_registry 292 menu_model = registry.create_component('base:menu_node_model') 293 menu_model.has_children = True 294 menu_model.text = label 295 menu_model.activity = self 296 menu_model.uri = uri 297 298 self._set_icon(menu_model, node.find('Icon'), 299 fallback=config['DefaultDirsIcon']) 300 301 self.model_configs[menu_model] = config 302 303 parent.children.append(menu_model) 304 self.debug("appending %s to %s" % (menu_model.text, parent.text)) 305 306 return defer.succeed([menu_model])
307
308 - def unload(self, model):
309 model_id = id(model) 310 if self._observers.has_key(model_id): 311 self._observers[model_id].stop() 312 del self._observers[model_id] 313 model[:] = []
314
315 - def loadmore(self, model):
316 self.debug("got loadmore for %s" % model.text) 317 uri = model.uri 318 config = self.model_configs[model] 319 320 if model.children == None: 321 registry = common.application.plugin_registry 322 model.children = registry.create_component('base:list_model') 323 model.children.content_type = config['ContentType'] 324 325 model.children.activity = self 326 self.debug("set activity to self") 327 328 if len(model.children) > 0: 329 return defer.succeed(model.children) 330 331 332 def got_children(children, uri, model, dfr, observer): 333 model.has_children = (len(children) > 0) 334 # FIXME: another ugly hack and breaks on AudioCD 335 if observer.running: 336 observer.check_on_empty = True 337 else: 338 try: 339 dfr.errback(Exception) 340 except defer.AlreadyCalledError: 341 pass 342 343 model.loading = False 344 return model.children
345 346 def erroneous_get_children(error, uri, model): 347 model.has_children = False 348 model.loading = False 349 try: 350 error.raiseException() 351 except: 352 self.warning("Error while retrieving children of %s" % uri) 353 common.application.handle_traceback() 354 return model.children
355 356 first_element_dfr = defer.Deferred() 357 if uri: 358 children = ListObservable() 359 uri_observer = UriObserver(model, config, first_element_dfr) 360 children.add_observer(uri_observer) 361 self._observers[id(model.children)] = uri_observer 362 363 model.has_children = False 364 model.loading = True 365 media_manager = common.application.media_manager 366 try: 367 dfr = media_manager.get_direct_children(uri, children) 368 except MediaProviderNotFound, exc: 369 # FIXME: we should return a kind of message to the user 370 self.warning("No Media provider found for %s" % uri) 371 model.loading = False 372 return defer.fail(exc) 373 374 if dfr != None: 375 dfr.addCallback(got_children, uri, model, 376 first_element_dfr, 377 uri_observer) 378 dfr.addErrback(erroneous_get_children, uri, model) 379 else: 380 model.has_children = False 381 model.loading = False 382 else: 383 self.debug("No URI for %r", model) 384 return defer.fail(None) 385 return first_element_dfr 386
387 - def create_menu_for_uri(self, uri, config):
388 label = uri.label 389 registry = common.application.plugin_registry 390 # This might returns a deferred! 391 menu_model = registry.create_component('base:menu_node_model') 392 menu_model.uri = uri 393 menu_model.text = uri.label 394 menu_model.activity = self 395 self.model_configs[menu_model] = config 396 return menu_model
397