from django.contrib.staticfiles import finders
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.files.images import get_image_dimensions
from django.template.defaultfilters import filesizeformat
from boards import settings
from boards.abstracts.constants import THUMB_SIZES
from boards.settings import SECTION_EXTERNAL
from boards.utils import get_domain, cached_result, get_extension
PROTOCOL_DELIMITER = ':'
DOMAIN_DELIMITER = '.'
FILE_STUB_IMAGE = 'images/file.png'
FILE_STUB_URL = 'url'
FILE_FILEFORMAT = 'images/fileformats/{}.png'
FILE_TYPES_VIDEO = (
'video/webm',
'video/mp4',
'video/mpeg',
'video/ogv',
)
FILE_TYPE_SVG = 'image/svg+xml'
FILE_TYPES_AUDIO = (
'audio/ogg',
'audio/mpeg',
'audio/opus',
'audio/x-flac',
'audio/mpeg',
)
FILE_TYPES_IMAGE = (
'image/jpeg',
'image/jpg',
'image/png',
'image/bmp',
'image/gif',
)
PLAIN_FILE_FORMATS = {
'zip': 'archive',
'tar': 'archive',
'gz': 'archive',
'mid' : 'midi',
}
URL_PROTOCOLS = {
'magnet': 'magnet',
}
CSS_CLASS_IMAGE = 'image'
CSS_CLASS_THUMB = 'thumb'
ABSTRACT_VIEW = '
'
URL_VIEW = ''
ABSTRACT_FORMAT_VIEW = ''\
''\
''
VIDEO_FORMAT_VIEW = ''
AUDIO_FORMAT_VIEW = ''
IMAGE_FORMAT_VIEW = '' \
'' \
''
SVG_FORMAT_VIEW = ''\
''\
''
URL_FORMAT_VIEW = '' \
'' \
''
def get_viewers():
return AbstractViewer.__subclasses__()
class AbstractViewer:
def __init__(self, file, file_type, id, url):
self.file = file
self.file_type = file_type
self.id = id
self.url = url
self.extension = get_extension(self.file.name).lower()
@staticmethod
def supports(file_type):
return True
def get_view(self):
return ABSTRACT_VIEW.format(self.get_format_view(), self.file.url,
self.file_type, filesizeformat(self.file.size),
self.file_type, self._get_search_url(), self.file.name, self.id)
def _get_search_url(self):
search_host = settings.get(SECTION_EXTERNAL, 'ImageSearchHost')
if search_host:
if search_host.endswith('/'):
search_host = search_host[:-1]
search_url = search_host + self.file.url
else:
search_url = ''
return search_url
def get_format_view(self):
image_name = PLAIN_FILE_FORMATS.get(self.extension, self.extension)
file_name = FILE_FILEFORMAT.format(image_name)
if self.file_exists(file_name):
image = file_name
else:
image = FILE_STUB_IMAGE
w, h = self.get_static_dimensions(image)
return ABSTRACT_FORMAT_VIEW.format(self.file.url, static(image), w, h)
@cached_result()
def get_static_dimensions(self, filename):
file_path = finders.find(filename)
return get_image_dimensions(file_path)
@cached_result()
def file_exists(self, filename):
return finders.find(filename) is not None
class VideoViewer(AbstractViewer):
@staticmethod
def supports(file_type):
return file_type in FILE_TYPES_VIDEO
def get_format_view(self):
return VIDEO_FORMAT_VIEW.format(self.file.url)
class AudioViewer(AbstractViewer):
@staticmethod
def supports(file_type):
return file_type in FILE_TYPES_AUDIO
def get_format_view(self):
return AUDIO_FORMAT_VIEW.format(self.file.url)
class SvgViewer(AbstractViewer):
@staticmethod
def supports(file_type):
return file_type == FILE_TYPE_SVG
def get_format_view(self):
return SVG_FORMAT_VIEW.format(self.file.url, self.file.url)
class ImageViewer(AbstractViewer):
@staticmethod
def supports(file_type):
return file_type in FILE_TYPES_IMAGE
def get_format_view(self):
metadata = '{}, {}'.format(self.file.name.split('.')[-1],
filesizeformat(self.file.size))
try:
width, height = get_image_dimensions(self.file.path)
except Exception:
# If the image is a decompression bomb, treat it as just a regular
# file
return super().get_format_view()
preview_path = self.file.path.replace('.', '.200x150.')
try:
pre_width, pre_height = get_image_dimensions(preview_path)
except Exception:
return super().get_format_view()
split = self.file.url.rsplit('.', 1)
w, h = THUMB_SIZES[0]
thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
return IMAGE_FORMAT_VIEW.format(CSS_CLASS_THUMB,
thumb_url,
self.id,
str(pre_width),
str(pre_height), str(width), str(height),
full=self.file.url, image_meta=metadata)
class UrlViewer(AbstractViewer):
@staticmethod
def supports(file_type):
return file_type is None
def get_view(self):
return URL_VIEW.format(self.get_format_view(), get_domain(self.url))
def get_format_view(self):
protocol = self.url.split(PROTOCOL_DELIMITER)[0]
domain = get_domain(self.url)
image_path = 'images/{}.png'.format(self._get_image_name(protocol, domain))
image = static(image_path)
w, h = self.get_static_dimensions(image_path)
return URL_FORMAT_VIEW.format(self.url, image, w, h)
def _find_image_for_domains(self, domain):
"""
Searches for the domain image for every domain level except top.
E.g. for l3.example.co.uk it will search for l3.example.co.uk, then
example.co.uk, then co.uk
"""
levels = domain.split(DOMAIN_DELIMITER)
while len(levels) > 1:
domain = DOMAIN_DELIMITER.join(levels)
filename = 'images/domains/{}.png'.format(domain)
if self.file_exists(filename):
return 'domains/' + domain
else:
del levels[0]
@cached_result()
def _get_image_name(self, protocol, domain):
if protocol in URL_PROTOCOLS:
url_image_name = URL_PROTOCOLS.get(protocol)
elif domain:
url_image_name = self._find_image_for_domains(domain) or FILE_STUB_URL
else:
url_image_name = FILE_STUB_URL
return url_image_name