views.py 29.5 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
115
116
    tool = request.form["tool"] if "tool" in request.form else None
    alignfile = request.form["alignfile"] if "alignfile" in request.form else None
    alignfile_type = request.form["alignfile_type"] if "alignfile_type" in request.form else None
117
118
119

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

122
123
124
125
126
    if alignfile is not None and alignfile_type is None:
        errors.append("Server error: no alignfile_type in form. Please contact the support")
        form_pass = False

    if tool is not None and tool not in Tools().tools:
127
        errors.append("Tool unavailable: %s" % tool)
128
        form_pass = False
129

130
    if id_job == "":
131
        errors.append("Id of job not given")
132
133
        form_pass = False

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

    # Form pass
    if form_pass:
147
        # Get final job id:
148
        id_job = re.sub('[^A-Za-z0-9_\-]+', '', id_job.replace(" ", "_"))
149
        id_job_orig = id_job
150
        i = 2
151
        while os.path.exists(os.path.join(APP_DATA, id_job)):
152
153
            id_job = id_job_orig + ("_%d" % i)
            i += 1
154

155
        folder_files = os.path.join(APP_DATA, id_job)
156
157
158
        os.makedirs(folder_files)

        # Save files:
159
160
        query = None
        if file_query != "":
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
            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!")
187
                form_pass = False
188
        target = Fasta(name=target_name, path=target_path, type_f=file_target_type, example=example)
189

190
191
192
193
194
195
196
197
198
199
200
        align = None
        if alignfile is not None:
            Path(os.path.join(folder_files, ".align")).touch()
            alignfile_name = os.path.splitext(alignfile)[0] if alignfile_type == "local" else None
            alignfile_path = os.path.join(app.config["UPLOAD_FOLDER"], upload_folder, alignfile) \
                if alignfile_type == "local" else alignfile
            if alignfile_type == "local" and not os.path.exists(alignfile_path):
                errors.append("Alignment file not correct!")
                form_pass = False
            align = Fasta(name=alignfile_name, path=alignfile_path, type_f=alignfile_type)

201
202
        if form_pass:
            # Launch job:
203
204
205
206
207
208
209
            job = JobManager(id_job=id_job,
                             email=email,
                             query=query,
                             target=target,
                             align=align,
                             mailer=mailer,
                             tool=tool)
210
211
212
213
214
215
            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:
216
        return jsonify({"success": False, "errors": errors})
217
218
219
220
221
222


# Status of a job
@app.route('/status/<id_job>', methods=['GET'])
def status(id_job):
    job = JobManager(id_job)
223
224
225
226
227
228
229
230
231
232
233
234
    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)
235
236
237
238
239
240
241
242
243
    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
244
    return render_template("status.html", status=j_status["status"],
245
246
                           error=j_status["error"].replace("#ID#", ""),
                           id_job=id_job, menu="results", mem_peak=mem_peak,
247
                           time_elapsed=time_e, do_align=job.do_align(),
Floreal Cabanettes's avatar
Floreal Cabanettes committed
248
                           query_filtered=job.is_query_filtered(), target_filtered=job.is_target_filtered())
249
250


Floreal Cabanettes's avatar
Floreal Cabanettes committed
251
# Results path
252
@app.route("/result/<id_res>", methods=['GET'])
253
def result(id_res):
254
    res_dir = os.path.join(APP_DATA, id_res)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
255
    return render_template("result.html", id=id_res, menu="result", current_result=id_res,
256
257
                           is_gallery=Functions.is_in_gallery(id_res, MODE),
                           fasta_file=Functions.query_fasta_file_exists(res_dir))
258
259


260
261
@app.route("/gallery", methods=['GET'])
def gallery():
262
    if MODE == "webserver":
Floreal Cabanettes's avatar
Floreal Cabanettes committed
263
        return render_template("gallery.html", items=Functions.get_gallery_items(), menu="gallery")
264
    return abort(404)
265
266
267
268


