Home | Trees | Indices | Help |
---|
|
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 DAAP MediaProvider component 19 """ 20 21 22 __maintainer__ = 'Benjamin Kampmann <benjamin@fluendo.com>' 23 __maintainer2__ = 'Philippe Normand <philippe@fluendo.com>' 24 __maintainer3__ = 'Alessandro Decina <alessandro@fluendo.com>' 25 26 from elisa.base_components.media_provider import MediaProvider 27 from elisa.core import media_uri 28 from elisa.core import common 29 from elisa.core.bus import bus_message 30 from elisa.core.component import InitializeFailure 31 from elisa.core.media_uri import MediaUri 32 from elisa.core.observers.dict import DictObservable 33 from elisa.extern.natural_sort import natcasecmp 34 from elisa.extern.odict import SequenceOrderedDict 35 36 import socket 37 import gst 38 import gobject 39 40 import daap 41 42 try: 43 import dbus 44 if getattr(dbus, 'version', (0,0,0)) >= (0,41,0): 45 import dbus.glib 46 except ImportError: 47 dbus = None 48 49 if dbus: 50 try: 51 import avahi 52 except ImportError: 53 avahi = None 54 else: 55 avahi = None 56 57 from twisted.internet import defer, threads 58 59 """ 60 TODO: 61 62 - add a "copy to local library" feature? 63 """ 6466 __gsttemplates__ = ( 67 gst.PadTemplate("src", 68 gst.PAD_SRC, 69 gst.PAD_ALWAYS, 70 gst.caps_new_any()), 71 ) 72 73 __gstdetails__ = ("DAAP plugin", "Foo/Bar", "Read data on DAAP shares", 74 "Philippe Normand <philippe@fluendo.com>, " 75 "Alessandro Decina <alessandro@fluendo.com>") 76 77 blocksize = 4096 78158 159 # define this here so we can use it in the unittest without having to create the 160 # media provider80 super(DaapSource, self).__init__() 81 self.curoffset = 0 82 self._libs = {} 83 self.client = None 84 self.session = None 85 self.response = None 86 self.track = None 87 self.data = None88 89 @classmethod 92 93 @classmethod 96 103105 return self.uri106 107 # set_uri and get_uri are just syntactic sugar for do_set_uri and 108 # do_get_uri 111113 return self.do_get_uri()114116 m_uri = media_uri.MediaUri(self.uri) 117 host = m_uri.host 118 port = int(m_uri.port or '3689') 119 120 self.client = daap.DAAPClient() 121 self.client.connect(host, port) 122 self.session = self.client.login() 123 library = self.session.library() 124 track_id = int(m_uri.get_param('id','1')) 125 self.track = None 126 for track in library.tracks(): 127 if track.id == track_id: 128 self.track = track 129 break 130 131 if track is None: 132 return False 133 134 return True135137 self.session.logout() 138 self.client.socket.close() 139 self.client = None 140 self.session = None 141 self.track = None 142 self.data = None 143 144 return True145 148162 if gst.gst_version < (0, 10, 13): 163 raise InitializeFailure(name, 164 "The installed gst version doesn't support python plugins, " 165 "daap support will not be available.") 166 167 gobject.type_register(DaapSource) 168 if gst.pygst_version <= (0, 10, 8): 169 # use our workaround 170 try: 171 import _daap_uri_interface 172 except ImportError: 173 raise InitializeFailure(name, 174 "The installed pygst version doesn't support the " 175 "GstURIHandler interface and the custom GstURIInterface " 176 "wrapper has not been built") 177 178 # only register the first time (ComponentTestCase creates the daap 179 # provider multiple times, so we have to check this) 180 if gst.URIHandler.__gtype__ not in DaapSource.__gtype__.interfaces: 181 _daap_uri_interface.bind_to(DaapSource) 182 183 gst.element_register(type=DaapSource, elementname="daapsrc", 184 rank=gst.RANK_PRIMARY)185187235 241 247 254189 self._callbacks = {'new-service': [], 190 'remove-service': [] 191 } 192 self.bus = dbus.SystemBus() 193 avahi_bus = self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER) 194 self.server = dbus.Interface(avahi_bus, avahi.DBUS_INTERFACE_SERVER) 195 196 stype = '_daap._tcp' 197 domain = 'local' 198 self._plugged = {} 199 avahi_browser = self.server.ServiceBrowserNew(avahi.IF_UNSPEC, 200 avahi.PROTO_UNSPEC, 201 stype, domain, 202 dbus.UInt32(0)) 203 obj = self.bus.get_object(avahi.DBUS_NAME, avahi_browser) 204 self.browser = dbus.Interface(obj, avahi.DBUS_INTERFACE_SERVICE_BROWSER)205207 self.browser.connect_to_signal('ItemNew', self.new_service) 208 self.browser.connect_to_signal('ItemRemove', self.remove_service)209 212214 service = self.server.ResolveService(interface, protocol, 215 name, type, domain, 216 avahi.PROTO_UNSPEC, dbus.UInt32(0)) 217 218 address, port = service[-4:-2] 219 name = unicode(service[2]) 220 for cb in self._callbacks['new-service']: 221 self._plugged[name] = (address,port) 222 cb(name, address, port)223225 address, port = self._plugged[name] 226 for cb in self._callbacks['remove-service']: 227 cb(name, address, port)228 229231 self._callbacks[sig_name].append(callback)232234 self._callback[sig_name].remove(callback)256 261 265 268537270 if not dbus: 271 if not avahi: 272 self.warning("Please install python-avahi to get Avahi support") 273 self.warning("python-dbus is missing...") 274 self.warning("Avahi support disabled. DAAP shares won't appear automagically") 275 else: 276 try: 277 self._monitor = AvahiMonitor() 278 self._monitor.add_callback('new-service', self._add_location) 279 self._monitor.add_callback('remove-service', 280 self._remove_location) 281 self._monitor.start() 282 except Exception, ex: 283 error_msg = "Couldn't initialize Avahi monitor: %s" % str(ex) 284 raise InitializeFailure(self.name, error_msg)285287 self.info("New DAAP share at %s:%s : %s" % (address, port, name)) 288 uri = 'daap://%s:%s/' % (address, port) 289 action_type = bus_message.LocalNetworkLocation.ActionType.LOCATION_ADDED 290 msg = bus_message.LocalNetworkLocation(action_type, name, 'daap', 291 uri, ['audio',], 292 theme_icon='network_share_icon') 293 common.application.bus.send_message(msg)294296 self.info("DAAP share at %s:%s disappeared" % (address, port)) 297 uri = 'daap://%s:%s/' % (address, port) 298 action_type = bus_message.LocalNetworkLocation.ActionType.LOCATION_REMOVED 299 msg = bus_message.LocalNetworkLocation(action_type, name, 'daap', 300 uri, ['audio',]) 301 common.application.bus.send_message(msg)302304 self.debug("Retrieving DAAP library at %r", uri) 305 306 host = uri.host 307 if uri.port: 308 port = int(uri.port) 309 else: 310 port = 3689 311 312 address = (host, port) 313 if address not in self._libraries: 314 connection = daap.DAAPClient() 315 try: 316 connection.connect(*address) 317 except Exception, exc: 318 # FIXME: prezise the Exception 319 self.info("Couldn't connect to %s : %s" % (address,str(exc))) 320 else: 321 try: 322 # auth isn't supported yet. Just log in 323 session = connection.login() 324 library = session.library() 325 326 self._libraries[address] = self._build_library(library.tracks()) 327 except (socket.timeout, socket.error): 328 pass 329 except daap.DAAPError, error: 330 msg = "Error login into %s: %s" % (address, str(error)) 331 self.warning(msg) 332 raise daap.DAAPError(msg) 333 334 return self._libraries.get(address)335337 lib = SequenceOrderedDict() 338 for track in tracks: 339 if None in (track.artist, track.album): 340 continue 341 if not lib.get(track.artist): 342 lib[track.artist] = DaapArtist(track.artist) 343 if not lib[track.artist].get(track.album): 344 lib[track.artist][track.album] = DaapAlbum(track.album) 345 if not lib[track.artist][track.album].get(track.id): 346 lib[track.artist][track.album][track.id] = \ 347 DaapTrack(track.id, track.name) 348 349 for artist_name, album in lib.iteritems(): 350 for album_name, tracks in album.iteritems(): 351 tracks.sort(natcasecmp, key=lambda key: tracks[key].name) 352 album.sort(natcasecmp) 353 lib.sort(natcasecmp) 354 355 return lib356 359 362364 if not uri.get_param('id'): 365 media_type = {'file_type': 'directory', 'mime_type': ''} 366 else: 367 # DAAP only supports audio media, see DMAP for picture media 368 media_type = {'file_type': 'audio', 'mime_type': ''} 369 return media_type370 373 377379 self.debug("Retrieving children of: %s", uri) 380 lib = None 381 try: 382 lib = self._get_library(uri) 383 except daap.DAAPError, error: 384 self.warning(error) 385 386 if lib: 387 artist = uri.get_param('artist', None) 388 album = uri.get_param('album', None) 389 track_id = uri.get_param('track', None) 390 self.debug("URI artist and album are: %r - %r" % (artist,album)) 391 392 if track_id: 393 # no children 394 pass 395 elif artist and album: 396 # process songs of given album of the artist 397 for track_id, track in lib.get(artist).get(album).iteritems(): 398 track = track.name 399 metadata = DictObservable() 400 child_uri = media_uri.MediaUri(uri.parent) 401 child_dict = {'id': track_id, 'artist': artist, 402 'album': album, 'track': track} 403 child_uri.set_params(child_dict) 404 child_uri.label = track 405 ## FIXME: remove this ugly hack: 406 ## UGLY HACK, so that the albums are not shown for 407 ## track-items 408 metadata['song_artist'] = artist 409 metadata['song_album'] = album 410 metadata['song'] = track 411 self.debug("Appendind %r with metadata %s" % (child_uri, 412 metadata)) 413 children.append((child_uri, metadata)) 414 elif artist: 415 # list albums of given artist 416 for album_name, album in lib.get(artist,{}).iteritems(): 417 metadata = DictObservable() 418 child_uri = media_uri.MediaUri(uri.parent) 419 metadata['artist'] = artist 420 metadata['album'] = album_name 421 ## Don't use a DictObservable for params! 422 child_uri.set_params({'artist' : artist, 423 'album' : album_name}) 424 child_uri.label = album_name 425 self.debug("Appendind %r with metadata %s" % (child_uri, 426 metadata)) 427 428 children.append((child_uri, metadata)) 429 else: 430 # list artists 431 for artist in lib.iterkeys(): 432 metadata = DictObservable() 433 child_uri = media_uri.MediaUri(uri.parent) 434 child_uri.set_params({'artist': artist}) 435 child_uri.label = artist 436 metadata['artist'] = artist 437 self.debug("Appendind %r with metadata %s" % (child_uri, 438 metadata)) 439 children.append((child_uri,metadata)) 440 441 self.debug("Retrieved children: %r", children) 442 return children443 447449 artist_name = uri.get_param('artist') 450 album_name = uri.get_param('album') 451 track_id = uri.get_param('id') 452 if track_id: 453 track_id = int(track_id) 454 455 artist = None 456 album = None 457 track = None 458 459 lib = self._get_library(uri) 460 461 if artist_name: 462 artist = lib[artist_name] 463 464 if album_name: 465 album = artist[album_name] 466 467 while True: 468 if artist is None: 469 # start from the first artist 470 artist = lib.values[0] 471 472 if album is None: 473 # start from the first album of the current artist 474 album = artist.values[0] 475 476 if not track_id: 477 # start from the first track of the selected album of the 478 # selected artist 479 track = album.values[0] 480 481 # we have the track 482 break 483 484 track_index = album.index(track_id) 485 if track_index == len(album) - 1: 486 # the current track is the last in the album, we must 487 # find the next album if the current album is not the root of 488 # our search 489 490 if album.name == root.get_param('album'): 491 break 492 493 # reset the track_id so in the next iteration the first track of 494 # the next album will be picked 495 track_id = None 496 497 album_index = artist.index(album.name) 498 if album_index == len(artist) - 1: 499 # last album, find the next artist if the current one is no 500 # the root of the search 501 502 if artist.name == root.get_param('artist'): 503 break 504 505 # reset the album so in the next iteration the first album 506 # of the next artist will be picked 507 album = None 508 509 artist_index = lib.index(artist.name) 510 if artist_index == len(lib) - 1: 511 # no more tracks 512 break 513 else: 514 artist = lib.values[artist_index + 1] 515 else: 516 album = artist.values[album_index + 1] 517 else: 518 track = album.values[track_index + 1] 519 break 520 521 if track is None: 522 return None 523 524 uri = MediaUri(uri) 525 child_dict = {'id': track.id, 'artist': artist.name, 526 'album': album.name, 'track': track.name} 527 uri.label = track.name 528 uri.set_params(child_dict) 529 return uri530532 if uri.get_param('id'): 533 # a track 534 return False 535 536 return bool(set(['audio', 'directory']).intersection(media_types))
Home | Trees | Indices | Help |
---|
Generated by Epydoc 3.0beta1 on Wed Jan 16 19:10:55 2008 | http://epydoc.sourceforge.net |