[rhythmbox/wip/python3] context: ask last.fm for json responses rather than xml



commit dbf6660a8f9f448f054f4692a8aadc3b025c693e
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sat Apr 20 23:25:07 2013 +1000

    context: ask last.fm for json responses rather than xml
    
    minidom wasn't up to the job, json is easier.

 plugins/context/AlbumTab.py          | 136 ++++++++++++++---------------------
 plugins/context/ArtistTab.py         |  83 +++++----------------
 plugins/context/tmpl/album-tmpl.html |  12 ++--
 3 files changed, 74 insertions(+), 157 deletions(-)
---
diff --git a/plugins/context/AlbumTab.py b/plugins/context/AlbumTab.py
index 7d24f97..f0212b3 100644
--- a/plugins/context/AlbumTab.py
+++ b/plugins/context/AlbumTab.py
@@ -28,7 +28,7 @@ import os
 import cgi
 import urllib
 from mako.template import Template
-import xml.dom.minidom as dom
+import json
 
 import LastFM
 
@@ -131,7 +131,7 @@ class AlbumView (GObject.GObject):
 
     def album_list_ready (self, ds):
         self.file = self.album_template.render (error = ds.get_error(), 
-                                                list = ds.get_top_albums(), 
+                                                albums = ds.get_top_albums(), 
                                                 artist = ds.get_artist(),
                                                 datasource = LastFM.datasource_link (self.basepath),
                                                 stylesheet = self.styles)
@@ -150,19 +150,10 @@ class AlbumDataSource (GObject.GObject):
         self.error = None
         self.artist = None
         self.max_albums_fetched = 8
+        self.fetching = 0
         self.info_cache = info_cache
         self.ranking_cache = ranking_cache
 
-    def extract (self, data, position):
-        """
-        Safely extract the data from an xml node. Returns data
-        at position or None if position does not exist
-        """
-        try:
-            return data[position].firstChild.data
-        except Exception as e:
-            return None
-
     def get_artist (self):
         return self.artist
 
@@ -178,106 +169,83 @@ class AlbumDataSource (GObject.GObject):
         self.artist = artist
         qartist = urllib.parse.quote_plus(artist)
         self.error  = None
-        url = "%sartist.gettopalbums&artist=%s&api_key=%s" % (LastFM.URL_PREFIX,
-                                                              qartist,
-                                                              LastFM.API_KEY)
-        cachekey = 'lastfm:artist:gettopalbums:%s' % qartist
+        url = "%sartist.gettopalbums&artist=%s&api_key=%s&format=json" % (
+               LastFM.URL_PREFIX, qartist, LastFM.API_KEY)
+        cachekey = 'lastfm:artist:gettopalbumsjson:%s' % qartist
         self.ranking_cache.fetch(cachekey, url, self.parse_album_list, artist)
 
     def parse_album_list (self, data, artist):
         if data is None:
             print("Nothing fetched for %s top albums" % artist)
-            return
+            return False
 
         try:
-            parsed = dom.parseString (data)
+            parsed = json.loads(data.decode("utf-8"))
         except Exception as e:
             print("Error parsing album list: %s" % e)
             return False
 
-        lfm = parsed.getElementsByTagName ('lfm')[0]
-        if lfm.attributes['status'].value == 'failed':
-            self.error = lfm.childNodes[1].firstChild.data
+        self.error = parsed.get('error')
+        if self.error:
             self.emit ('albums-ready')
-            return
+            return False
 
-        self.albums = []
-        album_nodes = parsed.getElementsByTagName ('album') 
-        print("num albums: %d" % len(album_nodes))
-        if len(album_nodes) == 0:
+        albums = parsed['topalbums'].get('album', [])
+        if len(albums) == 0:
             self.error = "No albums found for %s" % artist
             self.emit('albums-ready')
-            return
-            
-        self.album_info_fetched = min (len (album_nodes) - 1, self.max_albums_fetched)
-
-        for i, album in enumerate (album_nodes): 
-            if i >= self.album_info_fetched:
-                break
+            return True
+        
+        self.albums = []
+        albums = parsed['topalbums'].get('album', [])[:self.max_albums_fetched]
+        self.fetching = len(albums)
+        for i, a in enumerate(albums):
+            images = [img['#text'] for img in a.get('image', [])]
+            self.albums.append({'title': a.get('name'), 'images': images[:3]})
+            self.fetch_album_info(artist, a.get('name'), i)
 
