views.py 28.1 KB
Newer Older
1
from dgenies import app, app_title, app_folder, config_reader, mailer, APP_DATA, MODE, DEBUG
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
import traceback
Floreal Cabanettes's avatar
Floreal Cabanettes committed
10
from flask import render_template, request, url_for, jsonify, Response, abort, send_file, Markup
11
from pathlib import Path
12
13
14
15
16
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
17
from dgenies.tools import Tools
Floreal Cabanettes's avatar
Floreal Cabanettes committed
18
19
from markdown import Markdown
from markdown.extensions.toc import TocExtension
20
21
22
if MODE == "webserver":
    from dgenies.database import Session, Gallery
    from peewee import DoesNotExist
23

24

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


35
36
37
# Main
@app.route("/", methods=['GET'])
def main():
38
39
40
41
42
43
    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
44
45
    else:
        pict = None
Floreal Cabanettes's avatar
Floreal Cabanettes committed
46
    return render_template("index.html", menu="index", pict=pict)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
47
48
49
50


@app.route("/run", methods=['GET'])
def run():
51
52
53
54
55
    tools = Tools().tools
    tools_names = sorted(list(tools.keys()), key=lambda x: (tools[x].order, tools[x].name))
    tools_ava = {}
    for tool_name, tool in tools.items():
        tools_ava[tool_name] = 1 if tool.all_vs_all is not None else 0
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    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
73
    return render_template("run.html", id_job=id_job, email=email,
74
                           menu="run", allowed_ext=ALLOWED_EXTENSIONS, s_id=s_id,
75
76
77
                           max_upload_file_size=config_reader.max_upload_file_size,
                           example=config_reader.example_target != "",
                           target=os.path.basename(config_reader.example_target),
78
79
                           query=os.path.basename(config_reader.example_query), tools_names=tools_names, tools=tools,
                           tools_ava=tools_ava)
80
81


82
83
@app.route("/run-test", methods=['GET'])
def run_test():
84
85
86
87
88
89
90
    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)
91
92


93
94
95
# Launch analysis
@app.route("/launch_analysis", methods=['POST'])
def launch_analysis():
96
97
98
99
100
101
102
103
104
105
106
107
    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"]

108
109
    id_job = request.form["id_job"]
    email = request.form["email"]
110
111
112
113
    file_query = request.form["query"]
    file_query_type = request.form["query_type"]
    file_target = request.form["target"]
    file_target_type = request.form["target_type"]
114
    tool = request.form["tool"]
115
116
117

    # Check form:
    form_pass = True
118
    errors = []
119
120
121
122

    if tool not in Tools().tools:
        errors.append("Tool unavailable: %s" % tool)

123
    if id_job == "":
124
        errors.append("Id of job not given")
125
126
        form_pass = False

127
128
129
130
    if MODE == "webserver":
        if email == "":
            errors.append("Email not given")
            form_pass = False
Floreal Cabanettes's avatar
Floreal Cabanettes committed
131
        elif not re.match(r"^[\w.\-]+@[\w\-.]{2,}\.[a-z]{2,4}$", email):
132
133
            errors.append("Email is invalid")
            form_pass = False
134
135
    if file_target == "":
        errors.append("No target fasta selected")
136
137
138
139
        form_pass = False

    # Form pass
    if form_pass:
140
        # Get final job id:
141
        id_job = re.sub('[^A-Za-z0-9_\-]+', '', id_job.replace(" ", "_"))
142
        id_job_orig = id_job
143
        i = 2
144
        while os.path.exists(os.path.join(APP_DATA, id_job)):
145
146
            id_job = id_job_orig + ("_%d" % i)
            i += 1
147

148
        folder_files = os.path.join(APP_DATA, id_job)
149
150
151
        os.makedirs(folder_files)

        # Save files:
152
153
        query = None
        if file_query != "":
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
            example = False
            if file_query.startswith("example://"):
                example = True
                query_path = config_reader.example_query
                query_name = os.path.basename(query_path)
                file_query_type = "local"
            else:
                query_name = os.path.splitext(file_query.replace(".gz", ""))[0] if file_query_type == "local" else None
                query_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, file_query) \
                    if file_query_type == "local" else file_query
                if file_query_type == "local" and not os.path.exists(query_path):
                    errors.append("Query file not correct!")
                    form_pass = False
            query = Fasta(name=query_name, path=query_path, type_f=file_query_type, example=example)
        example = False
        if file_target.startswith("example://"):
            example = True
            target_path = config_reader.example_target
            target_name = os.path.basename(target_path)
            file_target_type = "local"
        else:
            target_name = os.path.splitext(file_target.replace(".gz", ""))[0] if file_target_type == "local" else None
            target_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, file_target) \
                if file_target_type == "local" else file_target
            if file_target_type == "local" and not os.path.exists(target_path):
                errors.append("Target file not correct!")
180
                form_pass = False
181
        target = Fasta(name=target_name, path=target_path, type_f=file_target_type, example=example)
182

183
184
        if form_pass:
            # Launch job:
185
            job = JobManager(id_job, email, query, target, mailer, tool)
