Stop polling. Use SocketIO!

This commit is contained in:
Patrick Jentsch 2019-08-23 15:05:01 +02:00
parent 3d8b8e9182
commit 7aef3de81d
12 changed files with 58 additions and 5120 deletions

View File

@ -27,9 +27,6 @@ def create_app(config_name):
scheduler.start() scheduler.start()
socketio.init_app(app) socketio.init_app(app)
from .api import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api')
from .auth import auth as auth_blueprint from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth') app.register_blueprint(auth_blueprint, url_prefix='/auth')

View File

@ -1,5 +0,0 @@
from flask import Blueprint
api = Blueprint('api', __name__)
from . import views

View File

@ -1,103 +0,0 @@
from flask import abort, jsonify, make_response, request
from flask_login import current_user, login_required
from . import api
from ..decorators import admin_required
from ..models import Corpus, Job, User
@api.route('/v1.0/admin/corpora')
@login_required
@admin_required
def admin_corpora():
user_id = request.args.get('user_id', default=str(current_user.id))
if user_id == '*':
corpora = Corpus.query.all()
else:
user = User.query.filter_by(id=user_id).first()
if user:
corpora = user.corpora.all()
else:
return abort(404)
jsonifyable_corpora = []
for corpus in corpora:
jsonifyable_corpora.append(corpus.to_dict())
return jsonify(jsonifyable_corpora)
@api.route('/v1.0/admin/corpora/<int:corpus_id>')
@login_required
@admin_required
def admin_coprus(corpus_id):
corpus = Corpus.query.filter_by(id=corpus_id).first()
if corpus:
return jsonify(corpus.to_dict())
else:
return abort(404)
@api.route('/v1.0/admin/jobs')
@login_required
@admin_required
def admin_jobs():
user_id = request.args.get('user_id', default=str(current_user.id))
if user_id == '*':
jobs = Job.query.all()
else:
user = User.query.filter_by(id=user_id).first()
if user:
jobs = user.jobs.all()
else:
return abort(404)
jsonifyable_jobs = []
for job in jobs:
jsonifyable_jobs.append(job.to_dict())
return jsonify(jsonifyable_jobs)
@api.route('/v1.0/admin/jobs/<int:job_id>')
@login_required
@admin_required
def admin_job(job_id):
job = Job.query.filter_by(id=job_id).first()
if job:
return jsonify(job.to_dict())
else:
return abort(404)
@api.route('/v1.0/corpora')
@login_required
def corpora():
jsonifyable_corpora = []
for corpus in current_user.corpora.all():
jsonifyable_corpora.append(corpus.to_dict())
return jsonify(jsonifyable_corpora)
@api.route('/v1.0/corpora/<int:corpus_id>')
@login_required
def corpus(corpus_id):
corpus = current_user.corpora.filter_by(id=corpus_id).first()
if corpus:
return jsonify(corpus.to_dict())
else:
return abort(404)
@api.route('/v1.0/jobs')
@login_required
def jobs():
jsonifyable_jobs = []
for job in current_user.jobs.all():
jsonifyable_jobs.append(job.to_dict())
return jsonify(jsonifyable_jobs)
@api.route('/v1.0/jobs/<int:job_id>')
@login_required
def job(job_id):
job = current_user.jobs.filter_by(id=job_id).first()
if job:
return jsonify(job.to_dict())
else:
return abort(404)

View File

