backend: cache etag for thumbnails; deduplicate code for sending image
continuous-integration/drone/push Build is passing Details

master
Simon Bruder 2019-07-19 17:26:09 +00:00
parent 9800befe37
commit 6eabe96a70
No known key found for this signature in database
GPG Key ID: 6F03E0000CC5B62F
2 changed files with 24 additions and 19 deletions

View File

@ -2,6 +2,7 @@ from PIL import Image
from io import BytesIO from io import BytesIO
from mimetypes import types_map as mimetypes from mimetypes import types_map as mimetypes
from zipfile import ZipFile from zipfile import ZipFile
from zlib import crc32
import os.path import os.path
import sqlite3 import sqlite3
import werkzeug.exceptions as exceptions import werkzeug.exceptions as exceptions
@ -20,23 +21,28 @@ def dict_factory(cursor, row):
def generate_thumbnail(filepath): def generate_thumbnail(filepath):
if filepath in thumbnail_cache: if filepath not in thumbnail_cache:
thumbnail = BytesIO()
thumbnail.write(thumbnail_cache[filepath])
else:
image = Image.open(filepath) image = Image.open(filepath)
image.thumbnail((512, 512)) image.thumbnail((512, 512))
thumbnail = BytesIO() thumbnail = BytesIO()
image.save(thumbnail, 'webp') image.save(thumbnail, 'webp')
thumbnail.seek(0) thumbnail.seek(0)
thumbnail_cache[filepath] = thumbnail.read()
thumbnail.seek(0) data = thumbnail.read()
etag = str(crc32(data))
return { thumbnail_cache[filepath] = {
'data': thumbnail, 'data_raw': data,
'mimetype': 'image/webp' 'etag': etag,
} 'mimetype': 'image/webp'
}
thumbnail = thumbnail_cache[filepath]
thumbnail['data'] = BytesIO()
thumbnail['data'].write(thumbnail['data_raw'])
thumbnail['data'].seek(0)
return thumbnail
class CalibreDB: class CalibreDB:

View File

@ -3,7 +3,6 @@ from backend import CalibreDB
from flask import Flask, jsonify, send_from_directory, send_file, render_template, request from flask import Flask, jsonify, send_from_directory, send_file, render_template, request
from flask_cors import CORS from flask_cors import CORS
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter
from zlib import crc32
import os import os
@ -11,10 +10,7 @@ def send_from_cwd(filename):
return send_from_directory(os.getcwd(), filename) return send_from_directory(os.getcwd(), filename)
def send_file_with_etag(fp, etag=None, **kwargs): def send_file_with_etag(fp, etag, **kwargs):
if etag is None:
etag = str(crc32(fp.read()))
if request.if_none_match and etag in request.if_none_match: if request.if_none_match and etag in request.if_none_match:
return '', 304 return '', 304
@ -24,6 +20,10 @@ def send_file_with_etag(fp, etag=None, **kwargs):
return response return response
def send_image(image):
return send_file_with_etag(image['data'], image['etag'], mimetype=image['mimetype'])
app = Flask(__name__, static_folder='frontend/dist/static', template_folder='frontend/dist') app = Flask(__name__, static_folder='frontend/dist/static', template_folder='frontend/dist')
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 604800 # 1 week app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 604800 # 1 week
CORS(app) CORS(app)
@ -51,7 +51,7 @@ def get_series_cover(series_id):
@app.route('/api/series/<int:series_id>/cover/thumbnail') @app.route('/api/series/<int:series_id>/cover/thumbnail')
def get_series_cover_thumbnail(series_id): def get_series_cover_thumbnail(series_id):
thumbnail = db.get_series_cover_thumbnail(series_id) thumbnail = db.get_series_cover_thumbnail(series_id)
return send_file_with_etag(thumbnail['data'], mimetype=thumbnail['mimetype']) return send_image(thumbnail)
@app.route('/api/series/<int:series_id>') @app.route('/api/series/<int:series_id>')
@ -67,7 +67,7 @@ def get_volume_cover(volume_id):
@app.route('/api/volume/<int:volume_id>/cover/thumbnail') @app.route('/api/volume/<int:volume_id>/cover/thumbnail')
def get_volume_cover_thumbnail(volume_id): def get_volume_cover_thumbnail(volume_id):
thumbnail = db.get_volume_cover_thumbnail(volume_id) thumbnail = db.get_volume_cover_thumbnail(volume_id)
return send_file_with_etag(thumbnail['data'], mimetype=thumbnail['mimetype']) return send_image(thumbnail)
@app.route('/api/volume/<int:volume_id>') @app.route('/api/volume/<int:volume_id>')
@ -78,8 +78,7 @@ def get_volume_info(volume_id):
@app.route('/api/volume/<int:volume_id>/page/<int:page_number>') @app.route('/api/volume/<int:volume_id>/page/<int:page_number>')
def get_volume_page(volume_id, page_number): def get_volume_page(volume_id, page_number):
page = db.get_volume_page(volume_id, page_number) page = db.get_volume_page(volume_id, page_number)
response = send_file_with_etag(page['data'], etag=page['etag'], mimetype=page['mimetype']) return send_image(page)
return response
if __name__ == '__main__': if __name__ == '__main__':