186
187
188
189
190
191
            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:
192
        return jsonify({"success": False, "errors": errors})
193
194
195
196
197
198


# Status of a job
@app.route('/status/<id_job>', methods=['GET'])
def status(id_job):
    job = JobManager(id_job)
199
200
201
202
203
204
205
206
207
208
209
210
    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)
211
212
213
214
215
216
217
218
219
    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
220
    return render_template("status.html", status=j_status["status"],
221
222
                           error=j_status["error"].replace("#ID#", ""),
                           id_job=id_job, menu="results", mem_peak=mem_peak,
223
                           time_elapsed=time_e,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
224
                           query_filtered=job.is_query_filtered(), target_filtered=job.is_target_filtered())
225
226


Floreal Cabanettes's avatar
Floreal Cabanettes committed
227
# Results path
228
@app.route("/result/<id_res>", methods=['GET'])
229
def result(id_res):
Floreal Cabanettes's avatar
Floreal Cabanettes committed
230
    return render_template("result.html", id=id_res, menu="result", current_result=id_res,
231
                           is_gallery=Functions.is_in_gallery(id_res, MODE))
232
233


234
235
@app.route("/gallery", methods=['GET'])
def gallery():
236
    if MODE == "webserver":
Floreal Cabanettes's avatar
Floreal Cabanettes committed
237
        return render_template("gallery.html", items=Functions.get_gallery_items(), menu="gallery")
238
    return abort(404)
239
240
241
242


@app.route("/gallery/<filename>", methods=['GET'])
def gallery_file(filename):
243
244
245
246
247
248
    if MODE == "webserver":
        try:
            return send_file(os.path.join(config_reader.app_data, "gallery", filename))
        except FileNotFoundError:
            abort(404)
    return abort(404)
249
250


251
def get_file(file, gzip=False):  # pragma: no cover
Floreal Cabanettes's avatar
Floreal Cabanettes committed
252
253
254
255
256
257
    try:
        # Figure out how flask returns static files
        # Tried:
        # - render_template
        # - send_file
        # This should not be so non-obvious
258
        return open(file, "rb" if gzip else "r").read()
259
260
    except FileNotFoundError:
        abort(404)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
261
    except IOError as exc:
262
        print(exc.__traceback__)
263
        abort(500)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
264
265


Floreal Cabanettes's avatar
Floreal Cabanettes committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
@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
291
    return render_template("documentation.html", menu="documentation", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
292
293


294
295
296
297
298
299
300
301
302
303
304
@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
305
306
@app.route("/install", methods=['GET'])
def install():
307
    with open(os.path.join(app_folder, "INSTALL.md"), "r", encoding='utf-8') as install_instr:
Floreal Cabanettes's avatar
Floreal Cabanettes committed
308
309
310
311
        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
312
    return render_template("documentation.html", menu="install", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
313

314
315
316
317
@app.route("/contact", methods=['GET'])
def contact():
    return render_template("contact.html", menu="contact")

Floreal Cabanettes's avatar
Floreal Cabanettes committed
318

Floreal Cabanettes's avatar
Floreal Cabanettes committed
319
320
321
322
323
324
325
326
327
328
329
@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
330
# Get graph (ajax request)
331
332
333
@app.route('/get_graph', methods=['POST'])
def get_graph():
    id_f = request.form["id"]
334
335
336
    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")
337

338
    paf = Paf(paf, idx1, idx2)
339

340
341
    if paf.parsed:
        res = paf.get_d3js_data()
342
        res["success"] = True
343
344
345
346
347
348
        return jsonify(res)
    return jsonify({"success": False, "message": paf.error})


@app.route('/sort/<id_res>', methods=['POST'])
def sort_graph(id_res):
349
    if not os.path.exists(os.path.join(APP_DATA, id_res, ".all-vs-all")):
350
351
352
        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")
353
        paf = Paf(paf_file, idx1, idx2, False)
354
355
356
357
358
359
360
        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
361

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

363
364
365
366
@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")):
367
368
369
        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")
370
371
372
373
374
375
376
377
378
379
        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"})


380
381
@app.route('/freenoise/<id_res>', methods=['POST'])
def free_noise(id_res):
382
383
384
    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")
385
386
387
388
389
390
391
392
393
    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})


394
395
@app.route('/get-fasta-query/<id_res>', methods=['POST'])
def build_fasta(id_res):
396
    res_dir = os.path.join(APP_DATA, id_res)
397
398
    lock_query = os.path.join(res_dir, ".query-fasta-build")
    is_sorted = os.path.exists(os.path.join(res_dir, ".sorted"))
399
    compressed = request.form["gzip"].lower() == "true"
400
401
402
403
404
    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()
405
            if not compressed or MODE == "standalone":  # If compressed, it will took a long time, so not wait
