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:
Roberto Rosario
2018-12-08 01:38:59 -04:00
parent 0c7f9f50af
commit aaea84b386
35 changed files with 1848 additions and 310 deletions

View File

@@ -13,18 +13,18 @@ source_lang = en
source_file = mayan/apps/appearance/locale/en/LC_MESSAGES/django.po source_file = mayan/apps/appearance/locale/en/LC_MESSAGES/django.po
type = PO type = PO
[mayan-edms.autoadmin-2-0]
file_filter = mayan/apps/autoadmin/locale/<lang>/LC_MESSAGES/django.po
source_lang = en
source_file = mayan/apps/autoadmin/locale/en/LC_MESSAGES/django.po
type = PO
[mayan-edms.authentication-2-0] [mayan-edms.authentication-2-0]
file_filter = mayan/apps/authentication/locale/<lang>/LC_MESSAGES/django.po file_filter = mayan/apps/authentication/locale/<lang>/LC_MESSAGES/django.po
source_lang = en source_lang = en
source_file = mayan/apps/authentication/locale/en/LC_MESSAGES/django.po source_file = mayan/apps/authentication/locale/en/LC_MESSAGES/django.po
type = PO type = PO
[mayan-edms.autoadmin-2-0]
file_filter = mayan/apps/autoadmin/locale/<lang>/LC_MESSAGES/django.po
source_lang = en
source_file = mayan/apps/autoadmin/locale/en/LC_MESSAGES/django.po
type = PO
[mayan-edms.cabinets-2-0] [mayan-edms.cabinets-2-0]
file_filter = mayan/apps/cabinets/locale/<lang>/LC_MESSAGES/django.po file_filter = mayan/apps/cabinets/locale/<lang>/LC_MESSAGES/django.po
source_lang = en source_lang = en
@@ -103,6 +103,12 @@ source_lang = en
source_file = mayan/apps/events/locale/en/LC_MESSAGES/django.po source_file = mayan/apps/events/locale/en/LC_MESSAGES/django.po
type = PO type = PO
[mayan-edms.file_caching-2-0]
file_filter = mayan/apps/file_caching/locale/<lang>/LC_MESSAGES/django.po
source_lang = en
source_file = mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po
type = PO
[mayan-edms.linking-2-0] [mayan-edms.linking-2-0]
file_filter = mayan/apps/linking/locale/<lang>/LC_MESSAGES/django.po file_filter = mayan/apps/linking/locale/<lang>/LC_MESSAGES/django.po
source_lang = en source_lang = en

View File

@@ -12,10 +12,11 @@ APP_LIST = (
'acls', 'appearance', 'authentication', 'autoadmin', 'cabinets', 'acls', 'appearance', 'authentication', 'autoadmin', 'cabinets',
'checkouts', 'common', 'converter', 'django_gpg', 'document_comments', 'checkouts', 'common', 'converter', 'django_gpg', 'document_comments',
'document_indexing', 'document_parsing', 'document_signatures', 'document_indexing', 'document_parsing', 'document_signatures',
'document_states', 'documents', 'dynamic_search', 'events', 'linking', 'document_states', 'documents', 'dynamic_search', 'events',
'lock_manager', 'mayan_statistics', 'mailer', 'metadata', 'mirroring', 'file_caching', 'linking', 'lock_manager', 'mayan_statistics', 'mailer',
'motd', 'navigation', 'ocr', 'permissions', 'rest_api', 'smart_settings', 'metadata', 'mirroring', 'motd', 'navigation', 'ocr', 'permissions',
'sources', 'storage', 'tags', 'task_manager', 'user_management' 'rest_api', 'smart_settings', 'sources', 'storage', 'tags',
'task_manager', 'user_management'
) )
LANGUAGE_LIST = ( LANGUAGE_LIST = (

View File

@@ -1,124 +0,0 @@
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('common', '0011_auto_20180429_0758'),
]
operations = [
migrations.CreateModel(
name='Cache',
fields=[
(
'id', models.AutoField(
auto_created=True, primary_key=True, serialize=False,
verbose_name='ID'
)
),
(
'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'
)
),
],
options={
'verbose_name': 'Cache',
'verbose_name_plural': 'Caches',
},
),
migrations.CreateModel(
name='CachePartition',
fields=[
(
'id', models.AutoField(
auto_created=True, primary_key=True, serialize=False,
verbose_name='ID'
)
),
(
'name', models.CharField(
max_length=128, verbose_name='Name'
)
),
(
'cache', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='partitions', to='common.Cache',
verbose_name='Cache'
)
),
],
options={
'verbose_name': 'Cache partition',
'verbose_name_plural': 'Cache partitions',
},
),
migrations.CreateModel(
name='CachePartitionFile',
fields=[
(
'id', models.AutoField(
auto_created=True, primary_key=True, serialize=False,
verbose_name='ID'
)
),
(
'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'
)
),
(
'partition', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='files', to='common.CachePartition',
verbose_name='Cache partition'
)
),
],
options={
'get_latest_by': 'datetime',
'verbose_name': 'Cache partition file',
'verbose_name_plural': 'Cache partition files',
},
),
migrations.AlterUniqueTogether(
name='cachepartitionfile',
unique_together=set([('partition', 'filename')]),
),
migrations.AlterUniqueTogether(
name='cachepartition',
unique_together=set([('cache', 'name')]),
),
]

