From 714c46444aacc16e78b7dc5d7184a81d7b8f8aea Mon Sep 17 00:00:00 2001 From: LASER-Yi Date: Sun, 3 Sep 2023 15:54:23 +0800 Subject: [PATCH 1/9] Improve manual search modal. Change cache behavior of manual search api to no cache --- frontend/src/apis/hooks/providers.ts | 4 ++-- .../components/modals/ManualSearchModal.tsx | 24 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/frontend/src/apis/hooks/providers.ts b/frontend/src/apis/hooks/providers.ts index f1daf9f37..c804a9a8e 100644 --- a/frontend/src/apis/hooks/providers.ts +++ b/frontend/src/apis/hooks/providers.ts @@ -18,7 +18,7 @@ export function useMoviesProvider(radarrId?: number) { } }, { - staleTime: Infinity, + staleTime: 0, } ); } @@ -32,7 +32,7 @@ export function useEpisodesProvider(episodeId?: number) { } }, { - staleTime: Infinity, + staleTime: 0, } ); } diff --git a/frontend/src/components/modals/ManualSearchModal.tsx b/frontend/src/components/modals/ManualSearchModal.tsx index 7760fb26e..fa428c1a0 100644 --- a/frontend/src/components/modals/ManualSearchModal.tsx +++ b/frontend/src/components/modals/ManualSearchModal.tsx @@ -40,18 +40,18 @@ interface Props { function ManualSearchView(props: Props) { const { download, query: useSearch, item } = props; - const itemId = useMemo(() => GetItemId(item ?? {}), [item]); + const [searchStarted, setSearchStarted] = useState(false); - const [id, setId] = useState(undefined); + const itemId = useMemo(() => GetItemId(item), [item]); - const results = useSearch(id); + const results = useSearch(searchStarted ? itemId : undefined); - const isStale = results.data === undefined; + const haveResult = results.data !== undefined; const search = useCallback(() => { - setId(itemId); + setSearchStarted(true); results.refetch(); - }, [itemId, results]); + }, [results]); const columns = useMemo[]>( () => [ @@ -190,6 +190,14 @@ function ManualSearchView(props: Props) { const bSceneNameAvailable = isString(item.sceneName) && item.sceneName.length !== 0; + const searchButtonText = useMemo(() => { + if (results.isFetching) { + return "Searching"; + } + + return searchStarted ? "Search Again" : "Search"; + }, [results.isFetching, searchStarted]); + return ( (props: Props) { - + (props: Props) { ); From 8895dd68a8e155550a5362ffea96966aebce5221 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Mon, 4 Sep 2023 22:41:39 -0400 Subject: [PATCH 2/9] Improved responses marshalling to better deal with error messages sent to frontend. --- bazarr/api/badges/badges.py | 5 ++--- bazarr/api/episodes/blacklist.py | 7 +++---- bazarr/api/episodes/episodes.py | 7 +++---- bazarr/api/episodes/history.py | 5 ++--- bazarr/api/episodes/wanted.py | 5 ++--- bazarr/api/files/files.py | 5 ++--- bazarr/api/files/files_radarr.py | 5 ++--- bazarr/api/files/files_sonarr.py | 5 ++--- bazarr/api/history/stats.py | 5 ++--- bazarr/api/movies/blacklist.py | 7 +++---- bazarr/api/movies/history.py | 5 ++--- bazarr/api/movies/movies.py | 5 ++--- bazarr/api/movies/wanted.py | 5 ++--- bazarr/api/providers/providers.py | 5 ++--- bazarr/api/providers/providers_episodes.py | 5 ++--- bazarr/api/providers/providers_movies.py | 5 ++--- bazarr/api/series/series.py | 5 ++--- bazarr/api/subtitles/subtitles_info.py | 5 ++--- bazarr/api/system/backups.py | 5 ++--- bazarr/api/system/logs.py | 5 ++--- bazarr/api/system/releases.py | 5 ++--- bazarr/api/system/tasks.py | 5 ++--- 22 files changed, 47 insertions(+), 69 deletions(-) diff --git a/bazarr/api/badges/badges.py b/bazarr/api/badges/badges.py index c660ed968..aa0a1ff08 100644 --- a/bazarr/api/badges/badges.py +++ b/bazarr/api/badges/badges.py @@ -4,7 +4,7 @@ import ast from functools import reduce -from flask_restx import Resource, Namespace, fields +from flask_restx import Resource, Namespace, fields, marshal from app.database import get_exclusion_clause, TableEpisodes, TableShows, TableMovies, database, select from app.get_providers import get_throttled_providers @@ -31,7 +31,6 @@ class Badges(Resource): }) @authenticate - @api_ns_badges.marshal_with(get_model, code=200) @api_ns_badges.response(401, 'Not Authenticated') @api_ns_badges.doc(parser=None) def get(self): @@ -74,4 +73,4 @@ def get(self): 'radarr_signalr': "LIVE" if radarr_signalr_client.connected else "", 'announcements': len(get_all_announcements()), } - return result + return marshal(result, self.get_model) diff --git a/bazarr/api/episodes/blacklist.py b/bazarr/api/episodes/blacklist.py index 7bcab005b..25d7478a0 100644 --- a/bazarr/api/episodes/blacklist.py +++ b/bazarr/api/episodes/blacklist.py @@ -2,7 +2,7 @@ import pretty -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.database import TableEpisodes, TableShows, TableBlacklist, database, select from subtitles.tools.delete import delete_subtitles @@ -39,7 +39,6 @@ class EpisodesBlacklist(Resource): }) @authenticate - @api_ns_episodes_blacklist.marshal_with(get_response_model, envelope='data', code=200) @api_ns_episodes_blacklist.response(401, 'Not Authenticated') @api_ns_episodes_blacklist.doc(parser=get_request_parser) def get(self): @@ -63,7 +62,7 @@ def get(self): if length > 0: stmt = stmt.limit(length).offset(start) - return [postprocess({ + return marshal([postprocess({ 'seriesTitle': x.seriesTitle, 'episode_number': x.episode_number, 'episodeTitle': x.episodeTitle, @@ -73,7 +72,7 @@ def get(self): 'language': x.language, 'timestamp': pretty.date(x.timestamp), 'parsed_timestamp': x.timestamp.strftime('%x %X') - }) for x in database.execute(stmt).all()] + }) for x in database.execute(stmt).all()], self.get_response_model, envelope='data') post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('seriesid', type=int, required=True, help='Series ID') diff --git a/bazarr/api/episodes/episodes.py b/bazarr/api/episodes/episodes.py index 00cdfea6e..e2d460c80 100644 --- a/bazarr/api/episodes/episodes.py +++ b/bazarr/api/episodes/episodes.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.database import TableEpisodes, database, select from api.swaggerui import subtitles_model, subtitles_language_model, audio_language_model @@ -37,7 +37,6 @@ class Episodes(Resource): }) @authenticate - @api_ns_episodes.marshal_with(get_response_model, envelope='data', code=200) @api_ns_episodes.doc(parser=get_request_parser) @api_ns_episodes.response(200, 'Success') @api_ns_episodes.response(401, 'Not Authenticated') @@ -76,7 +75,7 @@ def get(self): else: return "Series or Episode ID not provided", 404 - return [postprocess({ + return marshal([postprocess({ 'audio_language': x.audio_language, 'episode': x.episode, 'missing_subtitles': x.missing_subtitles, @@ -88,4 +87,4 @@ def get(self): 'subtitles': x.subtitles, 'title': x.title, 'sceneName': x.sceneName, - }) for x in stmt_query] + }) for x in stmt_query], self.get_response_model, envelope='data') diff --git a/bazarr/api/episodes/history.py b/bazarr/api/episodes/history.py index 606a19840..ca3e8a500 100644 --- a/bazarr/api/episodes/history.py +++ b/bazarr/api/episodes/history.py @@ -9,7 +9,7 @@ from subtitles.upgrade import get_upgradable_episode_subtitles, _language_still_desired import pretty -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from ..utils import authenticate, postprocess api_ns_episodes_history = Namespace('Episodes History', description='List episodes history events') @@ -53,7 +53,6 @@ class EpisodesHistory(Resource): }) @authenticate - @api_ns_episodes_history.marshal_with(get_response_model, code=200) @api_ns_episodes_history.response(401, 'Not Authenticated') @api_ns_episodes_history.doc(parser=get_request_parser) def get(self): @@ -176,4 +175,4 @@ def get(self): .where(TableEpisodes.title.is_not(None))) \ .scalar() - return {'data': episode_history, 'total': count} + return marshal({'data': episode_history, 'total': count}, self.get_response_model) diff --git a/bazarr/api/episodes/wanted.py b/bazarr/api/episodes/wanted.py index af6dd582b..ae7337751 100644 --- a/bazarr/api/episodes/wanted.py +++ b/bazarr/api/episodes/wanted.py @@ -2,7 +2,7 @@ import operator -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from functools import reduce from app.database import get_exclusion_clause, TableEpisodes, TableShows, database, select, func @@ -41,7 +41,6 @@ class EpisodesWanted(Resource): }) @authenticate - @api_ns_episodes_wanted.marshal_with(get_response_model, code=200) @api_ns_episodes_wanted.response(401, 'Not Authenticated') @api_ns_episodes_wanted.doc(parser=get_request_parser) def get(self): @@ -96,4 +95,4 @@ def get(self): .where(wanted_condition)) \ .scalar() - return {'data': results, 'total': count} + return marshal({'data': results, 'total': count}, self.get_response_model) diff --git a/bazarr/api/files/files.py b/bazarr/api/files/files.py index 434faeae9..fccb05ce6 100644 --- a/bazarr/api/files/files.py +++ b/bazarr/api/files/files.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from utilities.filesystem import browse_bazarr_filesystem @@ -21,7 +21,6 @@ class BrowseBazarrFS(Resource): }) @authenticate - @api_ns_files.marshal_with(get_response_model, code=200) @api_ns_files.response(401, 'Not Authenticated') @api_ns_files.doc(parser=get_request_parser) def get(self): @@ -37,4 +36,4 @@ def get(self): return [] for item in result['directories']: data.append({'name': item['name'], 'children': True, 'path': item['path']}) - return data + return marshal(data, self.get_response_model) diff --git a/bazarr/api/files/files_radarr.py b/bazarr/api/files/files_radarr.py index b6d6eed33..2f4d7e802 100644 --- a/bazarr/api/files/files_radarr.py +++ b/bazarr/api/files/files_radarr.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from radarr.filesystem import browse_radarr_filesystem @@ -22,7 +22,6 @@ class BrowseRadarrFS(Resource): }) @authenticate - @api_ns_files_radarr.marshal_with(get_response_model, code=200) @api_ns_files_radarr.response(401, 'Not Authenticated') @api_ns_files_radarr.doc(parser=get_request_parser) def get(self): @@ -38,4 +37,4 @@ def get(self): return [] for item in result['directories']: data.append({'name': item['name'], 'children': True, 'path': item['path']}) - return data + return marshal(data, self.get_response_model) diff --git a/bazarr/api/files/files_sonarr.py b/bazarr/api/files/files_sonarr.py index f82bab9b7..61dd2d587 100644 --- a/bazarr/api/files/files_sonarr.py +++ b/bazarr/api/files/files_sonarr.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from sonarr.filesystem import browse_sonarr_filesystem @@ -22,7 +22,6 @@ class BrowseSonarrFS(Resource): }) @authenticate - @api_ns_files_sonarr.marshal_with(get_response_model, code=200) @api_ns_files_sonarr.response(401, 'Not Authenticated') @api_ns_files_sonarr.doc(parser=get_request_parser) def get(self): @@ -38,4 +37,4 @@ def get(self): return [] for item in result['directories']: data.append({'name': item['name'], 'children': True, 'path': item['path']}) - return data + return marshal(data, self.get_response_model) diff --git a/bazarr/api/history/stats.py b/bazarr/api/history/stats.py index 73c54cfb7..bc891f3ae 100644 --- a/bazarr/api/history/stats.py +++ b/bazarr/api/history/stats.py @@ -5,7 +5,7 @@ import itertools from dateutil import rrule -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from functools import reduce from app.database import TableHistory, TableHistoryMovie, database, select @@ -41,7 +41,6 @@ class HistoryStats(Resource): }) @authenticate - @api_ns_history_stats.marshal_with(get_response_model, code=200) @api_ns_history_stats.response(401, 'Not Authenticated') @api_ns_history_stats.doc(parser=get_request_parser) def get(self): @@ -121,4 +120,4 @@ def get(self): sorted_data_series = sorted(data_series, key=lambda i: i['date']) sorted_data_movies = sorted(data_movies, key=lambda i: i['date']) - return {'series': sorted_data_series, 'movies': sorted_data_movies} + return marshal({'series': sorted_data_series, 'movies': sorted_data_movies}, self.get_response_model) diff --git a/bazarr/api/movies/blacklist.py b/bazarr/api/movies/blacklist.py index 22bb09ce9..6e679ffae 100644 --- a/bazarr/api/movies/blacklist.py +++ b/bazarr/api/movies/blacklist.py @@ -2,7 +2,7 @@ import pretty -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.database import TableMovies, TableBlacklistMovie, database, select from subtitles.tools.delete import delete_subtitles @@ -37,7 +37,6 @@ class MoviesBlacklist(Resource): }) @authenticate - @api_ns_movies_blacklist.marshal_with(get_response_model, envelope='data', code=200) @api_ns_movies_blacklist.response(401, 'Not Authenticated') @api_ns_movies_blacklist.doc(parser=get_request_parser) def get(self): @@ -59,7 +58,7 @@ def get(self): if length > 0: data = data.limit(length).offset(start) - return [postprocess({ + return marshal([postprocess({ 'title': x.title, 'radarrId': x.radarrId, 'provider': x.provider, @@ -67,7 +66,7 @@ def get(self): 'language': x.language, 'timestamp': pretty.date(x.timestamp), 'parsed_timestamp': x.timestamp.strftime('%x %X'), - }) for x in data.all()] + }) for x in data.all()], self.get_response_model, envelope='data') post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('radarrid', type=int, required=True, help='Radarr ID') diff --git a/bazarr/api/movies/history.py b/bazarr/api/movies/history.py index 0469439b4..d7587607c 100644 --- a/bazarr/api/movies/history.py +++ b/bazarr/api/movies/history.py @@ -4,7 +4,7 @@ import pretty import ast -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from functools import reduce from app.database import TableMovies, TableHistoryMovie, TableBlacklistMovie, database, select, func @@ -52,7 +52,6 @@ class MoviesHistory(Resource): }) @authenticate - @api_ns_movies_history.marshal_with(get_response_model, code=200) @api_ns_movies_history.response(401, 'Not Authenticated') @api_ns_movies_history.doc(parser=get_request_parser) def get(self): @@ -167,4 +166,4 @@ def get(self): .where(TableMovies.title.is_not(None))) \ .scalar() - return {'data': movie_history, 'total': count} + return marshal({'data': movie_history, 'total': count}, self.get_response_model) diff --git a/bazarr/api/movies/movies.py b/bazarr/api/movies/movies.py index ec181b2f8..7cd77d71f 100644 --- a/bazarr/api/movies/movies.py +++ b/bazarr/api/movies/movies.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.database import TableMovies, database, update, select, func from subtitles.indexer.movies import list_missing_subtitles_movies, movies_scan_subtitles @@ -52,7 +52,6 @@ class Movies(Resource): }) @authenticate - @api_ns_movies.marshal_with(get_response_model, code=200) @api_ns_movies.doc(parser=get_request_parser) @api_ns_movies.response(200, 'Success') @api_ns_movies.response(401, 'Not Authenticated') @@ -112,7 +111,7 @@ def get(self): .select_from(TableMovies)) \ .scalar() - return {'data': results, 'total': count} + return marshal({'data': results, 'total': count}, self.get_response_model) post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('radarrid', type=int, action='append', required=False, default=[], diff --git a/bazarr/api/movies/wanted.py b/bazarr/api/movies/wanted.py index 256788954..7ee648fc5 100644 --- a/bazarr/api/movies/wanted.py +++ b/bazarr/api/movies/wanted.py @@ -2,7 +2,7 @@ import operator -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from functools import reduce from app.database import get_exclusion_clause, TableMovies, database, select, func @@ -38,7 +38,6 @@ class MoviesWanted(Resource): }) @authenticate - @api_ns_movies_wanted.marshal_with(get_response_model, code=200) @api_ns_movies_wanted.response(401, 'Not Authenticated') @api_ns_movies_wanted.doc(parser=get_request_parser) def get(self): @@ -81,4 +80,4 @@ def get(self): .where(wanted_condition)) \ .scalar() - return {'data': results, 'total': count} + return marshal({'data': results, 'total': count}, self.get_response_model) diff --git a/bazarr/api/providers/providers.py b/bazarr/api/providers/providers.py index 41f054fea..eb74987d7 100644 --- a/bazarr/api/providers/providers.py +++ b/bazarr/api/providers/providers.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from operator import itemgetter from app.database import TableHistory, TableHistoryMovie, database, select @@ -23,7 +23,6 @@ class Providers(Resource): }) @authenticate - @api_ns_providers.marshal_with(get_response_model, envelope='data', code=200) @api_ns_providers.response(200, 'Success') @api_ns_providers.response(401, 'Not Authenticated') @api_ns_providers.doc(parser=get_request_parser) @@ -61,7 +60,7 @@ def get(self): "status": provider[1] if provider[1] is not None else "Good", "retry": provider[2] if provider[2] != "now" else "-" }) - return sorted(providers_dicts, key=itemgetter('name')) + return marshal(sorted(providers_dicts, key=itemgetter('name')), self.get_response_model, envelope='data') post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('action', type=str, required=True, help='Action to perform from ["reset"]') diff --git a/bazarr/api/providers/providers_episodes.py b/bazarr/api/providers/providers_episodes.py index 8b9c72228..1192de4c1 100644 --- a/bazarr/api/providers/providers_episodes.py +++ b/bazarr/api/providers/providers_episodes.py @@ -2,7 +2,7 @@ import os -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.database import TableEpisodes, TableShows, get_audio_profile_languages, get_profile_id, database, select from utilities.path_mappings import path_mappings @@ -41,7 +41,6 @@ class ProviderEpisodes(Resource): }) @authenticate - @api_ns_providers_episodes.marshal_with(get_response_model, envelope='data', code=200) @api_ns_providers_episodes.response(401, 'Not Authenticated') @api_ns_providers_episodes.response(404, 'Episode not found') @api_ns_providers_episodes.response(410, 'Episode file not found. Path mapping issue?') @@ -77,7 +76,7 @@ def get(self): data = manual_search(episodePath, profileId, providers_list, sceneName, title, 'series') if not data: data = [] - return data + return marshal(data, self.get_response_model, envelope='data') post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('seriesid', type=int, required=True, help='Series ID') diff --git a/bazarr/api/providers/providers_movies.py b/bazarr/api/providers/providers_movies.py index 07df28785..3d84e102e 100644 --- a/bazarr/api/providers/providers_movies.py +++ b/bazarr/api/providers/providers_movies.py @@ -2,7 +2,7 @@ import os -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.database import TableMovies, get_audio_profile_languages, get_profile_id, database, select from utilities.path_mappings import path_mappings @@ -42,7 +42,6 @@ class ProviderMovies(Resource): }) @authenticate - @api_ns_providers_movies.marshal_with(get_response_model, envelope='data', code=200) @api_ns_providers_movies.response(401, 'Not Authenticated') @api_ns_providers_movies.response(404, 'Movie not found') @api_ns_providers_movies.response(410, 'Movie file not found. Path mapping issue?') @@ -76,7 +75,7 @@ def get(self): data = manual_search(moviePath, profileId, providers_list, sceneName, title, 'movie') if not data: data = [] - return data + return marshal(data, self.get_response_model, envelope='data') post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('radarrid', type=int, required=True, help='Movie ID') diff --git a/bazarr/api/series/series.py b/bazarr/api/series/series.py index 0d6d1e8de..2e366c561 100644 --- a/bazarr/api/series/series.py +++ b/bazarr/api/series/series.py @@ -2,7 +2,7 @@ import operator -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from functools import reduce from app.database import get_exclusion_clause, TableEpisodes, TableShows, database, select, update, func @@ -56,7 +56,6 @@ class Series(Resource): }) @authenticate - @api_ns_series.marshal_with(get_response_model, code=200) @api_ns_series.doc(parser=get_request_parser) @api_ns_series.response(200, 'Success') @api_ns_series.response(401, 'Not Authenticated') @@ -137,7 +136,7 @@ def get(self): .select_from(TableShows)) \ .scalar() - return {'data': results, 'total': count} + return marshal({'data': results, 'total': count}, self.get_response_model) post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('seriesid', type=int, action='append', required=False, default=[], diff --git a/bazarr/api/subtitles/subtitles_info.py b/bazarr/api/subtitles/subtitles_info.py index 405a44cae..a154be24f 100644 --- a/bazarr/api/subtitles/subtitles_info.py +++ b/bazarr/api/subtitles/subtitles_info.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from subliminal_patch.core import guessit from ..utils import authenticate @@ -24,7 +24,6 @@ class SubtitleNameInfo(Resource): }) @authenticate - @api_ns_subtitles_info.marshal_with(get_response_model, envelope='data', code=200) @api_ns_subtitles_info.response(200, 'Success') @api_ns_subtitles_info.response(401, 'Not Authenticated') @api_ns_subtitles_info.doc(parser=get_request_parser) @@ -60,4 +59,4 @@ def get(self): results.append(result) - return results + return marshal(results, self.get_response_model, envelope='data') diff --git a/bazarr/api/system/backups.py b/bazarr/api/system/backups.py index a2ee5c2f4..bbb499800 100644 --- a/bazarr/api/system/backups.py +++ b/bazarr/api/system/backups.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from utilities.backup import get_backup_files, prepare_restore, delete_backup_file, backup_to_zip @@ -19,14 +19,13 @@ class SystemBackups(Resource): }) @authenticate - @api_ns_system_backups.marshal_with(get_response_model, envelope='data', code=200) @api_ns_system_backups.doc(parser=None) @api_ns_system_backups.response(204, 'Success') @api_ns_system_backups.response(401, 'Not Authenticated') def get(self): """List backup files""" backups = get_backup_files(fullpath=False) - return backups + return marshal(backups, self.get_response_model, envelope='data') @authenticate @api_ns_system_backups.doc(parser=None) diff --git a/bazarr/api/system/logs.py b/bazarr/api/system/logs.py index 501ff65d0..606b900d5 100644 --- a/bazarr/api/system/logs.py +++ b/bazarr/api/system/logs.py @@ -3,7 +3,7 @@ import io import os -from flask_restx import Resource, Namespace, fields +from flask_restx import Resource, Namespace, fields, marshal from app.logger import empty_log from app.get_args import args @@ -23,7 +23,6 @@ class SystemLogs(Resource): }) @authenticate - @api_ns_system_logs.marshal_with(get_response_model, envelope='data', code=200) @api_ns_system_logs.doc(parser=None) @api_ns_system_logs.response(200, 'Success') @api_ns_system_logs.response(401, 'Not Authenticated') @@ -50,7 +49,7 @@ def get(self): logs.append(log) logs.reverse() - return logs + return marshal(logs, self.get_response_model, envelope='data') @authenticate @api_ns_system_logs.doc(parser=None) diff --git a/bazarr/api/system/releases.py b/bazarr/api/system/releases.py index 7abd05ee7..e4848c597 100644 --- a/bazarr/api/system/releases.py +++ b/bazarr/api/system/releases.py @@ -5,7 +5,7 @@ import os import logging -from flask_restx import Resource, Namespace, fields +from flask_restx import Resource, Namespace, fields, marshal from app.config import settings from app.get_args import args @@ -26,7 +26,6 @@ class SystemReleases(Resource): }) @authenticate - @api_ns_system_releases.marshal_with(get_response_model, envelope='data', code=200) @api_ns_system_releases.doc(parser=None) @api_ns_system_releases.response(200, 'Success') @api_ns_system_releases.response(401, 'Not Authenticated') @@ -60,4 +59,4 @@ def get(self): except Exception: logging.exception( 'BAZARR cannot parse releases caching file: ' + os.path.join(args.config_dir, 'config', 'releases.txt')) - return filtered_releases + return marshal(filtered_releases, self.get_response_model, envelope='data') diff --git a/bazarr/api/system/tasks.py b/bazarr/api/system/tasks.py index 21d513d4b..264427735 100644 --- a/bazarr/api/system/tasks.py +++ b/bazarr/api/system/tasks.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask_restx import Resource, Namespace, reqparse, fields +from flask_restx import Resource, Namespace, reqparse, fields, marshal from app.scheduler import scheduler @@ -24,7 +24,6 @@ class SystemTasks(Resource): get_request_parser.add_argument('taskid', type=str, required=False, help='List tasks or a single task properties') @authenticate - @api_ns_system_tasks.marshal_with(get_response_model, envelope='data', code=200) @api_ns_system_tasks.doc(parser=None) @api_ns_system_tasks.response(200, 'Success') @api_ns_system_tasks.response(401, 'Not Authenticated') @@ -41,7 +40,7 @@ def get(self): task_list = [item] continue - return task_list + return marshal(task_list, self.get_response_model, envelope='data') post_request_parser = reqparse.RequestParser() post_request_parser.add_argument('taskid', type=str, required=True, help='Task id of the task to run') From 77283e406371c8049f6bc3c92a4fe2ac02b4db25 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Tue, 5 Sep 2023 08:29:40 -0400 Subject: [PATCH 3/9] Fixed improper http status code returned that prevented proper cache management on browser side. --- bazarr/api/episodes/blacklist.py | 4 ++-- bazarr/api/episodes/episodes_subtitles.py | 12 ++++++------ bazarr/api/movies/blacklist.py | 4 ++-- bazarr/api/movies/movies.py | 4 ++-- bazarr/api/movies/movies_subtitles.py | 12 ++++++------ bazarr/api/providers/providers_episodes.py | 4 ++-- bazarr/api/providers/providers_movies.py | 4 ++-- bazarr/api/series/series.py | 4 ++-- bazarr/api/subtitles/subtitles.py | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/bazarr/api/episodes/blacklist.py b/bazarr/api/episodes/blacklist.py index 25d7478a0..8c5ffc0c0 100644 --- a/bazarr/api/episodes/blacklist.py +++ b/bazarr/api/episodes/blacklist.py @@ -87,7 +87,7 @@ def get(self): @api_ns_episodes_blacklist.response(200, 'Success') @api_ns_episodes_blacklist.response(401, 'Not Authenticated') @api_ns_episodes_blacklist.response(404, 'Episode not found') - @api_ns_episodes_blacklist.response(410, 'Subtitles file not found or permission issue.') + @api_ns_episodes_blacklist.response(500, 'Subtitles file not found or permission issue.') def post(self): """Add an episodes subtitles to blacklist""" args = self.post_request_parser.parse_args() @@ -125,7 +125,7 @@ def post(self): event_stream(type='episode-history') return '', 200 else: - return 'Subtitles file not found or permission issue.', 410 + return 'Subtitles file not found or permission issue.', 500 delete_request_parser = reqparse.RequestParser() delete_request_parser.add_argument('all', type=str, required=False, help='Empty episodes subtitles blacklist') diff --git a/bazarr/api/episodes/episodes_subtitles.py b/bazarr/api/episodes/episodes_subtitles.py index 520a809a6..2c25bb6de 100644 --- a/bazarr/api/episodes/episodes_subtitles.py +++ b/bazarr/api/episodes/episodes_subtitles.py @@ -38,7 +38,7 @@ class EpisodesSubtitles(Resource): @api_ns_episodes_subtitles.response(401, 'Not Authenticated') @api_ns_episodes_subtitles.response(404, 'Episode not found') @api_ns_episodes_subtitles.response(409, 'Unable to save subtitles file. Permission or path mapping issue?') - @api_ns_episodes_subtitles.response(410, 'Episode file not found. Path mapping issue?') + @api_ns_episodes_subtitles.response(500, 'Episode file not found. Path mapping issue?') def patch(self): """Download an episode subtitles""" args = self.patch_request_parser.parse_args() @@ -60,7 +60,7 @@ def patch(self): episodePath = path_mappings.path_replace(episodeInfo.path) if not os.path.exists(episodePath): - return 'Episode file not found. Path mapping issue?', 410 + return 'Episode file not found. Path mapping issue?', 500 sceneName = episodeInfo.sceneName or "None" @@ -106,7 +106,7 @@ def patch(self): @api_ns_episodes_subtitles.response(401, 'Not Authenticated') @api_ns_episodes_subtitles.response(404, 'Episode not found') @api_ns_episodes_subtitles.response(409, 'Unable to save subtitles file. Permission or path mapping issue?') - @api_ns_episodes_subtitles.response(410, 'Episode file not found. Path mapping issue?') + @api_ns_episodes_subtitles.response(500, 'Episode file not found. Path mapping issue?') def post(self): """Upload an episode subtitles""" args = self.post_request_parser.parse_args() @@ -124,7 +124,7 @@ def post(self): episodePath = path_mappings.path_replace(episodeInfo.path) if not os.path.exists(episodePath): - return 'Episode file not found. Path mapping issue?', 410 + return 'Episode file not found. Path mapping issue?', 500 audio_language = get_audio_profile_languages(episodeInfo.audio_language) if len(audio_language) and isinstance(audio_language[0], dict): @@ -178,7 +178,7 @@ def post(self): @api_ns_episodes_subtitles.response(204, 'Success') @api_ns_episodes_subtitles.response(401, 'Not Authenticated') @api_ns_episodes_subtitles.response(404, 'Episode not found') - @api_ns_episodes_subtitles.response(410, 'Subtitles file not found or permission issue.') + @api_ns_episodes_subtitles.response(500, 'Subtitles file not found or permission issue.') def delete(self): """Delete an episode subtitles""" args = self.delete_request_parser.parse_args() @@ -211,4 +211,4 @@ def delete(self): sonarr_episode_id=sonarrEpisodeId): return '', 204 else: - return 'Subtitles file not found or permission issue.', 410 + return 'Subtitles file not found or permission issue.', 500 diff --git a/bazarr/api/movies/blacklist.py b/bazarr/api/movies/blacklist.py index 6e679ffae..60c069597 100644 --- a/bazarr/api/movies/blacklist.py +++ b/bazarr/api/movies/blacklist.py @@ -80,7 +80,7 @@ def get(self): @api_ns_movies_blacklist.response(200, 'Success') @api_ns_movies_blacklist.response(401, 'Not Authenticated') @api_ns_movies_blacklist.response(404, 'Movie not found') - @api_ns_movies_blacklist.response(410, 'Subtitles file not found or permission issue.') + @api_ns_movies_blacklist.response(500, 'Subtitles file not found or permission issue.') def post(self): """Add a movies subtitles to blacklist""" args = self.post_request_parser.parse_args() @@ -118,7 +118,7 @@ def post(self): event_stream(type='movie-history') return '', 200 else: - return 'Subtitles file not found or permission issue.', 410 + return 'Subtitles file not found or permission issue.', 500 delete_request_parser = reqparse.RequestParser() delete_request_parser.add_argument('all', type=str, required=False, help='Empty movies subtitles blacklist') diff --git a/bazarr/api/movies/movies.py b/bazarr/api/movies/movies.py index 7cd77d71f..022769d9d 100644 --- a/bazarr/api/movies/movies.py +++ b/bazarr/api/movies/movies.py @@ -165,7 +165,7 @@ def post(self): @api_ns_movies.response(204, 'Success') @api_ns_movies.response(400, 'Unknown action') @api_ns_movies.response(401, 'Not Authenticated') - @api_ns_movies.response(410, 'Movie file not found. Path mapping issue?') + @api_ns_movies.response(500, 'Movie file not found. Path mapping issue?') def patch(self): """Run actions on specific movies""" args = self.patch_request_parser.parse_args() @@ -178,7 +178,7 @@ def patch(self): try: movies_download_subtitles(radarrid) except OSError: - return 'Movie file not found. Path mapping issue?', 410 + return 'Movie file not found. Path mapping issue?', 500 else: return '', 204 elif action == "search-wanted": diff --git a/bazarr/api/movies/movies_subtitles.py b/bazarr/api/movies/movies_subtitles.py index 38fd4a428..98c359cdc 100644 --- a/bazarr/api/movies/movies_subtitles.py +++ b/bazarr/api/movies/movies_subtitles.py @@ -37,7 +37,7 @@ class MoviesSubtitles(Resource): @api_ns_movies_subtitles.response(401, 'Not Authenticated') @api_ns_movies_subtitles.response(404, 'Movie not found') @api_ns_movies_subtitles.response(409, 'Unable to save subtitles file. Permission or path mapping issue?') - @api_ns_movies_subtitles.response(410, 'Movie file not found. Path mapping issue?') + @api_ns_movies_subtitles.response(500, 'Movie file not found. Path mapping issue?') def patch(self): """Download a movie subtitles""" args = self.patch_request_parser.parse_args() @@ -58,7 +58,7 @@ def patch(self): moviePath = path_mappings.path_replace_movie(movieInfo.path) if not os.path.exists(moviePath): - return 'Movie file not found. Path mapping issue?', 410 + return 'Movie file not found. Path mapping issue?', 500 sceneName = movieInfo.sceneName or 'None' @@ -103,7 +103,7 @@ def patch(self): @api_ns_movies_subtitles.response(401, 'Not Authenticated') @api_ns_movies_subtitles.response(404, 'Movie not found') @api_ns_movies_subtitles.response(409, 'Unable to save subtitles file. Permission or path mapping issue?') - @api_ns_movies_subtitles.response(410, 'Movie file not found. Path mapping issue?') + @api_ns_movies_subtitles.response(500, 'Movie file not found. Path mapping issue?') def post(self): """Upload a movie subtitles""" # TODO: Support Multiply Upload @@ -120,7 +120,7 @@ def post(self): moviePath = path_mappings.path_replace_movie(movieInfo.path) if not os.path.exists(moviePath): - return 'Movie file not found. Path mapping issue?', 410 + return 'Movie file not found. Path mapping issue?', 500 audio_language = get_audio_profile_languages(movieInfo.audio_language) if len(audio_language) and isinstance(audio_language[0], dict): @@ -174,7 +174,7 @@ def post(self): @api_ns_movies_subtitles.response(204, 'Success') @api_ns_movies_subtitles.response(401, 'Not Authenticated') @api_ns_movies_subtitles.response(404, 'Movie not found') - @api_ns_movies_subtitles.response(410, 'Subtitles file not found or permission issue.') + @api_ns_movies_subtitles.response(500, 'Subtitles file not found or permission issue.') def delete(self): """Delete a movie subtitles""" args = self.delete_request_parser.parse_args() @@ -205,4 +205,4 @@ def delete(self): radarr_id=radarrId): return '', 204 else: - return 'Subtitles file not found or permission issue.', 410 + return 'Subtitles file not found or permission issue.', 500 diff --git a/bazarr/api/providers/providers_episodes.py b/bazarr/api/providers/providers_episodes.py index 1192de4c1..9d819dc59 100644 --- a/bazarr/api/providers/providers_episodes.py +++ b/bazarr/api/providers/providers_episodes.py @@ -43,7 +43,7 @@ class ProviderEpisodes(Resource): @authenticate @api_ns_providers_episodes.response(401, 'Not Authenticated') @api_ns_providers_episodes.response(404, 'Episode not found') - @api_ns_providers_episodes.response(410, 'Episode file not found. Path mapping issue?') + @api_ns_providers_episodes.response(500, 'Episode file not found. Path mapping issue?') @api_ns_providers_episodes.doc(parser=get_request_parser) def get(self): """Search manually for an episode subtitles""" @@ -66,7 +66,7 @@ def get(self): episodePath = path_mappings.path_replace(episodeInfo.path) if not os.path.exists(episodePath): - return 'Episode file not found. Path mapping issue?', 410 + return 'Episode file not found. Path mapping issue?', 500 sceneName = episodeInfo.sceneName or "None" profileId = episodeInfo.profileId diff --git a/bazarr/api/providers/providers_movies.py b/bazarr/api/providers/providers_movies.py index 3d84e102e..af3b55358 100644 --- a/bazarr/api/providers/providers_movies.py +++ b/bazarr/api/providers/providers_movies.py @@ -44,7 +44,7 @@ class ProviderMovies(Resource): @authenticate @api_ns_providers_movies.response(401, 'Not Authenticated') @api_ns_providers_movies.response(404, 'Movie not found') - @api_ns_providers_movies.response(410, 'Movie file not found. Path mapping issue?') + @api_ns_providers_movies.response(500, 'Movie file not found. Path mapping issue?') @api_ns_providers_movies.doc(parser=get_request_parser) def get(self): """Search manually for a movie subtitles""" @@ -65,7 +65,7 @@ def get(self): moviePath = path_mappings.path_replace_movie(movieInfo.path) if not os.path.exists(moviePath): - return 'Movie file not found. Path mapping issue?', 410 + return 'Movie file not found. Path mapping issue?', 500 sceneName = movieInfo.sceneName or "None" profileId = movieInfo.profileId diff --git a/bazarr/api/series/series.py b/bazarr/api/series/series.py index 2e366c561..13bda6f0d 100644 --- a/bazarr/api/series/series.py +++ b/bazarr/api/series/series.py @@ -198,7 +198,7 @@ def post(self): @api_ns_series.response(204, 'Success') @api_ns_series.response(400, 'Unknown action') @api_ns_series.response(401, 'Not Authenticated') - @api_ns_series.response(410, 'Series directory not found. Path mapping issue?') + @api_ns_series.response(500, 'Series directory not found. Path mapping issue?') def patch(self): """Run actions on specific series""" args = self.patch_request_parser.parse_args() @@ -211,7 +211,7 @@ def patch(self): try: series_download_subtitles(seriesid) except OSError: - return 'Series directory not found. Path mapping issue?', 410 + return 'Series directory not found. Path mapping issue?', 500 else: return '', 204 elif action == "search-wanted": diff --git a/bazarr/api/subtitles/subtitles.py b/bazarr/api/subtitles/subtitles.py index bf655099a..4822ae644 100644 --- a/bazarr/api/subtitles/subtitles.py +++ b/bazarr/api/subtitles/subtitles.py @@ -43,7 +43,7 @@ class Subtitles(Resource): @api_ns_subtitles.response(401, 'Not Authenticated') @api_ns_subtitles.response(404, 'Episode/movie not found') @api_ns_subtitles.response(409, 'Unable to edit subtitles file. Check logs.') - @api_ns_subtitles.response(410, 'Subtitles file not found. Path mapping issue?') + @api_ns_subtitles.response(500, 'Subtitles file not found. Path mapping issue?') def patch(self): """Apply mods/tools on external subtitles""" args = self.patch_request_parser.parse_args() @@ -55,7 +55,7 @@ def patch(self): id = args.get('id') if not os.path.exists(subtitles_path): - return 'Subtitles file not found. Path mapping issue?', 410 + return 'Subtitles file not found. Path mapping issue?', 500 if media_type == 'episode': metadata = database.execute( From 1489926b6f52e8881e45773486fe2530d5dfeca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Blahovi=C4=8D?= <45385678+a-blaho@users.noreply.github.com> Date: Tue, 5 Sep 2023 20:20:33 +0200 Subject: [PATCH 4/9] Fixed minimal value for time offset input field --- .../src/components/forms/TimeOffsetForm.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/forms/TimeOffsetForm.tsx b/frontend/src/components/forms/TimeOffsetForm.tsx index b02e75781..6d213b359 100644 --- a/frontend/src/components/forms/TimeOffsetForm.tsx +++ b/frontend/src/components/forms/TimeOffsetForm.tsx @@ -83,12 +83,25 @@ const TimeOffsetForm: FunctionComponent = ({ selections, onSubmit }) => { > - - - + + +