rhythmbox r5563 - in trunk: . plugins/coherence plugins/coherence/upnp_coherence



Author: jrl
Date: Tue Feb  5 12:02:46 2008
New Revision: 5563
URL: http://svn.gnome.org/viewvc/rhythmbox?rev=5563&view=rev

Log:
2008-02-05  James Livingston  <doclivingston gmail com>

	patch by: Frank Scholz  <dev netzflocken de>


	* plugins/coherence/coherence.rb-plugin.in:
	* plugins/coherence/upnp_coherence/Makefile.am:
	* plugins/coherence/upnp_coherence/MediaPlayer.py:
	* plugins/coherence/upnp_coherence/MediaStore.py:
	* plugins/coherence/upnp_coherence/UpnpSource.py:
	* plugins/coherence/upnp_coherence/__init__.py: Improved the UPnP plugin
	by making us act as a Renderer, exporting artist/album lists and the
	like. Closes #512870


Added:
   trunk/plugins/coherence/upnp_coherence/MediaPlayer.py
Modified:
   trunk/ChangeLog
   trunk/plugins/coherence/coherence.rb-plugin.in
   trunk/plugins/coherence/upnp_coherence/Makefile.am
   trunk/plugins/coherence/upnp_coherence/MediaStore.py
   trunk/plugins/coherence/upnp_coherence/UpnpSource.py
   trunk/plugins/coherence/upnp_coherence/__init__.py

Modified: trunk/plugins/coherence/coherence.rb-plugin.in
==============================================================================
--- trunk/plugins/coherence/coherence.rb-plugin.in	(original)
+++ trunk/plugins/coherence/coherence.rb-plugin.in	Tue Feb  5 12:02:46 2008
@@ -2,8 +2,8 @@
 Loader=python
 Module=upnp_coherence
 IAge=1
-_Name=UPnP sharing support
-_Description=Adds support for playing media from, and sending media to UPnP/DLNA network devices
-Authors=James Livingston <doclivingston gmail com>
-Copyright=Copyright  2007 James Livingston
-Website=http://www.rhythmbox.org/
+_Name=DLNA/UPnP sharing and control support
+_Description=Adds support for playing media from and sending media to DLNA/UPnP network devices, and enables Rhythmbox to be controlled by a DLNA/UPnP ControlPoint
+Authors=James Livingston <doclivingston gmail com>, Frank Scholz <fs beebits net>
+Copyright=Copyright  2007,2008 James Livingston & Frank Scholz
+Website=http://www.rhythmbox.org/ - https://coherence.beebits.net

Modified: trunk/plugins/coherence/upnp_coherence/Makefile.am
==============================================================================
--- trunk/plugins/coherence/upnp_coherence/Makefile.am	(original)
+++ trunk/plugins/coherence/upnp_coherence/Makefile.am	Tue Feb  5 12:02:46 2008
@@ -4,4 +4,5 @@
 plugin_PYTHON =			\
        UpnpSource.py		\
        MediaStore.py		\
+       MediaPlayer.py		\
        __init__.py