@ -1,10 +1,12 @@
from flask import (abort, current_app, flash, redirect, request, from flask import (abort, current_app, flash, redirect, request,
render_template, url_for, send_from_directory) render_template, url_for, send_from_directory)
from flask_login import current_user, login_required from flask_login import current_user, login_required
from flask_socketio import emit
from . import main from . import main
from .forms import CreateCorpusForm from .forms import CreateCorpusForm
from .. import db, socketio from .. import db, socketio
from ..models import Corpus from ..models import Corpus
import json
import os import os
@ -19,6 +21,21 @@ def handle_message(message):
print('received message: ' + str(message)) print('received message: ' + str(message))
@socketio.on('connect')
@login_required
def connect():
corpora = []
jobs = []
for corpus in current_user.corpora:
corpora.append(corpus.to_dict())
for job in current_user.jobs:
jobs.append(job.to_dict())
emit('corpora', {'data': json.dumps(corpora)})
emit('jobs', {'data': json.dumps(jobs)})
@main.route('/corpora/<int:corpus_id>') @main.route('/corpora/<int:corpus_id>')
@login_required @login_required
def corpus(corpus_id): def corpus(corpus_id):

View File

@ -288,7 +288,7 @@ class Corpus(db.Model):
def to_dict(self): def to_dict(self):
return {'id': self.id, return {'id': self.id,
'creation_date': self.creation_date, 'creation_date': self.creation_date.timestamp(),
'description': self.description, 'description': self.description,
'title': self.title, 'title': self.title,
'user_id': self.user_id} 'user_id': self.user_id}

View File

@ -1,10 +1,12 @@
class CorpusList extends List { class CorpusList extends List {
constructor(idOrElement, options, live=false) { constructor(idOrElement, options) {
super(idOrElement, options); super(idOrElement, options);
corporaSubscribers.push(this);
}
init() {
this.createCorpusElements(corpora); this.createCorpusElements(corpora);
if (live) {
subscribers.corpora.push(this);
}
} }
@ -48,7 +50,7 @@ class CorpusList extends List {
List.updatePagination(this); List.updatePagination(this);
} }
/*
corporaUpdateHandler(delta) { corporaUpdateHandler(delta) {
var corpusElement, key, listItem; var corpusElement, key, listItem;
@ -76,4 +78,5 @@ class CorpusList extends List {
} }
} }
} }
*/
} }

View File

@ -1,10 +1,12 @@
class JobList extends List { class JobList extends List {
constructor(idOrElement, options, live=false) { constructor(idOrElement, options) {
super(idOrElement, options); super(idOrElement, options);
jobsSubscribers.push(this);
}
init() {
this.createJobElements(jobs); this.createJobElements(jobs);
if (live) {
subscribers.jobs.push(this);
}
} }
@ -51,7 +53,7 @@ class JobList extends List {
List.updatePagination(this); List.updatePagination(this);
} }
/*
jobsUpdateHandler(delta) { jobsUpdateHandler(delta) {
var jobElement, jobStatusElement, key, listItem; var jobElement, jobStatusElement, key, listItem;
@ -85,6 +87,7 @@ class JobList extends List {
} }
} }
} }
*/
} }
JobList.SERVICE_COLORS = {"nlp": "blue", JobList.SERVICE_COLORS = {"nlp": "blue",

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
var subscribers = {"corpora": [], "jobs": []};
function getCorpora() {
fetch("/api/v1.0/corpora")
.then(function(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response);
} else {
return Promise.reject(new Error(response.statusText));
}
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (JSON.stringify(corpora) != JSON.stringify(data)) {
corpora = data;
for (subscriber of subscribers.corpora) {
subscriber.corporaUpdateHandler();
}
}
})
.catch(function(error) {
console.log('Request failed', error);
});
}
function getJobs() {
fetch("/api/v1.0/jobs")
.then(function(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response);
} else {
return Promise.reject(new Error(response.statusText));
}
})
.then(function(response) {
return response.json();
})
.then(function(json) {
var delta = jsondiffpatch.diff(jobs, json);
if (delta) {
jobs = json;
for (subscriber of subscribers.jobs) {
subscriber.jobsUpdateHandler(delta);
}
}
})
.catch(function(error) {
console.log('Request failed', error);
});
}
setInterval(getCorpora, 5000);
setInterval(getJobs, 5000);

View File

