Commits

bj0  committed 695f7db

Add caching for Lyrics to the LyricsManager and make the LyricsViewer refresh button refresh the cache.

  • Participants
  • Parent commits a4ebe70

Comments (0)

Files changed (2)

File plugins/lyricsviewer/__init__.py

         webbrowser.open_new_tab(url)
 
     def on_refresh_button_clicked(self, button):
-        self.update_lyrics()
+        self.update_lyrics(refresh=True)
 
     def on_combo_active_changed(self, combobox):
         """
         if self.lyrics_found:
             self.update_lyrics_text()
 
-    def update_lyrics(self):
+    def update_lyrics(self, refresh=False):
         self.track_text_buffer.set_text("")
         self.lyrics_text_buffer.set_text("")
         self.lyrics_source_text_buffer.set_text("")
         self.lyrics_found=[]
         if player.PLAYER.current:
             self.set_top_box_widgets(False)
-            self.get_lyrics(player.PLAYER, player.PLAYER.current)
+            self.get_lyrics(player.PLAYER, player.PLAYER.current, refresh)
         else:
             glib.idle_add(self.lyrics_text_buffer.set_text, _('Not playing.'))
 
     @common.threaded
-    def get_lyrics(self, player, track):
+    def get_lyrics(self, player, track, refresh=False):
         lyrics_found=[]
         try:
             try:
             except Exception:
                 raise LyricsNotFoundException
             self.track_text_buffer.set_text(text_track)
-            lyrics_found = self.exaile.lyrics.find_all_lyrics(track)
+            lyrics_found = self.exaile.lyrics.find_all_lyrics(track, refresh)
         except LyricsNotFoundException:
             lyrics_found=[]
             return

File xl/lyrics.py

 
 #Lyrics manager.
 #
-from xl import providers, event
+from xl import providers, event, xdg
 from xl import settings
 from xl.nls import gettext as _
+from datetime import datetime, timedelta
+import threading
+import shelve
+import os
 
 class LyricsNotFoundException(Exception):
     pass
 
+class LyricsCache:
+    '''
+        Basically just a thread-safe shelf for convinience.  
+        Supports container syntax.
+    '''
+    def __init__(self, location, default=None):
+        '''
+            @param location: specify the shelve file location
+            
+            @param default: can specify a default to return from getter when
+                there is nothing in the shelve
+        '''
+        self.location = location
+        self.db = shelve.open(location, flag='c', protocol=2, writeback=False)
+        self.lock = threading.Lock()
+        self.default = default
+
+    def keys(self):
+        '''
+            Return the shelve keys
+        '''
+        return self.db.keys()
+        
+    def _get(self, key, default=None):
+        with self.lock:
+            try:
+                return self.db[key]
+            except:
+                return default if default is not None else self.default
+                
+    def _set(self, key, value):
+        with self.lock:
+            self.db[key] = value
+            # force save, wasn't auto-saving...
+            self.db.sync()
+                
+    def __getitem__(self, key):
+        return self._get(key)
+        
+    def __setitem__(self, key, value):
+        self._set(key, value)
+        
+    def __contains__(self, key):
+        return key in self.db
+
+    def __delitem__(self, key):
+        with self.lock:
+            del self.db[key]
+
+    def __iter__(self):
+        return self.db.__iter__()
+        
+    def __len__(self):
+        return len(self.db)
+
 class LyricsManager(providers.ProviderHandler):
     """
         Lyrics Manager
         self.preferred_order = settings.get_option(
                 'lyrics/preferred_order', [])
         self.add_defaults()
+        self.cache = LyricsCache(os.path.join(xdg.get_cache_dir(),'lyrics.cache'))
 
     def get_method_names(self):
         """
         """
         self.add_search_method(LocalLyricSearch())
 
-    def find_lyrics(self, track):
+    def find_lyrics(self, track, refresh=False):
         """
             Fetches lyrics for a track either from
                 1. a backend lyric plugin
 
             :param track: the track we want lyrics for, it
                 must have artist/title tags
+                
+            :param refresh: if True, try to refresh cached data even if
+                not expired
 
             :return: tuple of the following format (lyrics, source, url)
                 where lyrics are the lyrics to the track
         lyrics = None
         source = None
         url = None
+
         for method in self.get_methods():
             try:
-                (lyrics, source, url) = method.find_lyrics(track)
+                (lyrics, source, url) = self._find_cached_lyrics(method, track, refresh)
             except LyricsNotFoundException:
                 pass
             if lyrics:
 
         return (lyrics, source, url)
 
-    def find_all_lyrics(self, track):
+    def find_all_lyrics(self, track, refresh=False):
         """
             Like find_lyrics but fetches all sources and returns
             a list of lyrics.
             :param track: the track we want lyrics for, it
                 must have artist/title tags
 
+            :param refresh: if True, try to refresh cached data even if
+                not expired
+
             :return: list of tuples in the same format as
                 find_lyrics's return value
 
             source = None
             url = None
             try:
-                (lyrics, source, url) = method.find_lyrics(track)
+                (lyrics, source, url) = self._find_cached_lyrics(method, track, refresh)
             except LyricsNotFoundException:
                 pass
             if lyrics:
             raise LyricsNotFoundException()
 
         return lyrics_found
+        
+    def _find_cached_lyrics(self, method, track, refresh=False):
+        """
+            Checks the cache for lyrics.  If found and not expired, returns
+            cached results, otherwise tries to fetch from method.
+            
+            :param method: the LyricSearchMethod to fetch lyrics from.
+            
+            :param track: the track we want lyrics for, it 
+                must have artist/title tags
+                
+            :param refresh: if True, try to refresh cached data even if
+                not expired
+                
+            :return: list of tuples in the same format as 
+                find_lyric's return value
+                
+            :raise LyricsNotFoundException: when lyrics are not found 
+                in cache or fetched from method
+        """
+        lyrics = None
+        source = None
+        url = None
+        cache_time = settings.get_option('lyrics/cache_time', 720) # in hours
+        key = str(track.get_loc_for_io()+method.display_name)
+
+        try:
+            # check cache for lyrics
+            if key in self.cache:
+                (lyrics, source, url, time) = self.cache[key]
+                # return if they are not expired
+                now = datetime.now()
+                if (now-time < timedelta(hours=cache_time) and not refresh):
+                    return (lyrics, source, url)
+
+            (lyrics, source, url) = method.find_lyrics(track)
+        except LyricsNotFoundException:
+            pass
+        else:
+            if lyrics:
+                # update cache
+                time = datetime.now()
+                self.cache[key] = (lyrics, source, url, time)
+                
+        if not lyrics:
+            # no lyrcs were found, raise an exception
+            raise LyricsNotFoundException()
+
+        return (lyrics, source, url)        
 
 class LyricSearchMethod(object):
     """