From 8c064c953ad80875406a14a527ffd31fd2bb8c4b Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Mon, 15 Jul 2019 01:33:32 -0400 Subject: [PATCH] Add file caching app Convert document image cache to use file cache manager app. Add setting DOCUMENTS_CACHE_MAXIMUM_SIZE defaults to 500 MB. Signed-off-by: Roberto Rosario --- .tx/config | 6 + HISTORY.rst | 4 + contrib/scripts/process_messages.py | 10 +- docs/releases/3.3.rst | 4 + mayan/apps/documents/api_views.py | 7 +- mayan/apps/documents/apps.py | 10 +- mayan/apps/documents/handlers.py | 18 +- mayan/apps/documents/literals.py | 3 + .../migrations/0049_auto_20190715_0454.py | 37 ++++ .../documents/models/document_page_models.py | 91 +++------ .../models/document_version_models.py | 72 +++---- mayan/apps/documents/settings.py | 13 +- mayan/apps/documents/utils.py | 12 +- mayan/apps/file_caching/__init__.py | 3 + mayan/apps/file_caching/admin.py | 10 + mayan/apps/file_caching/apps.py | 8 + .../locale/ar/LC_MESSAGES/django.po | 72 +++++++ .../locale/bg/LC_MESSAGES/django.po | 71 +++++++ .../locale/bs_BA/LC_MESSAGES/django.po | 70 +++++++ .../locale/da/LC_MESSAGES/django.po | 71 +++++++ .../locale/de_DE/LC_MESSAGES/django.po | 70 +++++++ .../locale/en/LC_MESSAGES/django.po | 70 +++++++ .../locale/es/LC_MESSAGES/django.po | 71 +++++++ .../locale/fa/LC_MESSAGES/django.po | 71 +++++++ .../locale/fr/LC_MESSAGES/django.po | 71 +++++++ .../locale/hu/LC_MESSAGES/django.po | 71 +++++++ .../locale/id/LC_MESSAGES/django.po | 71 +++++++ .../locale/it/LC_MESSAGES/django.po | 71 +++++++ .../locale/nl_NL/LC_MESSAGES/django.po | 70 +++++++ .../locale/pl/LC_MESSAGES/django.po | 73 +++++++ .../locale/pt/LC_MESSAGES/django.po | 71 +++++++ .../locale/pt_BR/LC_MESSAGES/django.po | 71 +++++++ .../locale/ro_RO/LC_MESSAGES/django.po | 70 +++++++ .../locale/ru/LC_MESSAGES/django.po | 73 +++++++ .../locale/sl_SI/LC_MESSAGES/django.po | 70 +++++++ .../locale/tr_TR/LC_MESSAGES/django.po | 70 +++++++ .../locale/vi_VN/LC_MESSAGES/django.po | 70 +++++++ .../locale/zh_CN/LC_MESSAGES/django.po | 70 +++++++ .../file_caching/migrations/0001_initial.py | 64 ++++++ .../apps/file_caching/migrations/__init__.py | 0 mayan/apps/file_caching/models.py | 184 ++++++++++++++++++ mayan/settings/base.py | 1 + 42 files changed, 2001 insertions(+), 114 deletions(-) create mode 100644 mayan/apps/documents/migrations/0049_auto_20190715_0454.py create mode 100644 mayan/apps/file_caching/__init__.py create mode 100644 mayan/apps/file_caching/admin.py create mode 100644 mayan/apps/file_caching/apps.py create mode 100644 mayan/apps/file_caching/locale/ar/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/bg/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/bs_BA/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/da/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/de_DE/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/es/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/fa/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/fr/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/hu/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/id/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/it/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/nl_NL/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/pl/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/pt/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/ro_RO/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/ru/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/sl_SI/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/tr_TR/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/vi_VN/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 mayan/apps/file_caching/migrations/0001_initial.py create mode 100644 mayan/apps/file_caching/migrations/__init__.py create mode 100644 mayan/apps/file_caching/models.py diff --git a/.tx/config b/.tx/config index 158957b39c..5f5d77b3af 100644 --- a/.tx/config +++ b/.tx/config @@ -115,6 +115,12 @@ source_lang = en source_file = mayan/apps/events/locale/en/LC_MESSAGES/django.po type = PO +[mayan-edms.file_caching-3-0] +file_filter = mayan/apps/file_caching/locale//LC_MESSAGES/django.po +source_lang = en +source_file = mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po +type = PO + [mayan-edms.file_metadata-3-0] file_filter = mayan/apps/file_metadata/locale//LC_MESSAGES/django.po source_lang = en diff --git a/HISTORY.rst b/HISTORY.rst index 500bf6bc57..aded82fb4f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -36,6 +36,10 @@ - Remove encapsulate helper. - Add support for menu inheritance. - Emphasize source column labels. +- Backport file cache manager app. +- Convert document image cache to use file cache manager app. + Add setting DOCUMENTS_CACHE_MAXIMUM_SIZE defaults to 500 MB. + 3.2.6 (2019-07-10) ================== diff --git a/contrib/scripts/process_messages.py b/contrib/scripts/process_messages.py index 29b7efc416..5c43e5846e 100755 --- a/contrib/scripts/process_messages.py +++ b/contrib/scripts/process_messages.py @@ -13,11 +13,11 @@ APP_LIST = ( 'checkouts', 'common', 'converter', 'dashboards', 'dependencies', 'django_gpg', 'document_comments', 'document_indexing', 'document_parsing', 'document_signatures', 'document_states', - 'documents', 'dynamic_search', 'events', 'file_metadata', 'linking', - 'lock_manager', 'mailer', 'mayan_statistics', 'metadata', 'mirroring', - 'motd', 'navigation', 'ocr', 'permissions', 'platform', 'rest_api', - 'smart_settings', 'sources', 'storage', 'tags', 'task_manager', - 'user_management' + 'documents', 'dynamic_search', 'events', 'file_caching', + 'file_metadata', 'linking', 'lock_manager', 'mailer', + 'mayan_statistics', 'metadata', 'mirroring', 'motd', 'navigation', + 'ocr', 'permissions', 'platform', 'rest_api', 'smart_settings', + 'sources', 'storage', 'tags', 'task_manager', 'user_management' ) LANGUAGE_LIST = ( diff --git a/docs/releases/3.3.rst b/docs/releases/3.3.rst index 6bc9685c3c..d49868c468 100644 --- a/docs/releases/3.3.rst +++ b/docs/releases/3.3.rst @@ -49,6 +49,10 @@ Changes - Remove encapsulate helper. - Add support for menu inheritance. - Emphasize source column labels. +- Backport file cache manager app. +- Convert document image cache to use file cache manager app. + Add setting DOCUMENTS_CACHE_MAXIMUM_SIZE defaults to 500 MB. + Removals -------- diff --git a/mayan/apps/documents/api_views.py b/mayan/apps/documents/api_views.py index c42a8d50c2..f80f5ec67d 100644 --- a/mayan/apps/documents/api_views.py +++ b/mayan/apps/documents/api_views.py @@ -36,7 +36,6 @@ from .serializers import ( WritableDocumentTypeSerializer, WritableDocumentVersionSerializer ) from .settings import settings_document_page_image_cache_time -from .storages import storage_documentimagecache from .tasks import task_generate_document_page_image logger = logging.getLogger(__name__) @@ -205,11 +204,13 @@ class APIDocumentPageImageView(generics.RetrieveAPIView): ) cache_filename = task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT) - with storage_documentimagecache.open(cache_filename) as file_object: + cache_file = self.get_object().cache_partition.get_file(filename=cache_filename) + with cache_file.open() as file_object: response = HttpResponse(file_object.read(), content_type='image') if '_hash' in request.GET: patch_cache_control( - response, max_age=settings_document_page_image_cache_time.value + response=response, + max_age=settings_document_page_image_cache_time.value ) return response diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py index 0032127a92..25a943294b 100644 --- a/mayan/apps/documents/apps.py +++ b/mayan/apps/documents/apps.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -from django.db.models.signals import post_delete +from django.db.models.signals import post_delete, post_migrate from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.classes import ModelPermission @@ -43,8 +43,8 @@ from .events import ( event_document_view ) from .handlers import ( - handler_create_default_document_type, handler_remove_empty_duplicates_lists, - handler_scan_duplicates_for, + handler_create_default_document_type, handler_create_document_cache, + handler_remove_empty_duplicates_lists, handler_scan_duplicates_for ) from .links import ( link_clear_image_cache, link_document_clear_transformations, @@ -527,6 +527,10 @@ class DocumentsApp(MayanAppConfig): dispatch_uid='handler_create_default_document_type', receiver=handler_create_default_document_type ) + post_migrate.connect( + dispatch_uid='documents_handler_create_document_cache', + receiver=handler_create_document_cache, + ) post_version_upload.connect( dispatch_uid='handler_scan_duplicates_for', receiver=handler_scan_duplicates_for diff --git a/mayan/apps/documents/handlers.py b/mayan/apps/documents/handlers.py index 597a38c40d..a033ec609f 100644 --- a/mayan/apps/documents/handlers.py +++ b/mayan/apps/documents/handlers.py @@ -1,8 +1,13 @@ from __future__ import unicode_literals from django.apps import apps +from django.utils.translation import ugettext_lazy as _ -from .literals import DEFAULT_DOCUMENT_TYPE_LABEL +from .literals import ( + DEFAULT_DOCUMENT_TYPE_LABEL, DOCUMENT_CACHE_STORAGE_INSTANCE_PATH, + DOCUMENT_IMAGES_CACHE_NAME +) +from .settings import setting_document_cache_maximum_size from .signals import post_initial_document_type from .tasks import task_clean_empty_duplicate_lists, task_scan_duplicates_for @@ -21,6 +26,17 @@ def handler_create_default_document_type(sender, **kwargs): ) +def handler_create_document_cache(sender, **kwargs): + Cache = apps.get_model(app_label='file_caching', model_name='Cache') + Cache.objects.update_or_create( + defaults={ + 'label': _('Document images'), + 'storage_instance_path': DOCUMENT_CACHE_STORAGE_INSTANCE_PATH, + 'maximum_size': setting_document_cache_maximum_size.value, + }, name=DOCUMENT_IMAGES_CACHE_NAME, + ) + + def handler_scan_duplicates_for(sender, instance, **kwargs): task_scan_duplicates_for.apply_async( kwargs={'document_id': instance.document.pk} diff --git a/mayan/apps/documents/literals.py b/mayan/apps/documents/literals.py index c56f92d39b..e29ed17e8d 100644 --- a/mayan/apps/documents/literals.py +++ b/mayan/apps/documents/literals.py @@ -9,6 +9,7 @@ CHECK_TRASH_PERIOD_INTERVAL = 60 DELETE_STALE_STUBS_INTERVAL = 60 * 10 # 10 minutes DEFAULT_DELETE_PERIOD = 30 DEFAULT_DELETE_TIME_UNIT = TIME_DELTA_UNIT_DAYS +DEFAULT_DOCUMENTS_CACHE_MAXIMUM_SIZE = 500 * 2 ** 20 # 500 Megabytes DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE = 65535 DEFAULT_LANGUAGE = 'eng' DEFAULT_LANGUAGE_CODES = ( @@ -30,6 +31,8 @@ DEFAULT_LANGUAGE_CODES = ( DEFAULT_ZIP_FILENAME = 'document_bundle.zip' DEFAULT_DOCUMENT_TYPE_LABEL = _('Default') DOCUMENT_IMAGE_TASK_TIMEOUT = 120 +DOCUMENT_IMAGES_CACHE_NAME = 'document_images' +DOCUMENT_CACHE_STORAGE_INSTANCE_PATH = 'mayan.apps.documents.storages.storage_documentimagecache' STUB_EXPIRATION_INTERVAL = 60 * 60 * 24 # 24 hours UPDATE_PAGE_COUNT_RETRY_DELAY = 10 UPLOAD_NEW_VERSION_RETRY_DELAY = 10 diff --git a/mayan/apps/documents/migrations/0049_auto_20190715_0454.py b/mayan/apps/documents/migrations/0049_auto_20190715_0454.py new file mode 100644 index 0000000000..889d244bfe --- /dev/null +++ b/mayan/apps/documents/migrations/0049_auto_20190715_0454.py @@ -0,0 +1,37 @@ +from __future__ import unicode_literals + +from django.db import migrations + +from ..storages import storage_documentimagecache + + +def operation_clear_old_cache(apps, schema_editor): + DocumentPageCachedImage = apps.get_model( + 'documents', 'DocumentPageCachedImage' + ) + + for cached_image in DocumentPageCachedImage.objects.using(schema_editor.connection.alias).all(): + # Delete each cached image directly since the model doesn't exists and + # will not trigger the physical deletion of the stored file + storage_documentimagecache.delete(cached_image.filename) + cached_image.delete() + + +class Migration(migrations.Migration): + dependencies = [ + ('documents', '0048_auto_20190711_0544'), + ] + + operations = [ + migrations.RunPython( + code=operation_clear_old_cache, + reverse_code=migrations.RunPython.noop + ), + migrations.RemoveField( + model_name='documentpagecachedimage', + name='document_page', + ), + migrations.DeleteModel( + name='DocumentPageCachedImage', + ), + ] diff --git a/mayan/apps/documents/models/document_page_models.py b/mayan/apps/documents/models/document_page_models.py index afbf34cef3..8aa9119445 100644 --- a/mayan/apps/documents/models/document_page_models.py +++ b/mayan/apps/documents/models/document_page_models.py @@ -4,13 +4,14 @@ import logging from furl import furl -from django.core.files.base import ContentFile from django.db import models from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION + from mayan.apps.converter.models import Transformation from mayan.apps.converter.transformations import ( BaseTransformation, TransformationResize, TransformationRotate, @@ -24,7 +25,6 @@ from ..settings import ( setting_display_width, setting_display_height, setting_zoom_max_level, setting_zoom_min_level ) -from ..storages import storage_documentimagecache from .document_version_models import DocumentVersion @@ -56,9 +56,12 @@ class DocumentPage(models.Model): def __str__(self): return self.get_label() - @property - def cache_filename(self): - return 'page-cache-{}'.format(self.uuid) + @cached_property + def cache_partition(self): + partition, created = self.document_version.cache.partitions.get_or_create( + name=self.uuid + ) + return partition def delete(self, *args, **kwargs): self.invalidate_cache() @@ -80,29 +83,24 @@ class DocumentPage(models.Model): def generate_image(self, *args, **kwargs): transformation_list = self.get_combined_transformation_list(*args, **kwargs) - - cache_filename = '{}-{}'.format( - self.cache_filename, BaseTransformation.combine(transformation_list) - ) + combined_cache_filename = BaseTransformation.combine(transformation_list) # Check is transformed image is available - logger.debug('transformations cache filename: %s', cache_filename) + logger.debug('transformations cache filename: %s', combined_cache_filename) - if not setting_disable_transformed_image_cache.value and storage_documentimagecache.exists(cache_filename): + if not setting_disable_transformed_image_cache.value and self.cache_partition.get_file(filename=combined_cache_filename): logger.debug( - 'transformations cache file "%s" found', cache_filename + 'transformations cache file "%s" found', combined_cache_filename ) else: logger.debug( - 'transformations cache file "%s" not found', cache_filename + 'transformations cache file "%s" not found', combined_cache_filename ) image = self.get_image(transformations=transformation_list) - with storage_documentimagecache.open(cache_filename, 'wb+') as file_object: + with self.cache_partition.create_file(filename=combined_cache_filename) as file_object: file_object.write(image.getvalue()) - self.cached_images.create(filename=cache_filename) - - return cache_filename + return combined_cache_filename def get_absolute_url(self): return reverse( @@ -159,7 +157,6 @@ class DocumentPage(models.Model): zoom_level = setting_zoom_max_level.value # Generate transformation hash - transformation_list = [] # Stored transformations first @@ -186,13 +183,15 @@ class DocumentPage(models.Model): return transformation_list def get_image(self, transformations=None): - cache_filename = self.cache_filename + cache_filename = 'base_image' logger.debug('Page cache filename: %s', cache_filename) - if not setting_disable_base_image_cache.value and storage_documentimagecache.exists(cache_filename): + cache_file = self.cache_partition.get_file(filename=cache_filename) + + if not setting_disable_base_image_cache.value and cache_file: logger.debug('Page cache file "%s" found', cache_filename) - with storage_documentimagecache.open(cache_filename) as file_object: + with cache_file.open as file_object: converter = get_converter_class()( file_object=file_object ) @@ -200,8 +199,8 @@ class DocumentPage(models.Model): converter.seek_page(page_number=0) # This code is also repeated below to allow using a context - # manager with storage_documentimagecache.open and close it - # automatically. + # manager with cache_file.open and close it automatically. + # Apply runtime transformations for transformation in transformations: converter.transform(transformation=transformation) @@ -218,14 +217,11 @@ class DocumentPage(models.Model): page_image = converter.get_page() - # Since open "wb+" doesn't create files, check if the file - # exists, if not then create it - if not storage_documentimagecache.exists(cache_filename): - storage_documentimagecache.save(name=cache_filename, content=ContentFile(content='')) - - with storage_documentimagecache.open(cache_filename, 'wb+') as file_object: + # Since open "wb+" doesn't create files, create it explicitly + with self.cache_partition.create_file(filename=cache_filename) as file_object: file_object.write(page_image.getvalue()) + # Apply runtime transformations for transformation in transformations: converter.transform(transformation=transformation) @@ -236,13 +232,10 @@ class DocumentPage(models.Model): 'Error creating page cache file "%s"; %s', cache_filename, exception ) - storage_documentimagecache.delete(cache_filename) raise def invalidate_cache(self): - storage_documentimagecache.delete(self.cache_filename) - for cached_image in self.cached_images.all(): - cached_image.delete() + self.cache_partition.purge() @property def is_in_trash(self): @@ -277,38 +270,6 @@ class DocumentPage(models.Model): return '{}-{}'.format(self.document_version.uuid, self.pk) -class DocumentPageCachedImage(models.Model): - document_page = models.ForeignKey( - on_delete=models.CASCADE, related_name='cached_images', - to=DocumentPage, verbose_name=_('Document page') - ) - datetime = models.DateTimeField( - auto_now_add=True, db_index=True, verbose_name=_('Date time') - ) - filename = models.CharField(max_length=128, verbose_name=_('Filename')) - file_size = models.PositiveIntegerField( - db_index=True, default=0, verbose_name=_('File size') - ) - - objects = DocumentPageCachedImage() - - class Meta: - verbose_name = _('Document page cached image') - verbose_name_plural = _('Document page cached images') - - def delete(self, *args, **kwargs): - storage_documentimagecache.delete(self.filename) - return super(DocumentPageCachedImage, self).delete(*args, **kwargs) - - def natural_key(self): - return (self.filename, self.document_page.natural_key()) - natural_key.dependencies = ['documents.DocumentPage'] - - def save(self, *args, **kwargs): - self.file_size = storage_documentimagecache.size(self.filename) - return super(DocumentPageCachedImage, self).save(*args, **kwargs) - - class DocumentPageResult(DocumentPage): class Meta: ordering = ('document_version__document', 'page_number') diff --git a/mayan/apps/documents/models/document_version_models.py b/mayan/apps/documents/models/document_version_models.py index 78b837860d..1428f9e615 100644 --- a/mayan/apps/documents/models/document_version_models.py +++ b/mayan/apps/documents/models/document_version_models.py @@ -7,11 +7,11 @@ import shutil import uuid from django.apps import apps -from django.core.files.base import ContentFile from django.db import models, transaction from django.template import Template, Context from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from mayan.apps.converter.exceptions import InvalidOfficeFormat, PageCountError @@ -21,10 +21,11 @@ from mayan.apps.converter.utils import get_converter_class from mayan.apps.mimetype.api import get_mimetype from ..events import event_document_new_version, event_document_version_revert +from ..literals import DOCUMENT_IMAGES_CACHE_NAME from ..managers import DocumentVersionManager from ..settings import setting_fix_orientation, setting_hash_block_size from ..signals import post_document_created, post_version_upload -from ..storages import storage_documentversion, storage_documentimagecache +from ..storages import storage_documentversion from .document_models import Document @@ -61,14 +62,6 @@ class DocumentVersion(models.Model): _pre_open_hooks = {} _post_save_hooks = {} - @classmethod - def register_pre_open_hook(cls, order, func): - cls._pre_open_hooks[order] = func - - @classmethod - def register_post_save_hook(cls, order, func): - cls._post_save_hooks[order] = func - document = models.ForeignKey( on_delete=models.CASCADE, related_name='versions', to=Document, verbose_name=_('Document') @@ -118,12 +111,28 @@ class DocumentVersion(models.Model): objects = DocumentVersionManager() + @classmethod + def register_pre_open_hook(cls, order, func): + cls._pre_open_hooks[order] = func + + @classmethod + def register_post_save_hook(cls, order, func): + cls._post_save_hooks[order] = func + def __str__(self): return self.get_rendered_string() - @property - def cache_filename(self): - return 'document-version-{}'.format(self.uuid) + @cached_property + def cache(self): + Cache = apps.get_model(app_label='file_caching', model_name='Cache') + return Cache.objects.get(name=DOCUMENT_IMAGES_CACHE_NAME) + + @cached_property + def cache_partition(self): + partition, created = self.cache.partitions.get_or_create( + name='version-{}'.format(self.uuid) + ) + return partition def delete(self, *args, **kwargs): for page in self.pages.all(): @@ -164,43 +173,36 @@ class DocumentVersion(models.Model): return first_page.get_api_image_url(*args, **kwargs) def get_intermediate_file(self): - cache_filename = self.cache_filename - logger.debug('Intermidiate filename: %s', cache_filename) - - if storage_documentimagecache.exists(cache_filename): - logger.debug('Intermidiate file "%s" found.', cache_filename) - - return storage_documentimagecache.open(cache_filename) + cache_filename = 'intermediate_file' + cache_file = self.cache_partition.get_file(filename=cache_filename) + if cache_file: + logger.debug('Intermidiate file found.') + return cache_file.open() else: - logger.debug('Intermidiate file "%s" not found.', cache_filename) + logger.debug('Intermidiate file not found.') try: with self.open() as version_file_object: - converter = get_converter_class()(file_object=version_file_object) + converter = get_converter_class()( + file_object=version_file_object + ) with converter.to_pdf() as pdf_file_object: - - # Since open "wb+" doesn't create files, check if the file - # exists, if not then create it - if not storage_documentimagecache.exists(cache_filename): - storage_documentimagecache.save( - name=cache_filename, content=ContentFile(content='') - ) - - with storage_documentimagecache.open(cache_filename, mode='wb+') as file_object: + with self.cache_partition.create_file(filename=cache_filename) as file_object: shutil.copyfileobj( fsrc=pdf_file_object, fdst=file_object ) - return storage_documentimagecache.open(cache_filename) + return self.cache_partition.get_file(filename=cache_filename).open() except InvalidOfficeFormat: return self.open() except Exception as exception: - # Cleanup in case of error logger.error( 'Error creating intermediate file "%s"; %s.', cache_filename, exception ) - storage_documentimagecache.delete(cache_filename) + cache_file = self.cache_partition.get_file(filename=cache_filename) + if cache_file: + cache_file.delete() raise def get_rendered_string(self, preserve_extension=False): @@ -224,7 +226,7 @@ class DocumentVersion(models.Model): natural_key.dependencies = ['documents.Document'] def invalidate_cache(self): - storage_documentimagecache.delete(self.cache_filename) + self.cache_partition.purge() for page in self.pages.all(): page.invalidate_cache() diff --git a/mayan/apps/documents/settings.py b/mayan/apps/documents/settings.py index a4d5014e91..8a666c8f11 100644 --- a/mayan/apps/documents/settings.py +++ b/mayan/apps/documents/settings.py @@ -8,11 +8,22 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.smart_settings.classes import Namespace from .literals import ( - DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE, DEFAULT_LANGUAGE, DEFAULT_LANGUAGE_CODES + DEFAULT_DOCUMENTS_CACHE_MAXIMUM_SIZE, DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE, + DEFAULT_LANGUAGE, DEFAULT_LANGUAGE_CODES ) +from .utils import callback_update_cache_size namespace = Namespace(label=_('Documents'), name='documents') +setting_document_cache_maximum_size = namespace.add_setting( + global_name='DOCUMENTS_CACHE_MAXIMUM_SIZE', + default=DEFAULT_DOCUMENTS_CACHE_MAXIMUM_SIZE, + help_text=_( + 'The threshold at which the DOCUMENT_CACHE_STORAGE_BACKEND will start ' + 'deleting the oldest document image cache files. Specify the size in ' + 'bytes.' + ), post_edit_function=callback_update_cache_size +) setting_documentimagecache_storage = namespace.add_setting( global_name='DOCUMENTS_CACHE_STORAGE_BACKEND', default='django.core.files.storage.FileSystemStorage', help_text=_( diff --git a/mayan/apps/documents/utils.py b/mayan/apps/documents/utils.py index 21b2df2107..7a7e16d455 100644 --- a/mayan/apps/documents/utils.py +++ b/mayan/apps/documents/utils.py @@ -2,9 +2,17 @@ from __future__ import unicode_literals import pycountry +from django.apps import apps from django.utils.translation import ugettext_lazy as _ -from .settings import setting_language_codes +from .literals import DOCUMENT_IMAGES_CACHE_NAME + + +def callback_update_cache_size(setting): + Cache = apps.get_model(app_label='common', model_name='Cache') + cache = Cache.objects.get(name=DOCUMENT_IMAGES_CACHE_NAME) + cache.maximum_size = setting.value + cache.save() def get_language(language_code): @@ -19,6 +27,8 @@ def get_language(language_code): def get_language_choices(): + from .settings import setting_language_codes + return sorted( [ ( diff --git a/mayan/apps/file_caching/__init__.py b/mayan/apps/file_caching/__init__.py new file mode 100644 index 0000000000..606c594dcf --- /dev/null +++ b/mayan/apps/file_caching/__init__.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals + +default_app_config = 'mayan.apps.file_caching.apps.FileCachingConfig' diff --git a/mayan/apps/file_caching/admin.py b/mayan/apps/file_caching/admin.py new file mode 100644 index 0000000000..a807f197c9 --- /dev/null +++ b/mayan/apps/file_caching/admin.py @@ -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') diff --git a/mayan/apps/file_caching/apps.py b/mayan/apps/file_caching/apps.py new file mode 100644 index 0000000000..bb333be6e0 --- /dev/null +++ b/mayan/apps/file_caching/apps.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals + +from mayan.apps.common.apps import MayanAppConfig + + +class FileCachingConfig(MayanAppConfig): + has_tests = False + name = 'mayan.apps.file_caching' diff --git a/mayan/apps/file_caching/locale/ar/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/ar/LC_MESSAGES/django.po new file mode 100644 index 0000000000..173a9d5ee2 --- /dev/null +++ b/mayan/apps/file_caching/locale/ar/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/bg/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/bg/LC_MESSAGES/django.po new file mode 100644 index 0000000000..4812952c38 --- /dev/null +++ b/mayan/apps/file_caching/locale/bg/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/bs_BA/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/bs_BA/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/bs_BA/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/da/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/da/LC_MESSAGES/django.po new file mode 100644 index 0000000000..4812952c38 --- /dev/null +++ b/mayan/apps/file_caching/locale/da/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/de_DE/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/de_DE/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/de_DE/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/es/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000000..4812952c38 --- /dev/null +++ b/mayan/apps/file_caching/locale/es/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/fa/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/fa/LC_MESSAGES/django.po new file mode 100644 index 0000000000..09b38b103d --- /dev/null +++ b/mayan/apps/file_caching/locale/fa/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/fr/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000000..c65665a79d --- /dev/null +++ b/mayan/apps/file_caching/locale/fr/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/hu/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/hu/LC_MESSAGES/django.po new file mode 100644 index 0000000000..4812952c38 --- /dev/null +++ b/mayan/apps/file_caching/locale/hu/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/id/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/id/LC_MESSAGES/django.po new file mode 100644 index 0000000000..09b38b103d --- /dev/null +++ b/mayan/apps/file_caching/locale/id/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/it/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000000..4812952c38 --- /dev/null +++ b/mayan/apps/file_caching/locale/it/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/nl_NL/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/nl_NL/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/nl_NL/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/pl/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 0000000000..2f8fc8f37b --- /dev/null +++ b/mayan/apps/file_caching/locale/pl/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/pt/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/pt/LC_MESSAGES/django.po new file mode 100644 index 0000000000..4812952c38 --- /dev/null +++ b/mayan/apps/file_caching/locale/pt/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/pt_BR/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 0000000000..c65665a79d --- /dev/null +++ b/mayan/apps/file_caching/locale/pt_BR/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/ro_RO/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/ro_RO/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/ro_RO/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/ru/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b73a01bbe4 --- /dev/null +++ b/mayan/apps/file_caching/locale/ru/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/sl_SI/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/sl_SI/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/sl_SI/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/tr_TR/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/tr_TR/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/tr_TR/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/vi_VN/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/vi_VN/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/vi_VN/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/locale/zh_CN/LC_MESSAGES/django.po b/mayan/apps/file_caching/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000000..b3bd609bb3 --- /dev/null +++ b/mayan/apps/file_caching/locale/zh_CN/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/mayan/apps/file_caching/migrations/0001_initial.py b/mayan/apps/file_caching/migrations/0001_initial.py new file mode 100644 index 0000000000..46c20637ab --- /dev/null +++ b/mayan/apps/file_caching/migrations/0001_initial.py @@ -0,0 +1,64 @@ +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')]), + ), + ] diff --git a/mayan/apps/file_caching/migrations/__init__.py b/mayan/apps/file_caching/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mayan/apps/file_caching/models.py b/mayan/apps/file_caching/models.py new file mode 100644 index 0000000000..6b8e6bb3cc --- /dev/null +++ b/mayan/apps/file_caching/models.py @@ -0,0 +1,184 @@ +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.exceptions 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'): + # Open the file for reading. If the file is written to, the + # .update_size() must be called. + 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() diff --git a/mayan/settings/base.py b/mayan/settings/base.py index 6292ecd168..c18ae75951 100644 --- a/mayan/settings/base.py +++ b/mayan/settings/base.py @@ -95,6 +95,7 @@ INSTALLED_APPS = ( 'mayan.apps.django_gpg', 'mayan.apps.dynamic_search', 'mayan.apps.events', + 'mayan.apps.file_caching', 'mayan.apps.lock_manager', 'mayan.apps.mimetype', 'mayan.apps.navigation',