-            album_name = self.extract(album.getElementsByTagName ('name'), 0)
-            imgs = album.getElementsByTagName ('image')
-            images = (self.extract(imgs, 0), self.extract(imgs, 1), self.extract(imgs, 2))
-            self.albums.append ({'title' : album_name, 'images' : images })
-            self.fetch_album_info (artist, album_name, i)
+        print(self.albums)
+        return True
 
     def get_top_albums (self):
         return self.albums
 
     def fetch_album_info (self, artist, album, index):
         qartist = urllib.parse.quote_plus(artist)
-        qalbum = urllib.parse.quote_plus(album.encode('utf-8'))
-        cachekey = "lastfm:album:getinfo:%s:%s" % (qartist, qalbum)
-        url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s" % (LastFM.URL_PREFIX,
-                                                                 qartist,
-                                                                 qalbum,
-                                                                 LastFM.API_KEY)
-        self.info_cache.fetch(cachekey, url, self.fetch_album_tracklist, album, index)
-
-    def fetch_album_tracklist (self, data, album, index):
-        if data is None:
-            self.assemble_info(None, None, None)
+        qalbum = urllib.parse.quote_plus(album)
+        cachekey = "lastfm:album:getinfojson:%s:%s" % (qartist, qalbum)
+        url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s&format=json" % (
+               LastFM.URL_PREFIX, qartist, qalbum, LastFM.API_KEY)
+        self.info_cache.fetch(cachekey, url, self.parse_album_info, album, index)
 
+    def parse_album_info (self, data, album, index):
+        rv = True
         try:
-            parsed = dom.parseString (data)
-            self.albums[index]['id'] = parsed.getElementsByTagName ('id')[0].firstChild.data
-        except Exception as e:
-            print("Error parsing album tracklist: %s" % e)
-            return False
+            parsed = json.loads(data.decode('utf-8'))
+            self.albums[index]['id'] = parsed['album']['id']
 
-        self.albums[index]['releasedate'] = self.extract(parsed.getElementsByTagName ('releasedate'),0)
-        self.albums[index]['summary'] = self.extract(parsed.getElementsByTagName ('summary'), 0)
+            for k in ('releasedate', 'summary'):
+                self.albums[index][k] = parsed['album'].get(k)
 
-        cachekey = "lastfm:album:tracks:%s" % self.albums[index]['id']
-        url = "%splaylist.fetch&playlistURL=lastfm://playlist/album/%s&api_key=%s" % (
-                     LastFM.URL_PREFIX, self.albums[index]['id'], LastFM.API_KEY)
+            tracklist = []
+            tracks = parsed['album']['tracks'].get('track', [])
+            for i, t in enumerate(tracks):
+                title = t['name']
+                duration = int(t['duration'])
+                tracklist.append((i, title, duration))
 
-        self.info_cache.fetch(cachekey, url, self.assemble_info, album, index)
+            print(tracklist)
+            self.albums[index]['tracklist'] = tracklist
+            self.albums[index]['duration']  = sum([t[2] for t in tracklist])
+            print("duration: %s" % self.albums[index]['duration'])
 
-    def assemble_info (self, data, album, index):
-        rv = True
-        if data is None:
-            print("nothing fetched for %s tracklist" % album)
-        else:
-            try:
-                parsed = dom.parseString (data)
-                list = parsed.getElementsByTagName ('track')
-                tracklist = []
-                album_length = 0
-                for i, track in enumerate(list):
-                    title = track.getElementsByTagName ('title')[0].firstChild.data
-                    duration = int(track.getElementsByTagName ('duration')[0].firstChild.data) / 1000
-                    album_length += duration
-                    tracklist.append ((i, title, duration))
-                self.albums[index]['tracklist'] = tracklist
-                self.albums[index]['duration']  = album_length
-            except Exception as e:
-                print("Error parsing album playlist: %s" % e)
-                rv = False
-
-        self.album_info_fetched -= 1
-        print("%s albums left to process" % self.album_info_fetched)
-        if self.album_info_fetched == 0:
+        except Exception as e:
+            print("Error parsing album tracklist: %s" % e)
+            rv = False
+
+        self.fetching -= 1
+        print("%s albums left to process" % self.fetching)
+        if self.fetching == 0:
             self.emit('albums-ready')
 
         return rv
