[rhythmbox] context: add basic request caching



commit 7551eadf08df41a6e1832cd026922cb029b089b8
Author: Jonathan Matthew <jonathan fibula d14n org>
Date:   Sat Oct 31 22:06:48 2009 +1000

    context: add basic request caching

 plugins/context/context/AlbumTab.py    |   51 +++++++++++++++++---------------
 plugins/context/context/ArtistTab.py   |   39 +++++++++++++++++-------
 plugins/context/context/ContextView.py |   27 ++++++++++++++---
 plugins/context/tmpl/artist-tmpl.html  |    5 +++
 4 files changed, 82 insertions(+), 40 deletions(-)
---
diff --git a/plugins/context/context/AlbumTab.py b/plugins/context/context/AlbumTab.py
index 9143c91..bda03a9 100644
--- a/plugins/context/context/AlbumTab.py
+++ b/plugins/context/context/AlbumTab.py
@@ -123,9 +123,8 @@ class AlbumView (gobject.GObject):
         self.styles = self.basepath + '/tmpl/main.css'
 
     def album_list_ready (self, ds):
-        list = ds.get_top_albums ()
         self.file = self.album_template.render (error = ds.get_error(), 
-                                                list = list, 
+                                                list = ds.get_top_albums(), 
                                                 artist = ds.get_artist(),
                                                 stylesheet = self.styles)
         self.load_view ()
@@ -137,11 +136,14 @@ class AlbumDataSource (gobject.GObject):
         'albums-ready' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
     }
 
-    def __init__ (self):
+    def __init__ (self, info_cache, ranking_cache):
         gobject.GObject.__init__ (self)
         self.albums = None
         self.error = None
+        self.artist = None
         self.max_albums_fetched = 8
