views.py 26 KB
Newer Older
1
from dgenies import app, app_title, app_folder, config_reader, mailer, APP_DATA, MODE
2

Floreal Cabanettes's avatar
Floreal Cabanettes committed
3
import os
4
5
import time
import datetime
6
import shutil
7
import re
8
import threading
Floreal Cabanettes's avatar
Floreal Cabanettes committed
9
from flask import render_template, request, url_for, jsonify, Response, abort, send_file, Markup
10
from pathlib import Path
11
12
13
14
15
from dgenies.lib.paf import Paf
from dgenies.lib.job_manager import JobManager
from dgenies.lib.functions import Functions, ALLOWED_EXTENSIONS
from dgenies.lib.upload_file import UploadFile
from dgenies.lib.fasta import Fasta
Floreal Cabanettes's avatar
Floreal Cabanettes committed
16
17
from markdown import Markdown
from markdown.extensions.toc import TocExtension
18
19
20
if MODE == "webserver":
    from dgenies.database import Session, Gallery
    from peewee import DoesNotExist
21

22

Floreal Cabanettes's avatar
Floreal Cabanettes committed
23
24
25
26
@app.context_processor
def global_templates_variables():
    return {
        "title": app_title,
27
28
        "mode": MODE,
        "all_jobs": Functions.get_list_all_jobs(MODE)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
29
30
31
    }


32
33
34
# Main
@app.route("/", methods=['GET'])
def main():
35
36
37
38
39
40
    if MODE == "webserver":
        pict = Gallery.select().order_by("id")
        if len(pict) > 0:
            pict = pict[0].picture
        else:
            pict = None
Floreal Cabanettes's avatar
Floreal Cabanettes committed
41
42
    else:
        pict = None
Floreal Cabanettes's avatar
Floreal Cabanettes committed
43
    return render_template("index.html", menu="index", pict=pict)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
44
45
46
47


@app.route("/run", methods=['GET'])
def run():
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    if MODE == "webserver":
        with Session.connect():
            s_id = Session.new()
    else:
        upload_folder = Functions.random_string(20)
        tmp_dir = config_reader.upload_folder
        upload_folder_path = os.path.join(tmp_dir, upload_folder)
        while os.path.exists(upload_folder_path):
            upload_folder = Functions.random_string(20)
            upload_folder_path = os.path.join(tmp_dir, upload_folder)
        s_id = upload_folder
    id_job = Functions.random_string(5) + "_" + datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d%H%M%S')
    if "id_job" in request.args:
        id_job = request.args["id_job"]
    email = ""
    if "email" in request.args:
        email = request.args["email"]
Floreal Cabanettes's avatar
Floreal Cabanettes committed
65
    return render_template("run.html", id_job=id_job, email=email,
66
                           menu="run", allowed_ext=ALLOWED_EXTENSIONS, s_id=s_id,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
67
                           max_upload_file_size=config_reader.max_upload_file_size)
68
69


70
71
@app.route("/run-test", methods=['GET'])
def run_test():
72
73
74
75
76
77
78
    if MODE == "webserver":
        print(config_reader.allowed_ip_tests)
        if request.remote_addr not in config_reader.allowed_ip_tests:
            return abort(404)
        with Session.connect():
            return Session.new()
    return abort(500)
79
80


81
82
83
# Launch analysis
@app.route("/launch_analysis", methods=['POST'])
def launch_analysis():
84
85
86
87
88
89
90
91
92
93
94
95
    if MODE == "webserver":
        try:
            with Session.connect():
                session = Session.get(s_id=request.form["s_id"])
        except DoesNotExist:
            return jsonify({"success": False, "errors": ["Session has expired. Please refresh the page and try again"]})
        upload_folder = session.upload_folder
        # Delete session:
        session.delete_instance()
    else:
        upload_folder = request.form["s_id"]

96
97
    id_job = request.form["id_job"]
    email = request.form["email"]
98
99
100
101
    file_query = request.form["query"]
    file_query_type = request.form["query_type"]
    file_target = request.form["target"]
    file_target_type = request.form["target_type"]
102
103
104

    # Check form:
    form_pass = True
105
    errors = []
106
    if id_job == "":
107
        errors.append("Id of job not given")
108
109
        form_pass = False

