]> 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.
 """
 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 pickle import load, dump
 from threading import Lock
 
-from .filelock import FileLock
+from ..utils.filelock import FileLock
 
 
 class BaseCache:
 
 
 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.
 """
 """
 The httplib2 algorithms ported for use with requests.
 """
@@ -5,9 +24,10 @@ import re
 import calendar
 import time
 
 import calendar
 import time
 
-from sima.lib.httpcli.cache import DictCache
 import email.utils
 
 import email.utils
 
+from .cache import DictCache
+
 
 URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
 
 
 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"""
 
     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()
         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 = {}
 
         """
         retval = {}
 
+        # requests provides a CaseInsensitiveDict as headers
         cc_header = 'cache-control'
         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 = [
         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 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)
 
         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
 
             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
 
         # 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.
     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)
             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
 
         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 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.
         # 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
             # 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
                     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 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
 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'
     timestamp = datetime.utcnow()
     ratelimit = None
     name = 'EchoNest'
-    cache = FileCache('/home/kaliko/.local/share/mpd_sima/http')
+    cache = False
 
     def __init__(self):
         self._ressource = None
 
     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
 """
 
 # standard library import
+from os.path import join
 
 # third parties components
 
 # local import
 from ...lib.simaecho import SimaEch
 from ...lib.webserv import WebService
 
 # third parties components
 
 # local import
 from ...lib.simaecho import SimaEch
 from ...lib.webserv import WebService
+from ...lib.cache import FileCache
 
 
 class EchoNest(WebService):
 
 
 class EchoNest(WebService):
@@ -36,6 +38,9 @@ class EchoNest(WebService):
 
     def __init__(self, daemon):
         WebService.__init__(self, daemon)
 
     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
         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()