From a62d8aaf5f5b3354804cf55479411bea2f6837ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Mon, 16 Dec 2019 12:46:03 -0500 Subject: [PATCH 1/6] Added test template. --- bazarr/api.py | 18 ++++++++++++++---- bazarr/templates/test.html | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 bazarr/templates/test.html diff --git a/bazarr/api.py b/bazarr/api.py index 4c45c06c1..0d3c45262 100644 --- a/bazarr/api.py +++ b/bazarr/api.py @@ -15,7 +15,7 @@ from helper import path_replace, path_replace_reverse, path_replace_movie, path_ from get_languages import load_language_in_db, alpha2_from_language, alpha3_from_language, language_from_alpha2, \ alpha3_from_alpha2 -from flask import Flask, jsonify, request +from flask import Flask, jsonify, request, render_template from flask_restful import Resource, Api app = Flask(__name__) @@ -23,6 +23,10 @@ api = Api(app) load_language_in_db() +@app.route('/') +def test(): + return render_template('test.html') + class Badges(Resource): def get(self): @@ -247,6 +251,10 @@ class HistorySeries(Resource): class HistoryMovies(Resource): def get(self): + start = request.args.get('start') + length = request.args.get('length') + draw = int(request.args.get('draw')) + upgradable_movies = [] upgradable_movies_not_perfect = [] if settings.general.getboolean('upgrade_subs'): @@ -280,11 +288,13 @@ class HistoryMovies(Resource): if int(upgradable_movie['score']) < 120: upgradable_movies_not_perfect.append(upgradable_movie) + row_count = database.execute("SELECT COUNT(*) as count FROM table_history_movie", only_one=True)['count'] data = database.execute("SELECT table_history_movie.action, table_movies.title, table_history_movie.timestamp, " "table_history_movie.description, table_history_movie.radarrId, " "table_history_movie.video_path, table_history_movie.language, " "table_history_movie.score FROM table_history_movie LEFT JOIN table_movies on " - "table_movies.radarrId = table_history_movie.radarrId ORDER BY timestamp DESC") + "table_movies.radarrId = table_history_movie.radarrId ORDER BY timestamp DESC LIMIT ? " + "OFFSET ?", (length, start)) for item in data: # Mark movies as upgradable or not @@ -316,7 +326,7 @@ class HistoryMovies(Resource): item.update({"mapped_path": None}) item.update({"exist": False}) - return jsonify(data) + return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data) class WantedSeries(Resource): @@ -399,4 +409,4 @@ api.add_resource(WantedSeries, '/api/wanted_series') api.add_resource(WantedMovies, '/api/wanted_movies') if __name__ == '__main__': - app.run(debug=True) + app.run(port=6767, debug=True) diff --git a/bazarr/templates/test.html b/bazarr/templates/test.html new file mode 100644 index 000000000..cc01a23fa --- /dev/null +++ b/bazarr/templates/test.html @@ -0,0 +1,37 @@ + + + + Server-side processing datatables test + + + + + + + + + + + + + + +
actiontitletimestampdescription
+ + + + \ No newline at end of file From 6f378ad2e3be6f4ff2e9e4adb072482366ca96fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Mon, 16 Dec 2019 19:41:50 -0500 Subject: [PATCH 2/6] Updated for server side and Material table. --- bazarr/api.py | 61 ++++++++++++++++++++++++++++---------- bazarr/templates/test.html | 6 ++-- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/bazarr/api.py b/bazarr/api.py index 0d3c45262..6e94b8932 100644 --- a/bazarr/api.py +++ b/bazarr/api.py @@ -42,11 +42,17 @@ class Badges(Resource): class Series(Resource): def get(self): + start = request.args.get('start') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') + seriesId = request.args.get('id') + row_count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count'] if seriesId: - result = database.execute("SELECT * FROM table_shows WHERE sonarrSeriesId=?", (seriesId,)) + result = database.execute("SELECT * FROM table_shows WHERE sonarrSeriesId=? LIMIT ? OFFSET ?", + (length, start), (seriesId,)) else: - result = database.execute("SELECT * FROM table_shows") + result = database.execute("SELECT * FROM table_shows LIMIT ? OFFSET ?", (length, start)) for item in result: # Parse audio language if item['audio_language']: @@ -83,7 +89,7 @@ class Series(Resource): item.update({"episodeFileCount": database.execute("SELECT COUNT(*) as count FROM table_episodes WHERE " "sonarrSeriesId=?", (seriesId,), only_one=True)['count']}) - return jsonify(result) + return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=result) class Episodes(Resource): @@ -92,7 +98,7 @@ class Episodes(Resource): if seriesId: result = database.execute("SELECT * FROM table_episodes WHERE sonarrSeriesId=?", (seriesId,)) else: - result = database.execute("SELECT * FROM table_episodes") + return "Series ID not provided", 400 for item in result: # Parse subtitles if item['subtitles']: @@ -121,11 +127,17 @@ class Episodes(Resource): class Movies(Resource): def get(self): + start = request.args.get('start') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') + moviesId = request.args.get('id') + row_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count'] if moviesId: - result = database.execute("SELECT * FROM table_movies WHERE radarrId=?", (moviesId,)) + result = database.execute("SELECT * FROM table_movies WHERE radarrId=? LIMIT ? OFFSET ?", (length, start), + (moviesId,)) else: - result = database.execute("SELECT * FROM table_movies") + result = database.execute("SELECT * FROM table_movies LIMIT ? OFFSET ?", (length, start)) for item in result: # Parse audio language if item['audio_language']: @@ -171,11 +183,15 @@ class Movies(Resource): # Confirm if path exist item.update({"exist": os.path.isfile(mapped_path)}) - return jsonify(result) + return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=result) class HistorySeries(Resource): def get(self): + start = request.args.get('start') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') + upgradable_episodes_not_perfect = [] if settings.general.getboolean('upgrade_subs'): days_to_upgrade_subs = settings.general.days_to_upgrade_subs @@ -211,6 +227,7 @@ class HistorySeries(Resource): if int(upgradable_episode['score']) < 360: upgradable_episodes_not_perfect.append(upgradable_episode) + row_count = database.execute("SELECT COUNT(*) as count FROM table_history", only_one=True)['count'] data = database.execute("SELECT table_history.action, table_shows.title as seriesTitle, " "table_episodes.season || 'x' || table_episodes.episode as episode_number, " "table_episodes.title as episodeTitle, table_history.timestamp, " @@ -218,7 +235,8 @@ class HistorySeries(Resource): "table_history.language, table_history.score FROM table_history LEFT JOIN table_shows " "on table_shows.sonarrSeriesId = table_history.sonarrSeriesId LEFT JOIN table_episodes " "on table_episodes.sonarrEpisodeId = table_history.sonarrEpisodeId WHERE " - "table_episodes.title is not NULL ORDER BY timestamp DESC") + "table_episodes.title is not NULL ORDER BY timestamp DESC LIMIT ? OFFSET ?", + (length, start)) for item in data: # Mark episode as upgradable or not @@ -246,14 +264,14 @@ class HistorySeries(Resource): # Confirm if path exist item.update({"exist": os.path.isfile(mapped_path)}) - return jsonify(data) + return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data) class HistoryMovies(Resource): def get(self): - start = request.args.get('start') - length = request.args.get('length') - draw = int(request.args.get('draw')) + start = request.args.get('start') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') upgradable_movies = [] upgradable_movies_not_perfect = [] @@ -331,11 +349,16 @@ class HistoryMovies(Resource): class WantedSeries(Resource): def get(self): + start = request.args.get('start') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') + if settings.sonarr.getboolean('only_monitored'): monitored_only_query_string = " AND monitored='True'" else: monitored_only_query_string = '' + row_count = database.execute("SELECT COUNT(*) as count FROM table_episodes", only_one=True)['count'] data = database.execute("SELECT table_shows.title as seriesTitle, " "table_episodes.season || 'x' || table_episodes.episode as episode_number, " "table_episodes.title as episodeTitle, table_episodes.missing_subtitles, " @@ -344,7 +367,7 @@ class WantedSeries(Resource): "table_episodes.failedAttempts FROM table_episodes INNER JOIN table_shows on " "table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE " "table_episodes.missing_subtitles != '[]'" + monitored_only_query_string + - " ORDER BY table_episodes._rowid_ DESC") + " ORDER BY table_episodes._rowid_ DESC LIMIT ? OFFSET ?", (length, start)) for item in data: # Parse missing subtitles @@ -364,19 +387,25 @@ class WantedSeries(Resource): # Confirm if path exist item.update({"exist": os.path.isfile(mapped_path)}) - return jsonify(data) + return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data) class WantedMovies(Resource): def get(self): + start = request.args.get('start') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') + if settings.radarr.getboolean('only_monitored'): monitored_only_query_string = " AND monitored='True'" else: monitored_only_query_string = '' + row_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count'] data = database.execute("SELECT title, missing_subtitles, radarrId, path, hearing_impaired, sceneName, " "failedAttempts FROM table_movies WHERE missing_subtitles != '[]'" + - monitored_only_query_string + " ORDER BY _rowid_ DESC") + monitored_only_query_string + " ORDER BY _rowid_ DESC LIMIT ? OFFSET ?", + (length, start)) for item in data: # Parse missing subtitles @@ -396,7 +425,7 @@ class WantedMovies(Resource): # Confirm if path exist item.update({"exist": os.path.isfile(mapped_path)}) - return jsonify(data) + return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data) api.add_resource(Badges, '/api/badges') diff --git a/bazarr/templates/test.html b/bazarr/templates/test.html index cc01a23fa..50fc7eff0 100644 --- a/bazarr/templates/test.html +++ b/bazarr/templates/test.html @@ -4,10 +4,12 @@ Server-side processing datatables test - + + + - +
From 43b9a8d0fee519857484cf548d7a87bf26ad811a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Tue, 17 Dec 2019 23:37:38 -0500 Subject: [PATCH 3/6] Added server side events (SSE) to update client datatables. --- bazarr/SSE.py | 33 +++++++++++++++++++++++++++++++++ bazarr/api.py | 16 +++++++++++++++- bazarr/templates/test.html | 7 ++++++- 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 bazarr/SSE.py diff --git a/bazarr/SSE.py b/bazarr/SSE.py new file mode 100644 index 000000000..9c9c03183 --- /dev/null +++ b/bazarr/SSE.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import +from collections import deque +import json +import time + + +class EventStream: + """ + This class is used to read or write items to the notifications deque. + """ + + def __init__(self): + self.queue = deque(maxlen=10) + + def write(self, msg): + """ + :param msg: The message to display. + :type msg: str + """ + + self.queue.append("data:" + msg + "\n\n") + + def read(self): + """ + :return: Return the oldest notification available. + :rtype: str + """ + + if self.queue or (len(self.queue) > 0): + return self.queue.popleft() + + +event_stream = EventStream() diff --git a/bazarr/api.py b/bazarr/api.py index 6e94b8932..0f093fa09 100644 --- a/bazarr/api.py +++ b/bazarr/api.py @@ -14,11 +14,14 @@ from database import database from helper import path_replace, path_replace_reverse, path_replace_movie, path_replace_reverse_movie from get_languages import load_language_in_db, alpha2_from_language, alpha3_from_language, language_from_alpha2, \ alpha3_from_alpha2 +from SSE import event_stream from flask import Flask, jsonify, request, render_template +import flask from flask_restful import Resource, Api app = Flask(__name__) +app.debug = True api = Api(app) load_language_in_db() @@ -28,6 +31,17 @@ def test(): return render_template('test.html') +@app.route('/event') +def event(): + return flask.Response(event_stream.read(), mimetype="text/event-stream") + + +@app.route('/write') +def write(): + event_stream.write('fake message') + return "", 200 + + class Badges(Resource): def get(self): result = { @@ -438,4 +452,4 @@ api.add_resource(WantedSeries, '/api/wanted_series') api.add_resource(WantedMovies, '/api/wanted_movies') if __name__ == '__main__': - app.run(port=6767, debug=True) + app.run(port=6767, threaded=True) diff --git a/bazarr/templates/test.html b/bazarr/templates/test.html index 50fc7eff0..a0519eb94 100644 --- a/bazarr/templates/test.html +++ b/bazarr/templates/test.html @@ -23,7 +23,7 @@ \ No newline at end of file From d76b821814126bbfa83d04ce33e8deee349e7393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Wed, 18 Dec 2019 07:05:25 -0500 Subject: [PATCH 4/6] Small fix. --- bazarr/SSE.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bazarr/SSE.py b/bazarr/SSE.py index 9c9c03183..1ce3665a1 100644 --- a/bazarr/SSE.py +++ b/bazarr/SSE.py @@ -26,8 +26,10 @@ class EventStream: :rtype: str """ - if self.queue or (len(self.queue) > 0): - return self.queue.popleft() + while True: + if self.queue or (len(self.queue) > 0): + yield self.queue.popleft() + time.sleep(0.1) event_stream = EventStream() From 343eedb8aca48f3cd3b1751df6f8867df5d56312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Fri, 20 Dec 2019 21:47:38 -0500 Subject: [PATCH 5/6] WIP --- bazarr/api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bazarr/api.py b/bazarr/api.py index 0f093fa09..03528bbfc 100644 --- a/bazarr/api.py +++ b/bazarr/api.py @@ -16,8 +16,7 @@ from get_languages import load_language_in_db, alpha2_from_language, alpha3_from alpha3_from_alpha2 from SSE import event_stream -from flask import Flask, jsonify, request, render_template -import flask +from flask import Flask, jsonify, request, render_template, Response from flask_restful import Resource, Api app = Flask(__name__) @@ -33,7 +32,7 @@ def test(): @app.route('/event') def event(): - return flask.Response(event_stream.read(), mimetype="text/event-stream") + return Response(event_stream.read(), mimetype="text/event-stream") @app.route('/write') From 79949ff80fdd716a3809fc2041118b1758d3e9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Tue, 24 Dec 2019 11:26:14 -0500 Subject: [PATCH 6/6] Fixed SSE. --- bazarr/SSE.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazarr/SSE.py b/bazarr/SSE.py index 1ce3665a1..4f98674bc 100644 --- a/bazarr/SSE.py +++ b/bazarr/SSE.py @@ -27,8 +27,8 @@ class EventStream: """ while True: - if self.queue or (len(self.queue) > 0): - yield self.queue.popleft() + if self.queue: + return self.queue.popleft() time.sleep(0.1)
action