Added: trunk/plugins/coherence/upnp_coherence/MediaPlayer.py
==============================================================================
--- (empty file)
+++ trunk/plugins/coherence/upnp_coherence/MediaPlayer.py	Tue Feb  5 12:02:46 2008
@@ -0,0 +1,437 @@
+# Licensed under the MIT license
+# http://opensource.org/licenses/mit-license.php
+
+# Copyright 2008, Frank Scholz <coherence beebits net>
+
+import urllib
+
+import rhythmdb
+
+from coherence.upnp.core.soap_service import errorCode
+from coherence.upnp.core import DIDLLite
+
+import louie
+
+from coherence.extern.simple_plugin import Plugin
+
+from coherence import log
+
+TRACK_COUNT = 1000000
+
+class RhythmboxPlayer(log.Loggable):
+
+    """ a backend to the Rhythmbox
+
+    """
+    logCategory = 'rb_media_renderer'
+
+    implements = ['MediaRenderer']
+    vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'}}
+    vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}}
+
+    def __init__(self, device, **kwargs):
+        self.warning("__init__ RhythmboxPlayer %r", kwargs)
+        self.shell = kwargs['shell']
+        self.server = device
+
+        self.player = None
+        self.metadata = None
+        self.host = '127.0.0.1'
+        self.name = "Rhythmbox on %s" % self.server.coherence.hostname
+
+        self.player = self.shell.get_player()
+        self.player.connect ('playing-song-changed',
+                                 self.playing_song_changed),
+        self.player.connect ('playing-changed',
+                                 self.playing_changed)
+        self.player.connect ('elapsed-changed',
+                                 self.elapsed_changed)
+        self.player.connect("notify::volume", self.volume_changed)
+        louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)
+
+        self.playing = False
+        self.state = None
+        self.duration = None
+        self.volume = 1.0
+        self.muted_volume = None
+        self.view = []
+        self.tags = {}
+
+    def __repr__(self):
+        return str(self.__class__).split('.')[-1]
+
+    def volume_changed(self, player, parameter):
+        self.volume = self.player.props.volume
+        self.warning('volume_changed to %r', self.volume)
+        if self.volume > 0:
+            rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
+            self.server.rendering_control_server.set_variable(rcs_id, 'Volume', self.volume*100)
+
+    def playing_song_changed(self, player, entry):
+        self.warning("playing_song_changed %r", entry)
+        if self.server != None:
+            connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
+        if entry == None:
+            self.update('STOPPED')
+            self.playing = False
+            #self.entry = None
+            self.metadata = None
+            self.duration = None
+        else:
+            self.id = self.shell.props.db.entry_get (entry, rhythmdb.PROP_ENTRY_ID)
+            bitrate = self.shell.props.db.entry_get(entry, rhythmdb.PROP_BITRATE) * 1024 / 8
+            # Duration is in HH:MM:SS format
+            seconds = self.shell.props.db.entry_get(entry, rhythmdb.PROP_DURATION)
+            hours = seconds / 3600
+            seconds = seconds - hours * 3600
+            minutes = seconds / 60
+            seconds = seconds - minutes * 60
+            self.duration = "%02d:%02d:%02d" % (hours, minutes, seconds)
+
+            mimetype = self.shell.props.db.entry_get(entry, rhythmdb.PROP_MIMETYPE)
+            # This isn't a real mime-type
+            if mimetype == "application/x-id3":
+                mimetype = "audio/mpeg"
+            size = self.shell.props.db.entry_get(entry, rhythmdb.PROP_FILE_SIZE)
+
+            # create item
+            item = DIDLLite.MusicTrack(self.id + TRACK_COUNT)
+            item.album = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ALBUM)
+            item.artist = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ARTIST)
+            item.genre = self.shell.props.db.entry_get(entry, rhythmdb.PROP_GENRE)
+            item.originalTrackNumber = str(self.shell.props.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER))
+            item.title = self.shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE) # much nicer if it was entry.title
+
+            item.res = []
+
+            uri = self.shell.props.db.entry_get(entry, rhythmdb.PROP_LOCATION)
+            if uri.startswith("file://"):
+                location = unicode(urllib.unquote(uri[len("file://"):]))
+
+                # add a fake resource for the moment
+                res = DIDLLite.Resource(location, 'http-get:*:%s:*' % mimetype)
+                if size > 0:
+                    res.size = size
+                if self.duration > 0:
+                    res.duration = self.duration
+                if bitrate > 0:
+                    res.bitrate = str(bitrate)
+                item.res.append(res)
+
+            elt = DIDLLite.DIDLElement()
+            elt.addItem(item)
+            self.metadata = elt.toString()
+            self.entry = entry
+            if self.server != None:
+                self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata)
+                self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata)
+            self.warning("playing_song_changed %r", self.metadata)
+        if self.server != None:
+            self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '00:00:00')
+            self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '00:00:00')
+
+    def playing_changed(self, player, state):
+        self.warning("playing_changed", state)
+        if state is True:
+            transport_state = 'PLAYING'
+        else:
+            if self.playing is False:
+                transport_state = 'STOPPED'
+            else:
+                transport_state = 'PAUSED_PLAYBACK'
+        self.update(transport_state)
+        try:
+            position = player.get_playing_time()
+        except:
+            position = None
+        try:
+            duration = player.get_playing_song_duration()
+        except:
+            duration = None
+        self.update_position(position,duration)
+        self.warning("playing_changed %r %r ", position, duration)
+
+    def elapsed_changed(self, player, time):
+        self.warning("elapsed_changed %r %r", player, time)
+        try:
+            duration = player.get_playing_song_duration()
+        except:
+            duration = None
+        self.update_position(time,duration)
+
+    def update(self, state):
+
+        self.warning("update %r", state)
+
+        if state in ('STOPPED','READY'):
+            transport_state = 'STOPPED'
+        if state == 'PLAYING':
+            transport_state = 'PLAYING'
+        if state == 'PAUSED_PLAYBACK':
+            transport_state = 'PAUSED_PLAYBACK'
+
+        if self.state != transport_state:
+            self.state = transport_state
+            if self.server != None:
+                connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
+                self.server.av_transport_server.set_variable(connection_id,
+                                                             'TransportState',
+                                                             transport_state)
+
+
+    def update_position(self, position,duration):
+        self.warning("update_position %r %r", position,duration)
+
+        if self.server != None:
+            connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
+            self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack', 0)
+
+        if position is not None:
+            m,s = divmod( position, 60)
+            h,m = divmod(m,60)
+            if self.server != None:
+                self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '%02d:%02d:%02d' % (h,m,s))
+                self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '%02d:%02d:%02d' % (h,m,s))
+
+        if duration <= 0:
+            duration = None
+
+        if duration is not None:
+            m,s = divmod( duration, 60)
+            h,m = divmod(m,60)
+
+            if self.server != None:
+                self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackDuration', '%02d:%02d:%02d' % (h,m,s))
+                self.server.av_transport_server.set_variable(connection_id, 'CurrentMediaDuration', '%02d:%02d:%02d' % (h,m,s))
+
+            if self.duration is None:
+                if self.metadata is not None:
+                    self.warning("update_position %r", self.metadata)
+                    elt = DIDLLite.DIDLElement.fromString(self.metadata)
+                    for item in elt:
+                        for res in item.findall('res'):
+                            res.attrib['duration'] = "%d:%02d:%02d" % (h,m,s)
+                    self.metadata = elt.toString()
+
+                    if self.server != None:
+                        self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata)
+                        self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata)
+
+                self.duration = duration
+
+    def load( self, uri, metadata):
+        self.warning("player load %r %r", uri, metadata)
+        #self.shell.load_uri(uri,play=False)
+        self.duration = None
+        self.metadata = metadata
+        self.tags = {}
+
+        if len(self.metadata)>0:
+            elt = DIDLLite.DIDLElement.fromString(self.metadata)
+            if elt.numItems() == 1:
+                item = elt.getItems()[0]
+
+                self.entry = self.shell.props.db.entry_lookup_by_location(uri)
+                self.warning("check for entry %r %r", self.entry, item.server_uuid)
+                if self.entry == None:
+                    if item.server_uuid is not None:
+                        entry_type = self.shell.props.db.entry_register_type("CoherenceUpnp:" + item.server_uuid)
+                        self.entry = self.shell.props.db.entry_new(entry_type, uri)
+                        self.warning("create new entry %r", self.entry)
+                    else:
+                        self.shell.load_uri(uri,play=False)
+                        self.entry = self.shell.props.db.entry_lookup_by_location(uri)
+                        self.warning("load and check for entry %r", self.entry)
+
+
+                duration = None
+                size = None
+                bitrate = None
+                for res in item.res:
+                    if res.data == uri:
+                        duration = res.duration
+                        size = res.size
+                        bitrate = res.bitrate
+                        break
+
+                self.shell.props.db.set(self.entry, rhythmdb.PROP_TITLE, item.title)
+                try:
+                    if item.artist is not None:
+                        self.shell.props.db.set(self.entry, rhythmdb.PROP_ARTIST, item.artist)
+                except AttributeError:
+                    pass
+                try:
+                    if item.album is not None:
+                        self.shell.props.db.set(self.entry, rhythmdb.PROP_ALBUM, item.album)
+                except AttributeError:
+                    pass
+
+                try:
+                    self.info("%r %r", item.title,item.originalTrackNumber)
+                    if item.originalTrackNumber is not None:
+                        self.shell.props.db.set(self.entry, rhythmdb.PROP_TRACK_NUMBER, int(item.originalTrackNumber))
+                except AttributeError:
+                    pass
+
+                if duration is not None:
+                    h,m,s = duration.split(':')
+                    seconds = int(h)*3600 + int(m)*60 + int(s)
+                    self.info("%r %r:%r:%r %r", duration, h, m , s, seconds)
+                    self.shell.props.db.set(self.entry, rhythmdb.PROP_DURATION, seconds)
+
+                if size is not None:
+                    self.shell.props.db.set(self.entry, rhythmdb.PROP_FILE_SIZE,int(size))
+
+        else:
+            self.shell.load_uri(uri,play=False)
+            self.entry = self.shell.props.db.entry_lookup_by_location(uri)
+
+        self.playing = False
+
+        connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
+        self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','Play,Stop,Pause')
+        self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',1)
+        self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri)
+        self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri)
+        self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',metadata)
+        self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri)
+        self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',metadata)
+
+    def start(self, uri):
+        self.load(uri)
+        self.play()
+
+    def stop(self):
+        self.warning("player stop")
+
+        self.player.stop()
+        self.playing = False
+        #self.server.av_transport_server.set_variable( \
+        #    self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
+        #                     'TransportState', 'STOPPED')
+
+    def play(self):
+        self.warning("player play")
+
+        if self.playing == False:
+            self.player.play_entry(self.entry)
+            self.playing = True
+        else:
+            self.player.playpause()
+        #self.server.av_transport_server.set_variable( \
+        #    self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
+        #                     'TransportState', 'PLAYING')
+
+    def pause(self):
+        self.player.pause()
+        #self.server.av_transport_server.set_variable( \
+        #    self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
+        #                     'TransportState', 'PAUSED_PLAYBACK')
+
+    def seek(self, location):
+        """
+        @param location:    simple number = time to seek to, in seconds
+                            +nL = relative seek forward n seconds
+                            -nL = relative seek backwards n seconds
+        """
+
+    def mute(self):
+        self.muted_volume = self.volume
+        self.player.set_volume(0)
+        rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
+        self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'True')
+
+    def unmute(self):
+        if self.muted_volume is not None:
+            self.player.set_volume(self.muted_volume)
+            self.muted_volume = None
+        self.player.set_mute(False)
+        rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
+        self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'False')
+
+    def get_mute(self):
+        return self.player.get_mute()
+
+    def get_volume(self):
+        self.volume = self.player.get_volume()
+        self.warning("get_volume %r", self.volume)
+        return self.volume * 100
+
+    def set_volume(self, volume):
+        self.warning("set_volume %r", volume)
+        volume = int(volume)
+        if volume < 0:
+            volume=0
+        if volume > 100:
+            volume=100
+
+        self.player.set_volume(float(volume/100.0))
+
+    def upnp_init(self):
+        self.current_connection_id = None
+        self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo',
+                            ['internal:%s:*:*' % self.host,
+                             'http-get:*:audio/mpeg:*'],
+                            default=True)
+        self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True)
+        self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True)
+        self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True)
+        self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True)
+        self.server.rendering_control_server.set_variable(0, 'Volume', self.get_volume())
+        self.server.rendering_control_server.set_variable(0, 'Mute', self.get_mute())
+
+    def upnp_Play(self, *args, **kwargs):
+        InstanceID = int(kwargs['InstanceID'])
+        Speed = int(kwargs['Speed'])
+        self.play()
+        return {}
+
+    def upnp_Pause(self, *args, **kwargs):
+        InstanceID = int(kwargs['InstanceID'])
+        self.pause()
+        return {}
+
+    def upnp_Stop(self, *args, **kwargs):
+        InstanceID = int(kwargs['InstanceID'])
+        self.stop()
+        return {}
+
+    def upnp_SetAVTransportURI(self, *args, **kwargs):
+        InstanceID = int(kwargs['InstanceID'])
+        CurrentURI = kwargs['CurrentURI']
+        CurrentURIMetaData = kwargs['CurrentURIMetaData']
+        local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',')
+        #print '>>>', local_protocol_infos
+        if len(CurrentURIMetaData)==0:
+            self.load(CurrentURI,CurrentURIMetaData)
+        else:
+            elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData)
+            #import pdb; pdb.set_trace()
+            if elt.numItems() == 1:
+                item = elt.getItems()[0]
+                res = item.res.get_matching(local_protocol_infos, protocol_type='internal')
+                if len(res) == 0:
+                    res = item.res.get_matching(local_protocol_infos)
+                if len(res) > 0:
+                    res = res[0]
+                    remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':')
+                    self.load(res.data,CurrentURIMetaData)
+                    return {}
+        return failure.Failure(errorCode(714))
+
+    def upnp_SetMute(self, *args, **kwargs):
+        InstanceID = int(kwargs['InstanceID'])
+        Channel = kwargs['Channel']
+        DesiredMute = kwargs['DesiredMute']
+        if DesiredMute in ['TRUE', 'True', 'true', '1','Yes','yes']:
+            self.mute()
+        else:
+            self.unmute()
+        return {}
+
+    def upnp_SetVolume(self, *args, **kwargs):
+        InstanceID = int(kwargs['InstanceID'])
+        Channel = kwargs['Channel']
+        DesiredVolume = int(kwargs['DesiredVolume'])
+        self.set_volume(DesiredVolume)
+        return {}