@app.route("/gallery/<filename>", methods=['GET'])
def gallery_file(filename):
269
270
271
272
273
274
    if MODE == "webserver":
        try:
            return send_file(os.path.join(config_reader.app_data, "gallery", filename))
        except FileNotFoundError:
            abort(404)
    return abort(404)
275
276


277
def get_file(file, gzip=False):  # pragma: no cover
Floreal Cabanettes's avatar
Floreal Cabanettes committed
278
279
280
281
282
283
    try:
        # Figure out how flask returns static files
        # Tried:
        # - render_template
        # - send_file
        # This should not be so non-obvious
284
        return open(file, "rb" if gzip else "r").read()
285
286
    except FileNotFoundError:
        abort(404)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
287
    except IOError as exc:
288
        print(exc.__traceback__)
289
        abort(500)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
290
291


Floreal Cabanettes's avatar
Floreal Cabanettes committed
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
@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
317
    return render_template("documentation.html", menu="documentation", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
318
319


320
321
322
323
324
325
326
327
328
329
330
@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
331
332
@app.route("/install", methods=['GET'])
def install():
333
    with open(os.path.join(app_folder, "INSTALL.md"), "r", encoding='utf-8') as install_instr:
Floreal Cabanettes's avatar
Floreal Cabanettes committed
334
335
336
337
        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
338
    return render_template("documentation.html", menu="install", content=content, toc=toc)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
339

340
341
342
343
@app.route("/contact", methods=['GET'])
def contact():
    return render_template("contact.html", menu="contact")

Floreal Cabanettes's avatar
Floreal Cabanettes committed
344

Floreal Cabanettes's avatar
Floreal Cabanettes committed
345
346
347
348
349
350
351
352
353
354
355
@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
356
# Get graph (ajax request)
357
358
359
@app.route('/get_graph', methods=['POST'])
def get_graph():
    id_f = request.form["id"]
360
361
362
    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")
363

364
    paf = Paf(paf, idx1, idx2)
365

366
367
    if paf.parsed:
        res = paf.get_d3js_data()
368
        res["success"] = True
369
370
371
372
373
374
        return jsonify(res)
    return jsonify({"success": False, "message": paf.error})


@app.route('/sort/<id_res>', methods=['POST'])
def sort_graph(id_res):
375
    if not os.path.exists(os.path.join(APP_DATA, id_res, ".all-vs-all")):
376
377
378
        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")
379
        paf = Paf(paf_file, idx1, idx2, False)
380
381
382
383
384
385
386
        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
387

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

389
390
391
392
@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")):
393
394
395
        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")
396
397
398
399
400
401
402
403
404
405
        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"})


406
407
@app.route('/freenoise/<id_res>', methods=['POST'])
def free_noise(id_res):
408
409
410
    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")
411
412
413
414
415
416
417
418
419
    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})


420
421
@app.route('/get-fasta-query/<id_res>', methods=['POST'])
def build_fasta(id_res):
422
    res_dir = os.path.join(APP_DATA, id_res)
423
424
    lock_query = os.path.join(res_dir, ".query-fasta-build")
    is_sorted = os.path.exists(os.path.join(res_dir, ".sorted"))
425
    compressed = request.form["gzip"].lower() == "true"
426
427
428
429
430
    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()
431
            if not compressed or MODE == "standalone":  # If compressed, it will took a long time, so not wait
432
                Path(lock_query + ".pending").touch()
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
            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)
453
            if not compressed or MODE == "standalone":
454
455
                if MODE == "webserver":
                    i = 0
456
                    time.sleep(5)
457
458
459
                    while os.path.exists(lock_query) and (i < 2 or MODE == "standalone"):
                        i += 1
                        time.sleep(5)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
460
                os.remove(lock_query + ".pending")
461
462
463
                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",
464
                                "gzip": compressed})
465
466
            else:
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
467
468
469
470
471
        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
472
473
474
475
476
477
478
            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"),
479
                    "lock_file": lock_query,
480
481
482
483
484
                    "compressed": compressed,
                    "mailer": mailer
                })
                thread.start()
                return jsonify({"success": True, "status": 1, "status_message": "In progress"})
