diff --git a/client/djrandom_client/djfuse.py b/client/djrandom_client/djfuse.py
index 6ece0f0cef2234021c536b5a2d77cdc606cb0f6d..b83d457b7d501c61397df4ebbe3299f5f0f424b9 100644
--- a/client/djrandom_client/djfuse.py
+++ b/client/djrandom_client/djfuse.py
@@ -1,13 +1,75 @@
 import json
 import os
-import shelve
 import shutil
+import threading
+import urllib
 import urllib2
 from errno import EINVAL, ENOENT
 from stat import S_IFDIR, S_IFREG
 from fuse import FUSE, Operations, LoggingMixIn
 
 
+
+class DJAPI(object):
+
+    def __init__(self, server_url, api_key='fuse'):
+        self._server_url = server_url
+        self._api_key = api_key
+        self._opener = urllib2.build_opener()
+
+    def get(self, url):
+	print 'HTTP GET:', url
+        req = urllib2.Request(self._server_url + url)
+        return json.load(self._opener.open(req))
+
+    def download(self, sha1, path):
+        print 'HTTP DOWNLOAD:', sha1
+        req = urllib2.Request('%s/dl/%s' % (self._server_url, sha1))
+        resp = self._opener.open(req)
+        with open(path, 'wb') as fd:
+            shutil.copyfileobj(resp, fd)
+
+
+
+class StatCache(object):
+
+    def __init__(self, api):
+        self._api = api
+        self._cache = {}
+        self._lock = threading.Lock()
+
+    def _key(self, artist, album, title):
+        return '%s|%s|%s' % (artist, album, title)
+
+    def _load(self, artist, album, songs):
+        with self._lock:
+            dir_key = self._key(artist, album, 'DIR')
+            self._cache[dir_key] = songs
+            for song in songs:
+                key = self._key(artist, album, song['title'])
+                self._cache[key] = song
+
+    def preload(self, artist, album):
+        contents = self._api.get('/json/album/%s/%s' % (
+            urllib.quote(artist), urllib.quote(album)))['songs']
+        self._load(artist, album, contents)
+
+    def get_dir(self, artist, album):
+        return self.get_file(artist, album, 'DIR')
+
+    def get_file(self, artist, album, title):
+	print 'CACHE: get_file', artist, album, title
+        key = self._key(artist, album, title)
+        with self._lock:
+            result = self._cache.get(key)
+        if not result:
+            self.preload(artist, album)
+            with self._lock:
+                result = self._cache.get(key)
+        return result
+
+
+
 class DJFS(LoggingMixIn, Operations):
     """Simple FUSE client for djrandom.
 
@@ -20,65 +82,47 @@ class DJFS(LoggingMixIn, Operations):
         if not os.path.isdir(cache_dir):
             os.makedirs(cache_dir)
         self._cache_dir = cache_dir
-        self._server_url = server_url.rstrip('/')
-        self._opener = urllib2.build_opener()
-        self._hash_cache = shelve.open(
-            os.path.join(cache_dir, 'artist_album'))
-
-    def _get(self, url):
-        req = urllib2.Request(self._server_url + url)
-        return json.load(self._opener.open(req))
-
-    @classmethod
-    def _hash_cache_key(cls, artist, album, title):
-        return '%s|%s|%s' % (artist, album, title)
-
-    def _load_into_cache(self, artist, album, songs):
-        for song in songs:
-            key = self._hash_cache_key(artist, album, song['title'])
-            self._hash_cache[key] = song['sha1']
+        self._api = DJAPI(server_url)
+        self._stat_cache = StatCache(self._api)
 
-    def _trigger_cache_load(self, artist, album):
-        contents = self._get('/json/album/%s/%s' % (artist, album))['songs']
-        self._load_into_cache(artist, album, contents)        
+    def destroy(self, path):
+        pass
 
     def _find_sha1(self, artist, album, title):
-        key = self._hash_cache_key(artist, album, title)
-        sha1 = self._hash_cache.get(key)
-        if not sha1:
-            self._trigger_cache_load(artist, album)
-            sha1 = self._hash_cache.get(key)
-        return sha1
+        return self._stat_cache.get_file(artist, album, title)
+
+    def _file_cache_path(self, sha1):
+        return os.path.join(self._cache_dir, sha1[0], sha1)
 
     def _in_file_cache(self, sha1):
-        path = os.path.join(self._cache_dir, sha1[0], sha1)
-        return os.path.exists(path)
+        return os.path.exists(self._file_cache_path(sha1))
 
     def _download_file(self, sha1):
-        path = os.path.join(self._cache_dir, sha1[0], sha1)
-        req = urllib2.Request('/dl/%s' % sha1)
-        resp = self._opener.open(req)
-        with open(path, 'wb') as fd:
-            shutil.copyfileobj(req, fd)
+        path = self._file_cache_path(sha1)
+        dl_dir = os.path.dirname(path)
+        if not os.path.isdir(dl_dir):
+            os.makedirs(dl_dir)
+        self._api.download(sha1, path)
 
     def _parse_path(self, path):
-        parts = path.split('/')
+        parts = path[1:].split('/')
         if len(parts) > 3:
             raise OSError(EINVAL)
         if len(parts) < 3:
-            parts.extend([None for x in (3 - len(parts))])
+            parts.extend([None for x in range(3 - len(parts))])
+        if parts[2] and parts[2].endswith('.mp3'):
+            parts[2] = parts[2][:-4]
         return parts
 
     def getattr(self, path, fh=None):
         artist, album, title = self._parse_path(path)
         if title:
-            sha1 = self._cache_get(artist, album, title)
-            if not sha1:
+            stats = self._stat_cache.get_file(artist, album, title)
+            if not stats:
                 raise OSError(ENOENT)
-            stats = self._get('/json/song/%s' % sha1)
-            mtime = float(stats['uploaded_at'])
+            mtime = float(stats.get('uploaded_at', '0'))
             return dict(st_mode=(S_IFREG | 0444), st_nlink=1,
-                        st_size=int(stats['size']),
+                        st_size=int(stats.get('size', 0)),
                         st_ctime=mtime,
                         st_mtime=mtime,
                         st_atime=mtime)
@@ -91,20 +135,27 @@ class DJFS(LoggingMixIn, Operations):
         if title:
             raise OSError(EINVAL)
         if not artist:
-            values = self._get('/json/artists')['artists']
+            values = self._api.get('/json/artists')['artists']
         elif not album:
-            values = self._get('/json/albums/%s' % artist)['albums']
+            values = self._api.get('/json/albums/%s' % urllib.quote(artist))['albums']
         else:
-            # equal to trigger_cache_load()
-            contents = self._get('/json/album/%s/%s' % (artist, album))['songs']
-            self._load_into_cache(artist, album, contents)
-            values = [x['sha1'] for x in contents]
-        return ['.', '..'] + [x for x in values]
+            songs = self._stat_cache.get_dir(artist, album)
+            if not songs:
+                raise OSError(ENOENT)
+            values = ['%s.mp3' % (x['title'] || 'UNKNOWN') for x in songs]
+        return ['.', '..'] + [x.encode('utf-8') for x in values]
 
     def read(self, path, size, offset, fh=None):
-        if not self._in_file_cache(path):
-            self._download_file(path)
-        with open(path, 'rb') as fd:
+        artist, album, title = self._parse_path(path)
+        if not title:
+            raise OSError(ENOENT)
+        sha1 = self._find_sha1(artist, album, title)
+        if not sha1:
+            raise OSError(ENOENT)
+        if not self._in_file_cache(sha1):
+            self._download_file(sha1)
+        real_path = self._file_cache_path(sha1)
+        with open(real_path, 'rb') as fd:
             fd.seek(offset)
             return fd.read(size)
 
@@ -138,7 +189,7 @@ def main():
         parser.error('Wrong number of args')
 
     fuse = FUSE(DJFS(opts.server_url, opts.cache_dir),
-                args[0], foreground=True)
+                args[0], foreground=True, debug=True)
 
 
 if __name__ == '__main__':