Modified: trunk/plugins/coherence/upnp_coherence/MediaStore.py
==============================================================================
--- trunk/plugins/coherence/upnp_coherence/MediaStore.py	(original)
+++ trunk/plugins/coherence/upnp_coherence/MediaStore.py	Tue Feb  5 12:02:46 2008
@@ -1,10 +1,12 @@
 # Copyright 2007, James Livingston  <doclivingston gmail com>
+# Copyright 2007, Frank Scholz <coherence beebits net>
 
 import rhythmdb
 import louie
 import urllib
 from coherence.upnp.core import DIDLLite
 
+from coherence import log
 
 ROOT_CONTAINER_ID = 0
 AUDIO_CONTAINER = 10
@@ -12,12 +14,16 @@
 AUDIO_ARTIST_CONTAINER_ID = 12
 AUDIO_ALBUM_CONTAINER_ID = 13
 
-CONTAINER_COUNT = 1000
+CONTAINER_COUNT = 10000
 
+TRACK_COUNT = 1000000
+
+# most of this class is from Coherence, originally under the MIT licence
+
+class Container(log.Loggable):
+
+    logCategory = 'rb_media_store'
 
-# this class is from Coherence, originally under the MIT licence
-# Copyright 2007, Frank Scholz <coherence beebits net>
-class Container(object):
     def __init__(self, id, parent_id, name, children_callback=None):
         self.id = id
         self.parent_id = parent_id
@@ -25,11 +31,11 @@
         self.mimetype = 'directory'
         self.item = DIDLLite.Container(id, parent_id,self.name)
         self.update_id = 0
+        self.item.childCount = 0
         if children_callback != None:
             self.children = children_callback
         else:
             self.children = []
-        self.item.childCount = self.get_child_count()
 
     def add_child(self, child):
         self.children.append(child)
@@ -40,18 +46,22 @@
             children = self.children()
         else:
             children = self.children
+
+        self.info("Container get_children %r (%r,%r)", children, start, request_count)
         if request_count == 0:
             return children[start:]
         else:
             return children[start:request_count]
 
     def get_child_count(self):
+
         if callable(self.children):
             return len(self.children())
         else:
             return len(self.children)
 
     def get_item(self):
+        self.item.childCount = self.get_child_count()
         return self.item
 
     def get_name(self):
@@ -61,162 +71,349 @@
         return self.id
 
 
