summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Bachelier <laurent@bachelier.name>2010-08-23 22:45:10 +0200
committerLaurent Bachelier <laurent@bachelier.name>2010-08-23 22:48:04 +0200
commit415630d657067695d460b19eba1509bdd3bf610a (patch)
tree04e1ee4eba99368a3c5e4dd5430bb254e0e449c4
parentSome method documentation (diff)
downloadmpdat-415630d657067695d460b19eba1509bdd3bf610a.tar.xz
Cache and autoupdate final dirs
When and only when the MPD database is updated, the final dirs are computed again, on demand. The client code has been enhanced and simplified. It's not perfect yet (it reads the file each time the function is called, we could have an in-memory cache too).
-rwxr-xr-xfeeding_example.py10
-rw-r--r--mpdrast/cache.py82
-rw-r--r--[-rwxr-xr-x]mpdrast/client.py46
3 files changed, 114 insertions, 24 deletions
diff --git a/feeding_example.py b/feeding_example.py
index 404af27..d9bde2b 100755
--- a/feeding_example.py
+++ b/feeding_example.py
@@ -12,10 +12,14 @@ m.connect_from_env(mpd_host, mpd_port)
m.wait_for_update()
# get all "final" directories
-m.update_final_dirs("")
+nb_dirs = len(m.get_final_dirs())
+print "We have found %s albums." % nb_dirs
# if the playlist is empty, add one random album
-if m.is_playlist_hungry(1):
- m.add(m.get_random_dir())
+while m.is_playlist_hungry(100) and nb_dirs:
+ rdir = m.get_random_dir()
+ m.add(rdir)
+ print "Added %s" % rdir
print m.status()
+print m.stats()
diff --git a/mpdrast/cache.py b/mpdrast/cache.py
new file mode 100644
index 0000000..67ddc0f
--- /dev/null
+++ b/mpdrast/cache.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import pickle
+from os.path import join, expanduser, exists, getmtime, isdir
+from os import makedirs
+from hashlib import sha1
+
+USER_DIR = join(expanduser('~'), '.mpdrast')
+
+def cache(name):
+ def decorator(fn):
+ def wrapper(*args, **kwargs):
+ _updated = kwargs["_updated"]
+ del kwargs["_updated"]
+
+ if kwargs.has_key("_hash"):
+ _hash = kwargs["_hash"]
+ del kwargs["_hash"]
+ else:
+ _hash = uhash()
+
+ cache_dir = join(USER_DIR, "cache", name)
+ if not isdir(cache_dir):
+ makedirs(cache_dir)
+
+ cache_file = join(cache_dir, _hash)
+ if not exists(cache_file) or \
+ getmtime(cache_file) < _updated:
+ data = fn(*args, **kwargs)
+ with open(cache_file, 'w') as handle:
+ pickle.dump(data, handle)
+ else:
+ with open(cache_file, 'r') as handle:
+ data = pickle.load(handle)
+
+ return data
+
+ return wrapper
+
+ return decorator
+
+def uhash(*args):
+ return sha1(pickle.dumps(args)).hexdigest()
+
+def test():
+ from tempfile import mkdtemp
+ from time import time
+
+ # TODO we should clean up after, also… it's kinda ugly
+ globals()["USER_DIR"] = mkdtemp(prefix='mpdrast')
+ print "Testing in %s." % USER_DIR
+
+ we_should_be_here = False
+ testval = "plop"
+
+ @cache("test")
+ def f1():
+ assert we_should_be_here
+ return testval
+
+ @cache("test2")
+ def f2(testparam):
+ assert we_should_be_here
+ return testparam
+
+ # Handling of the updated parameter
+ we_should_be_here = True
+ assert "plop" == f1(_updated=1)
+ we_should_be_here = False
+ testval = "plop2"
+ assert "plop" == f1(_updated=1)
+ we_should_be_here = True
+ assert "plop2" == f1(_updated=time()+42)
+
+ # Different cache for different parameters
+ we_should_be_here = True
+ assert "plap" == f2(_updated=1, _hash=uhash("plap"), testparam="plap")
+ we_should_be_here = False
+ assert "plap" == f2(_updated=1, _hash=uhash("plap"), testparam="plap")
+ we_should_be_here = True
+ assert "plip" == f2(_updated=1, _hash=uhash("plip"), testparam="plip")
+
diff --git a/mpdrast/client.py b/mpdrast/client.py
index fbf471a..974350c 100755..100644
--- a/mpdrast/client.py
+++ b/mpdrast/client.py
@@ -4,11 +4,11 @@ import time
import random
import mpdrast.process as process
+from mpdrast.cache import cache, uhash
class MPDrastClient(mpd.MPDClient):
def __init__(self):
mpd.MPDClient.__init__(self)
- self.final_dirs = []
def connect_from_env(self, host, port):
@@ -34,34 +34,38 @@ class MPDrastClient(mpd.MPDClient):
self.password(password)
- def update_final_dirs(self, path="", first=True):
+ def get_final_dirs(self, root=""):
"""
- Compute a list of directory containing only files.
+ Get a list of directory containing only files.
They can be considered as full albums. Remember that MPD indexes
only music; if a directory has subdirectories of non-music files,
it will not prevent the directory from being added (which is good).
- If first is True, the function considers that it represents the root,
- and will clear the existing list of "final dirs". If not, it will
- append to the list. The function is recursive, hence this paratemer.
- There should be no need to call it with first=False manually.
+ The root path can be any subdirectory of the database, any
+ directory not in the path will be ignored.
- The root path can be any subdirectory of the database, any directory
- not in the path will be ignored.
+ The list is cached, it will be repopulated after a database update.
"""
- if first:
- self.final_dirs = []
+ _updated = int(self.stats()["db_update"])
+ _hash = uhash(root)
+ return self._get_final_dirs(_updated=_updated, _hash=_hash, root=root)
- items = self.lsinfo(path)
- if first and not items:
- raise Exception("database is empty")
- files, dirs = process.get_files_and_dirs_from_db(items)
- if len(files) and len(dirs) == 0:
- self.final_dirs.append(path)
- else:
- for dir in dirs:
- self.update_final_dirs(dir, False)
+ @cache("final_dirs")
+ def _get_final_dirs(self, root):
+ final_dirs = []
+ def update_final_dirs(path=""):
+ items = self.lsinfo(path)
+
+ files, directories = process.get_files_and_dirs_from_db(items)
+ if len(files) and len(directories) == 0:
+ final_dirs.append(path)
+ else:
+ for directory in directories:
+ update_final_dirs(directory)
+
+ update_final_dirs(root)
+ return final_dirs
def wait_for_update(self):
@@ -73,7 +77,7 @@ class MPDrastClient(mpd.MPDClient):
def get_random_dir(self):
- return random.choice(self.final_dirs)
+ return random.choice(self.get_final_dirs())
def is_playlist_hungry(self, hungriness=100):