views.py 24.9 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
    if email == "" and MODE == "webserver":
111
        errors.append("Email not given")
112
        form_pass = False
113
114
    if file_target == "":
        errors.append("No target fasta selected")
115
116
117
118
        form_pass = False

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

127
        folder_files = os.path.join(APP_DATA, id_job)
128
129
130
        os.makedirs(folder_files)

        # Save files:
131
132
133
        query = None
        if file_query != "":
            query_name = os.path.splitext(file_query.replace(".gz", ""))[0] if file_query_type == "local" else None
134
            query_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, file_query) \
135
136
137
                if file_query_type == "local" else file_query
            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
138
        target_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, file_target) \
139
140
            if file_target_type == "local" else file_target
        target = Fasta(name=target_name, path=target_path, type_f=file_target_type)
141
142

        # Launch job:
Floreal Cabanettes's avatar
Floreal Cabanettes committed
143
        job = JobManager(id_job, email, query, target, mailer)
144
145
146
147
        if MODE == "webserver":
            job.launch()
        else:
            job.launch_standalone()
148
        return jsonify({"success": True, "redirect": url_for(".status", id_job=id_job)})
149
    else:
150
        return jsonify({"success": False, "errors": errors})
151
152
153
154
155
156


# Status of a job
@app.route('/status/<id_job>', methods=['GET'])
def status(id_job):
    job = JobManager(id_job)
157
158
159
160
161
162
163
164
165
166
167
168
    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)
169
170
171
172
173
174
175
176
177
    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
178
    return render_template("status.html", status=j_status["status"],
179
180
                           error=j_status["error"].replace("#ID#", ""),
                           id_job=id_job, menu="results", mem_peak=mem_peak,
181
                           time_elapsed=time_e,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
182
                           query_filtered=job.is_query_filtered(), target_filtered=job.is_target_filtered())
183
184


Floreal Cabanettes's avatar
Floreal Cabanettes committed
185
# Results path
186
@app.route("/result/<id_res>", methods=['GET'])
187
def result(id_res):
Floreal Cabanettes's avatar
Floreal Cabanettes committed
188
    return render_template("result.html", id=id_res, menu="result", current_result=id_res,
189
                           is_gallery=Functions.is_in_gallery(id_res, MODE))
190
191


192
193
@app.route("/gallery", methods=['GET'])
def gallery():
194
    if MODE == "webserver":
Floreal Cabanettes's avatar
Floreal Cabanettes committed
195
        return render_template("gallery.html", items=Functions.get_gallery_items(), menu="gallery")
196
    return abort(404)
197
198
199
200


@app.route("/gallery/<filename>", methods=['GET'])
def gallery_file(filename):
201
202
203
204
205
206
    if MODE == "webserver":
        try:
            return send_file(os.path.join(config_reader.app_data, "gallery", filename))
        except FileNotFoundError:
            abort(404)
    return abort(404)
207
208


209
def get_file(file, gzip=False):  # pragma: no cover
Floreal Cabanettes's avatar
Floreal Cabanettes committed
210
211
212
213
214
215
    try:
        # Figure out how flask returns static files
        # Tried:
        # - render_template
        # - send_file
        # This should not be so non-obvious
216
        return open(file, "rb" if gzip else "r").read()
217
218
    except FileNotFoundError:
        abort(404)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
219
    except IOError as exc:
220
        print(exc.__traceback__)
221
        abort(500)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
222
223


Floreal Cabanettes's avatar
Floreal Cabanettes committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
@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
249
    return render_template("documentation.html", menu="documentation", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
250
251


Floreal Cabanettes's avatar
Floreal Cabanettes committed
252
253
@app.route("/install", methods=['GET'])
def install():
254
    with open(os.path.join(app_folder, "INSTALL.md"), "r", encoding='utf-8') as install_instr:
Floreal Cabanettes's avatar
Floreal Cabanettes committed
255
256
257
258
        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
259
    return render_template("documentation.html", menu="install", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
260
261


Floreal Cabanettes's avatar
Floreal Cabanettes committed
262
263
264
265
266
267
268
269
270
271
272
@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
273
# Get graph (ajax request)
274
275
276
@app.route('/get_graph', methods=['POST'])
def get_graph():
    id_f = request.form["id"]
277
278
279
    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")
280

281
    paf = Paf(paf, idx1, idx2)
282

283
284
    if paf.parsed:
        res = paf.get_d3js_data()
285
        res["success"] = True
286
287
288
289
290
291
        return jsonify(res)
    return jsonify({"success": False, "message": paf.error})


@app.route('/sort/<id_res>', methods=['POST'])
def sort_graph(id_res):
292
    if not os.path.exists(os.path.join(APP_DATA, id_res, ".all-vs-all")):
293
294
295
        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")
296
        paf = Paf(paf_file, idx1, idx2, False)
297
298
299
300
301
302
303
        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
304

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

306
307
308
309
@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")):
310
311
312
        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")
313
314
315
316
317
318
319
320
321
322
        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"})


323
324
@app.route('/freenoise/<id_res>', methods=['POST'])
def free_noise(id_res):
325
326
327
    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")
328
329
330
331
332
333
334
335
336
    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})


