aboutsummaryrefslogtreecommitdiff
"""Entry-point module"""
import os
import mimetypes
from typing import Tuple
from zipfile import ZipFile, is_zipfile

from werkzeug.utils import secure_filename
from flask import (
    flash,
    request,
    url_for,
    redirect,
    Blueprint,
    render_template,
    current_app as app,
    send_from_directory)

from qc_app.db import species
from qc_app.db_utils import with_db_connection

entrybp = Blueprint("entry", __name__)

@entrybp.route("/favicon.ico", methods=["GET"])
def favicon():
    """Return the favicon."""
    return send_from_directory(os.path.join(app.root_path, "static"),
                               "images/CITGLogo.png",
                               mimetype="image/png")


def errors(rqst) -> Tuple[str, ...]:
    """Return a tuple of the errors found in the request `rqst`. If no error is
    found, then an empty tuple is returned."""
    def __filetype_error__():
        return (
            ("Invalid file type provided.",)
            if rqst.form.get("filetype") not in ("average", "standard-error")
            else tuple())

    def __file_missing_error__():
        return (
            ("No file was uploaded.",)
            if ("qc_text_file" not in rqst.files or
                rqst.files["qc_text_file"].filename == "")
            else tuple())

    def __file_mimetype_error__():
        text_file = rqst.files["qc_text_file"]
        return (
            (
                ("Invalid file! Expected a tab-separated-values file, or a zip "
                 "file of the a tab-separated-values file."),)
            if text_file.mimetype not in (
                    "text/plain", "text/tab-separated-values",
                    "application/zip")
            else tuple())

    return (
        __filetype_error__() +
        (__file_missing_error__() or __file_mimetype_error__()))

def zip_file_errors(filepath, upload_dir) -> Tuple[str, ...]:
    """Check the uploaded zip file for errors."""
    zfile_errors: Tuple[str, ...] = tuple()
    if is_zipfile(filepath):
        with ZipFile(filepath, "r") as zfile:
            infolist = zfile.infolist()
            if len(infolist) != 1:
                zfile_errors = zfile_errors + (
                    ("Expected exactly one (1) member file within the uploaded zip "
                     f"file. Got {len(infolist)} member files."),)
            if len(infolist) == 1 and infolist[0].is_dir():
                zfile_errors = zfile_errors + (
                    ("Expected a member text file in the uploaded zip file. Got a "
                     "directory/folder."),)

            if len(infolist) == 1 and not infolist[0].is_dir():
                zfile.extract(infolist[0], path=upload_dir)
                mime = mimetypes.guess_type(f"{upload_dir}/{infolist[0].filename}")
                if mime[0] != "text/tab-separated-values":
                    zfile_errors = zfile_errors + (
                        ("Expected the member text file in the uploaded zip file to"
                         " be a tab-separated file."),)

    return zfile_errors

@entrybp.route("/", methods=["GET"])
def index():
    """Load the landing page"""
    return render_template("index.html")

@entrybp.route("/upload", methods=["GET", "POST"])
def upload_file():
    """Enables uploading the files"""
    if request.method == "GET":
        return render_template(
            "select_species.html", species=with_db_connection(species))

    upload_dir = app.config["UPLOAD_FOLDER"]
    request_errors = errors(request)
    if request_errors:
        for error in request_errors:
            flash(error, "alert-danger error-expr-data")
        return redirect(url_for("entry.upload_file"))

    filename = secure_filename(request.files["qc_text_file"].filename)
    if not os.path.exists(upload_dir):
        os.mkdir(upload_dir)

    filepath = os.path.join(upload_dir, filename)
    request.files["qc_text_file"].save(os.path.join(upload_dir, filename))

    zip_errors = zip_file_errors(filepath, upload_dir)
    if zip_errors:
        for error in zip_errors:
            flash(error, "alert-danger error-expr-data")
        return redirect(url_for("entry.upload_file"))

    return redirect(url_for("parse.parse",
                            speciesid=request.form["speciesid"],
                            filename=filename,
                            filetype=request.form["filetype"]))

@entrybp.route("/data-review", methods=["GET"])
def data_review():
    """Provide some help on data expectations to the user."""
    return render_template("data_review.html")