-class Track:
-	def __init__(self, store, id):
-		self.id = id
-		self.store = store
-
-	def get_children(self, start=0, request_count=0):
-		return []
-
-	def get_child_count(self):
-		return 0
-
-	def get_item(self):
-		host = ""
-
-		# load common values
-		entry = self.store.db.entry_lookup_by_id (self.id)
-		# Bitrate is in bytes/second, not kilobits/second
-		bitrate = self.store.db.entry_get (entry, rhythmdb.PROP_BITRATE) * 1024 / 8
-		# Duration is in HH:MM:SS format
-		seconds = self.store.db.entry_get (entry, rhythmdb.PROP_DURATION)
-		hours = seconds / 3600
-		seconds = seconds - hours * 3600
-		minutes = seconds / 60
-		seconds = seconds - minutes * 60
-		duration = ("%02d:%02d:%02d") % (hours, minutes, seconds)
-
-		location = self.store.db.entry_get (entry, rhythmdb.PROP_LOCATION)
-		if location.startswith("file://"):
-			location = unicode(urllib.url2pathname(location)[len("file://"):])
-		else:
-			location = None
-		mimetype = self.store.db.entry_get (entry, rhythmdb.PROP_MIMETYPE)
-		# This isn't a real mime-type
-		if mimetype == "application/x-id3":
-			mimetype = "audio/mpeg"
-		size = self.store.db.entry_get (entry, rhythmdb.PROP_FILE_SIZE)
-
-		# create item
-		item = DIDLLite.MusicTrack(self.id + CONTAINER_COUNT)
-		item.album = self.store.db.entry_get (entry, rhythmdb.PROP_ALBUM)
-		#item.albumArtURI = ## can we somehow store art in the upnp share??
-		item.artist = self.store.db.entry_get (entry, rhythmdb.PROP_ARTIST)
-		#item.date =
-		item.genre = self.store.db.entry_get (entry, rhythmdb.PROP_GENRE)
-		item.originalTrackNumber = str(self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER))
-		item.title = self.store.db.entry_get (entry, rhythmdb.PROP_TITLE) # much nicer if it was entry.title
-		item.res = []
-
-		# add internal resource
-		#res = DIDLLite.Resource(location, 'internal:%s:%s:*' % (host, mimetype))
-		#res.size = size
-		#res.duration = duration
-		#res.bitrate = bitrate
-		#item.res.append(res)
-
-		# add http resource
-		res = DIDLLite.Resource(self.get_url(), 'http-get:*:%s:*' % mimetype)
-		if size > 0:
-			res.size = size
-		if duration > 0:
-			res.duration = str(duration)
-		if bitrate > 0:
-			res.bitrate = str(bitrate)
-		item.res.append(res)
-
-		return item
-
-	def get_id(self):
-		return self.id
-
-	def get_name(self):
-		entry = self.store.db.entry_lookup_by_id (self.id)
-		return self.store.db.entry_get (entry, rhythmdb.PROP_TITLE)
-
-	def get_url(self):
-		return self.store.urlbase + str(self.id + CONTAINER_COUNT)
-
-	def get_path(self):
-		entry = self.store.db.entry_lookup_by_id (self.id)
-		uri = self.store.db.entry_get (entry, rhythmdb.PROP_LOCATION)
-		if uri.startswith("file://"):
-			return unicode(urllib.url2pathname(uri)[len("file://"):])
-		else:
-			return None
-
-class MediaStore: 
-	implements = ['MediaServer']
-
-	def __init__(self, server, **kwargs):
-		print "creating UPnP MediaStore"
-		self.server = server
-		self.db = kwargs['db']
-		self.plugin = kwargs['plugin']
-
-		self.urlbase = kwargs.get('urlbase','')
-		if( len(self.urlbase) > 0 and self.urlbase[len(self.urlbase)-1] != '/'):
-			self.urlbase += '/'
-
-		self.name = self.server.coherence.hostname
-
-		self.containers = {}
-		self.containers[ROOT_CONTAINER_ID] = \
-		        Container( ROOT_CONTAINER_ID,-1, self.server.coherence.hostname)
-
-		self.containers[AUDIO_ALL_CONTAINER_ID] = \
-		        Container( AUDIO_ALL_CONTAINER_ID,ROOT_CONTAINER_ID, 'All tracks',
-		                  children_callback=self.children_tracks)
-		self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALL_CONTAINER_ID])
-
-		#self.containers[AUDIO_ALBUM_CONTAINER_ID] = \
-		#        Container( AUDIO_ALBUM_CONTAINER_ID,ROOT_CONTAINER_ID, 'Albums',
-		#                  children_callback=self.children_albums)
-		#self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALBUM_CONTAINER_ID])
-
-		#self.containers[AUDIO_ARTIST_CONTAINER_ID] = \
-		#        Container( AUDIO_ARTIST_CONTAINER_ID,ROOT_CONTAINER_ID, 'Artists',
-		#                  children_callback=self.children_artists)
-		#self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ARTIST_CONTAINER_ID])
-
-		louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)
-
-	def get_by_id(self,id):
-		print "getting resource id " + str(id)
-		if id.startswith('artist_all_tracks_'):
-			return self.containers[id]
-
-		id = int(id)
-		if id < 1000:
-			item = self.containers[id]
-		else:
-			item = Track(self, (id - CONTAINER_COUNT))
-
-		return item
-
-	def upnp_init(self):
-		if self.server:
-		    self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', [
-				#'internal:%s:*:*' % self.name,
-				'http-get:*:audio/mpeg:*',
-			])
-
-	def children_tracks(self):
-		tracks = []
-
-		def track_cb (entry):
-			if self.db.entry_get (entry, rhythmdb.PROP_HIDDEN):
-				return
-			id = self.db.entry_get (entry, rhythmdb.PROP_ENTRY_ID)
-			tracks.append(Track(self, id))
-		self.db.entry_foreach_by_type (self.db.entry_type_get_by_name('song'), track_cb)
+class Album(log.Loggable):
+
+    logCategory = 'rb_media_store'
+
+    def __init__(self, store, title, id):
+        self.id = id
+        self.title = title
+        self.store = store
+
+        query = self.store.db.query_new()
+        self.store.db.query_append(query,[rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_TYPE, self.store.db.entry_type_get_by_name('song')],
+                                      [rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_ALBUM, self.title])
+        self.tracks_per_album_query = self.store.db.query_model_new(query)
+        #self.tracks_per_album_query.set_sort_order(rhythmdb.rhythmdb_query_model_track_sort_func)
+        self.store.db.do_full_query_async_parsed(self.tracks_per_album_query, query)
+
+    def get_children(self,start=0,request_count=0):
+        children = []
+
+        def track_sort(x,y):
+            entry = self.store.db.entry_lookup_by_id (x.id)
+            x_track = self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER)
+            entry = self.store.db.entry_lookup_by_id (y.id)
+            y_track = self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER)
+            return cmp(x_track,y_track)
+
+        def collate (model, path, iter):
+            self.info("Album get_children %r %r %r" %(model, path, iter))
+            id = model.get(iter, 0)[0]
+            children.append(Track(self.store,id))
+
+        self.tracks_per_album_query.foreach(collate)
+
+        children.sort(cmp=track_sort)
+
+        if request_count == 0:
+            return children[start:]
+        else:
+            return children[start:request_count]
+
+    def get_child_count(self):
+        return len(self.get_children())
+
+    def get_item(self):
+        item = DIDLLite.MusicAlbum(self.id, AUDIO_ALBUM_CONTAINER_ID, self.title)
+        return item
+
+    def get_id(self):
+        return self.id
+
+    def get_name(self):
+        return self.title
+
+    def get_cover(self):
+        return self.cover
+
+
+class Artist(log.Loggable):
+
+    logCategory = 'rb_media_store'
+
+    def __init__(self, store, name, id):
+        self.id = id
+        self.name = name
+        self.store = store
+
+        query = self.store.db.query_new()
+        self.store.db.query_append(query,[rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_TYPE, self.store.db.entry_type_get_by_name('song')],
+                                      [rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_ARTIST, self.name])
+        qm = self.store.db.query_model_new(query)
+        self.store.db.do_full_query_async_parsed(qm, query)
+
+        self.albums_per_artist_query = self.store.db.property_model_new(rhythmdb.PROP_ALBUM)
+        self.albums_per_artist_query.props.query_model = qm
+
+    def get_children(self,start=0,request_count=0):
+        children = []
+
+        def collate (model, path, iter):
+            name = model.get(iter, 0)[0]
+            priority = model.get(iter, 1)[0]
+            self.info("get_children collate %r %r", name, priority)
+            if priority is False:
+                try:
+                    album = self.store.albums[name]
+                    children.append(album)
+                except:
+                    self.warning("hmm, a new album %r, that shouldn't happen", name)
+
+        self.albums_per_artist_query.foreach(collate)
+
+        if request_count == 0:
+            return children[start:]
+        else:
+            return children[start:request_count]
+
+    def get_child_count(self):
+        return len(self.get_children())
+
+    def get_item(self):
+        item = DIDLLite.MusicArtist(self.id, AUDIO_ARTIST_CONTAINER_ID, self.name)
+        return item
+
+    def get_id(self):
+        return self.id
+
+    def get_name(self):
+        return self.name
+
+
+class Track(log.Loggable):
+
+    logCategory = 'rb_media_store'
+
+    def __init__(self, store, id):
+        self.store = store
+        if type(id) == int:
+            self.id = id
+        else:
+            self.id = self.store.db.entry_get (id, rhythmdb.PROP_ENTRY_ID)
+
+    def get_children(self, start=0, request_count=0):
+        return []
+
+    def get_child_count(self):
+        return 0
+
+    def get_item(self):
+
+        self.info("Track get_item %r" %(self.id))
+
+        host = ""
+
+        # load common values
+        entry = self.store.db.entry_lookup_by_id(self.id)
+        # Bitrate is in bytes/second, not kilobits/second
+        bitrate = self.store.db.entry_get(entry, rhythmdb.PROP_BITRATE) * 1024 / 8
+        # Duration is in HH:MM:SS format
+        seconds = self.store.db.entry_get(entry, rhythmdb.PROP_DURATION)
+        hours = seconds / 3600
+        seconds = seconds - hours * 3600
+        minutes = seconds / 60
+        seconds = seconds - minutes * 60
+        duration = ("%02d:%02d:%02d") % (hours, minutes, seconds)
+
+        location = self.get_path(entry)
+        mimetype = self.store.db.entry_get(entry, rhythmdb.PROP_MIMETYPE)
+        # This isn't a real mime-type
+        if mimetype == "application/x-id3":
+            mimetype = "audio/mpeg"
+        size = self.store.db.entry_get(entry, rhythmdb.PROP_FILE_SIZE)
+
+        # create item
+        item = DIDLLite.MusicTrack(self.id + TRACK_COUNT)
+        item.album = self.store.db.entry_get(entry, rhythmdb.PROP_ALBUM)
+        item.artist = self.store.db.entry_get(entry, rhythmdb.PROP_ARTIST)
+        #item.date =
+        item.genre = self.store.db.entry_get(entry, rhythmdb.PROP_GENRE)
+        item.originalTrackNumber = str(self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER))
+        item.title = self.store.db.entry_get(entry, rhythmdb.PROP_TITLE) # much nicer if it was entry.title
+
+        #cover = self.store.db.entry_request_extra_metadata(entry, "rb:coverArt")
+        #self.warning("cover for %r is %r", item.title, cover)
+        #item.albumArtURI = ## can we somehow store art in the upnp share??
+
+        # add internal resource
+        #res = DIDLLite.Resource(location, 'internal:%s:%s:*' % (host, mimetype))
+        #res.size = size
+        #res.duration = duration
+        #res.bitrate = bitrate
+        #item.res.append(res)
+
+        # add http resource
+        res = DIDLLite.Resource(self.get_url(), 'http-get:*:%s:*' % mimetype)
+        if size > 0:
+            res.size = size
+        if duration > 0:
+            res.duration = str(duration)
+        if bitrate > 0:
+            res.bitrate = str(bitrate)
+        item.res.append(res)
+
+        return item
+
+    def get_id(self):
+        return self.id
+
+    def get_name(self):
+        entry = self.store.db.entry_lookup_by_id (self.id)
+        return self.store.db.entry_get(entry, rhythmdb.PROP_TITLE)
+
+    def get_url(self):
+        return self.store.urlbase + str(self.id + TRACK_COUNT)
+
+    def get_path(self, entry = None):
+        if entry is None:
+            entry = self.store.db.entry_lookup_by_id (self.id)
+        uri = self.store.db.entry_get(entry, rhythmdb.PROP_LOCATION)
+        self.warning("Track get_path uri = %r", uri)
+        location = None
+        if uri.startswith("file://"):
+            location = unicode(urllib.unquote(uri[len("file://"):]))
+            self.warning("Track get_path location = %r", location)
+
+        return location
+
+class MediaStore(log.Loggable):
+
+    logCategory = 'rb_media_store'
+    implements = ['MediaServer']
+
+    def __init__(self, server, **kwargs):
+        print "creating UPnP MediaStore"
+        self.server = server
+        self.db = kwargs['db']
+        self.plugin = kwargs['plugin']
 
