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
# -*- 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. |
|
""" |