diff --git a/app/events.py b/app/events.py index 96f6de26..2273e1f2 100644 --- a/app/events.py +++ b/app/events.py @@ -1,6 +1,6 @@ from flask import current_app, request from flask_login import current_user -from . import socketio +from . import db, socketio from .decorators import socketio_admin_required, socketio_login_required from .models import User import json @@ -33,24 +33,24 @@ def disconnect(): connected_sessions.remove(request.sid) -@socketio.on('user_ressources_init') +@socketio.on('user_data_stream_init') @socketio_login_required -def subscribe_user_ressources(): - socketio.start_background_task(user_ressource_session_handler, +def user_data_stream_init(): + socketio.start_background_task(user_data_stream, current_app._get_current_object(), current_user.id, request.sid) -@socketio.on('foreign_user_ressources_init') +@socketio.on('foreign_user_data_stream_init') @socketio_login_required @socketio_admin_required -def subscribe_foreign_user_ressources(user_id): - socketio.start_background_task(user_ressource_session_handler, +def foreign_user_data_stream_init(user_id): + socketio.start_background_task(user_data_stream, current_app._get_current_object(), user_id, request.sid, True) -def user_ressource_session_handler(app, user_id, session_id, foreign=False): +def user_data_stream(app, user_id, session_id, foreign=False): ''' ' Sends initial corpus and job lists to the client. Afterwards it checks ' every 3 seconds if changes to the initial values appeared. If changes are @@ -59,37 +59,29 @@ def user_ressource_session_handler(app, user_id, session_id, foreign=False): ' NOTE: The initial values are send as a init events. ' The JSON patches are send as update events. ''' - init_events = \ - {'corpora': 'foreign_corpora_init' if foreign else 'corpora_init', - 'jobs': 'foreign_jobs_init' if foreign else 'jobs_init'} - update_events = \ - {'corpora': 'foreign_corpora_update' if foreign else 'corpora_update', - 'jobs': 'foreign_jobs_update' if foreign else 'jobs_update'} + if foreign: + init_event = 'foreign_user_data_stream_init' + update_event = 'foreign_user_data_stream_update' + else: + init_event = 'user_data_stream_init' + update_event = 'user_data_stream_update' with app.app_context(): # Gather current values from database. user = User.query.get(user_id) - corpora = {corpus.id: corpus.to_dict() for corpus in user.corpora} - jobs = {job.id: job.to_dict() for job in user.jobs} + user_dict = user.to_dict() # Send initial values to the user. - socketio.emit(init_events['corpora'], json.dumps(corpora), - room=session_id) - socketio.emit(init_events['jobs'], json.dumps(jobs), room=session_id) + socketio.emit(init_event, json.dumps(user_dict), room=session_id) while session_id in connected_sessions: # Get new values from the database - new_corpora = {corpus.id: corpus.to_dict() - for corpus in user.corpora} - new_jobs = {job.id: job.to_dict() for job in user.jobs} + db.session.refresh(user) + new_user_dict = user.to_dict() # Compute JSON patches. - corpora_patch = jsonpatch.JsonPatch.from_diff(corpora, new_corpora) - jobs_patch = jsonpatch.JsonPatch.from_diff(jobs, new_jobs) + user_patch = jsonpatch.JsonPatch.from_diff(user_dict, + new_user_dict) # In case there are patches, send them to the user. - if corpora_patch: - socketio.emit(update_events['corpora'], - corpora_patch.to_string(), room=session_id) - if jobs_patch: - socketio.emit(update_events['jobs'], jobs_patch.to_string(), + if user_patch: + socketio.emit(update_event, user_patch.to_string(), room=session_id) # Set new values as references for the next iteration. - corpora = new_corpora - jobs = new_jobs + user_dict = new_user_dict socketio.sleep(3) diff --git a/app/models.py b/app/models.py index 8114560c..f5f31003 100644 --- a/app/models.py +++ b/app/models.py @@ -132,19 +132,20 @@ class User(UserMixin, db.Model): def to_dict(self): return {'id': self.id, + 'role_id': self.role_id, 'confirmed': self.confirmed, 'email': self.email, 'last_seen': self.last_seen.timestamp(), 'member_since': self.member_since.timestamp(), - 'role_id': self.role_id, 'username': self.username, 'settings': {'dark_mode': self.setting_dark_mode, 'job_status_mail_notifications': self.setting_job_status_mail_notifications, 'job_status_site_notifications': self.setting_job_status_site_notifications}, - 'corpora': [corpus.to_dict() for corpus in self.corpora], - 'jobs': [job.to_dict() for job in self.jobs]} + 'corpora': {corpus.id: corpus.to_dict() + for corpus in self.corpora}, + 'jobs': {job.id: job.to_dict() for job in self.jobs}} def __repr__(self): """ @@ -371,10 +372,11 @@ class Job(db.Model): 'description': self.description, 'end_date': (self.end_date.timestamp() if self.end_date else None), - 'inputs': [input.to_dict() for input in self.inputs], + 'inputs': {input.id: input.to_dict() for input in self.inputs}, 'mem_mb': self.mem_mb, 'n_cores': self.n_cores, - 'results': [result.to_dict() for result in self.results], + 'results': {result.id: result.to_dict() + for result in self.results}, 'service': self.service, 'service_args': self.service_args, 'service_version': self.service_version, @@ -455,7 +457,7 @@ class Corpus(db.Model): 'description': self.description, 'status': self.status, 'title': self.title, - 'files': [file.to_dict() for file in self.files]} + 'files': {file.id: file.to_dict() for file in self.files}} def delete(self): for corpus_file in self.files: diff --git a/app/static/js/nopaque.js b/app/static/js/nopaque.js index 2a689a75..44287d69 100644 --- a/app/static/js/nopaque.js +++ b/app/static/js/nopaque.js @@ -29,66 +29,48 @@ nopaque.socket = {}; nopaque.socket.init = function() { nopaque.socket = io({transports: ['websocket']}); // Add event handlers - nopaque.socket.on("corpora_init", function(msg) { - nopaque.corpora = JSON.parse(msg); - for (let subscriber of nopaque.corporaSubscribers) {subscriber._init(nopaque.corpora);} + nopaque.socket.on("user_data_stream_init", function(msg) { + nopaque.user = JSON.parse(msg); + for (let subscriber of nopaque.corporaSubscribers) {subscriber._init(nopaque.user.corpora);} + for (let subscriber of nopaque.jobsSubscribers) {subscriber._init(nopaque.user.jobs);} }); - nopaque.socket.on("jobs_init", function(msg) { - nopaque.jobs = JSON.parse(msg); - for (let subscriber of nopaque.jobsSubscribers) {subscriber._init(nopaque.jobs);} - }); - - nopaque.socket.on("corpora_update", function(msg) { + nopaque.socket.on("user_data_stream_update", function(msg) { var patch; patch = JSON.parse(msg); - nopaque.corpora = jsonpatch.apply_patch(nopaque.corpora, patch); - for (let subscriber of nopaque.corporaSubscribers) {subscriber._update(patch);} - }); - - nopaque.socket.on("jobs_update", function(msg) { - var patch; - - patch = JSON.parse(msg); - nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch); - if (["all", "end"].includes(nopaque.user.settings.jobStatusSiteNotifications)) { - for (operation of patch) { - /* "/jobId/valueName" -> ["jobId", "valueName"] */ - pathArray = operation.path.split("/").slice(1); + nopaque.user = jsonpatch.apply_patch(nopaque.user, patch); + corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); + jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); + for (let subscriber of nopaque.corporaSubscribers) {subscriber._update(corpora_patch);} + for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(jobs_patch);} + if (["all", "end"].includes(nopaque.user.settings.job_status_site_notifications)) { + for (operation of jobs_patch) { + /* "/jobs/{jobId}/..." -> ["{jobId}", ...] */ + pathArray = operation.path.split("/").slice(2); if (operation.op === "replace" && pathArray[1] === "status") { - if (nopaque.user.settings.jobStatusSiteNotifications === "end" && !["complete", "failed"].includes(operation.value)) {continue;} - nopaque.flash(`[${nopaque.jobs[pathArray[0]].title}] New status: ${operation.value}`, "job"); + if (nopaque.user.settings.job_status_site_notifications === "end" && !["complete", "failed"].includes(operation.value)) {continue;} + nopaque.flash(`[${nopaque.user.jobs[pathArray[0]].title}] New status: ${operation.value}`, "job"); } } } - for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(patch);} }); - nopaque.socket.on("foreign_corpora_init", function(msg) { - nopaque.foreignCorpora = JSON.parse(msg); - for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._init(nopaque.foreignCorpora);} + nopaque.socket.on("foreign_user_data_stream_init", function(msg) { + nopaque.foreignUser = JSON.parse(msg); + for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._init(nopaque.foreignUser.corpora);} + for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._init(nopaque.foreignUser.jobs);} }); - nopaque.socket.on("foreign_jobs_init", function(msg) { - nopaque.foreignJobs = JSON.parse(msg); - for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._init(nopaque.foreignJobs);} - }); - - nopaque.socket.on("foreign_corpora_update", function(msg) { + nopaque.socket.on("foreign_user_data_stream_update", function(msg) { var patch; patch = JSON.parse(msg); - nopaque.foreignCorpora = jsonpatch.apply_patch(nopaque.foreignCorpora, patch); - for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._update(patch);} - }); - - nopaque.socket.on("foreign_jobs_update", function(msg) { - var patch; - - patch = JSON.parse(msg); - nopaque.foreignJobs = jsonpatch.apply_patch(nopaque.foreignJobs, patch); - for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._update(patch);} + nopaque.foreignUser = jsonpatch.apply_patch(nopaque.foreignUser, patch); + corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); + jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); + for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._update(corpora_patch);} + for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._update(jobs_patch);} }); } @@ -233,11 +215,6 @@ document.addEventListener("DOMContentLoaded", function() { flashedMessage = nopaque.flashedMessages.shift(); nopaque.flash(flashedMessage[1], flashedMessage[0]); } - if (nopaque.user.isAuthenticated) { - if (nopaque.user.settings.darkMode) { - DarkReader.enable({"brightness": 150, "contrast": 100, "sepia": 0}); - } - nopaque.socket.init(); - nopaque.socket.emit("user_ressources_init"); - } + nopaque.socket.init(); + nopaque.socket.emit("user_data_stream_init"); }); diff --git a/app/static/js/nopaque.lists.js b/app/static/js/nopaque.lists.js index c569c76d..bc6d989e 100644 --- a/app/static/js/nopaque.lists.js +++ b/app/static/js/nopaque.lists.js @@ -13,6 +13,7 @@ class RessourceList extends List { _init(ressources) { + this.clear(); this.addRessources(Object.values(ressources)); this.sort("creation_date", {order: "desc"}); } @@ -22,8 +23,8 @@ class RessourceList extends List { let item, pathArray; for (let operation of patch) { - /* "/ressourceId/valueName" -> ["ressourceId", "valueName"] */ - pathArray = operation.path.split("/").slice(1); + /* "/{ressourceName}/{ressourceId}/..." -> ["{ressourceId}", "..."] */ + pathArray = operation.path.split("/").slice(2); switch(operation.op) { case "add": if (pathArray.includes("results")) {break;} @@ -48,7 +49,6 @@ class RessourceList extends List { } } - addRessources(ressources) { this.add(ressources.map(x => RessourceList.dataMapper[this.type](x))); } diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index a5f5b259..fb3f73ab 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -130,19 +130,19 @@ _init() { let corpus; - corpus = (this.foreignCorpusFlag ? nopaque.foreignCorpora[this.corpusId] - : nopaque.corpora[this.corpusId]); + corpus = (this.foreignCorpusFlag ? nopaque.foreignUser.corpora[this.corpusId] + : nopaque.user.corpora[this.corpusId]); // Status - this.setStatus(corpus.status, corpus.files.length); + this.setStatus(corpus.status); } _update(patch) { let pathArray; for (let operation of patch) { - /* "/corpusId/valueName" -> ["corpusId", "valueName"] */ - pathArray = operation.path.split("/").slice(1); + /* "/corpora/{corpusId}/valueName" -> ["{corpusId}", ...] */ + pathArray = operation.path.split("/").slice(2); if (pathArray[0] != this.corpusId) {continue;} switch(operation.op) { case "add": @@ -165,7 +165,7 @@ setStatus(status) { let analyzeElement, buildElement, numFiles, progressIndicatorElement, statusElement; - numFiles = (this.foreignCorpusFlag ? nopaque.foreignCorpora[this.corpusId] : nopaque.corpora[this.corpusId]).files.length; + numFiles = Object.keys((this.foreignCorpusFlag ? nopaque.foreignUser.corpora[this.corpusId] : nopaque.user.corpora[this.corpusId]).files).length; progressIndicatorElement = document.getElementById("progress-indicator"); if (["queued", "running", "start analysis", "stop analysis"].includes(status)) { diff --git a/app/templates/jobs/job.html.j2 b/app/templates/jobs/job.html.j2 index 5be1ed99..45b81cea 100644 --- a/app/templates/jobs/job.html.j2 +++ b/app/templates/jobs/job.html.j2 @@ -157,9 +157,8 @@ _init() { let job; - job = (this.foreignJobFlag ? nopaque.foreignJobs[this.jobId] - : nopaque.jobs[this.jobId]); - + job = (this.foreignJobFlag ? nopaque.foreignUser.jobs[this.jobId] + : nopaque.user.jobs[this.jobId]); // End date this.setEndDate(job.end_date); // Status @@ -174,8 +173,8 @@ let pathArray; for (let operation of patch) { - /* "/jobId/valueName" -> ["jobId", "valueName"] */ - pathArray = operation.path.split("/").slice(1); + /* "/jobs/{jobId}/..." -> ["{jobId}", ...] */ + pathArray = operation.path.split("/").slice(2); if (pathArray[0] != this.jobId) {continue;} switch(operation.op) { case "add": @@ -212,20 +211,15 @@ } setResults(results) { - let resultsElement; - results.sort(function (a, b) { - var filenameA = a.filename.toUpperCase(); - var filenameB = b.filename.toUpperCase(); - if (filenameA < filenameB) { - return -1; - } - if (filenameA > filenameB) { - return 1; - } + let resultsArray, resultsElement; + resultsArray = Object.values(results) + resultsArray.sort(function (a, b) { + if (a.filename < b.filename) {return -1;} + if (a.filename > b.filename) {return 1;} return 0; }); resultsElement = document.getElementById("results"); - for (let result of results) { + for (let result of resultsArray) { resultsElement.insertAdjacentHTML( "beforeend", `