Work in progress: django-imagekit but for all types of media files (movies, PDFs etc.). + private media
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.
 

161 lines
6.2 KiB

from copy import copy
from django.conf import settings
from django.core.files import File
from django.utils.functional import SimpleLazyObject
from imagekit.files import BaseIKFile
from imagekit.registry import generator_registry
from imagekit.signals import content_required, existence_required
from imagekit.utils import get_logger, get_singleton, generate, get_by_qname
class MediaAssetCacheFile(BaseIKFile, File):
"""
A file that represents the result of a generator. Creating an instance of
this class is not enough to trigger the generation of the file. In fact,
one of the main points of this class is to allow the creation of the file
to be deferred until the time that the cache file strategy requires it.
"""
def __init__(self, generator, name=None, storage=None, cachefile_backend=None, cachefile_strategy=None):
"""
:param generator: The object responsible for generating a new image.
:param name: The filename
:param storage: A Django storage object that will be used to save the
file.
:param cachefile_backend: The object responsible for managing the
state of the file.
:param cachefile_strategy: The object responsible for handling events
for this file.
"""
self.generator = generator
if not name:
try:
name = generator.cachefile_name
except AttributeError:
fn = get_by_qname(settings.IMAGEKIT_CACHEFILE_NAMER, 'namer')
name = fn(generator)
self.name = name
storage = storage or getattr(generator, 'cachefile_storage',
None) or get_singleton(settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
'file storage backend')
self.cachefile_backend = (
cachefile_backend
or getattr(generator, 'cachefile_backend', None)
or get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_BACKEND,
'cache file backend'))
self.cachefile_strategy = (
cachefile_strategy
or getattr(generator, 'cachefile_strategy', None)
or get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY,
'cache file strategy')
)
super(MediaAssetCacheFile, self).__init__(storage=storage)
def _require_file(self):
if getattr(self, '_file', None) is None:
content_required.send(sender=self, file=self)
self._file = self.storage.open(self.name, 'rb')
# The ``path`` and ``url`` properties are overridden so as to not call
# ``_require_file``, which is only meant to be called when the file object
# will be directly interacted with (e.g. when using ``read()``). These only
# require the file to exist; they do not need its contents to work. This
# distinction gives the user the flexibility to create a cache file
# strategy that assumes the existence of a file, but can still make the file
# available when its contents are required.
def _storage_attr(self, attr):
if getattr(self, '_file', None) is None:
existence_required.send(sender=self, file=self)
fn = getattr(self.storage, attr)
return fn(self.name)
@property
def path(self):
return self._storage_attr('path')
@property
def url(self):
return self._storage_attr('url')
def generate(self, force=False):
"""
Generate the file. If ``force`` is ``True``, the file will be generated
whether the file already exists or not.
"""
if force or getattr(self, '_file', None) is None:
self.cachefile_backend.generate(self, force)
def _generate(self):
# Generate the file
content = generate(self.generator)
actual_name = self.storage.save(self.name, content)
# We're going to reuse the generated file, so we need to reset the pointer.
content.seek(0)
# Store the generated file. If we don't do this, the next time the
# "file" attribute is accessed, it will result in a call to the storage
# backend (in ``BaseIKFile._get_file``). Since we already have the
# contents of the file, what would the point of that be?
self.file = File(content)
if actual_name != self.name:
get_logger().warning(
'The storage backend %s did not save the file with the'
' requested name ("%s") and instead used "%s". This may be'
' because a file already existed with the requested name. If'
' so, you may have meant to call generate() instead of'
' generate(force=True), or there may be a race condition in the'
' file backend %s. The saved file will not be used.' % (
self.storage,
self.name, actual_name,
self.cachefile_backend
)
)
def __bool__(self):
if not self.name:
return False
# Dispatch the existence_required signal before checking to see if the
# file exists. This gives the strategy a chance to create the file.
existence_required.send(sender=self, file=self)
try:
check = self.cachefile_strategy.should_verify_existence(self)
except AttributeError:
# All synchronous backends should have created the file as part of
# `existence_required` if they wanted to.
check = getattr(self.cachefile_backend, 'is_async', False)
return self.cachefile_backend.exists(self) if check else True
def __getstate__(self):
state = copy(self.__dict__)
# file is hidden link to "file" attribute
state.pop('_file', None)
return state
def __nonzero__(self):
# Python 2 compatibility
return self.__bool__()
class LazyMediaAssetCacheFile(SimpleLazyObject):
def __init__(self, generator_id, *args, **kwargs):
def setup():
generator = generator_registry.get(generator_id, *args, **kwargs)
return MediaAssetCacheFile(generator)
super(LazyMediaAssetCacheFile, self).__init__(setup)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, str(self) or 'None')