-		return tracks
+        self.update_id = 0
+
+        self.next_id = CONTAINER_COUNT
+        self.albums = None
+        self.artists = None
+        self.tracks = None
+
+        self.urlbase = kwargs.get('urlbase','')
+        if( len(self.urlbase) > 0 and self.urlbase[len(self.urlbase)-1] != '/'):
+            self.urlbase += '/'
+
+        self.name = "Rhythmbox on %s" % self.server.coherence.hostname
+
+        query = self.db.query_new()
+        self.info(query)
+        self.db.query_append(query, [rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_TYPE, self.db.entry_type_get_by_name('song')])
+        qm = self.db.query_model_new(query)
+        self.db.do_full_query_async_parsed(qm, query)
+
+        self.album_query = self.db.property_model_new(rhythmdb.PROP_ALBUM)
+        self.album_query.props.query_model = qm
+
+        self.artist_query = self.db.property_model_new(rhythmdb.PROP_ARTIST)
+        self.artist_query.props.query_model = qm
+
+        self.containers = {}
+        self.containers[ROOT_CONTAINER_ID] = \
+                Container( ROOT_CONTAINER_ID,-1, "Rhythmbox on %s" % self.server.coherence.hostname)
+
+        self.containers[AUDIO_ALL_CONTAINER_ID] = \
+                Container( AUDIO_ALL_CONTAINER_ID,ROOT_CONTAINER_ID, 'All tracks',
+                          children_callback=self.children_tracks)
+        self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALL_CONTAINER_ID])
+
+        self.containers[AUDIO_ALBUM_CONTAINER_ID] = \
+                Container( AUDIO_ALBUM_CONTAINER_ID,ROOT_CONTAINER_ID, 'Albums',
+                          children_callback=self.children_albums)
+        self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALBUM_CONTAINER_ID])
+
+        self.containers[AUDIO_ARTIST_CONTAINER_ID] = \
+                Container( AUDIO_ARTIST_CONTAINER_ID,ROOT_CONTAINER_ID, 'Artists',
+                          children_callback=self.children_artists)
+        self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ARTIST_CONTAINER_ID])
+
+        louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)
+
+    def get_by_id(self,id):
+
+        self.info("looking for id %r", id)
+        id = int(id)
+        if id < TRACK_COUNT:
+            item = self.containers[id]
+        else:
+            item = Track(self, (id - TRACK_COUNT))
 
-	def children_albums(self):
-		return []
+        return item
 
