main.py 8.54 KB
Newer Older
1
2
#!/usr/bin/env python3

Floreal Cabanettes's avatar
Floreal Cabanettes committed
3
import os
4
5
import time
import datetime
6
import shutil
7
import re
8
import gevent
Floreal Cabanettes's avatar
Floreal Cabanettes committed
9
from flask import Flask, render_template, request, url_for, jsonify, session, Response, abort
10
from flask_mail import Mail
11
from flask_socketio import SocketIO, emit, join_room, leave_room
12
from lib.paf import Paf
13
from config_reader import AppConfigReader
14
from lib.job_manager import JobManager
15
from lib.functions import Functions, ALLOWED_EXTENSIONS
16
from lib.upload_file import UploadFile
17
from lib.Fasta import Fasta
18
19

import sys
20

21
22
23
24
25
26
app_folder = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, app_folder)
os.environ["PATH"] = os.path.join(app_folder, "bin") + ":" + os.environ["PATH"]

sqlite_file = os.path.join(app_folder, "database.sqlite")

27
28
29
30

# Init config reader:
config_reader = AppConfigReader()

31
UPLOAD_FOLDER = config_reader.get_upload_folder()
Floreal Cabanettes's avatar
Floreal Cabanettes committed
32
APP_DATA = config_reader.get_app_data()
33

Floreal Cabanettes's avatar
Floreal Cabanettes committed
34
app_title = "D-GENIES - Dotplot for Genomes Interactive, E-connected and Speedy"
Floreal Cabanettes's avatar
Floreal Cabanettes committed
35

36
# Init Flask:
37
app = Flask(__name__, static_url_path='/static')
38
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
39
app.config['SECRET_KEY'] = 'dsqdsq-255sdA-fHfg52-25Asd5'
40

41
42
socketio = SocketIO(app, async_mode='gevent')

43
44
45
# Init mail:
mail = Mail(app)

46
# Folder containing data:
47
48
49
app_data = config_reader.get_app_data()


Floreal Cabanettes's avatar
Floreal Cabanettes committed
50
51
52
53
54
55
@app.context_processor
def get_launched_results():
    cookie = request.cookies.get("results")
    return {"results": cookie.split("|") if cookie is not None else set()}


56
57
58
# Main
@app.route("/", methods=['GET'])
def main():
Floreal Cabanettes's avatar
Floreal Cabanettes committed
59
60
61
62
63
    return render_template("index.html", title=app_title, menu="index")


@app.route("/run", methods=['GET'])
def run():
Floreal Cabanettes's avatar
Floreal Cabanettes committed
64
    session["user_tmp_dir"] = Functions.random_string(5) + "_" + \
65
                              datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d%H%M%S')
Floreal Cabanettes's avatar
Floreal Cabanettes committed
66
    id_job = Functions.random_string(5) + "_" + datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d%H%M%S')
67
68
69
70
71
    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
72
    return render_template("run.html", title=app_title, id_job=id_job, email=email,
73
                           menu="run", allowed_ext=ALLOWED_EXTENSIONS)
74
75
76
77
78
79
80


# Launch analysis
@app.route("/launch_analysis", methods=['POST'])
def launch_analysis():
    id_job = request.form["id_job"]
    email = request.form["email"]
81
82
83
84
    file_query = request.form["query"]
    file_query_type = request.form["query_type"]
    file_target = request.form["target"]
    file_target_type = request.form["target_type"]
85
86
87

    # Check form:
    form_pass = True
88
    errors = []
89
    if id_job == "":
90
        errors.append("Id of job not given")
91
92
93
        form_pass = False

    if email == "":
94
        errors.append("Email not given")
95
        form_pass = False
96
97
    if file_query == "":
        errors.append("No query fasta selected")
98
99
100
101
        form_pass = False

    # Form pass
    if form_pass:
102
        # Get final job id:
103
        id_job = re.sub('[^A-Za-z0-9_\-]+', '', id_job.replace(" ", "_"))
104
105
106
107
108
109
110
111
        id_job_orig = id_job
        while os.path.exists(os.path.join(app_data, id_job)):
            id_job = id_job_orig + "_2"

        folder_files = os.path.join(app_data, id_job)
        os.makedirs(folder_files)

        # Save files:
112
113
114
        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"], session["user_tmp_dir"], file_query) \
            if file_query_type == "local" else file_query
115
116
117
        query = Fasta(name=query_name, path=query_path, type_f=file_query_type)
        target = None
        if file_target != "":
118
119
120
            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"], session["user_tmp_dir"], file_target) \
                if file_target_type == "local" else file_target
121
122
123
            target = Fasta(name=target_name, path=target_path, type_f=file_target_type)

        # Launch job:
Floreal Cabanettes's avatar
Floreal Cabanettes committed
124
        job = JobManager(id_job, email, query, target, app, mail)
125
126
        job.launch()
        return jsonify({"success": True, "redirect": url_for(".status", id_job=id_job)})
127
    else:
128
        return jsonify({"success": False, "errors": errors})
129
130
131
132
133
134


# Status of a job
@app.route('/status/<id_job>', methods=['GET'])
def status(id_job):
    job = JobManager(id_job)
135
136
    j_status, error = job.status()
    return render_template("status.html", title=app_title, status=j_status, error=error, id_job=id_job,
Floreal Cabanettes's avatar
Floreal Cabanettes committed
137
                           menu="results")
138
139


Floreal Cabanettes's avatar
Floreal Cabanettes committed
140
# Results path
141
@app.route("/result/<id_res>", methods=['GET'])
142
def result(id_res):
Floreal Cabanettes's avatar
Floreal Cabanettes committed
143
144
145
146
147
148
149
150
    my_render = render_template("results.html", title=app_title, id=id_res, menu="results", current_result=id_res)
    response = app.make_response(my_render)
    cookie = request.cookies.get("results")
    cookie = cookie.split("|") if cookie is not None else []
    if id_res not in cookie:
        cookie.insert(0, id_res)
    response.set_cookie(key="results", value="|".join(cookie), path="/")
    return response
151
152


Floreal Cabanettes's avatar
Floreal Cabanettes committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def get_file(file):  # pragma: no cover
    try:
        # Figure out how flask returns static files
        # Tried:
        # - render_template
        # - send_file
        # This should not be so non-obvious
        return open(file).read()
    except IOError as exc:
        return str(exc)


@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
176
# Get graph (ajax request)
177
178
179
@app.route('/get_graph', methods=['POST'])
def get_graph():
    id_f = request.form["id"]
180
    paf = os.path.join(app_data, id_f, "map.paf")
181
182
    idx1 = os.path.join(app_data, id_f, "query.idx")
    idx2 = os.path.join(app_data, id_f, "target.idx")
183

184
    paf = Paf(paf, idx1, idx2)
185

186
187
    if paf.parsed:
        res = paf.get_d3js_data()
188
        res["success"] = True
189
190
191
192
        return jsonify(res)
    return jsonify({"success": False, "message": paf.error})


193
194
195
196
197
198
199
200
201
def sort_paf(paf, id_res):
    paf.sort()
    res = {"success": False}
    if paf.parsed:
        res = paf.get_d3js_data()
        res["success"] = True
    emit_event("update_graph", res, "res_" + id_res)


202
203
@app.route('/sort/<id_res>', methods=['POST'])
def sort_graph(id_res):
Floreal Cabanettes's avatar
Floreal Cabanettes committed
204
205
206
    paf = 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")
207
    paf = Paf(paf, idx1, idx2, False)
208
209
    gevent.spawn(sort_paf, paf=paf, id_res=id_res)
    return jsonify({"success": True})
Floreal Cabanettes's avatar
Floreal Cabanettes committed
210

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

212
213
@app.route("/upload", methods=['POST'])
def upload():
214
215
216
217
218
219
220
221
222
    if "user_tmp_dir" in session and session["user_tmp_dir"] != "":
        folder = session["user_tmp_dir"]
        files = request.files[list(request.files.keys())[0]]

        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)
Floreal Cabanettes's avatar
Floreal Cabanettes committed
223
            filename = Functions.get_valid_uploaded_filename(filename, folder_files)
224
225
            mime_type = files.content_type

Floreal Cabanettes's avatar
Floreal Cabanettes committed
226
            if not Functions.allowed_file(files.filename):
227
                result = UploadFile(name=filename, type_f=mime_type, size=0, not_allowed_msg="File type not allowed")
228
                shutil.rmtree(folder_files)
229
230
231
232
233
234
235
236
237
238

            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
239
                result = UploadFile(name=filename, type_f=mime_type, size=size)
240
241
242
243
244

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

        return jsonify({"files": [], "success": "404", "message": "No file provided"})
    return jsonify({"files": [], "success": "ERR", "message": "Session not initialized. Please refresh the page."})
245
246


247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
############
# SocketIO #
############


@socketio.on('join')
def on_join(data):
    username = Functions.random_string(10)
    room = data['room']
    join_room(room)
    emit("connected", {"username": username})


def emit_event(event, data, room=None):
    socketio.emit(event, data, room=room)
    socketio.sleep(0)


Floréal Cabanettes's avatar
Floréal Cabanettes committed
265
if __name__ == '__main__':
266
    socketio.run(app)