+        self.info_cache = info_cache
+        self.ranking_cache = ranking_cache
 
     def extract (self, data, position):
         """
@@ -165,19 +167,20 @@ class AlbumDataSource (gobject.GObject):
         url = "%sartist.gettopalbums&artist=%s&api_key=%s" % (LastFM.URL_PREFIX,
                                                               artist.replace(" ", "+"),
                                                               LastFM.API_KEY)
-        try:
-            ld = rb.Loader ()
-            ld.get_url (url, self.fetch_album_list_cb, artist) 
-        except Exception, e:
-            print "problem fetching %s: %s" % (artist, e)
-            return
+        cachekey = 'lastfm:artist:gettopalbums:%s' % artist
+        self.ranking_cache.fetch(cachekey, url, self.parse_album_list, artist)
 
-    def fetch_album_list_cb (self, data, artist):
+    def parse_album_list (self, data, artist):
         if data is None:
             print "Nothing fetched for %s top albums" % artist
             return
 
-        parsed = dom.parseString (data)
+        try:
+            parsed = dom.parseString (data)
+        except Exception, 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
@@ -208,41 +211,40 @@ class AlbumDataSource (gobject.GObject):
         return self.albums
 
     def fetch_album_info (self, artist, album, index):
+        cachekey = "lastfm:album:getinfo:%s:%s" % (artist, album)
         url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s" % (LastFM.URL_PREFIX,
                                                                  artist.replace(" ", "+"),
                                                                  album.replace(" ", "+"),
                                                                  LastFM.API_KEY)
-
-        ld = rb.Loader()
-        ld.get_url (url, self.fetch_album_tracklist, album, index)
+        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)
 
-        parsed = dom.parseString (data)
-        
         try:
+            parsed = dom.parseString (data)
             self.albums[index]['id'] = parsed.getElementsByTagName ('id')[0].firstChild.data
         except Exception, e:
-            print "Problem parsing id, exiting: %s" % e
-            return None
+            print "Error parsing album tracklist: %s" % e
+            return False
 
         self.albums[index]['releasedate'] = self.extract(parsed.getElementsByTagName ('releasedate'),0)
         self.albums[index]['summary'] = self.extract(parsed.getElementsByTagName ('summary'), 0)
 
+        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)
 
-        ld = rb.Loader()
-        ld.get_url (url, self.assemble_info, album, index)
+        self.info_cache.fetch(cachekey, url, self.assemble_info, album, index)
 
     def assemble_info (self, data, album, index):
+        rv = True
         if data is None:
             print "nothing fetched for %s tracklist" % album
         else:
-            parsed = dom.parseString (data)
             try:
+                parsed = dom.parseString (data)
                 list = parsed.getElementsByTagName ('track')
                 tracklist = []
                 album_length = 0
@@ -254,11 +256,12 @@ class AlbumDataSource (gobject.GObject):
                 self.albums[index]['tracklist'] = tracklist
                 self.albums[index]['duration']  = album_length
             except Exception, e:
-                print "Problem : %s" % e
+                print "Error parsing album playlist: %s" % e
+                rv = False
 
-        gtk.gdk.threads_enter ()
         self.album_info_fetched -= 1
         print "%s albums left to process" % self.album_info_fetched
-        gtk.gdk.threads_leave ()
         if self.album_info_fetched == 0:
             self.emit('albums-ready')
+
+        return rv
diff --git a/plugins/context/context/ArtistTab.py b/plugins/context/context/ArtistTab.py
index 368206f..5d1f776 100644
--- a/plugins/context/context/ArtistTab.py
+++ b/plugins/context/context/ArtistTab.py
@@ -126,9 +126,10 @@ class ArtistView (gobject.GObject):
         # If called any other time, the behavior is undefined
         try:
             info = ds.get_artist_info ()
-            small, med, big = info['images']
-            summary, full_bio = info['bio'] 
+            small, med, big = info['images'] or (None, None, None)
+            summary, full_bio = info['bio'] or (None, None)
             self.file = self.template.render (artist     = ds.get_current_artist (),
+                                              error      = ds.get_error (),
                                               image      = med,
                                               fullbio    = full_bio,
                                               shortbio   = summary,
@@ -147,15 +148,17 @@ class ArtistDataSource (gobject.GObject):
         'artist-top-albums-ready' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
     }
 
-    def __init__ (self):
+    def __init__ (self, info_cache, ranking_cache):
         gobject.GObject.__init__ (self)
 
         self.current_artist = None
+        self.error = None
         self.artist = {
             'info' : {
                 'data'      : None, 
                 'signal'    : 'artist-info-ready', 
                 'function'  : 'getinfo',
+                'cache'     : info_cache,
                 'parsed'    : False,
             },
             
@@ -163,6 +166,7 @@ class ArtistDataSource (gobject.GObject):
                 'data'      : None, 
                 'signal'    : 'artist-similar-ready', 
                 'function'  : 'getsimilar',
+                'cache'     : info_cache,
                 'parsed'    : False,
             },
 
@@ -170,6 +174,7 @@ class ArtistDataSource (gobject.GObject):
                 'data'      : None, 
                 'signal'    : 'artist-top-albums-ready',
                 'function'  : 'gettopalbums',
+                'cache'     : ranking_cache,
                 'parsed'    : False,
             },
 
@@ -177,6 +182,7 @@ class ArtistDataSource (gobject.GObject):
                 'data'      : None, 
                 'signal'    : 'artist-top-tracks-ready',
                 'function'  : 'gettoptracks',
+                'cache'     : ranking_cache,
                 'parsed'    : False,
             },
         }
@@ -194,10 +200,12 @@ class ArtistDataSource (gobject.GObject):
 
     def fetch_top_tracks (self, artist):
         artist = artist.replace (" ", "+")
+        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,
-            self.artist['top_tracks']['function'], artist, LastFM.API_KEY)
-        ld = rb.Loader()
-        ld.get_url (url, self.fetch_artist_data_cb, self.artist['top_tracks'])
+            function, artist, LastFM.API_KEY)
+        cache.fetch(cachekey, url, self.fetch_artist_data_cb, self.artist['top_tracks'])
 
     def fetch_artist_data (self, artist): 
         """