110
111
112
113
    if MODE == "webserver":
        if email == "":
            errors.append("Email not given")
            form_pass = False
Floreal Cabanettes's avatar
Floreal Cabanettes committed
114
        elif not re.match(r"^[\w.\-]+@[\w\-.]{2,}\.[a-z]{2,4}$", email):
115
116
            errors.append("Email is invalid")
            form_pass = False
117
118
    if file_target == "":
        errors.append("No target fasta selected")
119
120
121
122
        form_pass = False

    # Form pass
    if form_pass:
123
        # Get final job id:
124
        id_job = re.sub('[^A-Za-z0-9_\-]+', '', id_job.replace(" ", "_"))
125
        id_job_orig = id_job
126
        i = 2
127
        while os.path.exists(os.path.join(APP_DATA, id_job)):
128
129
            id_job = id_job_orig + ("_%d" % i)
            i += 1
130

131
        folder_files = os.path.join(APP_DATA, id_job)
132
133
134
        os.makedirs(folder_files)

        # Save files:
135
136
137
        query = None
        if file_query != "":
            query_name = os.path.splitext(file_query.replace(".gz", ""))[0] if file_query_type == "local" else None
138
            query_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, file_query) \
139
                if file_query_type == "local" else file_query
140
141
142
            if file_query_type == "local" and not os.path.exists(query_path):
                errors.append("Query file not correct!")
                form_pass = False
143
144
            query = Fasta(name=query_name, path=query_path, type_f=file_query_type)
        target_name = os.path.splitext(file_target.replace(".gz", ""))[0] if file_target_type == "local" else None
145
        target_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, file_target) \
146
            if file_target_type == "local" else file_target
147
148
149
        if file_target_type == "local" and not os.path.exists(target_path):
            errors.append("Target file not correct!")
            form_pass = False
150
        target = Fasta(name=target_name, path=target_path, type_f=file_target_type)
151

152
153
154
155
156
157
158
159
160
        if form_pass:
            # Launch job:
            job = JobManager(id_job, email, query, target, mailer)
            if MODE == "webserver":
                job.launch()
            else:
                job.launch_standalone()
            return jsonify({"success": True, "redirect": url_for(".status", id_job=id_job)})
    if not form_pass:
161
        return jsonify({"success": False, "errors": errors})
162
163
164
165
166
167


# Status of a job
@app.route('/status/<id_job>', methods=['GET'])
def status(id_job):
    job = JobManager(id_job)
168
169
170
171
172
173
174
175
176
177
178
179
    j_status = job.status()
    mem_peak = j_status["mem_peak"] if "mem_peak" in j_status else None
    if mem_peak is not None:
        mem_peak = "%.1f G" % (mem_peak / 1024.0 / 1024.0)
    time_e = j_status["time_elapsed"] if "time_elapsed" in j_status else None
    if time_e is not None:
        if time_e < 60:
            time_e = "%d secs" % time_e
        else:
            minutes = time_e // 60
            seconds = time_e - minutes * 60
            time_e = "%d min %d secs" % (minutes, seconds)
180
181
182
183
184
185
186
187
188
    format = request.args.get("format")
    if format is not None and format == "json":
        return jsonify({
            "status": j_status["status"],
            "error": j_status["error"].replace("#ID#", ""),
            "id_job": id_job,
            "mem_peak": mem_peak,
            "time_elapsed": time_e
        })
Floreal Cabanettes's avatar
Floreal Cabanettes committed
189
    return render_template("status.html", status=j_status["status"],
190
191
                           error=j_status["error"].replace("#ID#", ""),
                           id_job=id_job, menu="results", mem_peak=mem_peak,
192
                           time_elapsed=time_e,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
193
                           query_filtered=job.is_query_filtered(), target_filtered=job.is_target_filtered())
194
195


Floreal Cabanettes's avatar
Floreal Cabanettes committed
196
# Results path
197
@app.route("/result/<id_res>", methods=['GET'])
198
def result(id_res):
Floreal Cabanettes's avatar
Floreal Cabanettes committed
199
    return render_template("result.html", id=id_res, menu="result", current_result=id_res,
200
                           is_gallery=Functions.is_in_gallery(id_res, MODE))
201
202


203
204
@app.route("/gallery", methods=['GET'])
def gallery():
205
    if MODE == "webserver":
