Instead of inserting the path of the apps into the Python app, the apps are now referenced by their full import path. This solves name clashes with external or native Python libraries. Example: Mayan statistics app vs. Python new statistics library. Every app reference is now prepended with 'mayan.apps'. Existing config.yml files need to be updated manually. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
174 lines
5.7 KiB
Python
174 lines
5.7 KiB
Python
from __future__ import unicode_literals
|
|
|
|
import base64
|
|
import logging
|
|
import os
|
|
import time
|
|
|
|
from furl import furl
|
|
|
|
from django.core.files import File
|
|
from django.core.files.base import ContentFile
|
|
from django.urls import reverse
|
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
|
from django.utils.six.moves.urllib.parse import quote_plus, unquote_plus
|
|
|
|
from mayan.apps.converter import TransformationResize, converter_class
|
|
|
|
from .storages import storage_staging_file_image_cache
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PseudoFile(File):
|
|
def __init__(self, file, name):
|
|
self.name = name
|
|
self.file = file
|
|
self.file.seek(0, os.SEEK_END)
|
|
self.size = self.file.tell()
|
|
self.file.seek(0)
|
|
|
|
|
|
class SourceUploadedFile(File):
|
|
def __init__(self, source, file, extra_data=None):
|
|
self.file = file
|
|
self.source = source
|
|
self.extra_data = extra_data
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class StagingFile(object):
|
|
"""
|
|
Simple class to extend the File class to add preview capabilities
|
|
files in a directory on a storage
|
|
"""
|
|
def __init__(self, staging_folder, filename=None, encoded_filename=None):
|
|
self.staging_folder = staging_folder
|
|
if encoded_filename:
|
|
self.encoded_filename = str(encoded_filename)
|
|
self.filename = base64.urlsafe_b64decode(
|
|
unquote_plus(self.encoded_filename)
|
|
).decode('utf8')
|
|
else:
|
|
self.filename = filename
|
|
self.encoded_filename = quote_plus(base64.urlsafe_b64encode(
|
|
filename.encode('utf8')
|
|
))
|
|
|
|
def __str__(self):
|
|
return force_text(self.filename)
|
|
|
|
def as_file(self):
|
|
return File(
|
|
file=open(self.get_full_path(), mode='rb'), name=self.filename
|
|
)
|
|
|
|
@property
|
|
def cache_filename(self):
|
|
return '{}{}'.format(self.staging_folder.pk, self.encoded_filename)
|
|
|
|
def delete(self):
|
|
storage_staging_file_image_cache.delete(self.cache_filename)
|
|
os.unlink(self.get_full_path())
|
|
|
|
def generate_image(self, *args, **kwargs):
|
|
transformation_list = self.get_combined_transformation_list(*args, **kwargs)
|
|
|
|
# Check is transformed image is available
|
|
logger.debug('transformations cache filename: %s', self.cache_filename)
|
|
|
|
if storage_staging_file_image_cache.exists(self.cache_filename):
|
|
logger.debug(
|
|
'staging file cache file "%s" found', self.cache_filename
|
|
)
|
|
else:
|
|
logger.debug(
|
|
'staging file cache file "%s" not found', self.cache_filename
|
|
)
|
|
image = self.get_image(transformations=transformation_list)
|
|
with storage_staging_file_image_cache.open(self.cache_filename, 'wb+') as file_object:
|
|
file_object.write(image.getvalue())
|
|
|
|
return self.cache_filename
|
|
|
|
def get_api_image_url(self, *args, **kwargs):
|
|
final_url = furl()
|
|
final_url.args = kwargs
|
|
final_url.path = reverse(
|
|
'rest_api:stagingfolderfile-image-view', args=(
|
|
self.staging_folder.pk,
|
|
self.encoded_filename
|
|
)
|
|
)
|
|
|
|
return final_url.tostr()
|
|
|
|
def get_combined_transformation_list(self, *args, **kwargs):
|
|
"""
|
|
Return a list of transformation containing the server side
|
|
staging file transformation as well as tranformations created
|
|
from the arguments as transient interactive transformation.
|
|
"""
|
|
# Convert arguments into transformations
|
|
transformations = kwargs.get('transformations', [])
|
|
|
|
# Set sensible defaults if the argument is not specified or if the
|
|
# argument is None
|
|
width = self.staging_folder.preview_width
|
|
height = self.staging_folder.preview_height
|
|
|
|
# Generate transformation hash
|
|
transformation_list = []
|
|
|
|
# Interactive transformations second
|
|
for transformation in transformations:
|
|
transformation_list.append(transformation)
|
|
|
|
if width:
|
|
transformation_list.append(
|
|
TransformationResize(width=width, height=height)
|
|
)
|
|
|
|
return transformation_list
|
|
|
|
def get_date_time_created(self):
|
|
return time.ctime(os.path.getctime(self.get_full_path()))
|
|
|
|
def get_full_path(self):
|
|
return os.path.join(self.staging_folder.folder_path, self.filename)
|
|
|
|
def get_image(self, transformations=None):
|
|
cache_filename = self.cache_filename
|
|
file_object = None
|
|
|
|
try:
|
|
file_object = open(self.get_full_path(), mode='rb')
|
|
converter = converter_class(file_object=file_object)
|
|
|
|
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_staging_file_image_cache.exists(cache_filename):
|
|
storage_staging_file_image_cache.save(name=cache_filename, content=ContentFile(content=''))
|
|
|
|
with storage_staging_file_image_cache.open(cache_filename, 'wb+') as file_object:
|
|
file_object.write(page_image.getvalue())
|
|
except Exception as exception:
|
|
# Cleanup in case of error
|
|
logger.error(
|
|
'Error creating staging file cache "%s"; %s',
|
|
cache_filename, exception
|
|
)
|
|
storage_staging_file_image_cache.delete(cache_filename)
|
|
if file_object:
|
|
file_object.close()
|
|
raise
|
|
|
|
for transformation in transformations:
|
|
converter.transform(transformation=transformation)
|
|
|
|
result = converter.get_page()
|
|
file_object.close()
|
|
return result
|