-	def children_artists(self):
-		return []
+    def get_next_container_id(self):
+        ret = self.next_id
+        self.next_id += 1
+        return ret
+
+    def upnp_init(self):
+        if self.server:
+            self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', [
+                #'internal:%s:*:*' % self.name,
+                'http-get:*:audio/mpeg:*',
+            ])
+
+    def children_tracks(self):
+        tracks = []
+
+        def track_cb (entry):
+            if self.db.entry_get (entry, rhythmdb.PROP_HIDDEN):
+                return
+            id = self.db.entry_get (entry, rhythmdb.PROP_ENTRY_ID)
+            track = Track(self, id)
+            tracks.append(track)
+
+        self.db.entry_foreach_by_type (self.db.entry_type_get_by_name('song'), track_cb)
+        return tracks
+
+    def children_albums(self):
+        albums =  {}
+
+        self.info('children_albums')
+
+        def album_sort(x,y):
+            r = cmp(x.title,y.title)
+            self.info("sort %r - %r = %r", x.title, y.title, r)
+            return r
+
+        def collate (model, path, iter):
+            name = model.get(iter, 0)[0]
+            priority = model.get(iter, 1)[0]
+            self.info("children_albums collate %r %r", name, priority)
+            if priority is False:
+                id = self.get_next_container_id()
+                album = Album(self, name, id)
+                self.containers[id] = album
+                albums[name] = album
+
+        if self.albums is None:
+            self.album_query.foreach(collate)
+            self.albums = albums
+
+        albums = self.albums.values() #.sort(cmp=album_sort)
+        albums.sort(cmp=album_sort)
+        return albums
+
+    def children_artists(self,killbug=False):
+        artists = []
+
+        self.info('children_artists')
+
+        def collate (model, path, iter):
+            name = model.get(iter, 0)[0]
+            priority = model.get(iter, 1)[0]
+            if priority is False:
+                id = self.get_next_container_id()
+                artist = Artist(self,name, id)
+                self.containers[id] = artist
+                artists.append(artist)
+
+        if self.artists is None:
+            self.artist_query.foreach(collate)
+            self.artists = artists
 
+        return self.artists

Modified: trunk/plugins/coherence/upnp_coherence/UpnpSource.py
==============================================================================
--- trunk/plugins/coherence/upnp_coherence/UpnpSource.py	(original)
+++ trunk/plugins/coherence/upnp_coherence/UpnpSource.py	Tue Feb  5 12:02:46 2008
@@ -1,101 +1,177 @@
+# Copyright 2007, James Livingston  <doclivingston gmail com>
+# Copyright 2007, Frank Scholz <coherence beebits net>
+
 import rb, rhythmdb
 import gobject, gtk
 