diff --git a/plugins/context/ArtistTab.py b/plugins/context/ArtistTab.py
index 77a6de9..bac9293 100644
--- a/plugins/context/ArtistTab.py
+++ b/plugins/context/ArtistTab.py
@@ -28,6 +28,7 @@ import re, os
 import cgi
 import urllib
 import xml.dom.minidom as dom
+import json
 
 from mako.template import Template
 
@@ -187,39 +188,8 @@ class ArtistDataSource (GObject.GObject):
                 'cache'     : ranking_cache,
                 'parsed'    : False,
             },
-
-            'top_tracks' : {
-                'data'      : None, 
-                'signal'    : 'artist-top-tracks-ready',
-                'function'  : 'gettoptracks',
-                'cache'     : ranking_cache,
-                'parsed'    : False,
-            },
         }
        
-    def extract (self, data, position):
-        """
-        Safely extract the data from an xml node. Returns data
-        at position or None if position does not exist
-        """
-        
-        try:
-            return data[position].firstChild.data
-        except Exception as e:
-            return None
-
-    def fetch_top_tracks (self, artist):
-        if LastFM.user_has_account() is False:
-            return
-
-        artist = urllib.parse.quote_plus(artist)
-        function = self.artist['top_tracks']['function']
-        cache = self.artist['top_tracks']['cache']
-        cachekey = "lastfm:artist:%s:%s" % (function, artist)
-        url = '%sartist.%s&artist=%s&api_key=%s' % (LastFM.URL_PREFIX,
-            function, artist, LastFM.API_KEY)
-        cache.fetch(cachekey, url, self.fetch_artist_data_cb, self.artist['top_tracks'])
-
     def fetch_artist_data (self, artist): 
         """
         Initiate the fetching of all artist data. Fetches artist info, similar
@@ -236,9 +206,10 @@ class ArtistDataSource (GObject.GObject):
         self.error = None
         artist = urllib.parse.quote_plus(artist)
         for key, value in self.artist.items():
-            cachekey = "lastfm:artist:%s:%s" % (value['function'], artist)
-            url = '%sartist.%s&artist=%s&api_key=%s' % (LastFM.URL_PREFIX,
+            cachekey = "lastfm:artist:%sjson:%s" % (value['function'], artist)
+            url = '%sartist.%s&artist=%s&api_key=%s&format=json' % (LastFM.URL_PREFIX,
                 value['function'], artist, LastFM.API_KEY)
+            print("fetching %s" % url)
             value['cache'].fetch(cachekey, url, self.fetch_artist_data_cb, value)
 
     def fetch_artist_data_cb (self, data, category):
@@ -247,7 +218,7 @@ class ArtistDataSource (GObject.GObject):
             return
 
         try:
-            category['data'] = dom.parseString (data)
+            category['data'] = json.loads(data.decode('utf-8'))
             category['parsed'] = False
             self.emit (category['signal'])
         except Exception as e:
@@ -263,11 +234,11 @@ class ArtistDataSource (GObject.GObject):
     def get_top_albums (self):
         if not self.artist['top_albums']['parsed']:
             albums = []
-            for album in self.artist['top_albums']['data'].getElementsByTagName ('album'):
-                album_name = self.extract(album.getElementsByTagName ('name'), 0)
-                imgs = album.getElementsByTagName ('image') 
-                images = self.extract(imgs, 0), self.extract(imgs, 1), self.extract(imgs,2)
-                albums.append ((album_name, images))
+            d = self.artist['top_albums']['data']
+            for album in d['topalbums'].get('album', []):
+                images = [img['#text'] for img in album.get('image', ())]
+                albums.append((album.get('name'), images[:3]))
+
             self.artist['top_albums']['data'] = albums
             self.artist['top_albums']['parsed'] = True
 
@@ -283,11 +254,9 @@ class ArtistDataSource (GObject.GObject):
 
         if not self.artist['similar']['parsed']:
             lst = []
-            for node in data.getElementsByTagName ('artist'):
-                artist = self.extract(node.getElementsByTagName('name'), 0)
-                similar = self.extract(node.getElementsByTagName('match') ,0)
-                image = self.extract(node.getElementsByTagName('image'), 0)
-                lst.append ((artist, similar, image))
+            for node in data['similarartists'].get('artist', []):
+                image = [img['#text'] for img in node.get('image', [])]
+                lst.append ((node.get('name'), node.get('match'), image[:1]))
             data = lst
             self.artist['similar']['parsed'] = True
             self.artist['similar']['data'] = data
@@ -302,8 +271,8 @@ class ArtistDataSource (GObject.GObject):
         if data is None:
             return None
 
-        images = data.getElementsByTagName ('image')
-        return self.extract(images,0), self.extract(images,1), self.extract(images,2)
+        images = [img['#text'] for img in data['artist'].get('image', ())]
+        return images[:3]
         
     def get_artist_bio (self):
         """
