Caching: Turn the new caching into its own app
Extract the new smart file caching code from the common app and convert it into its own new app called file_caching. Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from contextlib import contextmanager
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
@@ -9,17 +8,10 @@ from pytz import common_timezones
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Sum
|
||||
from django.db import models
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.lock_manager import LockError
|
||||
from mayan.apps.lock_manager.runtime import locking_backend
|
||||
|
||||
from .managers import ErrorLogEntryManager, UserLocaleProfileManager
|
||||
from .storages import storage_sharedupload
|
||||
|
||||
@@ -30,171 +22,6 @@ def upload_to(instance, filename):
|
||||
return 'shared-file-{}'.format(uuid.uuid4().hex)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Cache(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=128, unique=True, verbose_name=_('Name')
|
||||
)
|
||||
label = models.CharField(max_length=128, verbose_name=_('Label'))
|
||||
maximum_size = models.PositiveIntegerField(verbose_name=_('Maximum size'))
|
||||
storage_instance_path = models.CharField(
|
||||
max_length=255, unique=True, verbose_name=_('Storage instance path')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Cache')
|
||||
verbose_name_plural = _('Caches')
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
def get_files(self):
|
||||
return CachePartitionFile.objects.filter(partition__cache__id=self.pk)
|
||||
|
||||
def get_total_size(self):
|
||||
return self.get_files().aggregate(
|
||||
file_size__sum=Sum('file_size')
|
||||
)['file_size__sum'] or 0
|
||||
|
||||
def prune(self):
|
||||
while self.get_total_size() > self.maximum_size:
|
||||
self.get_files().earliest().delete()
|
||||
|
||||
def purge(self):
|
||||
for partition in self.partitions.all():
|
||||
partition.purge()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
result = super(Cache, self).save(*args, **kwargs)
|
||||
self.prune()
|
||||
return result
|
||||
|
||||
@cached_property
|
||||
def storage(self):
|
||||
return import_string(self.storage_instance_path)
|
||||
|
||||
|
||||
class CachePartition(models.Model):
|
||||
cache = models.ForeignKey(
|
||||
on_delete=models.CASCADE, related_name='partitions',
|
||||
to=Cache, verbose_name=_('Cache')
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=128, verbose_name=_('Name')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('cache', 'name')
|
||||
verbose_name = _('Cache partition')
|
||||
verbose_name_plural = _('Cache partitions')
|
||||
|
||||
@staticmethod
|
||||
def get_combined_filename(parent, filename):
|
||||
return '{}-{}'.format(parent, filename)
|
||||
|
||||
@contextmanager
|
||||
def create_file(self, filename):
|
||||
lock_id = 'cache_partition-create_file-{}-{}'.format(self.pk, filename)
|
||||
try:
|
||||
logger.debug('trying to acquire lock: %s', lock_id)
|
||||
lock = locking_backend.acquire_lock(lock_id)
|
||||
logger.debug('acquired lock: %s', lock_id)
|
||||
try:
|
||||
self.cache.prune()
|
||||
|
||||
# Since open "wb+" doesn't create files force the creation of an
|
||||
# empty file.
|
||||
self.cache.storage.delete(
|
||||
name=self.get_full_filename(filename=filename)
|
||||
)
|
||||
self.cache.storage.save(
|
||||
name=self.get_full_filename(filename=filename),
|
||||
content=ContentFile(content='')
|
||||
)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
partition_file = self.files.create(filename=filename)
|
||||
yield partition_file.open(mode='wb')
|
||||
partition_file.update_size()
|
||||
except Exception as exception:
|
||||
logger.error(
|
||||
'Unexpected exception while trying to save new '
|
||||
'cache file; %s', exception
|
||||
)
|
||||
self.cache.storage.delete(
|
||||
name=self.get_full_filename(filename=filename)
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
lock.release()
|
||||
except LockError:
|
||||
logger.debug('unable to obtain lock: %s' % lock_id)
|
||||
raise
|
||||
|
||||
def get_file(self, filename):
|
||||
try:
|
||||
return self.files.get(filename=filename)
|
||||
except self.files.model.DoesNotExist:
|
||||
return None
|
||||
|
||||
def get_full_filename(self, filename):
|
||||
return CachePartition.get_combined_filename(
|
||||
parent=self.name, filename=filename
|
||||
)
|
||||
|
||||
def purge(self):
|
||||
for parition_file in self.files.all():
|
||||
parition_file.delete()
|
||||
|
||||
|
||||
class CachePartitionFile(models.Model):
|
||||
partition = models.ForeignKey(
|
||||
on_delete=models.CASCADE, related_name='files',
|
||||
to=CachePartition, verbose_name=_('Cache partition')
|
||||
)
|
||||
datetime = models.DateTimeField(
|
||||
auto_now_add=True, db_index=True, verbose_name=_('Date time')
|
||||
)
|
||||
filename = models.CharField(max_length=255, verbose_name=_('Filename'))
|
||||
file_size = models.PositiveIntegerField(
|
||||
default=0, verbose_name=_('File size')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
get_latest_by = 'datetime'
|
||||
unique_together = ('partition', 'filename')
|
||||
verbose_name = _('Cache partition file')
|
||||
verbose_name_plural = _('Cache partition files')
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.partition.cache.storage.delete(name=self.full_filename)
|
||||
return super(CachePartitionFile, self).delete(*args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def full_filename(self):
|
||||
return CachePartition.get_combined_filename(
|
||||
parent=self.partition.name, filename=self.filename
|
||||
)
|
||||
|
||||
def open(self, mode='rb'):
|
||||
try:
|
||||
return self.partition.cache.storage.open(
|
||||
name=self.full_filename, mode=mode
|
||||
)
|
||||
except Exception as exception:
|
||||
logger.error(
|
||||
'Unexpected exception opening the cache file; %s', exception
|
||||
)
|
||||
raise
|
||||
|
||||
def update_size(self):
|
||||
self.file_size = self.partition.cache.storage.size(
|
||||
name=self.full_filename
|
||||
)
|
||||
self.save()
|
||||
|
||||
|
||||
class ErrorLogEntry(models.Model):
|
||||
"""
|
||||
Class to store an error log for any object. Uses generic foreign keys to
|
||||
|
||||
Reference in New Issue
Block a user