Floreal Cabanettes's avatar
Floreal Cabanettes committed
206
        return render_template("gallery.html", items=Functions.get_gallery_items(), menu="gallery")
207
    return abort(404)
208
209
210
211


@app.route("/gallery/<filename>", methods=['GET'])
def gallery_file(filename):
212
213
214
215
216
217
    if MODE == "webserver":
        try:
            return send_file(os.path.join(config_reader.app_data, "gallery", filename))
        except FileNotFoundError:
            abort(404)
    return abort(404)
218
219


220
def get_file(file, gzip=False):  # pragma: no cover
Floreal Cabanettes's avatar
Floreal Cabanettes committed
221
222
223
224
225
226
    try:
        # Figure out how flask returns static files
        # Tried:
        # - render_template
        # - send_file
        # This should not be so non-obvious
227
        return open(file, "rb" if gzip else "r").read()
228
229
    except FileNotFoundError:
        abort(404)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
230
    except IOError as exc:
231
        print(exc.__traceback__)
232
        abort(500)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
233
234


Floreal Cabanettes's avatar
Floreal Cabanettes committed
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@app.route("/documentation/run", methods=['GET'])
def documentation_run():
    with open(os.path.join(app_folder, "doc_run.md" if MODE == "webserver" else "doc_run_standalone.md"), "r",
              encoding='utf-8') as install_instr:
        content = install_instr.read()
    md = Markdown(extensions=[TocExtension(baselevel=1)])
    max_upload_file_size = config_reader.max_upload_file_size
    if max_upload_file_size == -1:
        max_upload_file_size = "no limit"
    else:
        max_upload_file_size = Functions.get_readable_size(max_upload_file_size, 0)
    max_upload_size = config_reader.max_upload_size
    if max_upload_size == -1:
        max_upload_size = "no limit"
    else:
        max_upload_size = Functions.get_readable_size(max_upload_size, 0)
    max_upload_size_ava = config_reader.max_upload_size_ava
    if max_upload_size_ava == -1:
        max_upload_size_ava = "no limit"
    else:
        max_upload_size_ava = Functions.get_readable_size(max_upload_size_ava, 0)
    content = Markup(md.convert(content)).replace("###size###", max_upload_file_size)\
                                         .replace("###size_unc###", max_upload_size)\
                                         .replace("###size_ava###", max_upload_size_ava)
    toc = Markup(md.toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
260
    return render_template("documentation.html", menu="documentation", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
261
262


263
264
265
266
267
268
269
270
271
272
273
@app.route("/documentation/result", methods=['GET'])
def documentation_result():
    with open(os.path.join(app_folder, "user_manual.md"), "r",
              encoding='utf-8') as install_instr:
        content = install_instr.read()
    md = Markdown(extensions=[TocExtension(baselevel=1)])
    content = Markup(md.convert(content))
    toc = Markup(md.toc)
    return render_template("documentation.html", menu="documentation", content=content, toc=toc)


Floreal Cabanettes's avatar
Floreal Cabanettes committed
274
275
@app.route("/install", methods=['GET'])
def install():
276
    with open(os.path.join(app_folder, "INSTALL.md"), "r", encoding='utf-8') as install_instr:
Floreal Cabanettes's avatar
Floreal Cabanettes committed
277
278
279
280
        content = install_instr.read()
    md = Markdown(extensions=[TocExtension(baselevel=1)])
    content = Markup(md.convert(content))
    toc = Markup(md.toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
281
    return render_template("documentation.html", menu="install", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
282

283
284
285
286
@app.route("/contact", methods=['GET'])
def contact():
    return render_template("contact.html", menu="contact")

Floreal Cabanettes's avatar
Floreal Cabanettes committed
287

Floreal Cabanettes's avatar
Floreal Cabanettes committed
288
289
290
291
292
293
294
295
296
297
298
@app.route("/paf/<id_res>", methods=['GET'])
def download_paf(id_res):
    map_file = os.path.join(APP_DATA, id_res, "map.paf.sorted")
    if not os.path.exists(map_file):
        map_file = os.path.join(APP_DATA, id_res, "map.paf")
    if not os.path.exists(map_file):
        abort(404)
    content = get_file(map_file)
    return Response(content, mimetype="text/plain")


Floreal Cabanettes's avatar
Floreal Cabanettes committed
299
# Get graph (ajax request)
300
301
302
@app.route('/get_graph', methods=['POST'])
def get_graph():
    id_f = request.form["id"]
303
304
305
    paf = os.path.join(APP_DATA, id_f, "map.paf")
    idx1 = os.path.join(APP_DATA, id_f, "query.idx")
    idx2 = os.path.join(APP_DATA, id_f, "target.idx")
306

307
    paf = Paf(paf, idx1, idx2)
308

309
310
    if paf.parsed:
        res = paf.get_d3js_data()
311
        res["success"] = True
312
313
314
315
316
317
        return jsonify(res)
    return jsonify({"success": False, "message": paf.error})


@app.route('/sort/<id_res>', methods=['POST'])
def sort_graph(id_res):
318
    if not os.path.exists(os.path.join(APP_DATA, id_res, ".all-vs-all")):
319
320
321
        paf_file = os.path.join(APP_DATA, id_res, "map.paf")
        idx1 = os.path.join(APP_DATA, id_res, "query.idx")
        idx2 = os.path.join(APP_DATA, id_res, "target.idx")
322
        paf = Paf(paf_file, idx1, idx2, False)
323
324
325
326
327
328
329
        paf.sort()
        if paf.parsed:
            res = paf.get_d3js_data()
            res["success"] = True
            return jsonify(res)
        return jsonify({"success": False, "message": paf.error})
    return jsonify({"success": False, "message": "Sort is not available for All-vs-All mode"})
Floreal Cabanettes's avatar
Floreal Cabanettes committed
330

Floréal Cabanettes's avatar
Floréal Cabanettes committed
331

332
333
334
335
@app.route('/reverse-contig/<id_res>', methods=['POST'])
def reverse_contig(id_res):
    contig_name = request.form["contig"]
    if not os.path.exists(os.path.join(APP_DATA, id_res, ".all-vs-all")):
336
337
338
        paf_file = os.path.join(APP_DATA, id_res, "map.paf")
        idx1 = os.path.join(APP_DATA, id_res, "query.idx")
        idx2 = os.path.join(APP_DATA, id_res, "target.idx")
339
340
341
342
343
344
345
346
347
348
        paf = Paf(paf_file, idx1, idx2, False)
        paf.reverse_contig(contig_name)
        if paf.parsed:
            res = paf.get_d3js_data()
            res["success"] = True
            return jsonify(res)
        return jsonify({"success": False, "message": paf.error})
    return jsonify({"success": False, "message": "Sort is not available for All-vs-All mode"})


349
350
@app.route('/freenoise/<id_res>', methods=['POST'])
def free_noise(id_res):
351
352
353
    paf_file = os.path.join(APP_DATA, id_res, "map.paf")
    idx1 = os.path.join(APP_DATA, id_res, "query.idx")
    idx2 = os.path.join(APP_DATA, id_res, "target.idx")
354
355
356
357
358
359
360
361
362
    paf = Paf(paf_file, idx1, idx2, False)
    paf.parse_paf(noise=request.form["noise"] == "1")
    if paf.parsed:
        res = paf.get_d3js_data()
        res["success"] = True
        return jsonify(res)
    return jsonify({"success": False, "message": paf.error})


363
364
@app.route('/get-fasta-query/<id_res>', methods=['POST'])
def build_fasta(id_res):
365
    res_dir = os.path.join(APP_DATA, id_res)
366
367
    lock_query = os.path.join(res_dir, ".query-fasta-build")
    is_sorted = os.path.exists(os.path.join(res_dir, ".sorted"))
368
    compressed = request.form["gzip"].lower() == "true"
369
370
371
372
373
    query_fasta = Functions.get_fasta_file(res_dir, "query", is_sorted)
    if query_fasta is not None:
        if is_sorted and not query_fasta.endswith(".sorted"):
            # Do the sort
            Path(lock_query).touch()
374
            if not compressed or MODE == "standalone":  # If compressed, it will took a long time, so not wait
375
                Path(lock_query + ".pending").touch()
376
377
378
379
380
            thread = threading.Timer(1, Functions.sort_fasta, kwargs={
                "job_name": id_res,
                "fasta_file": query_fasta,
                "index_file": os.path.join(res_dir, "query.idx.sorted"),
                "lock_file": lock_query,
381
                "compress": compressed,
382
383
384
                "mailer": mailer
            })
            thread.start()
385
            if not compressed or MODE == "standalone":
386
387
                i = 0
                time.sleep(5)
388
                while os.path.exists(lock_query) and (i < 2 or MODE == "standalone"):
389
390
                    i += 1
                    time.sleep(5)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
391
                os.remove(lock_query + ".pending")
392
393
394
                if os.path.exists(lock_query):
                    return jsonify({"success": True, "status": 1, "status_message": "In progress"})
                return jsonify({"success": True, "status": 2, "status_message": "Done",
395
                                "gzip": compressed})
396
397
            else:
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
398
399
400
401
402
        elif is_sorted and os.path.exists(lock_query):
            # Sort is already in progress
            return jsonify({"success": True, "status": 1, "status_message": "In progress"})
        else:
            # No sort to do or sort done
403
404
405
406
407
408
409
            if compressed and not query_fasta.endswith(".gz.fasta"):
                # If compressed file is asked, we must compress it now if not done before...
                Path(lock_query).touch()
                thread = threading.Timer(1, Functions.compress_and_send_mail, kwargs={
                    "job_name": id_res,
                    "fasta_file": query_fasta,
                    "index_file": os.path.join(res_dir, "query.idx.sorted"),
410
                    "lock_file": lock_query,
411
412
413
414
415
                    "compressed": compressed,
                    "mailer": mailer
                })
                thread.start()
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
416
417
418
419
420
421
422
            return jsonify({"success": True, "status": 2, "status_message": "Done",
                            "gzip": query_fasta.endswith(".gz") or query_fasta.endswith(".gz.sorted")})
    else:
        return jsonify({"success": False,
                        "message": "Unable to get fasta file for query. Please contact us to report the bug"})


423
def build_query_as_reference(id_res):
424
425
426
427
428
    paf_file = os.path.join(APP_DATA, id_res, "map.paf")
    idx1 = os.path.join(APP_DATA, id_res, "query.idx")
    idx2 = os.path.join(APP_DATA, id_res, "target.idx")
    paf = Paf(paf_file, idx1, idx2, False, mailer=mailer, id_job=id_res)
    paf.parse_paf(False, True)
429
430
431
432
433
434
435
436
437
438
    if MODE == "webserver":
        thread = threading.Timer(0, paf.build_query_chr_as_reference)
        thread.start()
        return True
    return paf.build_query_chr_as_reference()


@app.route('/build-query-as-reference/<id_res>', methods=['POST'])
def post_query_as_reference(id_res):
    build_query_as_reference(id_res)
439
440
441
    return jsonify({"success": True})


442
443
444
445
446
447
448
@app.route('/get-query-as-reference/<id_res>', methods=['GET'])
def get_query_as_reference(id_res):
    if MODE != "standalone":
        return abort(404)
    return send_file(build_query_as_reference(id_res))


449
450
451
452
453
454
455
456
@app.route('/download/<id_res>/<filename>')
def download_file(id_res, filename):
    file_dl = os.path.join(APP_DATA, id_res, filename)
    if os.path.isfile(file_dl):
        return send_file(file_dl)
    return abort(404)


457
458
459
@app.route('/fasta-query/<id_res>', defaults={'filename': ""}, methods=['GET'])
@app.route('/fasta-query/<id_res>/<filename>', methods=['GET'])  # Use fake URL in mail to set download file name
def dl_fasta(id_res, filename):
460
    res_dir = os.path.join(APP_DATA, id_res)
461
462
463
464
465
466
467
468
469
470
471
472
473
    lock_query = os.path.join(res_dir, ".query-fasta-build")
    is_sorted = os.path.exists(os.path.join(res_dir, ".sorted"))
    if not os.path.exists(lock_query) or not is_sorted:
        query_fasta = Functions.get_fasta_file(res_dir, "query", is_sorted)
        if query_fasta is not None:
            if query_fasta.endswith(".gz") or query_fasta.endswith(".gz.sorted"):
                content = get_file(query_fasta, True)
                return Response(content, mimetype="application/gzip")
            content = get_file(query_fasta)
            return Response(content, mimetype="text/plain")
    abort(404)


474
475
@app.route('/qt-assoc/<id_res>', methods=['GET'])
def qt_assoc(id_res):
476
    res_dir = os.path.join(APP_DATA, id_res)
477
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
478
479
480
        paf_file = os.path.join(APP_DATA, id_res, "map.paf")
        idx1 = os.path.join(APP_DATA, id_res, "query.idx")
        idx2 = os.path.join(APP_DATA, id_res, "target.idx")
481
482
483
484
485
486
487
488
489
490
491
492
        try:
            paf = Paf(paf_file, idx1, idx2, False)
            paf.parse_paf(False)
        except FileNotFoundError:
            print("Unable to load data!")
            abort(404)
            return False
        csv_content = paf.build_query_on_target_association_file()
        return Response(csv_content, mimetype="text/plain")
    abort(404)


493
494
495
496
497
498
@app.route('/no-assoc/<id_res>', methods=['POST'])
def no_assoc(id_res):
    """
    Get contigs that match with None target
    :param id_res: id of the result
    """
499
    res_dir = os.path.join(APP_DATA, id_res)
500
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
501
502
503
        paf_file = os.path.join(APP_DATA, id_res, "map.paf")
        idx1 = os.path.join(APP_DATA, id_res, "query.idx")
        idx2 = os.path.join(APP_DATA, id_res, "target.idx")
504
505
506
507
508
509
        try:
            paf = Paf(paf_file, idx1, idx2, False)
        except FileNotFoundError:
            print("Unable to load data!")
            abort(404)
            return False
510
        file_content = paf.build_list_no_assoc(request.form["to"])
511
512
513
514
515
516
517
518
        empty = file_content == "\n"
        return jsonify({
            "file_content": file_content,
            "empty": empty
        })
    abort(404)


Floreal Cabanettes's avatar
Floreal Cabanettes committed
519
520
@app.route('/summary/<id_res>', methods=['POST'])
def summary(id_res):
521
522
523
524
525
526
527
528
529
530
    paf_file = os.path.join(APP_DATA, id_res, "map.paf")
    idx1 = os.path.join(APP_DATA, id_res, "query.idx")
    idx2 = os.path.join(APP_DATA, id_res, "target.idx")
    try:
        paf = Paf(paf_file, idx1, idx2, False)
    except FileNotFoundError:
        return jsonify({
            "success": False,
            "message": "Unable to load data!"
        })
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    percents = None
    s_status = "waiting"  # Accepted values: waiting, done, fail
    status_file = os.path.join(APP_DATA, id_res, ".summarize")
    fail_file = status_file + ".fail"
    if not os.path.exists(status_file):  # The job is finished or not started
        if not os.path.exists(fail_file):  # The job has not started yet or has successfully ended
            percents = paf.get_summary_stats()
            if percents is None:  # The job has not started yet
                Path(status_file).touch()
                thread = threading.Timer(0, paf.build_summary_stats, kwargs={"status_file": status_file})
                thread.start()
            else:  # The job has successfully ended
                s_status = "done"
        else:  # The job has failed
            s_status = "fail"

    if s_status == "waiting":  # The job is running
        # Check if the job end in the next 30 seconds
        nb_iter = 0
        while os.path.exists(status_file) and not os.path.exists(fail_file) and nb_iter < 10:
            time.sleep(3)
            nb_iter += 1
        if not os.path.exists(status_file):  # The job has ended
            percents = paf.get_summary_stats()
            if percents is None:  # The job has failed
                s_status = "fail"
            else:  # The job has successfully ended
                s_status = "done"

    if s_status == "fail":
561
562
563
564
565
566
        return jsonify({
            "success": False,
            "message": "Build of summary failed. Please contact us to report the bug"
        })
    return jsonify({
        "success": True,
567
568
        "percents": percents,
        "status": s_status
569
    })
Floreal Cabanettes's avatar
Floreal Cabanettes committed
570
571


572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
def get_filter_out(id_res, type_f):
    filter_file = os.path.join(APP_DATA, id_res, ".filter-" + type_f)
    return Response(get_file(filter_file), mimetype="text/plain")


@app.route('/filter-out/<id_res>/query')
def get_filter_out_query(id_res):
    return get_filter_out(id_res=id_res, type_f="query")


@app.route('/filter-out/<id_res>/target')
def get_filter_out_target(id_res):
    return get_filter_out(id_res=id_res, type_f="target")


587
588
@app.route("/ask-upload", methods=['POST'])
def ask_upload():
589
590
591
592
593
    if MODE == "standalone":
        return jsonify({
            "success": True,
            "allowed": True
        })
594
595
    try:
        s_id = request.form['s_id']
Floreal Cabanettes's avatar
Floreal Cabanettes committed
596
597
598
        with Session.connect():
            session = Session.get(s_id=s_id)
            allowed = session.ask_for_upload(True)
599
600
        return jsonify({
            "success": True,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
601
            "allowed": allowed
602
603
604
        })
    except DoesNotExist:
        return jsonify({"success": False, "message": "Session not initialized. Please refresh the page."})
605
606


607
608
@app.route("/ping-upload", methods=['POST'])
def ping_upload():
609
610
611
612
613
    if MODE == "webserver":
        s_id = request.form['s_id']
        with Session.connect():
            session = Session.get(s_id=s_id)
            session.ping()
614
    return "OK"
615
616


617
618
619
620
@app.route("/upload", methods=['POST'])
def upload():
    try:
        s_id = request.form['s_id']
621
622
623
624
625
626
        if MODE == "webserver":
            try:
                with Session.connect():
                    session = Session.get(s_id=s_id)
                    if session.ask_for_upload(False):
                        folder = session.upload_folder
Floreal Cabanettes's avatar
Floreal Cabanettes committed
627
                    else:
628
629
630
631
632
633
                        return jsonify({"files": [], "success": "ERR", "message": "Not allowed to upload!"})
            except DoesNotExist:
                return jsonify(
                    {"files": [], "success": "ERR", "message": "Session not initialized. Please refresh the page."})
        else:
            folder = s_id
Floreal Cabanettes's avatar
Floreal Cabanettes committed
634

635
        files = request.files[list(request.files.keys())[0]]
Floreal Cabanettes's avatar
Floreal Cabanettes committed
636

637
638
639
640
641
642
643
        if files:
            filename = files.filename
            folder_files = os.path.join(app.config["UPLOAD_FOLDER"], folder)
            if not os.path.exists(folder_files):
                os.makedirs(folder_files)
            filename = Functions.get_valid_uploaded_filename(filename, folder_files)
            mime_type = files.content_type
Floreal Cabanettes's avatar
Floreal Cabanettes committed
644

645
646
647
            if not Functions.allowed_file(files.filename):
                result = UploadFile(name=filename, type_f=mime_type, size=0, not_allowed_msg="File type not allowed")
                shutil.rmtree(folder_files)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
648

649
650
651
652
653
654
655
656
657
658
659
660
661
662
            else:
                # save file to disk
                uploaded_file_path = os.path.join(folder_files, filename)
                files.save(uploaded_file_path)

                # get file size after saving
                size = os.path.getsize(uploaded_file_path)

                # return json for js call back
                result = UploadFile(name=filename, type_f=mime_type, size=size)

            return jsonify({"files": [result.get_file()], "success": "OK"})

        return jsonify({"files": [], "success": "404", "message": "No file provided"})
663
664
665
    except:  # Except all possible exceptions to prevent crashes
        return jsonify({"files": [], "success": "ERR", "message": "An unexpected error has occurred on upload. "
                                                                  "Please contact the support."})
666
667


668
669
670
671
672
673
@app.route("/send-mail/<id_res>", methods=['POST'])
def send_mail(id_res):
    allowed = False
    key_file = None
    if "key" in request.form:
        key = request.form["key"]
674
        res_dir = os.path.join(APP_DATA, id_res)
675
676
677
678
679
680
681
682
683
684
685
686
        key_file = os.path.join(res_dir, ".key")
        if os.path.exists(key_file):
            with open(key_file) as k_f:
                true_key = k_f.readline().strip("\n")
                allowed = key == true_key
    if allowed:
        os.remove(key_file)
        job_mng = JobManager(id_job=id_res, mailer=mailer)
        job_mng.set_inputs_from_res_dir()
        job_mng.send_mail()
        return "OK"
    else:
687
        abort(403)
688
689
690
691
692
693
694
695
696
697


@app.route("/delete/<id_res>", methods=['POST'])
def delete_job(id_res):
    job = JobManager(id_job=id_res)
    success, error = job.delete()
    return jsonify({
        "success": success,
        "error": error
    })