337
338
@app.route('/get-fasta-query/<id_res>', methods=['POST'])
def build_fasta(id_res):
339
    res_dir = os.path.join(APP_DATA, id_res)
340
341
    lock_query = os.path.join(res_dir, ".query-fasta-build")
    is_sorted = os.path.exists(os.path.join(res_dir, ".sorted"))
342
    compressed = request.form["gzip"].lower() == "true"
343
344
345
346
347
    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()
348
            if not compressed or MODE == "standalone":  # If compressed, it will took a long time, so not wait
349
                Path(lock_query + ".pending").touch()
350
351
352
353
354
            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,
355
                "compress": compressed,
356
357
358
                "mailer": mailer
            })
            thread.start()
359
            if not compressed or MODE == "standalone":
360
361
                i = 0
                time.sleep(5)
362
                while os.path.exists(lock_query) and (i < 2 or MODE == "standalone"):
363
364
                    i += 1
                    time.sleep(5)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
365
                os.remove(lock_query + ".pending")
366
367
368
                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",
369
                                "gzip": compressed})
370
371
            else:
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
372
373
374
375
376
        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
377
378
379
380
381
382
383
            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"),
384
                    "lock_file": lock_query,
385
386
387
388
389
                    "compressed": compressed,
                    "mailer": mailer
                })
                thread.start()
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
390
391
392
393
394
395
396
            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"})


397
def build_query_as_reference(id_res):
398
399
400
401
402
    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)
403
404
405
406
407
408
409
410
411
412
    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)
413
414
415
    return jsonify({"success": True})


416
417
418
419
420
421
422
@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))


423
424
425
426
427
428
429
430
@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)


431
432
433
@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):
434
    res_dir = os.path.join(APP_DATA, id_res)
435
436
437
438
439
440
441
442
443
444
445
446
447
    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)


448
449
@app.route('/qt-assoc/<id_res>', methods=['GET'])
def qt_assoc(id_res):
450
    res_dir = os.path.join(APP_DATA, id_res)
451
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
452
453
454
        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")
455
456
457
458
459
460
461
462
463
464
465
466
        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)


467
468
469
470
471
472
@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
    """
473
    res_dir = os.path.join(APP_DATA, id_res)
474
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
475
476
477
        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")
478
479
480
481
482
483
        try:
            paf = Paf(paf_file, idx1, idx2, False)
        except FileNotFoundError:
            print("Unable to load data!")
            abort(404)
            return False
484
        file_content = paf.build_list_no_assoc(request.form["to"])
485
486
487
488
489
490
491
492
        empty = file_content == "\n"
        return jsonify({
            "file_content": file_content,
            "empty": empty
        })
    abort(404)


Floreal Cabanettes's avatar
Floreal Cabanettes committed
493
494
@app.route('/summary/<id_res>', methods=['POST'])
def summary(id_res):
495
496
497
498
499
500
501
502
503
504
    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!"
        })
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
    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":
535
536
537
538
539
540
        return jsonify({
            "success": False,
            "message": "Build of summary failed. Please contact us to report the bug"
        })
    return jsonify({
        "success": True,
541
542
        "percents": percents,
        "status": s_status
543
    })
Floreal Cabanettes's avatar
Floreal Cabanettes committed
544
545


546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
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")


561
562
@app.route("/ask-upload", methods=['POST'])
def ask_upload():
563
564
565
566
567
    if MODE == "standalone":
        return jsonify({
            "success": True,
            "allowed": True
        })
568
569
    try:
        s_id = request.form['s_id']
Floreal Cabanettes's avatar
Floreal Cabanettes committed
570
571
572
        with Session.connect():
            session = Session.get(s_id=s_id)
            allowed = session.ask_for_upload(True)
573
574
        return jsonify({
            "success": True,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
575
            "allowed": allowed
576
577
578
        })
    except DoesNotExist:
        return jsonify({"success": False, "message": "Session not initialized. Please refresh the page."})
579
580


581
582
@app.route("/ping-upload", methods=['POST'])
def ping_upload():
583
584
585
586
587
    if MODE == "webserver":
        s_id = request.form['s_id']
        with Session.connect():
            session = Session.get(s_id=s_id)
            session.ping()
588
    return "OK"
589
590


591
592
593
594
@app.route("/upload", methods=['POST'])
def upload():
    try:
        s_id = request.form['s_id']
595
596
597
598
599
600
        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
601
                    else:
602
603
604
605
606
607
                        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
608

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

611
612
613
614
615
616
617
        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
618

619
620
621
            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
622

623
624
625
626
627
628
629
630
631
632
633
634
635
636
            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"})
637
638
639
    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."})
640
641


642
643
644
645
646
647
@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"]
648
        res_dir = os.path.join(APP_DATA, id_res)
649
650
651
652
653
654
655
656
657
658
659
660
        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:
661
        abort(403)
662
663
664
665
666
667
668
669
670
671


@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
    })