485
486
487
488
489
490
491
            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"})


492
def build_query_as_reference(id_res):
493
494
495
496
497
    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)
498
499
500
501
502
503
504
505
506
507
    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)
508
509
510
    return jsonify({"success": True})


511
512
513
514
515
516
517
@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))


518
519
520
521
522
523
524
525
@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)


526
527
528
@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):
529
    res_dir = os.path.join(APP_DATA, id_res)
530
531
532
533
534
535
536
537
538
539
540
541
542
    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)


543
544
@app.route('/qt-assoc/<id_res>', methods=['GET'])
def qt_assoc(id_res):
545
    res_dir = os.path.join(APP_DATA, id_res)
546
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
547
548
549
        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")
550
551
552
553
554
555
556
557
558
559
560
561
        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)


562
563
564
565
566
567
@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
    """
568
    res_dir = os.path.join(APP_DATA, id_res)
569
    if os.path.exists(res_dir) and os.path.isdir(res_dir):
570
571
572
        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")
573
574
575
576
577
578
        try:
            paf = Paf(paf_file, idx1, idx2, False)
        except FileNotFoundError:
            print("Unable to load data!")
            abort(404)
            return False
579
        file_content = paf.build_list_no_assoc(request.form["to"])
580
581
582
583
584
585
586
587
        empty = file_content == "\n"
        return jsonify({
            "file_content": file_content,
            "empty": empty
        })
    abort(404)


Floreal Cabanettes's avatar
Floreal Cabanettes committed
588
589
@app.route('/summary/<id_res>', methods=['POST'])
def summary(id_res):
590
591
592
593
594
595
596
597
598
599
    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!"
        })
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
    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":
630
631
632
633
634
635
        return jsonify({
            "success": False,
            "message": "Build of summary failed. Please contact us to report the bug"
        })
    return jsonify({
        "success": True,
636
637
        "percents": percents,
        "status": s_status
638
    })
Floreal Cabanettes's avatar
Floreal Cabanettes committed
639
640


641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
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")


656
657
@app.route("/ask-upload", methods=['POST'])
def ask_upload():
658
659
660
661
662
    if MODE == "standalone":
        return jsonify({
            "success": True,
            "allowed": True
        })
663
664
    try:
        s_id = request.form['s_id']
Floreal Cabanettes's avatar
Floreal Cabanettes committed
665
666
667
        with Session.connect():
            session = Session.get(s_id=s_id)
            allowed = session.ask_for_upload(True)
668
669
        return jsonify({
            "success": True,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
670
            "allowed": allowed
671
672
673
        })
    except DoesNotExist:
        return jsonify({"success": False, "message": "Session not initialized. Please refresh the page."})
674
675


676
677
@app.route("/ping-upload", methods=['POST'])
def ping_upload():
678
679
680
681
682
    if MODE == "webserver":
        s_id = request.form['s_id']
        with Session.connect():
            session = Session.get(s_id=s_id)
            session.ping()
683
    return "OK"
684
685


686
687
688
689
@app.route("/upload", methods=['POST'])
def upload():
    try:
        s_id = request.form['s_id']
690
691
692
693
694
695
        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
696
                    else:
697
698
699
700
701
702
                        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
703

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

706
707
708
709
710
711
712
        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
713

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

718
719
720
721
722
723
724
725
726
727
728
729
730
731
            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"})
732
    except:  # Except all possible exceptions to prevent crashes
Floreal Cabanettes's avatar
Floreal Cabanettes committed
733
        traceback.print_exc()
734
735
        return jsonify({"files": [], "success": "ERR", "message": "An unexpected error has occurred on upload. "
                                                                  "Please contact the support."})
736
737


738
739
740
741
742
743
@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"]
744
        res_dir = os.path.join(APP_DATA, id_res)
745
746
747
748
749
750
751
752
753
754
755
756
        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:
757
        abort(403)
758
759
760
761
762
763
764
765
766
767


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