From 15f693fc054e745a22b86dd9d94f4eea2ca2f543 Mon Sep 17 00:00:00 2001 From: Taka Date: Thu, 27 Dec 2018 14:39:36 +0000 Subject: [PATCH] Tightened up how we serve images. --- .../webapp/data/default-template-pack/atmm.j2 | 2 +- .../webapp/data/default-template-pack/baz.j2 | 2 +- .../extras/kgs/grenade-bundles.j2 | 2 +- .../extras/kgs/molotov-cocktails.j2 | 2 +- .../data/default-template-pack/mol-p.j2 | 2 +- .../webapp/data/default-template-pack/mol.j2 | 2 +- .../data/default-template-pack/ob_ordnance.j2 | 2 +- .../ob_ordnance_ma_notes.j2 | 2 +- .../default-template-pack/ob_ordnance_note.j2 | 2 +- .../data/default-template-pack/ob_setup.j2 | 2 +- .../default-template-pack/ob_vehicle_note.j2 | 2 +- .../data/default-template-pack/ob_vehicles.j2 | 2 +- .../ob_vehicles_ma_notes.j2 | 2 +- .../webapp/data/default-template-pack/pf.j2 | 2 +- .../webapp/data/default-template-pack/piat.j2 | 2 +- .../data/default-template-pack/players.j2 | 4 +- .../webapp/data/default-template-pack/psk.j2 | 2 +- vasl_templates/webapp/files.py | 7 ++- vasl_templates/webapp/snippets.py | 59 ++++++++++++------- vasl_templates/webapp/utils.py | 58 ++++++++++++++++++ vasl_templates/webapp/vo_notes.py | 29 ++------- 21 files changed, 123 insertions(+), 66 deletions(-) diff --git a/vasl_templates/webapp/data/default-template-pack/atmm.j2 b/vasl_templates/webapp/data/default-template-pack/atmm.j2 index a800135..7cc3295 100644 --- a/vasl_templates/webapp/data/default-template-pack/atmm.j2 +++ b/vasl_templates/webapp/data/default-template-pack/atmm.j2 @@ -16,7 +16,7 @@ td { margin: 0 ; padding: 0 ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}Anti-Tank Magnetic Mines + {%if PLAYER_FLAG%} {%endif%}Anti-Tank Magnetic Mines diff --git a/vasl_templates/webapp/data/default-template-pack/baz.j2 b/vasl_templates/webapp/data/default-template-pack/baz.j2 index 1b11cc1..5192a36 100644 --- a/vasl_templates/webapp/data/default-template-pack/baz.j2 +++ b/vasl_templates/webapp/data/default-template-pack/baz.j2 @@ -18,7 +18,7 @@ td.r { text-align: right ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}Bazooka {%if BAZ_TYPE%} ('{{BAZ_TYPE}}) {%endif%} + {%if PLAYER_FLAG%} {%endif%}Bazooka {%if BAZ_TYPE%} ('{{BAZ_TYPE}}) {%endif%} diff --git a/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 b/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 index c22878d..5069b54 100644 --- a/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 +++ b/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 @@ -14,7 +14,7 @@ td { margin: 0 ; padding: 0 ; } - {%if PLAYER_FLAGS["german"]%} {%endif%}Grenade Bundles + {%if PLAYER_FLAGS["german"]%} {%endif%}Grenade Bundles diff --git a/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 b/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 index 1835c33..7817327 100644 --- a/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 +++ b/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 @@ -15,7 +15,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; } - {%if PLAYER_FLAGS["german"]%} {%endif%}Molotov Cocktails + {%if PLAYER_FLAGS["german"]%} {%endif%}Molotov Cocktails diff --git a/vasl_templates/webapp/data/default-template-pack/mol-p.j2 b/vasl_templates/webapp/data/default-template-pack/mol-p.j2 index b6ba16e..053f103 100644 --- a/vasl_templates/webapp/data/default-template-pack/mol-p.j2 +++ b/vasl_templates/webapp/data/default-template-pack/mol-p.j2 @@ -19,7 +19,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}MOL Projector + {%if PLAYER_FLAG%} {%endif%}MOL Projector diff --git a/vasl_templates/webapp/data/default-template-pack/mol.j2 b/vasl_templates/webapp/data/default-template-pack/mol.j2 index 09cbb18..be5bdee 100644 --- a/vasl_templates/webapp/data/default-template-pack/mol.j2 +++ b/vasl_templates/webapp/data/default-template-pack/mol.j2 @@ -17,7 +17,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}Molotov Cocktail + {%if PLAYER_FLAG%} {%endif%}Molotov Cocktail diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 index 52052a8..7418a2f 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 @@ -20,7 +20,7 @@ sup { font-size: 75% ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance + {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance {%for ord in OB_ORDNANCE%} diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 index 2024f61..77f675b 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 @@ -21,7 +21,7 @@ sup { font-size: 75% ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance Notes + {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance Notes {%if OB_ORDNANCE_MA_NOTES%} diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 index f87db92..28a2bd8 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 @@ -13,7 +13,7 @@ padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}{{ORDNANCE_NAME}} + {%if PLAYER_FLAG%} {%endif%}{{ORDNANCE_NAME}} diff --git a/vasl_templates/webapp/data/default-template-pack/ob_setup.j2 b/vasl_templates/webapp/data/default-template-pack/ob_setup.j2 index 6cb9242..976291f 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_setup.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_setup.j2 @@ -14,7 +14,7 @@ font-weight: bold ; {%if OB_SETUP_WIDTH%} width: {{OB_SETUP_WIDTH}} ; {%endif%} "> - {%if PLAYER_FLAG%} {%endif%}{{OB_SETUP}} + {%if PLAYER_FLAG%} {%endif%}{{OB_SETUP}} diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 index 85eb718..26eeed3 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 @@ -13,7 +13,7 @@ padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}{{VEHICLE_NAME}} + {%if PLAYER_FLAG%} {%endif%}{{VEHICLE_NAME}} diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 index 88f32a3..f53d4df 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 @@ -20,7 +20,7 @@ sup { font-size: 75% ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicles + {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicles {%for veh in OB_VEHICLES%} diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 index 724dfca..d993fb6 100644 --- a/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 +++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 @@ -21,7 +21,7 @@ sup { font-size: 75% ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicle Notes + {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicle Notes {%if OB_VEHICLES_MA_NOTES%} diff --git a/vasl_templates/webapp/data/default-template-pack/pf.j2 b/vasl_templates/webapp/data/default-template-pack/pf.j2 index d59f446..f0c2262 100644 --- a/vasl_templates/webapp/data/default-template-pack/pf.j2 +++ b/vasl_templates/webapp/data/default-template-pack/pf.j2 @@ -18,7 +18,7 @@ td.r { text-align: right ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}Panzerfaust + {%if PLAYER_FLAG%} {%endif%}Panzerfaust diff --git a/vasl_templates/webapp/data/default-template-pack/piat.j2 b/vasl_templates/webapp/data/default-template-pack/piat.j2 index 5a12515..02b9aa0 100644 --- a/vasl_templates/webapp/data/default-template-pack/piat.j2 +++ b/vasl_templates/webapp/data/default-template-pack/piat.j2 @@ -18,7 +18,7 @@ td.r { text-align: right ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}PIAT + {%if PLAYER_FLAG%} {%endif%}PIAT diff --git a/vasl_templates/webapp/data/default-template-pack/players.j2 b/vasl_templates/webapp/data/default-template-pack/players.j2 index 879dab5..1539db5 100644 --- a/vasl_templates/webapp/data/default-template-pack/players.j2 +++ b/vasl_templates/webapp/data/default-template-pack/players.j2 @@ -10,8 +10,8 @@ "> - {%if PLAYER_FLAG_1%} {%endif%}{{PLAYER_1_NAME}}:
- {%if PLAYER_FLAG_2%} {%endif%}{{PLAYER_2_NAME}}: + {%if PLAYER_FLAG_1%} {%endif%}{{PLAYER_1_NAME}}:
+ {%if PLAYER_FLAG_2%} {%endif%}{{PLAYER_2_NAME}}: ELR: {{PLAYER_1_ELR}}
ELR: {{PLAYER_2_ELR}} diff --git a/vasl_templates/webapp/data/default-template-pack/psk.j2 b/vasl_templates/webapp/data/default-template-pack/psk.j2 index 398eb50..a2fc7f8 100644 --- a/vasl_templates/webapp/data/default-template-pack/psk.j2 +++ b/vasl_templates/webapp/data/default-template-pack/psk.j2 @@ -18,7 +18,7 @@ td.r { text-align: right ; } padding: 2px 5px ; font-weight: bold ; "> - {%if PLAYER_FLAG%} {%endif%}Panzerschrek + {%if PLAYER_FLAG%} {%endif%}Panzerschrek diff --git a/vasl_templates/webapp/files.py b/vasl_templates/webapp/files.py index 07e2a76..4bff877 100644 --- a/vasl_templates/webapp/files.py +++ b/vasl_templates/webapp/files.py @@ -10,6 +10,7 @@ from flask import send_file, send_from_directory, jsonify, redirect, url_for, ab from vasl_templates.webapp import app from vasl_templates.webapp.file_server.vasl_mod import get_vasl_mod +from vasl_templates.webapp.utils import resize_image_response # --------------------------------------------------------------------- @@ -24,6 +25,9 @@ class FileServer: def serve_file( self, path ): """Serve a file.""" + # NOTE: We return a Flask Response object, instead of the file data, so that (1) we can use + # send_from_directory() and (2) if we have to download a file from a URL, we can include + # the MIME type in what we return. if FileServer.is_remote_path( self.base_dir ): url = "{}/{}".format( self.base_dir, path ) # NOTE: We download the target file and serve it ourself (instead of just redirecting) @@ -55,7 +59,8 @@ def get_user_file( path ): dname = app.config.get( "USER_FILES_DIR" ) if not dname: abort( 404 ) - return FileServer( dname ).serve_file( path ) + resp = FileServer( dname ).serve_file( path ) + return resize_image_response( resp ) # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/snippets.py b/vasl_templates/webapp/snippets.py index e98bdfb..bde5a5b 100644 --- a/vasl_templates/webapp/snippets.py +++ b/vasl_templates/webapp/snippets.py @@ -6,6 +6,7 @@ import re import zipfile import io import base64 +import threading from flask import request, jsonify, send_file, abort from PIL import Image @@ -137,6 +138,9 @@ def make_snippet_image(): # --------------------------------------------------------------------- +_flag_image_cache = {} +_flag_image_cache_lock = threading.Lock() + @app.route( "/flags/" ) def get_flag( nat ): """Get a flag image.""" @@ -145,25 +149,36 @@ def get_flag( nat ): if not re.search( "^[-a-z]+$", nat ): abort( 404 ) - fname = "static/images/flags/{}.png".format( nat ) - with app.open_resource( fname, "rb" ) as fp: - - # load the image - img = Image.open( fp ) - - # check if we should resize the image - # NOTE: Resizing images in the HTML snippets looks dreadful (presumably - # because VASSAL's HTML engine is so ancient), so we do it ourself :-/ - height = request.args.get( "height" ) - if height: - height = int( height ) - if height > 0: - width = img.size[0] / ( float(img.size[1]) / height ) - width = int( width + 0.5 ) - img = img.resize( (width,height), Image.ANTIALIAS ) - - # return the image - buf = io.BytesIO() - img.save( buf, format="PNG" ) - buf.seek( 0 ) - return send_file( buf, mimetype="image/png" ) + # check how we should resize the image + # NOTE: Resizing images in the HTML snippets looks dreadful (presumably + # because VASSAL's HTML engine is so ancient), so we do it ourself :-/ + default_height = app.config.get( "DEFAULT_FLAG_HEIGHT", 11 ) + height = int( request.args.get( "height", default_height ) ) + if height <= 0: + abort( 400 ) + + with _flag_image_cache_lock: + + # check if we have the image in the cache + cache_key = ( nat, height ) + if cache_key not in _flag_image_cache: + + # nope - load it + fname = "static/images/flags/{}.png".format( nat ) + with app.open_resource( fname, "rb" ) as fp: + img = Image.open( fp ) + # resize the image + height = int( height ) + if height > 0: + width = img.size[0] / ( float(img.size[1]) / height ) + width = int( width + 0.5 ) + img = img.resize( (width,height), Image.ANTIALIAS ) + # add the image to the cache + buf = io.BytesIO() + img.save( buf, format="PNG" ) + buf.seek( 0 ) + _flag_image_cache[ cache_key ] = buf.read() + + # return the flag image + img_data =_flag_image_cache[ cache_key ] + return send_file( io.BytesIO(img_data), mimetype="image/png" ) diff --git a/vasl_templates/webapp/utils.py b/vasl_templates/webapp/utils.py index 2af3e7f..6887631 100644 --- a/vasl_templates/webapp/utils.py +++ b/vasl_templates/webapp/utils.py @@ -1,10 +1,14 @@ """ Miscellaneous utilities. """ import os +import io import tempfile import pathlib from collections import defaultdict +from flask import request, Response, send_file +from PIL import Image + # --------------------------------------------------------------------- class MsgStore: @@ -75,6 +79,60 @@ class TempFile: # --------------------------------------------------------------------- +def resize_image_response( resp, default_width=None, default_height=None, default_scaling=None ): + """Resize an image that will be returned as a Flask response.""" + + assert isinstance( resp, Response ) + + def get_image(): + """Get the the image from the Flask response that was passed in.""" + resp.direct_passthrough = False + buf = io.BytesIO() + buf.write( resp.get_data() ) + buf.seek( 0 ) + return Image.open( buf ) + + # check if the caller specified a width and/or height + width = request.args.get( "width", default_width ) + height = request.args.get( "height", default_height ) + if width and height: + # width and height were specified, just use them as-is + img = get_image() + width = int( width ) + height = int( height ) + elif width and not height: + # width only, calculate the height + img = get_image() + aspect_ratio = float(img.size[0]) / float(img.size[1]) + height = int(width) / aspect_ratio + elif not width and height: + # height only, calculate the width + img = get_image() + aspect_ratio = float(img.size[0]) / float(img.size[1]) + width = int(height) * aspect_ratio + elif not width and not height: + # check if the caller specified a scaling factor + scaling = request.args.get( "scaling", default_scaling ) + if scaling and scaling != 100: + img = get_image() + width = img.size[0] * float(scaling)/100 + height = img.size[1] * float(scaling)/100 + + # check if we need to resize the image + if width or height: + assert width and height + # yup - make it so + img = img.resize( (int(width),int(height)), Image.ANTIALIAS ) + buf = io.BytesIO() + img.save( buf, format="PNG" ) + buf.seek( 0 ) + return send_file( buf, mimetype="image/png" ) + else: + # nope - return the image as-is + return resp + +# --------------------------------------------------------------------- + def change_extn( fname, extn ): """Change a filename's extension.""" return pathlib.Path( fname ).with_suffix( extn ) diff --git a/vasl_templates/webapp/vo_notes.py b/vasl_templates/webapp/vo_notes.py index d300fbc..4b4a894 100644 --- a/vasl_templates/webapp/vo_notes.py +++ b/vasl_templates/webapp/vo_notes.py @@ -3,16 +3,14 @@ import os import threading -import io import logging from collections import defaultdict -from flask import request, render_template, send_file, jsonify, abort -from PIL import Image +from flask import render_template, jsonify, abort from vasl_templates.webapp import app from vasl_templates.webapp.files import FileServer -from vasl_templates.webapp.utils import is_image_file +from vasl_templates.webapp.utils import is_image_file, resize_image_response _vo_notes_lock = threading.RLock() # nb: this controls the cached V/O notes and the FileServer _cached_vo_notes = None @@ -127,27 +125,8 @@ def get_vo_note( vo_type, nat, key ): fname = vo_notes.get( nat, {} ).get( key ) resp = _vo_notes_file_server.serve_file( fname ) - # check if we should resize the file - scaling = request.args.get( "scaling" ) # nb: allow individual notes to set their scaling - if not scaling: - scaling = app.config.get( "CHAPTER_H_NOTE_SCALING", 100 ) - if scaling == 100: - # nope - just return the file as it is - return resp - else: - # yup - make it so - buf = io.BytesIO() - resp.direct_passthrough = False - buf.write( resp.get_data() ) - buf.seek( 0 ) - img = Image.open( buf ) - width = int( img.size[0] * float(scaling) / 100 ) - height = int( img.size[1] * float(scaling) / 100 ) - img = img.resize( (width,height), Image.ANTIALIAS ) - buf = io.BytesIO() - img.save( buf, format="PNG" ) - buf.seek( 0 ) - return send_file( buf, mimetype="image/png" ) + default_scaling = app.config.get( "CHAPTER_H_NOTE_SCALING", 100 ) + return resize_image_response( resp, default_scaling=default_scaling ) # ---------------------------------------------------------------------