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
+
+
+
+
+
+
+
+
+ action |
+ title |
+ timestamp |
+ description |
+
+
+
+
+
+
+
\ 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
-
+
+
+
-
+
action |
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)