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

Source Code for Module elisa.plugins.good.xmlmenu.xmlmenu_components.locations_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   
 22  from elisa.core.observers.list import ListObserver, ListObservable 
 23  from elisa.core.bus import bus_message 
 24  from elisa.core.utils import misc 
 25  from elisa.core import common 
 26  from elisa.core.media_manager import MediaProviderNotFound 
 27  from elisa.core.media_uri import MediaUri, ParseException 
 28   
 29  from elisa.extern.coherence.et import parse_xml 
 30   
 31  from twisted.internet import defer 
 32   
 33  import platform, os, re 
 34   
 35   
36 -class FilteredLocationObserver(ListObserver):
37
38 - def __init__(self, model, handle_call, icon=None, config=None):
39 ListObserver.__init__(self) 40 self._model = model 41 self._handle_call = handle_call 42 self.location_types = set() 43 self.media_types = set() 44 self.uri_schemes = set() 45 self._icon = icon 46 self._config = config 47 self._location_map = {}
48 49
50 - def inserted(self, elements, position):
51 for location in elements: 52 score = 0 53 if len(self.media_types) == 0 or \ 54 self.media_types.intersection(set(location.media_types)): 55 score += 1 56 57 if len(self.location_types) == 0 or \ 58 location.location_type in self.location_types: 59 score += 1 60 61 if len(self.uri_schemes) == 0 or \ 62 location.uri.scheme in self.uri_schemes: 63 score += 1 64 65 if score == 3: 66 # FIXME: a bit stupid, or? 67 uri = location.uri 68 69 c_e = parse_xml("<MenuEntry type='uri_node'>" \ 70 "<Label>%s</Label>\n"\ 71 "<URI>%s</URI>\n" \ 72 "</MenuEntry>" % 73 (uri.label, uri)).getroot() 74 75 if location.theme_icon: 76 c_e.insert(1, parse_xml('<Icon>%s</Icon>' % 77 location.theme_icon).getroot()) 78 elif self._icon != None: 79 c_e.insert(1, self._icon) 80 81 if self._config != None: 82 c_e.insert(2, self._config) 83 84 dfr = self._handle_call(self._model, c_e) 85 dfr.addCallback(self._models_added, location)
86
87 - def removed(self, elements, position):
88 89 for element in elements: 90 element_id = id(element) 91 92 if self._location_map.has_key(element_id): 93 for model in self._location_map[element_id]: 94 try: 95 self._model.children.remove(model) 96 except ValueError: 97 # The item is not in the list anymore. whatsoever, 98 # that is also what we wanted ;) 99 pass 100 101 del self._location_map[element_id]
102
103 - def _models_added(self, models, location):
104 location_id = id(location) 105 106 if not self._location_map.has_key(location_id): 107 self._location_map[location_id] = [] 108 109 for model in models: 110 self._location_map[location_id].append(model)
111 112 113
114 -class Location(object):
115 """ 116 each Location has some parameters 117 """
118 - def __init__(self):
119 self.media_types = [] 120 self.location_type = '' 121 self.uri = None 122 self.theme_icon = None
123
124 -class MediaManagerInformer(ListObserver):
125 """ 126 Inform the MediaManager about the information, if they are scannable. 127 """ 128
129 - def inserted(self, elements, position):
130 media_manager = common.application.media_manager 131 for element in elements: 132 uri = element.uri 133 try: 134 if media_manager.is_scannable(uri): 135 media_manager.add_source(uri) 136 except MediaProviderNotFound: 137 pass
138
139 - def removed(self, elements, position):
140 media_manager = common.application.media_manager 141 for element in elements: 142 uri = element.uri 143 media_manager.remove_source(uri)
144
145 -class LocationsBuilder(MenuEntryBuilder):
146 """ 147 This class can handle a special MenuEntry-XML-Tag and create 148 MenuNodeModels according to the data it gets. 149 """ 150 151 """ 152 The XDG directories mapped to the elisa internal media type 153 """ 154 xdg_media_types = { 155 'XDG_MUSIC_DIR' : ['audio'], 156 'XDG_PICTURES_DIR' : ['image'], 157 'XDG_VIDEOS_DIR' : ['video'] 158 } 159 160 """ 161 The different bus_messages mapped to the location_type 162 """ 163 location_map = {bus_message.DeviceAction : 'device', 164 bus_message.ForeignApplication : 'app', 165 bus_message.InternetLocation : 'internet', 166 bus_message.LocalNetworkLocation : 'network' 167 } 168 169 default_config = { 'auto_locations' : 1, 170 'locations' : []} 171 172
173 - def initialize(self):
174 self._locations = ListObservable() 175 self._media_informer = MediaManagerInformer() 176 self._locations.add_observer(self._media_informer) 177 self._observers = [] 178 179 self._load_media_locations() 180 181 182 bus = common.application.bus 183 bus.send_message(bus_message.LocationsList(self._locations), 184 sender=self) 185 bus.register(self._got_bus_message, bus_message.MediaLocation)
186 #return defer.succeed(True) 187
188 - def menu_entry_identifiers__get(self):
189 return ['locations']
190
191 - def loadmore(self, model):
192 return defer.succeed(model.children)
193
194 - def unload(self, model):
195 pass
196
197 - def build_menu_entry(self, real_parent, node):
198 parent_node = node.find('ParentNode') 199 show_on_empty = False 200 if parent_node != None: 201 plugin_registry = common.application.plugin_registry 202 203 # FIXME: this should become deferred base soon 204 parent = plugin_registry.create_component('base:menu_node_model') 205 parent.children = plugin_registry.create_component('base:list_model') 206 parent.children.activity = parent.activity 207 208 self._set_icon(parent, parent_node.find('Icon')) 209 210 parent.text = self._make_label(parent_node.find('Label')) 211 if parent_node.get('show-on-empty', 'False').upper() == 'TRUE': 212 show_on_empty = True 213 self.model_configs[parent] = self.model_configs[real_parent] 214 else: 215 parent = real_parent 216 217 218 lObserver = FilteredLocationObserver(parent, 219 self.activity.handle_menu_entry, 220 icon=node.find('Icon'), 221 config=node.find('Configuration') 222 ) 223 224 for filter in node.findall('Filter'): 225 filter_type = filter.get('type', None) 226 if filter_type == None: 227 self.warning("Ignoring Filter without type: %s" % filter) 228 elif filter_type == 'media_type': 229 lObserver.media_types.add(filter.text) 230 elif filter_type == 'location_type': 231 lObserver.location_types.add(filter.text) 232 elif filter_type == 'uri_scheme': 233 lObserver.uri_schemes.add(filter.text) 234 235 lObserver.inserted(self._locations, 0) 236 self._locations.add_observer(lObserver) 237 self._observers.append(lObserver) 238 if real_parent != parent: 239 if len(parent.children) != 0 or show_on_empty: 240 parent.has_children = True 241 parent.activity = real_parent.activity 242 parent.children.activity = self 243 real_parent.children.append(parent) 244 245 return defer.succeed([])
246
247 - def _load_xdg_env(self):
248 xdg_ok = True 249 config_home = os.path.expanduser(os.getenv('XDG_CONFIG_HOME', 250 '~/.config')) 251 user_dirs_file = os.path.join(config_home, 'user-dirs.dirs') 252 253 # Lazily install XDG user-dirs files 254 if not os.path.exists(user_dirs_file): 255 self.debug("Executing xdg-user-dirs-update") 256 retval = os.system('xdg-user-dirs-update') 257 if retval != 0: 258 self.debug("xdg-user-dirs-update program not found") 259 xdg_ok = False 260 261 if xdg_ok: 262 variables = filter(lambda i: i.startswith('XDG'), 263 os.environ.keys()) 264 # TODO: simplify this if possible, with the xdg python package 265 for xdg in self.xdg_media_types.keys(): 266 if xdg not in variables: 267 self.debug('XDG variables not found in os.environ, loading %r', 268 user_dirs_file) 269 key_val_re = re.compile('(.*)="(.*)"') 270 for line in open(user_dirs_file).readlines(): 271 match = key_val_re.search(line) 272 if match and not match.groups()[0].startswith('#'): 273 var, value = match.groups() 274 self.debug('Found XDG variable %s, injecting in os.environ', 275 var) 276 value = misc.env_var_expand(value) 277 os.environ[var] = value 278 else: 279 self.debug("XDG variables %r already in os.environ", 280 variables)
281
282 - def _load_windows_env(self):
283 from win32 import tools 284 d = tools.get_multimedia_dir() 285 286 os.environ['XDG_MUSIC_DIR'] = d['My Music'].encode('utf8') 287 os.environ['XDG_PICTURES_DIR'] = d['My Pictures'].encode('utf8') 288 os.environ['XDG_VIDEOS_DIR'] = d['My Video'].encode('utf8')
289
290 - def _add_location(self, location_uri, media, location_type='local', 291 theme_icon=None):
292 current_locations = [ l.uri for l in self._locations ] 293 if location_uri in current_locations: 294 self.warning('Location %s given twice', location_uri) 295 else: 296 new_location = Location() 297 if theme_icon: 298 new_location.theme_icon = theme_icon 299 new_location.location_type = location_type 300 new_location.uri = location_uri 301 new_location.media_types = media 302 self._locations.append(new_location) 303 self.info("Added %r (%r)", location_uri, media)
304
305 - def _load_media_locations(self):
306 307 # Load media locations configured by the user 308 self._load_user_media_locations() 309 310 # Optionally load user's default media locations 311 self._load_default_media_locations() 312 313 self.debug("Loaded Locations: %s", self._locations)
314
315 - def _load_user_media_locations(self):
316 for location in self.config.get('locations', []): 317 try: 318 uri = MediaUri(location) 319 except (TypeError, ParseException): 320 self.warning('Location %s is no valid MediaUri' % location) 321 continue 322 323 # FIXME: remove this hack! 324 media = ['audio', 'video', 'image'] 325 loca_dict = self.config.get(location, {}) 326 327 if loca_dict.has_key('only_media'): 328 media = loca_dict['only_media'] 329 330 if loca_dict.has_key('label'): 331 uri.label = loca_dict['label'] 332 333 location_type = 'local' 334 # FIXME: a scheme-based location-type getting from the 335 # media_provider would be really useful 336 if loca_dict.has_key('location_type'): 337 location_type = loca_dict['location_type'] 338 339 self._add_location(uri, media, location_type)
340
341 - def _load_default_media_locations(self):
342 if bool(self.config.get('auto_locations', 1)): 343 if platform.system() == 'Windows': 344 self._load_windows_env() 345 else: 346 self._load_xdg_env() 347 348 for key, media in self.xdg_media_types.iteritems(): 349 location = os.environ.get(key) 350 if not location: 351 self.debug('%s not found in enviroment' % key) 352 continue 353 354 location_uri = MediaUri({'scheme': 'file', 'path': location}) 355 356 # Simplified XDG support, disabled, hackish 357 """ 358 media_locations = [ l.uri for l in self._locations 359 if media in l.media ] 360 if len(media_locations) == 0: 361 # only one directory to display, skip to its 362 # contents directly 363 media_manager = common.application.media_manager 364 365 def got_media_type(media_type, child_uri, media): 366 file_type = media_type['file_type'] 367 can_add = False 368 if file_type == 'directory': 369 theme_icon = None 370 can_add = True 371 elif file_type == media: 372 if file_type == 'audio': 373 theme_icon = '%s_file_icon' % file_type 374 else: 375 theme_icon = child_uri 376 can_add = True 377 378 if can_add: 379 self._add_location(child_uri, media, 380 theme_icon=theme_icon) 381 382 def got_children(children, media): 383 for (child_uri, metadata) in children: 384 d = media_manager.get_media_type(child_uri) 385 d.addCallback(got_media_type, child_uri, media) 386 387 dfr = media_manager.get_direct_children(location_uri, []) 388 dfr.addCallback(got_children, media) 389 else: 390 self._add_location(location_uri, media) 391 """ 392 self._add_location(location_uri, media) 393 else: 394 self.info("Auto locations lookup disabled")
395
396 - def _got_bus_message(self, message, sender):
397 398 if not self.location_map.has_key(type(message)): 399 self.warning("Unkown location of %s" % message) 400 return 401 402 ActionType = bus_message.MediaLocation.ActionType 403 404 if message.action == ActionType.LOCATION_ADDED: 405 # TODO: make use of can_eject variable 406 can_eject = message.removable 407 location = Location() 408 409 location.location_type = self.location_map[type(message)] 410 411 location.uri = MediaUri(message.mount_point) 412 location.uri.label = message.name 413 location.media_types = message.media_types 414 location.theme_icon = message.theme_icon 415 self._locations.append(location) 416 417 elif message.action == ActionType.LOCATION_REMOVED: 418 for location in self._locations: 419 if unicode(location.uri) == unicode(message.mount_point): 420 self._locations.remove(location)
421