##// END OF EJS Templates
Search for image for every domain level starting from the lowest one. Cache this into memcached
neko259 -
r1772:1342675d default
parent child Browse files
Show More
@@ -1,201 +1,210 b''
1 import re
1 import re
2
2
3 from django.contrib.staticfiles import finders
3 from django.contrib.staticfiles import finders
4 from django.contrib.staticfiles.templatetags.staticfiles import static
4 from django.contrib.staticfiles.templatetags.staticfiles import static
5 from django.core.files.images import get_image_dimensions
5 from django.core.files.images import get_image_dimensions
6 from django.template.defaultfilters import filesizeformat
6 from django.template.defaultfilters import filesizeformat
7
7
8 from boards.utils import get_domain
8 from boards.utils import get_domain, cached_result
9
9
10
10
11 FILE_STUB_IMAGE = 'images/file.png'
11 FILE_STUB_IMAGE = 'images/file.png'
12 FILE_STUB_URL = 'url'
12 FILE_STUB_URL = 'url'
13
13
14
14
15 FILE_TYPES_VIDEO = (
15 FILE_TYPES_VIDEO = (
16 'webm',
16 'webm',
17 'mp4',
17 'mp4',
18 'mpeg',
18 'mpeg',
19 'ogv',
19 'ogv',
20 )
20 )
21 FILE_TYPE_SVG = 'svg'
21 FILE_TYPE_SVG = 'svg'
22 FILE_TYPES_AUDIO = (
22 FILE_TYPES_AUDIO = (
23 'ogg',
23 'ogg',
24 'mp3',
24 'mp3',
25 'opus',
25 'opus',
26 )
26 )
27 FILE_TYPES_IMAGE = (
27 FILE_TYPES_IMAGE = (
28 'jpeg',
28 'jpeg',
29 'jpg',
29 'jpg',
30 'png',
30 'png',
31 'bmp',
31 'bmp',
32 'gif',
32 'gif',
33 )
33 )
34
34
35 PLAIN_FILE_FORMATS = {
35 PLAIN_FILE_FORMATS = {
36 'pdf': 'pdf',
36 'pdf': 'pdf',
37 'djvu': 'djvu',
37 'djvu': 'djvu',
38 'txt': 'txt',
38 'txt': 'txt',
39 'tex': 'tex',
39 'tex': 'tex',
40 'xcf': 'xcf',
40 'xcf': 'xcf',
41 'zip': 'archive',
41 'zip': 'archive',
42 'tar': 'archive',
42 'tar': 'archive',
43 'gz': 'archive',
43 'gz': 'archive',
44 }
44 }
45
45
46 URL_PROTOCOLS = {
46 URL_PROTOCOLS = {
47 'magnet': 'magnet',
47 'magnet': 'magnet',
48 }
48 }
49
49
50 CSS_CLASS_IMAGE = 'image'
50 CSS_CLASS_IMAGE = 'image'
51 CSS_CLASS_THUMB = 'thumb'
51 CSS_CLASS_THUMB = 'thumb'
52
52
53
53
54 def get_viewers():
54 def get_viewers():
55 return AbstractViewer.__subclasses__()
55 return AbstractViewer.__subclasses__()
56
56
57
57
58 def get_static_dimensions(filename):
58 def get_static_dimensions(filename):
59 file_path = finders.find(filename)
59 file_path = finders.find(filename)
60 return get_image_dimensions(file_path)
60 return get_image_dimensions(file_path)
61
61
62
62
63 # TODO Move this to utils
63 # TODO Move this to utils
64 def file_exists(filename):
64 def file_exists(filename):
65 return finders.find(filename) is not None
65 return finders.find(filename) is not None
66
66
67
67
68 class AbstractViewer:
68 class AbstractViewer:
69 def __init__(self, file, file_type, hash, url):
69 def __init__(self, file, file_type, hash, url):
70 self.file = file
70 self.file = file
71 self.file_type = file_type
71 self.file_type = file_type
72 self.hash = hash
72 self.hash = hash
73 self.url = url
73 self.url = url
74
74
75 @staticmethod
75 @staticmethod
76 def supports(file_type):
76 def supports(file_type):
77 return True
77 return True
78
78
79 def get_view(self):
79 def get_view(self):
80 return '<div class="image">'\
80 return '<div class="image">'\
81 '{}'\
81 '{}'\
82 '<div class="image-metadata"><a href="{}" download >{}, {}</a></div>'\
82 '<div class="image-metadata"><a href="{}" download >{}, {}</a></div>'\
83 '</div>'.format(self.get_format_view(), self.file.url,
83 '</div>'.format(self.get_format_view(), self.file.url,
84 self.file_type, filesizeformat(self.file.size))
84 self.file_type, filesizeformat(self.file.size))
85
85
86 def get_format_view(self):
86 def get_format_view(self):
87 if self.file_type in PLAIN_FILE_FORMATS:
87 if self.file_type in PLAIN_FILE_FORMATS:
88 image = 'images/fileformats/{}.png'.format(
88 image = 'images/fileformats/{}.png'.format(
89 PLAIN_FILE_FORMATS[self.file_type])
89 PLAIN_FILE_FORMATS[self.file_type])
90 else:
90 else:
91 image = FILE_STUB_IMAGE
91 image = FILE_STUB_IMAGE
92
92
93 w, h = get_static_dimensions(image)
93 w, h = get_static_dimensions(image)
94
94
95 return '<a href="{}">'\
95 return '<a href="{}">'\
96 '<img class="url-image" src="{}" width="{}" height="{}"/>'\
96 '<img class="url-image" src="{}" width="{}" height="{}"/>'\
97 '</a>'.format(self.file.url, static(image), w, h)
97 '</a>'.format(self.file.url, static(image), w, h)
98
98
99
99
100 class VideoViewer(AbstractViewer):
100 class VideoViewer(AbstractViewer):
101 @staticmethod
101 @staticmethod
102 def supports(file_type):
102 def supports(file_type):
103 return file_type in FILE_TYPES_VIDEO
103 return file_type in FILE_TYPES_VIDEO
104
104
105 def get_format_view(self):
105 def get_format_view(self):
106 return '<video width="200" height="150" controls src="{}"></video>'\
106 return '<video width="200" height="150" controls src="{}"></video>'\
107 .format(self.file.url)
107 .format(self.file.url)
108
108
109
109
110 class AudioViewer(AbstractViewer):
110 class AudioViewer(AbstractViewer):
111 @staticmethod
111 @staticmethod
112 def supports(file_type):
112 def supports(file_type):
113 return file_type in FILE_TYPES_AUDIO
113 return file_type in FILE_TYPES_AUDIO
114
114
115 def get_format_view(self):
115 def get_format_view(self):
116 return '<audio controls src="{}"></audio>'.format(self.file.url)
116 return '<audio controls src="{}"></audio>'.format(self.file.url)
117
117
118
118
119 class SvgViewer(AbstractViewer):
119 class SvgViewer(AbstractViewer):
120 @staticmethod
120 @staticmethod
121 def supports(file_type):
121 def supports(file_type):
122 return file_type == FILE_TYPE_SVG
122 return file_type == FILE_TYPE_SVG
123
123
124 def get_format_view(self):
124 def get_format_view(self):
125 return '<a class="thumb" href="{}">'\
125 return '<a class="thumb" href="{}">'\
126 '<img class="post-image-preview" width="200" height="150" src="{}" />'\
126 '<img class="post-image-preview" width="200" height="150" src="{}" />'\
127 '</a>'.format(self.file.url, self.file.url)
127 '</a>'.format(self.file.url, self.file.url)
128
128
129
129
130 class ImageViewer(AbstractViewer):
130 class ImageViewer(AbstractViewer):
131 @staticmethod
131 @staticmethod
132 def supports(file_type):
132 def supports(file_type):
133 return file_type in FILE_TYPES_IMAGE
133 return file_type in FILE_TYPES_IMAGE
134
134
135 def get_format_view(self):
135 def get_format_view(self):
136 metadata = '{}, {}'.format(self.file.name.split('.')[-1],
136 metadata = '{}, {}'.format(self.file.name.split('.')[-1],
137 filesizeformat(self.file.size))
137 filesizeformat(self.file.size))
138 width, height = get_image_dimensions(self.file.file)
138 width, height = get_image_dimensions(self.file.file)
139 preview_path = self.file.path.replace('.', '.200x150.')
139 preview_path = self.file.path.replace('.', '.200x150.')
140 pre_width, pre_height = get_image_dimensions(preview_path)
140 pre_width, pre_height = get_image_dimensions(preview_path)
141
141
142 split = self.file.url.rsplit('.', 1)
142 split = self.file.url.rsplit('.', 1)
143 w, h = 200, 150
143 w, h = 200, 150
144 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
144 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
145
145
146 return '<a class="{}" href="{full}">' \
146 return '<a class="{}" href="{full}">' \
147 '<img class="post-image-preview"' \
147 '<img class="post-image-preview"' \
148 ' src="{}"' \
148 ' src="{}"' \
149 ' alt="{}"' \
149 ' alt="{}"' \
150 ' width="{}"' \
150 ' width="{}"' \
151 ' height="{}"' \
151 ' height="{}"' \
152 ' data-width="{}"' \
152 ' data-width="{}"' \
153 ' data-height="{}" />' \
153 ' data-height="{}" />' \
154 '</a>' \
154 '</a>' \
155 .format(CSS_CLASS_THUMB,
155 .format(CSS_CLASS_THUMB,
156 thumb_url,
156 thumb_url,
157 self.hash,
157 self.hash,
158 str(pre_width),
158 str(pre_width),
159 str(pre_height), str(width), str(height),
159 str(pre_height), str(width), str(height),
160 full=self.file.url, image_meta=metadata)
160 full=self.file.url, image_meta=metadata)
161
161
162
162
163 class UrlViewer(AbstractViewer):
163 class UrlViewer(AbstractViewer):
164 @staticmethod
164 @staticmethod
165 def supports(file_type):
165 def supports(file_type):
166 return file_type is None
166 return file_type is None
167
167
168 def get_view(self):
168 def get_view(self):
169 return '<div class="image">' \
169 return '<div class="image">' \
170 '{}' \
170 '{}' \
171 '<div class="image-metadata">{}</div>' \
171 '<div class="image-metadata">{}</div>' \
172 '</div>'.format(self.get_format_view(), get_domain(self.url))
172 '</div>'.format(self.get_format_view(), get_domain(self.url))
173
173
174 def get_format_view(self):
174 def get_format_view(self):
175 protocol = self.url.split('://')[0]
175 protocol = self.url.split('://')[0]
176
176
177 domain = get_domain(self.url)
177 domain = get_domain(self.url)
178
178
179 if protocol in URL_PROTOCOLS:
179 if protocol in URL_PROTOCOLS:
180 url_image_name = URL_PROTOCOLS.get(protocol)
180 url_image_name = URL_PROTOCOLS.get(protocol)
181 elif domain:
181 elif domain:
182 filename = 'images/domains/{}.png'.format(domain)
182 url_image_name = self._find_image_for_domains(domain) or FILE_STUB_URL
183 if file_exists(filename):
184 url_image_name = 'domains/' + domain
185 else:
186 url_image_name = FILE_STUB_URL
187 else:
183 else:
188 url_image_name = FILE_STUB_URL
184 url_image_name = FILE_STUB_URL
189
185
190 image_path = 'images/{}.png'.format(url_image_name)
186 image_path = 'images/{}.png'.format(url_image_name)
191 image = static(image_path)
187 image = static(image_path)
192 w, h = get_static_dimensions(image_path)
188 w, h = get_static_dimensions(image_path)
193
189
194 return '<a href="{}">' \
190 return '<a href="{}">' \
195 '<img class="url-image" src="{}" width="{}" height="{}"/>' \
191 '<img class="url-image" src="{}" width="{}" height="{}"/>' \
196 '</a>'.format(self.url, image, w, h)
192 '</a>'.format(self.url, image, w, h)
197
193
194 @cached_result()
195 def _find_image_for_domains(self, domain):
196 """
197 Searches for the domain image for every domain level except top.
198 E.g. for l3.example.co.uk it will search for l3.example.co.uk, then
199 example.co.uk, then co.uk
200 """
201 levels = domain.split('.')
202 while len(levels) > 1:
203 domain = '.'.join(levels)
198
204
199 def _get_protocol(self):
205 filename = 'images/domains/{}.png'.format(domain)
200 pass
206 if file_exists(filename):
207 return 'domains/' + domain
208 else:
209 del levels[0]
201
210
@@ -1,177 +1,157 b''
1 """
1 """
2 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 import hashlib
4 import hashlib
5 import uuid
5 import uuid
6
6
7 from boards.abstracts.constants import FILE_DIRECTORY
7 from boards.abstracts.constants import FILE_DIRECTORY
8 from random import random
8 from random import random
9 import time
9 import time
10 import hmac
10 import hmac
11
11
12 from django.core.cache import cache
12 from django.core.cache import cache
13 from django.db.models import Model
13 from django.db.models import Model
14 from django import forms
14 from django import forms
15 from django.template.defaultfilters import filesizeformat
15 from django.template.defaultfilters import filesizeformat
16 from django.utils import timezone
16 from django.utils import timezone
17 from django.utils.translation import ugettext_lazy as _
17 from django.utils.translation import ugettext_lazy as _
18 import magic
18 import magic
19 import os
19 import os
20
20
21 import boards
21 import boards
22 from boards.settings import get_bool
22 from boards.settings import get_bool
23 from neboard import settings
23 from neboard import settings
24
24
25 CACHE_KEY_DELIMITER = '_'
25 CACHE_KEY_DELIMITER = '_'
26
26
27 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
27 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
28 META_REMOTE_ADDR = 'REMOTE_ADDR'
28 META_REMOTE_ADDR = 'REMOTE_ADDR'
29
29
30 SETTING_MESSAGES = 'Messages'
30 SETTING_MESSAGES = 'Messages'
31 SETTING_ANON_MODE = 'AnonymousMode'
31 SETTING_ANON_MODE = 'AnonymousMode'
32
32
33 ANON_IP = '127.0.0.1'
33 ANON_IP = '127.0.0.1'
34
34
35 FILE_EXTENSION_DELIMITER = '.'
35 FILE_EXTENSION_DELIMITER = '.'
36
36
37 KNOWN_DOMAINS = (
38 'org.ru',
39 )
40
41
37
42 def is_anonymous_mode():
38 def is_anonymous_mode():
43 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
39 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
44
40
45
41
46 def get_client_ip(request):
42 def get_client_ip(request):
47 if is_anonymous_mode():
43 if is_anonymous_mode():
48 ip = ANON_IP
44 ip = ANON_IP
49 else:
45 else:
50 x_forwarded_for = request.META.get(HTTP_FORWARDED)
46 x_forwarded_for = request.META.get(HTTP_FORWARDED)
51 if x_forwarded_for:
47 if x_forwarded_for:
52 ip = x_forwarded_for.split(',')[-1].strip()
48 ip = x_forwarded_for.split(',')[-1].strip()
53 else:
49 else:
54 ip = request.META.get(META_REMOTE_ADDR)
50 ip = request.META.get(META_REMOTE_ADDR)
55 return ip
51 return ip
56
52
57
53
58 # TODO The output format is not epoch because it includes microseconds
54 # TODO The output format is not epoch because it includes microseconds
59 def datetime_to_epoch(datetime):
55 def datetime_to_epoch(datetime):
60 return int(time.mktime(timezone.localtime(
56 return int(time.mktime(timezone.localtime(
61 datetime,timezone.get_current_timezone()).timetuple())
57 datetime,timezone.get_current_timezone()).timetuple())
62 * 1000000 + datetime.microsecond)
58 * 1000000 + datetime.microsecond)
63
59
64
60
65 def get_websocket_token(user_id='', timestamp=''):
61 def get_websocket_token(user_id='', timestamp=''):
66 """
62 """
67 Create token to validate information provided by new connection.
63 Create token to validate information provided by new connection.
68 """
64 """
69
65
70 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
66 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
71 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
67 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
72 sign.update(user_id.encode())
68 sign.update(user_id.encode())
73 sign.update(timestamp.encode())
69 sign.update(timestamp.encode())
74 token = sign.hexdigest()
70 token = sign.hexdigest()
75
71
76 return token
72 return token
77
73
78
74
79 # TODO Test this carefully
75 # TODO Test this carefully
80 def cached_result(key_method=None):
76 def cached_result(key_method=None):
81 """
77 """
82 Caches method result in the Django's cache system, persisted by object name,
78 Caches method result in the Django's cache system, persisted by object name,
83 object name, model id if object is a Django model, args and kwargs if any.
79 object name, model id if object is a Django model, args and kwargs if any.
84 """
80 """
85 def _cached_result(function):
81 def _cached_result(function):
86 def inner_func(obj, *args, **kwargs):
82 def inner_func(obj, *args, **kwargs):
87 cache_key_params = [obj.__class__.__name__, function.__name__]
83 cache_key_params = [obj.__class__.__name__, function.__name__]
88
84
89 cache_key_params += args
85 cache_key_params += args
90 for key, value in kwargs:
86 for key, value in kwargs:
91 cache_key_params.append(key + ':' + value)
87 cache_key_params.append(key + ':' + value)
92
88
93 if isinstance(obj, Model):
89 if isinstance(obj, Model):
94 cache_key_params.append(str(obj.id))
90 cache_key_params.append(str(obj.id))
95
91
96 if key_method is not None:
92 if key_method is not None:
97 cache_key_params += [str(arg) for arg in key_method(obj)]
93 cache_key_params += [str(arg) for arg in key_method(obj)]
98
94
99 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
95 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
100
96
101 persisted_result = cache.get(cache_key)
97 persisted_result = cache.get(cache_key)
102 if persisted_result is not None:
98 if persisted_result is not None:
103 result = persisted_result
99 result = persisted_result
104 else:
100 else:
105 result = function(obj, *args, **kwargs)
101 result = function(obj, *args, **kwargs)
106 if result is not None:
102 if result is not None:
107 cache.set(cache_key, result)
103 cache.set(cache_key, result)
108
104
109 return result
105 return result
110
106
111 return inner_func
107 return inner_func
112 return _cached_result
108 return _cached_result
113
109
114
110
115 def get_file_hash(file) -> str:
111 def get_file_hash(file) -> str:
116 md5 = hashlib.md5()
112 md5 = hashlib.md5()
117 for chunk in file.chunks():
113 for chunk in file.chunks():
118 md5.update(chunk)
114 md5.update(chunk)
119 return md5.hexdigest()
115 return md5.hexdigest()
120
116
121
117
122 def validate_file_size(size: int):
118 def validate_file_size(size: int):
123 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
119 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
124 if size > max_size:
120 if size > max_size:
125 raise forms.ValidationError(
121 raise forms.ValidationError(
126 _('File must be less than %s but is %s.')
122 _('File must be less than %s but is %s.')
127 % (filesizeformat(max_size), filesizeformat(size)))
123 % (filesizeformat(max_size), filesizeformat(size)))
128
124
129
125
130 def get_extension(filename):
126 def get_extension(filename):
131 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
127 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
132
128
133
129
134 def get_upload_filename(model_instance, old_filename):
130 def get_upload_filename(model_instance, old_filename):
135 extension = get_extension(old_filename)
131 extension = get_extension(old_filename)
136 new_name = '{}.{}'.format(uuid.uuid4(), extension)
132 new_name = '{}.{}'.format(uuid.uuid4(), extension)
137
133
138 return os.path.join(FILE_DIRECTORY, new_name)
134 return os.path.join(FILE_DIRECTORY, new_name)
139
135
140
136
141 def get_file_mimetype(file) -> str:
137 def get_file_mimetype(file) -> str:
142 file_type = magic.from_buffer(file.chunks().__next__(), mime=True)
138 file_type = magic.from_buffer(file.chunks().__next__(), mime=True)
143 if file_type is None:
139 if file_type is None:
144 file_type = 'application/octet-stream'
140 file_type = 'application/octet-stream'
145 elif type(file_type) == bytes:
141 elif type(file_type) == bytes:
146 file_type = file_type.decode()
142 file_type = file_type.decode()
147 return file_type
143 return file_type
148
144
149
145
150 def get_domain(url: str) -> str:
146 def get_domain(url: str) -> str:
151 """
147 """
152 Gets domain from an URL with random number of domain levels.
148 Gets domain from an URL with random number of domain levels.
153 """
149 """
154 domain_parts = url.split('/')
150 domain_parts = url.split('/')
155 if len(domain_parts) >= 2:
151 if len(domain_parts) >= 2:
156 full_domain = domain_parts[2]
152 full_domain = domain_parts[2]
157 else:
153 else:
158 full_domain = ''
154 full_domain = ''
159
155
160 result = full_domain
156 return full_domain
161 if full_domain:
162 levels = full_domain.split('.')
163 if len(levels) >= 2:
164 top = levels[-1]
165 second = levels[-2]
166
157
167 has_third_level = len(levels) > 2
168 if has_third_level:
169 third = levels[-3]
170
171 if has_third_level and ('{}.{}'.format(second, top) in KNOWN_DOMAINS):
172 result = '{}.{}.{}'.format(third, second, top)
173 else:
174 result = '{}.{}'.format(second, top)
175
176 return result
177
General Comments 0
You need to be logged in to leave comments. Login now