]> kaliko git repositories - mpd-sima.git/commitdiff
Integrate persistent cache with "var_dir" conf option
authorkaliko <efrim@azylum.org>
Fri, 21 Feb 2014 00:53:07 +0000 (01:53 +0100)
committerkaliko <efrim@azylum.org>
Fri, 21 Feb 2014 00:53:07 +0000 (01:53 +0100)
sima/lib/cache.py [moved from sima/lib/httpcli/cache.py with 66% similarity]
sima/lib/http.py [moved from sima/lib/httpcli/controller.py with 83% similarity]
sima/lib/httpcli/__init__.py [deleted file]
sima/lib/httpcli/filelock.py [deleted file]
sima/lib/simaecho.py
sima/plugins/internal/echonest.py
sima/utils/filelock.py [new file with mode: 0644]

similarity index 66%
rename from sima/lib/httpcli/cache.py
rename to sima/lib/cache.py
index 22c751d90baae593c81516373154634a2985ae7f..ebed3fcb3977bf12741163ff35c4a50f25e27f11 100644 (file)
@@ -1,3 +1,22 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
+# Copyright (c) 2012, 2013 Eric Larson <eric@ionrock.org>
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#
 """
 The cache object API for implementing caches. The default is just a
 dictionary, which in turns means it is not threadsafe for writing.
@@ -11,7 +30,7 @@ from hashlib import md5
 from pickle import load, dump
 from threading import Lock
 
-from .filelock import FileLock
+from ..utils.filelock import FileLock
 
 
 class BaseCache:
similarity index 83%
rename from sima/lib/httpcli/controller.py
rename to sima/lib/http.py
index c44789512299399265c9e3c17bcf6c25fd624b65..1040c2a98f949daae3a9e15261136b3e77a0df6a 100644 (file)
@@ -1,3 +1,22 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
+# Copyright (c) 2012, 2013 Eric Larson <eric@ionrock.org>
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#
 """
 The httplib2 algorithms ported for use with requests.
 """
@@ -5,9 +24,10 @@ import re
 import calendar
 import time
 
-from sima.lib.httpcli.cache import DictCache
 import email.utils
 
+from .cache import DictCache
+
 
 URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
 
@@ -30,7 +50,7 @@ class CacheController(object):
 
     def _urlnorm(self, uri):
         """Normalize the URL to create a safe key for the cache"""
-        (scheme, authority, path, query, fragment) = parse_uri(uri)
+        (scheme, authority, path, query, _) = parse_uri(uri)
         if not scheme or not authority:
             raise Exception("Only absolute URIs are allowed. uri = %s" % uri)
         authority = authority.lower()
@@ -56,10 +76,8 @@ class CacheController(object):
         """
         retval = {}
 
+        # requests provides a CaseInsensitiveDict as headers
         cc_header = 'cache-control'