View File

@@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from contextlib import contextmanager
import logging import logging
import uuid import uuid
@@ -9,17 +8,10 @@ from pytz import common_timezones
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.files.base import ContentFile from django.db import models
from django.db import models, transaction
from django.db.models import Sum
from django.utils.encoding import force_text, python_2_unicode_compatible 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 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 .managers import ErrorLogEntryManager, UserLocaleProfileManager
from .storages import storage_sharedupload from .storages import storage_sharedupload
@@ -30,171 +22,6 @@ def upload_to(instance, filename):
return 'shared-file-{}'.format(uuid.uuid4().hex) 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 ErrorLogEntry(models.Model):
""" """
Class to store an error log for any object. Uses generic foreign keys to Class to store an error log for any object. Uses generic foreign keys to

View File

@@ -27,7 +27,7 @@ def create_default_document_type(sender, **kwargs):
def handler_create_document_cache(sender, **kwargs): def handler_create_document_cache(sender, **kwargs):
Cache = apps.get_model(app_label='common', model_name='Cache') Cache = apps.get_model(app_label='file_caching', model_name='Cache')
Cache.objects.update_or_create( Cache.objects.update_or_create(
defaults={ defaults={
'label': _('Document images'), 'label': _('Document images'),

View File

@@ -112,7 +112,7 @@ class DocumentVersion(models.Model):
@cached_property @cached_property
def cache(self): def cache(self):
Cache = apps.get_model(app_label='common', model_name='Cache') Cache = apps.get_model(app_label='file_caching', model_name='Cache')
return Cache.objects.get(name=DOCUMENT_IMAGES_CACHE_NAME) return Cache.objects.get(name=DOCUMENT_IMAGES_CACHE_NAME)
@cached_property @cached_property

View File

@@ -0,0 +1,3 @@
from __future__ import unicode_literals
default_app_config = 'mayan.apps.file_caching.apps.FileCachingConfig'

View File

@@ -0,0 +1,10 @@
from __future__ import unicode_literals
from django.contrib import admin
from .models import Cache
@admin.register(Cache)
class CacheAdmin(admin.ModelAdmin):
list_display = ('name', 'label', 'storage_instance_path', 'maximum_size')

View File

@@ -0,0 +1,8 @@
from __future__ import unicode_literals
from mayan.apps.common import MayanAppConfig
class FileCachingConfig(MayanAppConfig):
has_tests = False
name = 'mayan.apps.file_caching'

View File

@@ -0,0 +1,72 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,73 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
"%100<12 || n%100>=14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,71 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,73 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,70 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 21:32-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:23 models.py:70
msgid "Name"
msgstr ""
#: models.py:25
msgid "Label"
msgstr ""
#: models.py:26
msgid "Maximum size"
msgstr ""
#: models.py:28
msgid "Storage instance path"
msgstr ""
#: models.py:32 models.py:67
msgid "Cache"
msgstr ""
#: models.py:33
msgid "Caches"
msgstr ""
#: models.py:75 models.py:141
msgid "Cache partition"
msgstr ""
#: models.py:76
msgid "Cache partitions"
msgstr ""
#: models.py:144
msgid "Date time"
msgstr ""
#: models.py:146
msgid "Filename"
msgstr ""
#: models.py:148
msgid "File size"
msgstr ""
#: models.py:154
msgid "Cache partition file"
msgstr ""
#: models.py:155
msgid "Cache partition files"
msgstr ""

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2018-12-08 01:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Cache',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
],
options={
'verbose_name': 'Cache',
'verbose_name_plural': 'Caches',
},
),
migrations.CreateModel(
name='CachePartition',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('cache', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='partitions', to='file_caching.Cache', verbose_name='Cache')),
],
options={
'verbose_name': 'Cache partition',
'verbose_name_plural': 'Cache partitions',
},
),
migrations.CreateModel(
name='CachePartitionFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
('partition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='file_caching.CachePartition', verbose_name='Cache partition')),
],
options={
'get_latest_by': 'datetime',
'verbose_name': 'Cache partition file',
'verbose_name_plural': 'Cache partition files',
},
),
migrations.AlterUniqueTogether(
name='cachepartitionfile',
unique_together=set([('partition', 'filename')]),
),
migrations.AlterUniqueTogether(
name='cachepartition',
unique_together=set([('cache', 'name')]),
),
]

View File

@@ -0,0 +1,182 @@
from __future__ import unicode_literals
from contextlib import contextmanager
import logging
from django.core.files.base import ContentFile
from django.db import models, transaction
from django.db.models import Sum
from django.utils.encoding import 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
logger = logging.getLogger(__name__)
@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()

View File

@@ -89,6 +89,7 @@ INSTALLED_APPS = (
'mayan.apps.django_gpg', 'mayan.apps.django_gpg',
'mayan.apps.dynamic_search', 'mayan.apps.dynamic_search',
'mayan.apps.events', 'mayan.apps.events',
'mayan.apps.file_caching',
'mayan.apps.lock_manager', 'mayan.apps.lock_manager',
'mayan.apps.mimetype', 'mayan.apps.mimetype',
'mayan.apps.navigation', 'mayan.apps.navigation',