@ -11,33 +11,31 @@
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='fonts/material-icons/material-icons.css') }}"> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='fonts/material-icons/material-icons.css') }}">
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/materialize.min.css') }}" media="screen,projection"/> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/materialize.min.css') }}" media="screen,projection"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/opaque.css') }}" media="screen,projection"/> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/opaque.css') }}" media="screen,projection"/>
{% if current_user.is_authenticated %}
<script>
var corpora = [
{% for corpus in current_user.corpora.all() %}
{{ corpus.to_dict()|tojson }},
{% endfor %}
];
var jobs = [
{% for job in current_user.jobs.all() %}
{{ job.to_dict()|tojson }},
{% endfor %}
];
</script>
<script src="{{ url_for('static', filename='js/jsondiffpatch.umd.js') }}"></script>
<script src="{{ url_for('static', filename='js/polls.js') }}"></script>
{% endif %}
<script src="{{ url_for('static', filename='js/socket.io.js') }}"></script> <script src="{{ url_for('static', filename='js/socket.io.js') }}"></script>
<script type="text/javascript" charset="utf-8"> <script src="{{ url_for('static', filename='js/list.js') }}"></script>
var socket = io(); <script src="{{ url_for('static', filename='js/list.utils.js') }}"></script>
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'});
});
</script>
<script src="{{ url_for('static', filename='js/list.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
<script src="{{ url_for('static', filename='js/CorpusList.js') }}"></script> <script src="{{ url_for('static', filename='js/CorpusList.js') }}"></script>
<script src="{{ url_for('static', filename='js/JobList.js') }}"></script> <script src="{{ url_for('static', filename='js/JobList.js') }}"></script>
<script>
var corpora;
var corporaSubscribers = [];
var jobs;
var jobsSubscribers = [];
</script>
<script>
var socket = io();
socket.on('corpora', function(msg) {
corpora = JSON.parse(msg.data);
for (subscriber of corporaSubscribers) {subscriber.init();}
});
socket.on('jobs', function(msg) {
jobs = JSON.parse(msg.data);
for (subscriber of jobsSubscribers) {subscriber.init();}
});
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head> </head>
<body> <body>

View File

@ -35,7 +35,7 @@
page: 4, page: 4,
pagination: true, pagination: true,
valueNames: ["description", "title", {data: ["id"]}]} valueNames: ["description", "title", {data: ["id"]}]}
var corpusList = new CorpusList("corpus-list", corpusListOptions, true); var corpusList = new CorpusList("corpus-list", corpusListOptions);
corpusList.on("filterComplete", List.updatePagination); corpusList.on("filterComplete", List.updatePagination);
corpusList.on("searchComplete", List.updatePagination); corpusList.on("searchComplete", List.updatePagination);
</script> </script>
@ -78,7 +78,7 @@
page: 4, page: 4,
pagination: true, pagination: true,
valueNames: ["description", "title", {data: ["id"]}]} valueNames: ["description", "title", {data: ["id"]}]}
var jobList = new JobList("job-list", jobListOptions, true); var jobList = new JobList("job-list", jobListOptions);
jobList.on("filterComplete", List.updatePagination); jobList.on("filterComplete", List.updatePagination);
jobList.on("searchComplete", List.updatePagination); jobList.on("searchComplete", List.updatePagination);
</script> </script>
@ -135,5 +135,4 @@
<li><a href="{{ url_for('services.nlp') }}"><i class="material-icons">format_textdirection_l_to_r</i>NLP</a></li> <li><a href="{{ url_for('services.nlp') }}"><i class="material-icons">format_textdirection_l_to_r</i>NLP</a></li>
<li><a href="{{ url_for('services.ocr') }}"><i class="material-icons">find_in_page</i>OCR</a></li> <li><a href="{{ url_for('services.ocr') }}"><i class="material-icons">find_in_page</i>OCR</a></li>
</ul> </ul>
{% endblock %} {% endblock %}