Initial commit

This commit is contained in:
Pünkösd Marcell 2020-11-27 05:44:32 +01:00
commit e0abc48efd
6 changed files with 173 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.swp
venv/*
*.pyc
__pycache__/*
__pycache__
*.wpr
*.log
.idea/

View File

@ -0,0 +1,76 @@
import tempfile
import os
import os.path
import shutil
from flask import Flask, current_app
from config import Config
from utils import write_file_to_fd_while_calculating_md5, create_md5_sum_for_file
from converter import convert_caff_to_tga, convert_tga_to_png
app = Flask(__name__)
app.config.from_object(Config)
def do_everything(workdir: str):
# Recieve file
uploaded_caff_fd, uploaded_caff_path = tempfile.mkstemp(suffix='.caff', dir=workdir)
uploaded_caff_md5sum = write_file_to_fd_while_calculating_md5(uploaded_caff_fd) # Throws overflow error
# Convert CAFF to TGA
converted_tga_fd, converted_tga_path = tempfile.mkstemp(suffix='.tga', dir=workdir)
os.close(converted_tga_fd)
convert_caff_to_tga(uploaded_caff_path, converted_tga_path)
if not os.path.isfile(converted_tga_path):
raise FileNotFoundError("Conversion output is missing")
# Convert TGA to PNG
converted_png_fd, converted_png_path = tempfile.mkstemp(suffix='.png', dir=workdir)
os.close(converted_png_fd)
convert_tga_to_png(converted_tga_path, converted_png_path)
if not os.path.isfile(converted_tga_path):
raise FileNotFoundError("Conversion output is missing")
converted_png_md5sum = create_md5_sum_for_file(converted_png_path)
# Send back converted file
converted_png_handle = open(converted_png_path, 'rb')
def stream_and_remove_file():
# This really is some black magic here
# When flask transmits the file ...
yield from converted_png_handle # <- It transmits from file handle
# After it's done the rest of this function will be called, so it cleans up after itself
converted_png_handle.close()
shutil.rmtree(workdir)
return current_app.response_class(
stream_and_remove_file(),
headers={
'X-request-checksum': uploaded_caff_md5sum,
'X-response-checksum': converted_png_md5sum,
'Content-type': 'image/png'
}
)
@app.route('/preview', methods=['POST'])
def perform_conversion():
workdir = tempfile.mkdtemp(prefix='caff')
try:
response = do_everything(workdir) # normally this would clean up workdir
except:
shutil.rmtree(workdir) # but sometimes it must be done externally
raise
return response
if __name__ == '__main__':
app.run(debug=True)

View File

@ -0,0 +1,9 @@
import os
class Config:
CAFF_PREVIEWER_BINARY = os.environ.get('CAFF_PREVIEWER_BINARY', '/usr/local/bin/caff_previewer')
IMAGEMAGICK_CONVERT_BINARY = os.environ.get('IMAGEMAGICK_CONVERT_BINARY', '/usr/bin/convert')
CONVERSION_TIMEOUT = int(os.environ.get('IMAGEMAGICK_CONVERT_BINARY', 30))
RECIEVE_CHUNKSIZE = int(os.environ.get('RECIEVE_CHUNKSIZE', 2048))
MAX_RECIEVE_SIZE = int(os.environ.get('MAX_RECIEVE_SIZE', 536870912)) # 512 MB

View File

@ -0,0 +1,41 @@
import subprocess
from flask import current_app
import werkzeug.exceptions
def run_abstract_converter(converter: str, source: str, destination: str) -> int:
"""
Just runs a binary and gives it two arguments
:param converter: the converter binary to run
:param source: source file
:param destination: destination file
:returns: exitcode of the converter
"""
completed_process = subprocess.run([converter, source, destination],
timeout=current_app.config['CONVERSION_TIMEOUT'], env={})
return completed_process.returncode
def convert_caff_to_tga(source: str, destination: str):
"""
This function uses caff_previewer to convert a CAFF file into a TGA file
:param source: path of the source TGA file (must exists)
:param destination: path of the destination TGA file (will be created)
"""
INTERNAL_ERROR_CODES = [0x01, 0x03, 0x04, 0x05, 0x32, 0x51]
ret = run_abstract_converter(current_app.config['CAFF_PREVIEWER_BINARY'], source, destination)
if ret in INTERNAL_ERROR_CODES:
raise RuntimeError(f"Caff Previewer returned an unexpected error code: {ret}")
elif ret != 0:
raise werkzeug.exceptions.BadRequest("CAFF format violation")
def convert_tga_to_png(source: str, destination: str):
"""
This function uses ImageMagick to convert a TGA file into a PNG file
:param source: path of the source TGA file (must exists)
:param destination: path of the destination TGA file (will be created)
"""
ret = run_abstract_converter(current_app.config['IMAGEMAGICK_CONVERT_BINARY'], source, destination)
if ret != 0:
raise RuntimeError(f"Image magick convert returned an unexpected error code: {ret}")

View File

@ -0,0 +1,37 @@
from flask import request, current_app
import werkzeug.exceptions
import hashlib
def write_file_to_fd_while_calculating_md5(fd: int) -> str:
chunksize = current_app.config['RECIEVE_CHUNKSIZE']
m = hashlib.md5()
total_recieved = 0
# Begin recieving the file
with open(fd, "bw") as f:
while True: # This is where uploading happens
chunk = request.stream.read(chunksize)
if len(chunk) == 0:
break
total_recieved += len(chunk)
if total_recieved > current_app.config['MAX_RECIEVE_SIZE']:
raise werkzeug.exceptions.RequestEntityTooLarge()
m.update(chunk)
f.write(chunk)
return m.hexdigest()
def create_md5_sum_for_file(fname):
m = hashlib.md5()
with open(fname, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
m.update(chunk)
return m.hexdigest()

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
Flask~=1.1.2
gunicorn~=20.0.4