-class UpnpSource(rb.BrowserSource):
-	__gproperties__ = {
-		'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
-		'client': (gobject.TYPE_PYOBJECT, 'client', 'client', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
-		'usn': (gobject.TYPE_PYOBJECT, 'usn', 'usn', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
-	}
-
-	def __init__(self):
-		rb.BrowserSource.__init__(self)
-		self.__db = None
-		self.__activated = False
-
-
-	def do_set_property(self, property, value):
-		if property.name == 'plugin':
-			self.__plugin = value
-		elif property.name == 'client':
-			self.__client = value
-			self.props.name = self.__client.device.get_friendly_name()
-		elif property.name == 'usn':
-			self.__usn = value
-		else:
-			raise AttributeError, 'unknown property %s' % property.name
-
-
-	def do_impl_activate(self):
-		if not self.__activated:
-			print "activating upnp source"
-			self.__activated = True
-
-			shell = self.get_property('shell')
-			self.__db = shell.get_property('db')
-			self.__entry_type = self.get_property('entry-type')
-
-			# load upnp db
-			self.load_db(0)
-			self.__client.content_directory.subscribe_for_variable('ContainerUpdateIDs', self.state_variable_change)
-			self.__client.content_directory.subscribe_for_variable('SystemUpdateID', self.state_variable_change)
-
-
-	def load_db(self, id):
-		d = self.__client.content_directory.browse(id, browse_flag='BrowseDirectChildren', backward_compatibility=False)
-		d.addCallback(self.process_media_server_browse, self.__usn)
-
-
-	def state_variable_change(self, variable, usn):
-		print "%s changed from %s to %s" % (variable.name, variable.old_value, variable.value)
-		if variable.old_value == '':
-			return
-
-		if variable.name == 'SystemUpdateID':
-			self.load_db(0)
-		elif variable.name == 'ContainerUpdateIDs':
-			changes = variable.value.split(',')
-			while len(changes) > 1:
-				container = changes.pop(0).strip()
-				update_id = changes.pop(0).strip()
-				if container in self.container_watch:
-					print "we have a change in %s, container needs a reload" % container
-					self.load_db(container)
-
-
-	def process_media_server_browse(self, results, usn):
-		for k,v in results.iteritems():
-			if k == 'items':
-				for id, values in v.iteritems():
-					if values['upnp_class'].startswith('object.container'):
-						self.load_db(id)
-					if values['upnp_class'].startswith('object.item.audioItem'):
-						# (url, [method, something which is in asterix,  format, semicolon delimited key=value map of something])
-						resources = [(k, v.split(':')) for (k, v) in values['resources'].iteritems()]
-						# break data into map
-						for r in resources:
-							if r[1][3] is not '*':
-								r[1][3] = dict([v.split('=') for v in r[1][3].split(';')])
-							else:
-								r[1][3] = dict()
-
-						url = None
-						for r in resources:
-							if r[1][3].has_key('DLNA.ORG_CI') and r[1][3]['DLNA.ORG_CI'] is not '1':
-								url = r[0]
-								break
-
-						if url is None:
-							# use transcoded format, since we can't find a normal one
-							url = resources[0][0]
-
-						entry = self.__db.entry_lookup_by_location (url)
-						if entry == None:
-							entry = self.__db.entry_new(self.__entry_type, url)
-
-						self.__db.set(entry, rhythmdb.PROP_TITLE, values['title'])
-						
-						self.__db.commit()
+from coherence import __version_info__ as coherence_version
 
-gobject.type_register(UpnpSource)
+from coherence import log
+
+class UpnpSource(rb.BrowserSource,log.Loggable):
+
+    logCategory = 'rb_media_store'
 
+    __gproperties__ = {
+        'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
+        'client': (gobject.TYPE_PYOBJECT, 'client', 'client', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
+        'usn': (gobject.TYPE_PYOBJECT, 'usn', 'usn', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
+    }
+
+    def __init__(self):
+        rb.BrowserSource.__init__(self)
+        self.__db = None
+        self.__activated = False
+        self.container_watch = []
+        if coherence_version < (0,5,1):
+            self.process_media_server_browse = self.old_process_media_server_browse
+        else:
+            self.process_media_server_browse = self.new_process_media_server_browse
+
+    def do_set_property(self, property, value):
+        if property.name == 'plugin':
+            self.__plugin = value
+        elif property.name == 'client':
+            self.__client = value
+            self.props.name = self.__client.device.get_friendly_name()
+        elif property.name == 'usn':
+            self.__usn = value
+        else:
+            raise AttributeError, 'unknown property %s' % property.name
+
+
+    def do_impl_activate(self):
+        if not self.__activated:
+            print "activating upnp source"
+            self.__activated = True
+
+            shell = self.get_property('shell')
+            self.__db = shell.get_property('db')
+            self.__entry_type = self.get_property('entry-type')
+
+            # load upnp db
+            self.load_db(0)
+            self.__client.content_directory.subscribe_for_variable('ContainerUpdateIDs', self.state_variable_change)
+            self.__client.content_directory.subscribe_for_variable('SystemUpdateID', self.state_variable_change)
+
+
+    def load_db(self, id):
+        if coherence_version < (0,5,1):
+            d = self.__client.content_directory.browse(id, browse_flag='BrowseDirectChildren', backward_compatibility=False)
+        else:
+            d = self.__client.content_directory.browse(id, browse_flag='BrowseDirectChildren', process_result=False, backward_compatibility=False)
+        d.addCallback(self.process_media_server_browse, self.__usn)
+
+
+    def state_variable_change(self, variable, usn=None):
+        print "%s changed from %s to %s" % (variable.name, variable.old_value, variable.value)
+        if variable.old_value == '':
+            return
+
+        if variable.name == 'SystemUpdateID':
+            self.load_db(0)
+        elif variable.name == 'ContainerUpdateIDs':
+            changes = variable.value.split(',')
+            while len(changes) > 1:
+                container = changes.pop(0).strip()
+                update_id = changes.pop(0).strip()
+                if container in self.container_watch:
+                    print "we have a change in %s, container needs a reload" % container
+                    self.load_db(container)
+
+
+    def new_process_media_server_browse(self, results, usn):
+        for item in results:
+            self.info("process_media_server_browse %r %r", item.id, item)
+            if item.upnp_class.startswith('object.container'):
+                self.load_db(item.id)
+            if item.upnp_class.startswith('object.item.audioItem'):
+
+                url = None
+                duration = None
+                size = None
+                bitrate = None
+
+                for res in item.res:
+                    remote_protocol,remote_network,remote_content_format,remote_flags = res.protocolInfo.split(':')
+                    self.info("%r %r %r %r",remote_protocol,remote_network,remote_content_format,remote_flags)
+                    if remote_protocol == 'http-get':
+                        url = res.data
+                        duration = res.duration
+                        size = res.size
+                        bitrate = res.bitrate
+                        break
+
+                if url is not None:
+                    self.info("url %r %r",url,item.title)
+
+                    entry = self.__db.entry_lookup_by_location (url)
+                    if entry == None:
+                        entry = self.__db.entry_new(self.__entry_type, url)
+
+                    self.__db.set(entry, rhythmdb.PROP_TITLE, item.title)
+                    try:
+                        if item.artist is not None:
+                            self.__db.set(entry, rhythmdb.PROP_ARTIST, item.artist)
+                    except AttributeError:
+                        pass
+                    try:
+                        if item.album is not None:
+                            self.__db.set(entry, rhythmdb.PROP_ALBUM, item.album)
+                    except AttributeError:
+                        pass
+
+                    try:
+                        self.info("%r %r", item.title,item.originalTrackNumber)
+                        if item.originalTrackNumber is not None:
+                            self.__db.set(entry, rhythmdb.PROP_TRACK_NUMBER, int(item.originalTrackNumber))
+                    except AttributeError:
+                        pass
+
+                    if duration is not None:
+                        h,m,s = duration.split(':')
+                        seconds = int(h)*3600 + int(m)*60 + int(s)
+                        self.info("%r %r:%r:%r %r", duration, h, m , s, seconds)
+                        self.__db.set(entry, rhythmdb.PROP_DURATION, seconds)
+
+                    if size is not None:
+                        self.__db.set(entry, rhythmdb.PROP_FILE_SIZE,int(size))
+
+                    self.__db.commit()
+
+
+    def old_process_media_server_browse(self, results, usn):
+        for k,v in results.iteritems():
+            if k == 'items':
+                for id, values in v.iteritems():
+                    if values['upnp_class'].startswith('object.container'):
+                        self.load_db(id)
+                    if values['upnp_class'].startswith('object.item.audioItem'):
+                        # (url, [method, something which is in asterix,  format, semicolon delimited key=value map of something])
+                        resources = [(k, v.split(':')) for (k, v) in values['resources'].iteritems()]
+                        # break data into map
+                        for r in resources:
+                            if r[1][3] is not '*':
+                                r[1][3] = dict([v.split('=') for v in r[1][3].split(';')])
+                            else:
+                                r[1][3] = dict()
+
+                        url = None
+                        for r in resources:
+                            if r[1][3].has_key('DLNA.ORG_CI') and r[1][3]['DLNA.ORG_CI'] is not '1':
+                                url = r[0]
+                                break
+
+                        if url is None:
+                            # use transcoded format, since we can't find a normal one
+                            url = resources[0][0]
+
+                        entry = self.__db.entry_lookup_by_location (url)
+                        if entry == None:
+                            entry = self.__db.entry_new(self.__entry_type, url)
+
+                        self.__db.set(entry, rhythmdb.PROP_TITLE, values['title'])
+
+                        self.__db.commit()
+
+gobject.type_register(UpnpSource)

Modified: trunk/plugins/coherence/upnp_coherence/__init__.py
==============================================================================
--- trunk/plugins/coherence/upnp_coherence/__init__.py	(original)
+++ trunk/plugins/coherence/upnp_coherence/__init__.py	Tue Feb  5 12:02:46 2008
@@ -1,144 +1,165 @@
 import rhythmdb, rb
 import gobject, gtk
+
 import louie
+
+from coherence import log
+
 # For the icon
 import os.path, urllib, gnomevfs, gtk.gdk
 
+class CoherencePlugin(rb.Plugin,log.Loggable):
+
+    logCategory = 'rb_coherence_plugin'
 
-class CoherencePlugin(rb.Plugin):
-	def __init__(self):
-		rb.Plugin.__init__(self)
-			
-	def activate(self, shell):
-		from twisted.internet import gtk2reactor
-		try:
-			gtk2reactor.install()
-		except AssertionError, e:
-			# sometimes it's already installed
-			print e
-
-		self.coherence = self.get_coherence()
-		if self.coherence is None:
-			print "Coherence is not installed or too old, aborting"
-			return
-
-		print "coherence UPnP plugin activated"
-		self.shell = shell
-		self.sources = {}
-
-		# watch for media servers
-		louie.connect(self.detected_media_server,
-				'Coherence.UPnP.ControlPoint.MediaServer.detected',
-				louie.Any)
-		louie.connect(self.removed_media_server,
-				'Coherence.UPnP.ControlPoint.MediaServer.removed',
-				louie.Any)
-
-		# Set up our icon
-		face_path = os.path.join(os.path.expanduser('~'), ".face")
-		if os.path.exists(face_path):
-			url = "file://" + urllib.pathname2url(face_path)
-		else:
-			url = None
-
-		if url:
-			mimetype = gnomevfs.get_mime_type(url)
-			pixbuf = gtk.gdk.pixbuf_new_from_file(face_path)
-			width = "%s" % pixbuf.get_width()
-			height = "%s" % pixbuf.get_height()
-			depth = '24'
-			the_icon = {
-				'url':url,
-				'mimetype':mimetype,
-				'width':width,
-				'height':height,
-				'depth':depth
-				}
-		else:
-			the_icon = None
-
-		# create our own media server
-		from coherence.upnp.devices.media_server import MediaServer
-		from MediaStore import MediaStore
-		if the_icon:
-			server = MediaServer(self.coherence, MediaStore, no_thread_needed=True, db=self.shell.props.db, plugin=self, icon=the_icon)
-		else:
-			server = MediaServer(self.coherence, MediaStore, no_thread_needed=True, db=self.shell.props.db, plugin=self)
-
-	def deactivate(self, shell):
-		print "coherence UPnP plugin deactivated"
-		if self.coherence is None:
-			return
-
-		self.coherence.shutdown()
-
-		louie.disconnect(self.detected_media_server,
-				'Coherence.UPnP.ControlPoint.MediaServer.detected',
-				louie.Any)
-		louie.disconnect(self.removed_media_server,
-				'Coherence.UPnP.ControlPoint.MediaServer.removed',
-				louie.Any)
-
-		del self.shell
-		del self.coherence
-
-		for usn, source in self.sources.iteritems():
-			source.delete_thyself()
-		del self.sources
-
-		# uninstall twisted reactor? probably not, since other thigngs may have used it
-
-
-	def get_coherence (self):
-		coherence_instance = None
-		required_version = (0, 3, 2)
-
-		try:
-			from coherence.base import Coherence
-			from coherence import __version_info__
-		except ImportError, e:
-			print "Coherence not found"
-			return None
-
-		if __version_info__ < required_version:
-			required = '.'.join([str(i) for i in required_version])
-			found = '.'.join([str(i) for i in __version_info__])
-			print "Coherence %s required. %s found. Please upgrade" % (required, found)
-			return None
-
-		coherence_config = {
-			#'logmode': 'info',
-			'controlpoint': 'yes',
-			'plugins':{}
-		}
-		coherence_instance = Coherence(coherence_config)
-
-		return coherence_instance
-
-
-	def removed_media_server(self, usn):
-		print "upnp server went away %s" % usn
-		if self.sources.has_key(usn):
-			self.sources[usn].delete_thyself()
-			del self.sources[usn]
-
-	def detected_media_server(self, client, usn):
-		print "found upnp server %s (%s)"  %  (client.device.get_friendly_name(), usn)
-
-		db = self.shell.props.db
-		group = rb.rb_source_group_get_by_name ("shared")
-		entry_type = db.entry_register_type("CoherenceUpnp:" + usn)
-
-		from UpnpSource import UpnpSource
-		source = gobject.new (UpnpSource,
-					shell=self.shell,
-					entry_type=entry_type,
-					source_group=group,
-					plugin=self,
-					client=client,
-					usn=usn)
+    def __init__(self):
+        rb.Plugin.__init__(self)
 
-		self.sources[usn] = source
+    def activate(self, shell):
+        from twisted.internet import gtk2reactor
+        try:
+            gtk2reactor.install()
+        except AssertionError, e:
+            # sometimes it's already installed
+            print e
+
+        self.coherence = self.get_coherence()
+        if self.coherence is None:
+            print "Coherence is not installed or too old, aborting"
+            return
+
+        print "coherence UPnP plugin activated"
+        self.shell = shell
+        self.sources = {}
+
+        # Set up our icon
+        the_icon = None
+        face_path = os.path.join(os.path.expanduser('~'), ".face")
+        if os.path.exists(face_path):
+            url = "file://" + urllib.pathname2url(face_path)
+            mimetype = gnomevfs.get_mime_type(url)
+            pixbuf = gtk.gdk.pixbuf_new_from_file(face_path)
+            width = "%s" % pixbuf.get_width()
+            height = "%s" % pixbuf.get_height()
+            depth = '24'
+            the_icon = {
+                'url':url,
+                'mimetype':mimetype,
+                'width':width,
+                'height':height,
+                'depth':depth
+                }
+        else:
+            the_icon = None
+
+        # create our own media server
+        from coherence.upnp.devices.media_server import MediaServer
+        from MediaStore import MediaStore
+        if the_icon:
+            server = MediaServer(self.coherence, MediaStore, no_thread_needed=True, db=self.shell.props.db, plugin=self, icon=the_icon)
+        else:
+            server = MediaServer(self.coherence, MediaStore, no_thread_needed=True, db=self.shell.props.db, plugin=self)
+
+        self.uuid = str(server.uuid)
+
+        if self.coherence_version >= (0,5,2):
+            # create our own media renderer
+            # but only if we have a matching Coherence package installed
+            from coherence.upnp.devices.media_renderer import MediaRenderer
+            from MediaPlayer import RhythmboxPlayer
+            if the_icon:
+                MediaRenderer(self.coherence, RhythmboxPlayer, no_thread_needed=True, shell=self.shell, icon=the_icon)
+            else:
+                MediaRenderer(self.coherence, RhythmboxPlayer, no_thread_needed=True, shell=self.shell)
+
+        # watch for media servers
+        louie.connect(self.detected_media_server,
+                'Coherence.UPnP.ControlPoint.MediaServer.detected',
+                louie.Any)
+        louie.connect(self.removed_media_server,
+                'Coherence.UPnP.ControlPoint.MediaServer.removed',
+                louie.Any)
+
+
+    def deactivate(self, shell):
+        print "coherence UPnP plugin deactivated"
+        if self.coherence is None:
+            return
+
+        self.coherence.shutdown()
+
+        louie.disconnect(self.detected_media_server,
+                'Coherence.UPnP.ControlPoint.MediaServer.detected',
+                louie.Any)
+        louie.disconnect(self.removed_media_server,
+                'Coherence.UPnP.ControlPoint.MediaServer.removed',
+                louie.Any)
+
+        del self.shell
+        del self.coherence
+
+        for usn, source in self.sources.iteritems():
+            source.delete_thyself()
+        del self.sources
+
+        # uninstall twisted reactor? probably not, since other thigngs may have used it
+
+
+    def get_coherence (self):
+        coherence_instance = None
+        required_version = (0, 3, 2)
+
+        try:
+            from coherence.base import Coherence
+            from coherence import __version_info__
+        except ImportError, e:
+            print "Coherence not found"
+            return None
+
+        if __version_info__ < required_version:
+            required = '.'.join([str(i) for i in required_version])
+            found = '.'.join([str(i) for i in __version_info__])
+            print "Coherence %s required. %s found. Please upgrade" % (required, found)
+            return None
+
+        self.coherence_version = __version_info__
+
+        coherence_config = {
+            #'logmode': 'info',
+            'controlpoint': 'yes',
+            'plugins': {},
+            'interface': 'eth0',
+        }
+        coherence_instance = Coherence(coherence_config)
+
+        return coherence_instance
+
+    def removed_media_server(self, usn):
+        print "upnp server went away %s" % usn
+        if self.sources.has_key(usn):
+            self.sources[usn].delete_thyself()
+            del self.sources[usn]
+
+    def detected_media_server(self, client, usn):
+        print "found upnp server %s (%s)"  %  (client.device.get_friendly_name(), usn)
+        self.warning("found upnp server %s (%s)"  %  (client.device.get_friendly_name(), usn))
+        if client.device.get_id() == self.uuid:
+            """ don't react on our own MediaServer"""
+            return
+
+        db = self.shell.props.db
+        group = rb.rb_source_group_get_by_name ("shared")
+        entry_type = db.entry_register_type("CoherenceUpnp:" + client.device.get_id()[5:])
+
+        from UpnpSource import UpnpSource
+        source = gobject.new (UpnpSource,
+                    shell=self.shell,
+                    entry_type=entry_type,
+                    source_group=group,
+                    plugin=self,
+                    client=client,
+                    usn=usn)
 
-		self.shell.append_source (source, None)
+        self.sources[usn] = source
 
+        self.shell.append_source (source, None)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]