406
                Path(lock_query + ".pending").touch()
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
            index_file = os.path.join(res_dir, "query.idx.sorted")
            if MODE == "webserver":
                thread = threading.Timer(1, Functions.sort_fasta, kwargs={
                    "job_name": id_res,
                    "fasta_file": query_fasta,
                    "index_file": index_file,
                    "lock_file": lock_query,
                    "compress": compressed,
                    "mailer": mailer,
                    "mode": MODE
                })
                thread.start()
            else:
                Functions.sort_fasta(job_name=id_res,
                                     fasta_file=query_fasta,
                                     index_file=index_file,
                                     lock_file=lock_query,
                                     compress=compressed,
                                     mailer=None,
                                     mode=MODE)
427
            if not compressed or MODE == "standalone":
428
429
                if MODE == "webserver":
                    i = 0
430
                    time.sleep(5)
431
432
433
                    while os.path.exists(lock_query) and (i < 2 or MODE == "standalone"):
                        i += 1
                        time.sleep(5)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
434
                os.remove(lock_query + ".pending")
435
436
437
                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",
438
                                "gzip": compressed})
439
440
            else:
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
441
442
443
444
445
        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
446
447
448
449
450
451
452
            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"),
453
                    "lock_file": lock_query,
454
455
456
457
458
                    "compressed": compressed,
                    "mailer": mailer
                })
                thread.start()
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
459
460
461
462
463
464
465
            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"})


466
def build_query_as_reference(id_res):
467
468
469
470
471
    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)
472
473
474
475
476
477
478
479
480
481
    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)
482
483
484
    return jsonify({"success": True})


485
486
487
488
489
490
491
@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))


492
493
494
495
496
497
498
499
@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)


500
501
502
@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):
503
    res_dir = os.path.join(APP_DATA, id_res)
504
505
506
507
508
509
510
511
512
513
514
515
516
    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)


517
518
@app.route('/qt-assoc/<id_res>', methods=['GET'])
def qt_assoc(id_res):
519
    res_dir = os.path.join(APP_DATA, id_res)
520
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
521
522
523
        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")
524
525
526
527
528
529
530
531
532
533
534
535
        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)


536
537
538
539
540
541
@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
    """
542
    res_dir = os.path.join(APP_DATA, id_res)
543
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
544
545
546
        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")
547
548
549
550
551
552
        try:
            paf = Paf(paf_file, idx1, idx2, False)
        except FileNotFoundError:
            print("Unable to load data!")
            abort(404)
            return False
553
        file_content = paf.build_list_no_assoc(request.form["to"])
554
555
556
557
558
559
560
561
        empty = file_content == "\n"
        return jsonify({
            "file_content": file_content,
            "empty": empty
        })
    abort(404)


Floreal Cabanettes's avatar
Floreal Cabanettes committed
562
563
@app.route('/summary/<id_res>', methods=['POST'])
def summary(id_res):
564
565
566
567
568
569
570
571
572
573
    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!"
        })
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
    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":
604
605
606
607
608
609
        return jsonify({
            "success": False,
            "message": "Build of summary failed. Please contact us to report the bug"
        })
    return jsonify({
        "success": True,
610
611
        "percents": percents,
        "status": s_status
612
    })
Floreal Cabanettes's avatar
Floreal Cabanettes committed
613
614


615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
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")


630
631
@app.route("/ask-upload", methods=['POST'])
def ask_upload():
632
633
634
635
636
    if MODE == "standalone":
        return jsonify({
            "success": True,
            "allowed": True
        })
637
638
    try:
        s_id = request.form['s_id']
Floreal Cabanettes's avatar
Floreal Cabanettes committed
639
640
641
        with Session.connect():
            session = Session.get(s_id=s_id)
            allowed = session.ask_for_upload(True)
642
643
        return jsonify({
            "success": True,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
644
            "allowed": allowed
645
646
647
        })
    except DoesNotExist:
        return jsonify({"success": False, "message": "Session not initialized. Please refresh the page."})
648
649


650
651
@app.route("/ping-upload", methods=['POST'])
def ping_upload():
652
653
654
655
656
    if MODE == "webserver":
        s_id = request.form['s_id']
        with Session.connect():
            session = Session.get(s_id=s_id)
            session.ping()
657
    return "OK"
658
659


660
661
662
663
@app.route("/upload", methods=['POST'])
def upload():
    try:
        s_id = request.form['s_id']
664
665
666
667
668
669
        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
670
                    else:
671
672
673
674
675
676
                        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
677

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

680
681
682
683
684
685
686
        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
687

Floreal Cabanettes's avatar
Floreal Cabanettes committed
688
            if not Functions.allowed_file(files.filename, request.form['formats'].split(",")):
689
690
                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
691

692
693
694
695
696
697
698
699
700
701
702
703
704
705
            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"})
706
    except:  # Except all possible exceptions to prevent crashes
Floreal Cabanettes's avatar
Floreal Cabanettes committed
707
        traceback.print_exc()
708
709
        return jsonify({"files": [], "success": "ERR", "message": "An unexpected error has occurred on upload. "
                                                                  "Please contact the support."})
710
711


712
713
714
715
716
717
@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"]
718
        res_dir = os.path.join(APP_DATA, id_res)
719
720
721
722
723
724
725
726
727
728
729
730
        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:
731
        abort(403)
732
733
734
735
736
737
738
739
740
741


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