@@ -314,8 +283,8 @@ class ArtistDataSource (GObject.GObject):
             return None
 
         if not self.artist['info']['parsed']:
-            content = self.extract(data.getElementsByTagName ('content'), 0)
-            summary = self.extract(data.getElementsByTagName ('summary'), 0)
+            content = data['artist']['bio']['content']
+            summary = data['artist']['bio']['summary']
             return summary, content
 
         return self.artist['info']['data']['bio']
@@ -332,21 +301,3 @@ class ArtistDataSource (GObject.GObject):
             self.artist['info']['parsed'] = True
 
         return self.artist['info']['data']
-
-    def get_top_tracks (self):
-        """
-        Returns a list of the top track titles
-        """
-        data = self.artist['top_tracks']['data']
-        if data is None:
-            return None
-
-        if not self.artist['top_tracks']['parsed']:
-            tracks = []
-            for track in data.getElementsByTagName ('track'):
-                name = self.extract(track.getElementsByTagName('name'), 0)
-                tracks.append (name)
-            self.artist['top_tracks']['data'] = tracks
-            self.artist['top_tracks']['parsed'] = True
-
-        return self.artist['top_tracks']['data']
diff --git a/plugins/context/tmpl/album-tmpl.html b/plugins/context/tmpl/album-tmpl.html
index 90b736a..a1b1f7b 100644
--- a/plugins/context/tmpl/album-tmpl.html
+++ b/plugins/context/tmpl/album-tmpl.html
@@ -1,4 +1,4 @@
-<%page args="error, list, artist, stylesheet, datasource" />
+<%page args="error, albums, artist, stylesheet, datasource" />
 <html> <head> <meta http-equiv="content-type" content="text-html; charset=utf-8">
 <%!
     import re
@@ -52,15 +52,13 @@
 <body>
 %if error is None:
 <%  
-    num_albums = min(8, len(list))
+    num_albums = len(albums)
 %>
     <h1>${ _("Top albums by %s") % ("<em>" + cgi.escape(artist, True) + "</em>") }</h1>
-%for i, entry in enumerate(list) :
+%for i, entry in enumerate(albums) :
     <%
     if 'tracklist' not in entry or len(entry['tracklist']) == 0:
         continue
-    if i == num_albums:
-        break
     %>
     <div id="album${entry['id'] | h}" class="album">
     <img width="64" src="${entry['images'][1] | h}" alt="${entry['images'] | h}"/>
@@ -73,9 +71,9 @@
     <% 
         album_time = sec2hms(entry['duration'])
        tracks = len(entry['tracklist'])
-       str = ngettext("%s (%d track)", "%s (%d tracks)", tracks)
+       s = ngettext("%s (%d track)", "%s (%d tracks)", tracks)
     %>
-    <p class="duration">${ str % (album_time, tracks) }</p>
+    <p class="duration">${ s % (album_time, tracks) }</p>
     %endif
     %if 'tracklist' in entry:
     <button id="btn_${entry['id'] | h}" onclick="toggle_vis(${entry['id'] | h})">


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