2013-04-30 13:09:08 +00:00
|
|
|
from ..utils import get_singleton, sanitize_cache_key
|
2014-03-18 15:37:18 +00:00
|
|
|
import warnings
|
2013-06-20 13:17:12 +00:00
|
|
|
from copy import copy
|
2013-01-31 08:51:29 +00:00
|
|
|
from django.core.cache import get_cache
|
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
|
|
|
|
|
|
|
|
|
2013-03-15 03:34:27 +00:00
|
|
|
class CacheFileState(object):
|
|
|
|
|
EXISTS = 'exists'
|
2013-07-17 01:02:48 +00:00
|
|
|
GENERATING = 'generating'
|
2013-03-15 03:34:27 +00:00
|
|
|
DOES_NOT_EXIST = 'does_not_exist'
|
|
|
|
|
|
|
|
|
|
|
2013-02-05 00:39:25 +00:00
|
|
|
def get_default_cachefile_backend():
|
2013-01-31 08:51:29 +00:00
|
|
|
"""
|
|
|
|
|
Get the default file backend.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
from django.conf import settings
|
2013-02-05 00:39:25 +00:00
|
|
|
return get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_BACKEND,
|
2013-10-27 12:27:21 +00:00
|
|
|
'file backend')
|
2013-01-31 08:51:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class InvalidFileBackendError(ImproperlyConfigured):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2013-03-15 03:36:21 +00:00
|
|
|
class AbstractCacheFileBackend(object):
|
|
|
|
|
"""
|
|
|
|
|
An abstract cache file backend. This isn't used by any internal classes and
|
|
|
|
|
is included simply to illustrate the minimum interface of a cache file
|
|
|
|
|
backend for users who wish to implement their own.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def generate(self, file, force=False):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def exists(self, file):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
2013-01-31 08:51:29 +00:00
|
|
|
class CachedFileBackend(object):
|
2013-03-15 05:05:53 +00:00
|
|
|
existence_check_timeout = 5
|
|
|
|
|
"""
|
|
|
|
|
The number of seconds to wait before rechecking to see if the file exists.
|
|
|
|
|
If the image is found to exist, that information will be cached using the
|
|
|
|
|
timeout specified in your CACHES setting (which should be very high).
|
|
|
|
|
However, when the file does not exist, you probably want to check again
|
|
|
|
|
in a relatively short amount of time. This attribute allows you to do that.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2013-01-31 08:51:29 +00:00
|
|
|
@property
|
|
|
|
|
def cache(self):
|
|
|
|
|
if not getattr(self, '_cache', None):
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
self._cache = get_cache(settings.IMAGEKIT_CACHE_BACKEND)
|
|
|
|
|
return self._cache
|
|
|
|
|
|
|
|
|
|
def get_key(self, file):
|
|
|
|
|
from django.conf import settings
|
2013-04-30 13:09:08 +00:00
|
|
|
return sanitize_cache_key('%s%s-state' %
|
|
|
|
|
(settings.IMAGEKIT_CACHE_PREFIX, file.name))
|
2013-01-31 08:51:29 +00:00
|
|
|
|
2013-07-17 01:02:48 +00:00
|
|
|
def get_state(self, file, check_if_unknown=True):
|
2013-01-31 08:51:29 +00:00
|
|
|
key = self.get_key(file)
|
2013-03-15 03:34:27 +00:00
|
|
|
state = self.cache.get(key)
|
2013-07-17 01:02:48 +00:00
|
|
|
if state is None and check_if_unknown:
|
2013-03-15 02:58:28 +00:00
|
|
|
exists = self._exists(file)
|
2013-03-15 03:34:27 +00:00
|
|
|
state = CacheFileState.EXISTS if exists else CacheFileState.DOES_NOT_EXIST
|
|
|
|
|
self.set_state(file, state)
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
def set_state(self, file, state):
|
|
|
|
|
key = self.get_key(file)
|
2013-07-17 00:40:48 +00:00
|
|
|
if state == CacheFileState.DOES_NOT_EXIST:
|
2013-03-15 05:05:53 +00:00
|
|
|
self.cache.set(key, state, self.existence_check_timeout)
|
|
|
|
|
else:
|
|
|
|
|
self.cache.set(key, state)
|
2013-01-31 08:51:29 +00:00
|
|
|
|
2013-06-20 13:17:12 +00:00
|
|
|
def __getstate__(self):
|
|
|
|
|
state = copy(self.__dict__)
|
|
|
|
|
# Don't include the cache when pickling. It'll be reconstituted based
|
|
|
|
|
# on the settings.
|
|
|
|
|
state.pop('_cache', None)
|
|
|
|
|
return state
|
|
|
|
|
|
2013-03-15 03:34:27 +00:00
|
|
|
def exists(self, file):
|
2013-07-17 00:40:48 +00:00
|
|
|
return self.get_state(file) == CacheFileState.EXISTS
|
2013-03-15 03:34:27 +00:00
|
|
|
|
|
|
|
|
def generate(self, file, force=False):
|
2013-04-03 02:55:44 +00:00
|
|
|
raise NotImplementedError
|
2013-04-03 02:37:52 +00:00
|
|
|
|
|
|
|
|
def generate_now(self, file, force=False):
|
2013-07-17 01:02:48 +00:00
|
|
|
if force or self.get_state(file) not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
|
|
|
|
|
self.set_state(file, CacheFileState.GENERATING)
|
2013-03-15 03:34:27 +00:00
|
|
|
file._generate()
|
2013-03-15 04:30:58 +00:00
|
|
|
self.set_state(file, CacheFileState.EXISTS)
|
2013-01-31 08:51:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Simple(CachedFileBackend):
|
|
|
|
|
"""
|
|
|
|
|
The most basic file backend. The storage is consulted to see if the file
|
2013-03-15 03:34:27 +00:00
|
|
|
exists. Files are generated synchronously.
|
2013-01-31 08:51:29 +00:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2013-04-03 02:55:44 +00:00
|
|
|
def generate(self, file, force=False):
|
|
|
|
|
self.generate_now(file, force=force)
|
2013-03-15 03:34:27 +00:00
|
|
|
|
2013-03-15 02:58:28 +00:00
|
|
|
def _exists(self, file):
|
2013-03-15 04:49:24 +00:00
|
|
|
return bool(getattr(file, '_file', None)
|
|
|
|
|
or file.storage.exists(file.name))
|
2013-04-03 02:29:53 +00:00
|
|
|
|
|
|
|
|
|
2013-04-03 02:55:44 +00:00
|
|
|
def _generate_file(backend, file, force=False):
|
|
|
|
|
backend.generate_now(file, force=force)
|
2013-04-03 02:29:53 +00:00
|
|
|
|
|
|
|
|
|
2013-10-27 12:27:21 +00:00
|
|
|
class BaseAsync(Simple):
|
|
|
|
|
"""
|
|
|
|
|
Base class for cache file backends that generate files asynchronously.
|
|
|
|
|
"""
|
2014-09-23 22:35:38 +00:00
|
|
|
is_async = True
|
|
|
|
|
|
2013-10-27 12:27:21 +00:00
|
|
|
def generate(self, file, force=False):
|
|
|
|
|
# Schedule the file for generation, unless we know for sure we don't
|
|
|
|
|
# need to. If an already-generated file sneaks through, that's okay;
|
|
|
|
|
# ``generate_now`` will catch it. We just want to make sure we don't
|
|
|
|
|
# schedule anything we know is unnecessary--but we also don't want to
|
|
|
|
|
# force a costly existence check.
|
|
|
|
|
state = self.get_state(file, check_if_unknown=False)
|
|
|
|
|
if state not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
|
|
|
|
|
self.schedule_generation(file, force=force)
|
|
|
|
|
|
|
|
|
|
def schedule_generation(self, file, force=False):
|
|
|
|
|
# overwrite this to have the file generated in the background,
|
|
|
|
|
# e. g. in a worker queue.
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
2013-04-03 02:29:53 +00:00
|
|
|
try:
|
2013-11-28 05:39:57 +00:00
|
|
|
from celery import task
|
2013-04-03 02:29:53 +00:00
|
|
|
except ImportError:
|
|
|
|
|
pass
|
2013-11-28 05:39:57 +00:00
|
|
|
else:
|
|
|
|
|
_celery_task = task(ignore_result=True)(_generate_file)
|
2013-04-03 02:29:53 +00:00
|
|
|
|
|
|
|
|
|
2013-11-04 12:12:02 +00:00
|
|
|
class Celery(BaseAsync):
|
2013-04-03 02:29:53 +00:00
|
|
|
"""
|
|
|
|
|
A backend that uses Celery to generate the images.
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
try:
|
|
|
|
|
import celery
|
|
|
|
|
except ImportError:
|
|
|
|
|
raise ImproperlyConfigured('You must install celery to use'
|
2013-11-04 12:12:02 +00:00
|
|
|
' imagekit.cachefiles.backends.Celery.')
|
|
|
|
|
super(Celery, self).__init__(*args, **kwargs)
|
2013-04-03 02:29:53 +00:00
|
|
|
|
2013-10-27 12:27:21 +00:00
|
|
|
def schedule_generation(self, file, force=False):
|
2013-11-28 05:39:57 +00:00
|
|
|
_celery_task.delay(self, file, force=force)
|
2013-10-27 12:27:21 +00:00
|
|
|
|
|
|
|
|
|
2014-03-18 15:37:18 +00:00
|
|
|
# Stub class to preserve backwards compatibility and issue a warning
|
|
|
|
|
class Async(Celery):
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
message = '{path}.Async is deprecated. Use {path}.Celery instead.'
|
|
|
|
|
warnings.warn(message.format(path=__name__), DeprecationWarning)
|
|
|
|
|
super(Async, self).__init__(*args, **kwargs)
|
2013-10-27 12:27:21 +00:00
|
|
|
|
|
|
|
|
|
2014-03-18 16:20:24 +00:00
|
|
|
try:
|
|
|
|
|
from django_rq import job
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
_rq_job = job('default', result_ttl=0)(_generate_file)
|
|
|
|
|
|
|
|
|
|
|
2013-11-04 12:12:02 +00:00
|
|
|
class RQ(BaseAsync):
|
2013-10-27 12:27:21 +00:00
|
|
|
"""
|
|
|
|
|
A backend that uses RQ to generate the images.
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
try:
|
|
|
|
|
import django_rq
|
|
|
|
|
except ImportError:
|
2014-03-18 16:20:24 +00:00
|
|
|
raise ImproperlyConfigured('You must install django-rq to use'
|
2013-11-04 12:12:02 +00:00
|
|
|
' imagekit.cachefiles.backends.RQ.')
|
|
|
|
|
super(RQ, self).__init__(*args, **kwargs)
|
2013-10-27 12:27:21 +00:00
|
|
|
|
|
|
|
|
def schedule_generation(self, file, force=False):
|
2014-03-18 16:20:24 +00:00
|
|
|
_rq_job.delay(self, file, force=force)
|