1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
from PIL import Image
from app import blure
from sanic.response import raw, BaseHTTPResponse
from sanic.exceptions import NotFound
from pathlib import Path
from io import BytesIO
_IMAGE_URL = blure.config.NGX_IMAGE_URL
_IMAGE_PATH = blure.config.NGX_IMAGE_PATH
_NOT_FOUND_IMAGE = blure.config.NOT_FOUND_IMAGE
_NOT_FOUND_IMAGE_CONTENT_TYPE = blure.config.NOT_FOUND_IMAGE_CONTENT_TYPE
class InvalidImageFormat(ValueError):
pass
async def is_image_exists(id: int):
async with blure.pool.acquire() as conn:
rec = await conn.fetchval('SELECT TRUE FROM pics WHERE id=$1', id)
return rec is not None
class NGXImage:
def __init__(self, from_id: int):
self.id = from_id
@staticmethod
def pillow_format(content_type: str):
mapping = {
'': 'PNG', # try png if content type is not available
'image/bmp': 'BMP',
'image/gif': 'GIF',
'image/jpeg': 'JPEG',
'image/png': 'PNG'
}
if content_type in mapping.keys():
return mapping[content_type]
else:
raise InvalidImageFormat(f'{content_type} is not supported')
@classmethod
async def create_from_bytes(cls, bytes_io: BytesIO, content_type: str):
async with blure.pool.acquire() as conn:
# TODO: make it a transaction
new_id = await conn.fetchval(
'''
INSERT INTO pics(src_url, content_type)
VALUES ($1, $2)
RETURNING id
''',
'',
content_type
)
ngx_image = NGXImage(from_id=new_id)
with ngx_image.orig_path.open('wb') as f:
f.write(bytes_io.getvalue())
image = Image.open(bytes_io)
image.thumbnail(blure.config.CUT_SIZES[2])
image.save(
ngx_image.thumb_path,
format=cls.pillow_format(content_type)
)
return ngx_image
async def __aenter__(self):
if not await is_image_exists(self.id):
raise NotFound(
f'image {blure.url.to_url(self.id)} does not exist'
)
else:
return self
async def __aexit__(self, *exc):
pass
@property
def orig_path(self):
return Path(_IMAGE_PATH.format(blure.url.to_url(self.id)))
@property
def thumb_path(self):
return Path(_IMAGE_PATH.format(blure.url.to_url(self.id)) + '_thumb')
@classmethod
def _send_image(cls, filepath: Path) -> BaseHTTPResponse:
if not filepath.is_file():
return cls.not_found()
return raw(b'',
content_type='image',
headers={'X-Accel-Redirect': str(filepath)[4:]}, # FIXME: this should NOT be '[4:]' # noqa
status=200)
def orig(self):
return self._send_image(self.orig_path)
def thumb(self):
return self._send_image(self.thumb_path)
@staticmethod
def not_found():
return raw(_NOT_FOUND_IMAGE,
content_type=_NOT_FOUND_IMAGE_CONTENT_TYPE,
status=404)
async def delete_from_db(self):
async with blure.pool.acquire() as conn:
await conn.execute('DELETE FROM pics WHERE id=$1', self.id)
def delete_from_disk(self):
if self.orig_path.exists():
self.orig_path.unlink()
if self.thumb_path.exists():
self.thumb_path.unlink()
|