You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
4.8 KiB
148 lines
4.8 KiB
9 years ago
|
# -*- coding: utf-8 -*-
|
||
|
from __future__ import unicode_literals
|
||
|
# Erik Stein <code@classlibrary.net>, 2016
|
||
|
|
||
|
from ..utils import get_singleton, get_cache, sanitize_cache_key
|
||
|
import warnings
|
||
|
from copy import copy
|
||
|
from django.core.exceptions import ImproperlyConfigured
|
||
|
|
||
|
|
||
|
from imagekit.cachefiles.backends import CacheFileState, CachedFileBackend
|
||
|
|
||
|
|
||
|
class MediaAssetCacheFileState(CacheFileState):
|
||
|
"""
|
||
|
Manual means that the user has manually added
|
||
|
"""
|
||
|
|
||
|
MANUAL = 'manual'
|
||
|
|
||
|
|
||
|
class MediaAssetCachedFileBackend(CachedFileBackend):
|
||
|
# As we will defer a lot to a celery queue, set the timeout higher
|
||
|
existence_check_timeout = 20
|
||
|
"""
|
||
|
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.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def get_state(self, file, check_if_unknown=True):
|
||
|
key = self.get_key(file)
|
||
|
state = self.cache.get(key)
|
||
|
if state is None and check_if_unknown:
|
||
|
exists = self._exists(file)
|
||
|
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)
|
||
|
if state == CacheFileState.DOES_NOT_EXIST:
|
||
|
self.cache.set(key, state, self.existence_check_timeout)
|
||
|
else:
|
||
|
self.cache.set(key, state)
|
||
|
|
||
|
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
|
||
|
|
||
|
def exists(self, file):
|
||
|
return self.get_state(file) == CacheFileState.EXISTS
|
||
|
|
||
|
def generate(self, file, force=False):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def generate_now(self, file, force=False):
|
||
|
if force or self.get_state(file) not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
|
||
|
self.set_state(file, CacheFileState.GENERATING)
|
||
|
file._generate()
|
||
|
self.set_state(file, CacheFileState.EXISTS)
|
||
|
file.close()
|
||
|
|
||
|
|
||
|
class Simple(MediaAssetCachedFileBackend):
|
||
|
"""
|
||
|
The most basic file backend. The storage is consulted to see if the file
|
||
|
exists. Files are generated synchronously.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def generate(self, file, force=False):
|
||
|
# FIXME Don't try this for large files like movies or recordings
|
||
|
self.generate_now(file, force=force)
|
||
|
|
||
|
def _exists(self, file):
|
||
|
"""
|
||
|
The variant be either the automatically generated file or a
|
||
|
user uploaded file. Check both and set the state to OVERRIDEN
|
||
|
appropriately.
|
||
|
"""
|
||
|
return bool(getattr(file, '_file', None)
|
||
|
or file.storage.exists(file.name))
|
||
|
|
||
|
|
||
|
def _generate_file(backend, file, force=False):
|
||
|
backend.generate_now(file, force=force)
|
||
|
|
||
|
|
||
|
class BaseAsync(Simple):
|
||
|
"""
|
||
|
Base class for cache file backends that generate files asynchronously.
|
||
|
"""
|
||
|
is_async = True
|
||
|
|
||
|
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, CacheFileState.MANUAL):
|
||
|
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
|
||
|
|
||
|
|
||
|
try:
|
||
|
from celery import task
|
||
|
except ImportError:
|
||
|
pass
|
||
|
else:
|
||
|
_celery_task = task(ignore_result=True, serializer='pickle')(_generate_file)
|
||
|
|
||
|
|
||
|
class Celery(BaseAsync):
|
||
|
"""
|
||
|
A backend that uses Celery to generate the images.
|
||
|
"""
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
try:
|
||
|
import celery # noqa
|
||
|
except ImportError:
|
||
|
raise ImproperlyConfigured('You must install celery to use'
|
||
|
' imagekit.cachefiles.backends.Celery.')
|
||
|
super(Celery, self).__init__(*args, **kwargs)
|
||
|
|
||
|
def schedule_generation(self, file, force=False):
|
||
|
_celery_task.delay(self, file, force=force)
|
||
|
|
||
|
|
||
|
|
||
|
class Auto(BaseAsync):
|
||
|
"""
|
||
|
A backend that decides between immediately generating a variant
|
||
|
or queuing with celery, depending on media type, file size and
|
||
|
encoding.
|
||
|
"""
|