diff --git a/bazarr/SSE.py b/bazarr/SSE.py new file mode 100644 index 000000000..4f98674bc --- /dev/null +++ b/bazarr/SSE.py @@ -0,0 +1,35 @@ +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 + """ + + while True: + if self.queue: + return self.queue.popleft() + time.sleep(0.1) + + +event_stream = EventStream() diff --git a/bazarr/api.py b/bazarr/api.py index 5494005a4..68f4c82c6 100644 --- a/bazarr/api.py +++ b/bazarr/api.py @@ -14,13 +14,27 @@ 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, Response, Blueprint -from flask import Flask, jsonify, request, Blueprint from flask_restful import Resource, Api api_bp = Blueprint('api', __name__, url_prefix='/api') api = Api(api_bp) + +@app.route('/event') +def event(): + return 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 = { @@ -35,11 +49,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']: @@ -76,7 +96,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): @@ -85,7 +105,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']: @@ -114,11 +134,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']: @@ -164,11 +190,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 @@ -204,6 +234,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, " @@ -211,7 +242,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 @@ -239,11 +271,15 @@ 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') or 0 + length = request.args.get('length') or -1 + draw = request.args.get('draw') + upgradable_movies = [] upgradable_movies_not_perfect = [] if settings.general.getboolean('upgrade_subs'): @@ -277,11 +313,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 @@ -313,16 +351,21 @@ 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): 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, " @@ -331,7 +374,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 @@ -351,19 +394,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 @@ -383,14 +432,17 @@ 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, '/badges') -api.add_resource(Series, '/series') -api.add_resource(Episodes, '/episodes') -api.add_resource(Movies, '/movies') -api.add_resource(HistorySeries, '/history_series') -api.add_resource(HistoryMovies, '/history_movies') -api.add_resource(WantedSeries, '/wanted_series') -api.add_resource(WantedMovies, '/wanted_movies') +api.add_resource(Badges, '/api/badges') +api.add_resource(Series, '/api/series') +api.add_resource(Episodes, '/api/episodes') +api.add_resource(Movies, '/api/movies') +api.add_resource(HistorySeries, '/api/history_series') +api.add_resource(HistoryMovies, '/api/history_movies') +api.add_resource(WantedSeries, '/api/wanted_series') +api.add_resource(WantedMovies, '/api/wanted_movies') + +if __name__ == '__main__': + app.run(debug=True) diff --git a/bazarr/templates/test.html b/bazarr/templates/test.html new file mode 100644 index 000000000..a0519eb94 --- /dev/null +++ b/bazarr/templates/test.html @@ -0,0 +1,44 @@ + + + + Server-side processing datatables test + + + + + + + + + + + + + + + + +
actiontitletimestampdescription
+ + + + \ No newline at end of file