-        if 'Cache-Control' in headers:
-            cc_header = 'Cache-Control'
-
         if cc_header in headers:
             parts = headers[cc_header].split(',')
             parts_with_args = [
@@ -71,6 +89,8 @@ class CacheController(object):
         return retval
 
     def cached_request(self, url, headers):
+        """Return the cached resquest if available and fresh
+        """
         cache_url = self.cache_url(url)
         cc = self.parse_cache_control(headers)
 
@@ -153,25 +173,11 @@ class CacheController(object):
             resp.from_cache = True
             return resp
 
-        # we're not fresh. If we don't have an Etag, clear it out
-        if 'etag' not in resp.headers:
-            self.cache.delete(cache_url)
-
-        if 'etag' in resp.headers:
-            headers['If-None-Match'] = resp.headers['ETag']
-
-        if 'last-modified' in resp.headers:
-            headers['If-Modified-Since'] = resp.headers['Last-Modified']
-
+        # we're not fresh.
+        self.cache.delete(cache_url)
         # return the original handler
         return False
 
-    def add_headers(self, url):
-        resp = self.cache.get(url)
-        if resp and 'etag' in resp.headers:
-            return {'If-None-Match': resp.headers['etag']}
-        return {}
-
     def cache_response(self, request, resp):
         """
         Algorithm for caching requests.
@@ -184,26 +190,22 @@ class CacheController(object):
             return
 
         cc_req = self.parse_cache_control(request.headers)
-        cc = self.parse_cache_control(resp.headers)
+        cc_resp = self.parse_cache_control(resp.headers)
 
         cache_url = self.cache_url(request.url)
 
         # Delete it from the cache if we happen to have it stored there
-        no_store = cc.get('no-store') or cc_req.get('no-store')
+        no_store = cc_resp.get('no-store') or cc_req.get('no-store')
         if no_store and self.cache.get(cache_url):
             self.cache.delete(cache_url)
 
-        # If we've been given an etag, then keep the response
-        if self.cache_etags and 'etag' in resp.headers:
-            self.cache.set(cache_url, resp)
-
         # Add to the cache if the response headers demand it. If there
         # is no date header then we can't do anything about expiring
         # the cache.
-        elif 'date' in resp.headers:
+        if 'date' in resp.headers:
             # cache when there is a max-age > 0
-            if cc and cc.get('max-age'):
-                if int(cc['max-age']) > 0:
+            if cc_resp and cc_resp.get('max-age'):
+                if int(cc_resp['max-age']) > 0:
                     self.cache.set(cache_url, resp)
 
             # If the request can expire, it means we should cache it
diff --git a/sima/lib/httpcli/__init__.py b/sima/lib/httpcli/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sima/lib/httpcli/filelock.py b/sima/lib/httpcli/filelock.py
deleted file mode 100644 (file)
index 6dc331b..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-\r
-# https://github.com/dmfrey/FileLock\r
-\r
-import os\r
-import time\r
-import errno\r
-\r
-class FileLockException(Exception):\r
-    pass\r
-\r
-class FileLock:\r
-    """ A file locking mechanism that has context-manager support so\r
-        you can use it in a with statement. This should be relatively cross\r
-        compatible as it doesn't rely on msvcrt or fcntl for the locking.\r
-    """\r
-\r
-    def __init__(self, file_name, timeout=10, delay=.05):\r
-        """ Prepare the file locker. Specify the file to lock and optionally\r
-            the maximum timeout and the delay between each attempt to lock.\r
-        """\r
-        self.is_locked = False\r
-        self.lockfile = os.path.join(os.getcwd(), "%s.lock" % file_name)\r
-        self.file_name = file_name\r
-        self.timeout = timeout\r
-        self.delay = delay\r
-\r
-\r
-    def acquire(self):\r
-        """ Acquire the lock, if possible. If the lock is in use, it check again\r
-            every `wait` seconds. It does this until it either gets the lock or\r
-            exceeds `timeout` number of seconds, in which case it throws\r
-            an exception.\r
-        """\r
-        start_time = time.time()\r
-        while True:\r
-            try:\r
-                self.fd = os.open(self.lockfile, os.O_CREAT|os.O_EXCL|os.O_RDWR)\r
-                break;\r
-            except OSError as e:\r
-                if e.errno != errno.EEXIST:\r
-                    raise\r
-                if (time.time() - start_time) >= self.timeout:\r
-                    raise FileLockException("Timeout occured.")\r
-                time.sleep(self.delay)\r
-        self.is_locked = True\r
-\r
-\r
-    def release(self):\r
-        """ Get rid of the lock by deleting the lockfile.\r
-            When working in a `with` statement, this gets automatically\r
-            called at the end.\r
-        """\r
-        if self.is_locked:\r
-            os.close(self.fd)\r
-            os.unlink(self.lockfile)\r
-            self.is_locked = False\r
-\r
-\r
-    def __enter__(self):\r
-        """ Activated when used in the with statement.\r
-            Should automatically acquire a lock to be used in the with block.\r
-        """\r
-        if not self.is_locked:\r
-            self.acquire()\r
-        return self\r
-\r
-\r
-    def __exit__(self, type, value, traceback):\r
-        """ Activated at the end of the with statement.\r
-            It automatically releases the lock if it isn't locked.\r
-        """\r
-        if self.is_locked:\r
-            self.release()\r
-\r
-\r
-    def __del__(self):\r
-        """ Make sure that the FileLock instance doesn't leave a lockfile\r
-            lying around.\r
-        """\r
-        self.release()\r
index 592ea0387f9fd718312cf50c3028e3f70f271f07..caf8b5bfbc12b9e21cbd8a3e193f952621581f82 100644 (file)
@@ -32,8 +32,7 @@ from requests import Session, Request, Timeout, ConnectionError
 from sima import ECH
 from sima.lib.meta import Artist
 from sima.lib.track import Track
-from sima.lib.httpcli.controller import CacheController
-from sima.lib.httpcli.cache import FileCache
+from sima.lib.http import CacheController
 from sima.utils.utils import WSError, WSNotFound, WSTimeout, WSHTTPError
 from sima.utils.utils import getws, Throttle
 if len(ECH.get('apikey')) == 23:  # simple hack allowing imp.reload
@@ -51,7 +50,7 @@ class SimaEch:
     timestamp = datetime.utcnow()
     ratelimit = None
     name = 'EchoNest'
-    cache = FileCache('/home/kaliko/.local/share/mpd_sima/http')
+    cache = False
 
     def __init__(self):
         self._ressource = None
index 7d4c0e041384a55d1c5097f3bde671adeb323008..ad3a6f9d25967744c798bdeaa968d8dfec1bdcde 100644 (file)
@@ -22,12 +22,14 @@ Fetching similar artists from echonest web services
 """
 
 # standard library import
+from os.path import join
 
 # third parties components
 
 # local import
 from ...lib.simaecho import SimaEch
 from ...lib.webserv import WebService
+from ...lib.cache import FileCache
 
 
 class EchoNest(WebService):
@@ -36,6 +38,9 @@ class EchoNest(WebService):
 
     def __init__(self, daemon):
         WebService.__init__(self, daemon)
+        # Set persitent cache
+        vardir = daemon.config['sima']['var_dir']
+        SimaEch.cache = FileCache(join(vardir, 'http'))
         self.ws = SimaEch
 
 # VIM MODLINE
diff --git a/sima/utils/filelock.py b/sima/utils/filelock.py
new file mode 100644 (file)
index 0000000..8f7065f
--- /dev/null
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 Evan Fosmark
+# Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+# https://github.com/dmfrey/FileLock
+"""
+Plain file lock to une in context:
+    >>> with FileLock('/path/to/file/to/write'):
+    >>>     # a lock file is maintain within the scope of this context:
+    >>>     # /path/to/file/to/write.lock
+    >>>     ... # process file writing
+"""
+
+import errno
+import os
+import time
+
+class FileLockException(Exception):
+    """FileLock Exception"""
+    pass
+
+class FileLock:
+    """ A plain file lock whit context-manager"""
+
+    def __init__(self, file_name, timeout=10, delay=.05):
+        """
+        Setup file lock.
+        Setup timeout and the delay.
+        """
+        self.filedsc = None
+        self.is_locked = False
+        dirname = os.path.dirname(file_name)
+        self.lockfile = os.path.join(dirname, '{0}.lock'.format(file_name))
+        self.file_name = file_name
+        self.timeout = timeout
+        self.delay = delay
+
+    def acquire(self):
+        """Acquire the lock, if possible.
+        """
+        start_time = time.time()
+        while True:
+            try:
+                self.filedsc = os.open(self.lockfile,
+                                       os.O_CREAT|os.O_EXCL|os.O_RDWR)
+                break
+            except OSError as err:
+                if err.errno != errno.EEXIST:
+                    raise
+                if (time.time() - start_time) >= self.timeout:
+                    raise FileLockException('Timeout occured.')
+                time.sleep(self.delay)
+        self.is_locked = True
+
+    def release(self):
+        """Release the lock.
+        """
+        if self.is_locked:
+            os.close(self.filedsc)
+            os.unlink(self.lockfile)
+            self.is_locked = False
+
+    def __enter__(self):
+        """start of the with statement.
+        """
+        if not self.is_locked:
+            self.acquire()
+        return self
+
+    def __exit__(self, type, value, traceback):
+        """end of the with statement
+        """
+        if self.is_locked:
+            self.release()
+
+    def __del__(self):
+        """Cleanup
+        """
+        self.release()