@@ -207,24 +215,33 @@ class ArtistDataSource (gobject.GObject):
         before any of the get_* methods.
         """
         self.current_artist = artist
+        self.error = None
         artist = artist.replace(" ", "+")
         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,
                 value['function'], artist, LastFM.API_KEY)
-            ld = rb.Loader()
-            ld.get_url (url, self.fetch_artist_data_cb, value)
+            value['cache'].fetch(cachekey, url, self.fetch_artist_data_cb, value)
 
     def fetch_artist_data_cb (self, data, category):
         if data is None:
             print "no data fetched for artist %s" % category['function']
             return
-        category['data'] = dom.parseString (data)
-        category['parsed'] = False
-        self.emit (category['signal'])
+
+        try:
+            category['data'] = dom.parseString (data)
+            category['parsed'] = False
+            self.emit (category['signal'])
+        except Exception, e:
+            print "Error parsing artist %s: %s" % (category['function'], e)
+            return False
 
     def get_current_artist (self):
         return self.current_artist
 
+    def get_error (self):
+        return self.error
+
     def get_top_albums (self):
         if not self.artist['top_albums']['parsed']:
             albums = []
diff --git a/plugins/context/context/ContextView.py b/plugins/context/context/ContextView.py
index 58011be..5848665 100644
--- a/plugins/context/context/ContextView.py
+++ b/plugins/context/context/ContextView.py
@@ -26,8 +26,8 @@
 
 import rb, rhythmdb
 import gtk, gobject
-import pango
 import webkit
+import os
 
 import ArtistTab as at
 import AlbumTab as abt
@@ -55,7 +55,24 @@ class ContextView (gobject.GObject):
         self.current_album = None
         self.current_song = None
         self.visible = True
-        
+
+        # cache for artist/album information: valid for a month, can be used indefinitely
+        # if offline, discarded if unused for six months
+        self.info_cache = rb.URLCache(name = 'info',
+                                      path = os.path.join('context-pane', 'info'),
+                                      refresh = 30,
+                                      discard = 180)
+        # cache for rankings (artist top tracks and top albums): valid for a week,
+        # can be used for a month if offline
+        self.ranking_cache = rb.URLCache(name = 'ranking',
+                                         path = os.path.join('context-pane', 'ranking'),
+                                         refresh = 7,
+                                         lifetime = 30)
+
+        # maybe move this into an idle handler?
+        self.info_cache.clean()
+        self.ranking_cache.clean()
+
         self.init_gui ()
         self.init_tabs()
 
@@ -121,7 +138,7 @@ class ContextView (gobject.GObject):
             self.visible = False
 
     def change_tab (self, tab, newtab):
-        print "swaping tab from %s to %s" % (self.current, newtab)
+        print "swapping tab from %s to %s" % (self.current, newtab)
         if (self.current != newtab):
             self.tab[self.current].deactivate()
             self.tab[newtab].activate()
@@ -132,10 +149,10 @@ class ContextView (gobject.GObject):
         self.ds = {}
         self.view = {}
 
-        self.ds['artist'] = at.ArtistDataSource ()
+        self.ds['artist'] = at.ArtistDataSource (self.info_cache, self.ranking_cache)
         self.view['artist'] = at.ArtistView (self.shell, self.plugin, self.webview, self.ds['artist'])
         self.tab['artist']  = at.ArtistTab (self.shell, self.buttons, self.ds['artist'], self.view['artist'])
-        self.ds['album']    = abt.AlbumDataSource()
+        self.ds['album']    = abt.AlbumDataSource(self.info_cache, self.ranking_cache)
         self.view['album']  = abt.AlbumView(self.shell, self.plugin, self.webview, self.ds['album'])
         self.tab['album']   = abt.AlbumTab(self.shell, self.buttons, self.ds['album'], self.view['album'])
         self.ds['lyrics']   = lt.LyricsDataSource (self.db)
diff --git a/plugins/context/tmpl/artist-tmpl.html b/plugins/context/tmpl/artist-tmpl.html
index 1249078..bea180a 100644
--- a/plugins/context/tmpl/artist-tmpl.html
+++ b/plugins/context/tmpl/artist-tmpl.html
@@ -22,6 +22,7 @@
 </script>
 </head>
 <body class="artist">
+%if error is None:
 <h1>${artist}</h1>
 <img src="${image}" />
 <div id="shortbio" class="shown">
@@ -41,5 +42,9 @@ Read less
 Read less
 </button>
 </div>
+%else:
+<h1>Unable to retrieve artist information:</h1>
+<p class="error">${error}$</p>
+%endif
 </body>
 </html>



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