From b6e58adcdca4c9da94a63169ab39b03374379ef8 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Fri, 19 Apr 2019 20:43:55 +0200 Subject: [PATCH 01/12] * Added api/v2 search endpoint. Forced searches are now using the BacklogQueueItem. But the search is placed in the forced_search_queue_scheduler scheduler. This means that a scheduled proper, backlog or daily search will not interfere with a forced search --- medusa/search/manual.py | 12 +- medusa/search/queue.py | 122 +++++++++++++--- medusa/server/api/v2/search.py | 248 +++++++++++++++++++++++++++++++++ medusa/server/core.py | 6 + 4 files changed, 363 insertions(+), 25 deletions(-) create mode 100644 medusa/server/api/v2/search.py diff --git a/medusa/search/manual.py b/medusa/search/manual.py index 93aa6edc28..db438b1eec 100644 --- a/medusa/search/manual.py +++ b/medusa/search/manual.py @@ -22,7 +22,7 @@ from medusa.helper.common import enabled_providers, pretty_file_size from medusa.logger.adapters.style import BraceAdapter from medusa.network_timezones import app_timezone -from medusa.search.queue import FORCED_SEARCH_HISTORY, ForcedSearchQueueItem +from medusa.search.queue import SEARCH_HISTORY, ManualSearchQueueItem from medusa.show.naming import contains_at_least_one_word, filter_bad_releases from medusa.show.show import Show @@ -121,11 +121,11 @@ def update_finished_search_queue_item(snatch_queue_item): """ # Finished Searches - for search_thread in FORCED_SEARCH_HISTORY: + for search_thread in SEARCH_HISTORY: if snatch_queue_item.show and not search_thread.show.indexerid == snatch_queue_item.show.indexerid: continue - if isinstance(search_thread, ForcedSearchQueueItem): + if isinstance(search_thread, ManualSearchQueueItem): if not isinstance(search_thread.segment, list): search_thread.segment = [search_thread.segment] @@ -163,11 +163,11 @@ def collect_episodes_from_search_thread(series_obj): # Finished Searches searchstatus = SEARCH_STATUS_FINISHED - for search_thread in FORCED_SEARCH_HISTORY: + for search_thread in SEARCH_HISTORY: if series_obj and not search_thread.show.identifier == series_obj.identifier: continue - if isinstance(search_thread, ForcedSearchQueueItem): + if isinstance(search_thread, ManualSearchQueueItem): if not [x for x in episodes if x['episodeindexerid'] in [search.indexerid for search in search_thread.segment]]: episodes += get_episodes(search_thread, searchstatus) else: @@ -286,7 +286,7 @@ def get_provider_cache_results(series_obj, show_all_results=None, perform_search and episode: {1}x{2}'.format(series_obj.name, season, episode) # make a queue item for it and put it on the queue - ep_queue_item = ForcedSearchQueueItem(ep_obj.series, [ep_obj], bool(int(down_cur_quality)), True, manual_search_type) # pylint: disable=maybe-no-member + ep_queue_item = ManualSearchQueueItem(ep_obj.series, [ep_obj], manual_search_type) # pylint: disable=maybe-no-member app.forced_search_queue_scheduler.action.add_item(ep_queue_item) diff --git a/medusa/search/queue.py b/medusa/search/queue.py index fb2f927bf3..057ff60330 100644 --- a/medusa/search/queue.py +++ b/medusa/search/queue.py @@ -12,7 +12,7 @@ from medusa import app, common, failed_history, generic_queue, history, ui from medusa.helpers import pretty_file_size from medusa.logger.adapters.style import BraceAdapter -from medusa.search import BACKLOG_SEARCH, DAILY_SEARCH, FAILED_SEARCH, FORCED_SEARCH, SNATCH_RESULT, SearchType +from medusa.search import BACKLOG_SEARCH, DAILY_SEARCH, FAILED_SEARCH, FORCED_SEARCH, MANUAL_SEARCH, SNATCH_RESULT, SearchType from medusa.search.core import ( search_for_needed_episodes, search_providers, @@ -25,8 +25,8 @@ search_queue_lock = threading.Lock() -FORCED_SEARCH_HISTORY = [] -FORCED_SEARCH_HISTORY_SIZE = 100 +SEARCH_HISTORY = [] +SEARCH_HISTORY_SIZE = 100 class SearchQueue(generic_queue.GenericQueue): @@ -42,7 +42,7 @@ def is_in_queue(self, show, segment): """Check if item is in queue.""" for cur_item in self.queue: if isinstance(cur_item, (BacklogQueueItem, FailedQueueItem, - ForcedSearchQueueItem, SnatchQueueItem)) \ + SnatchQueueItem, ManualSearchQueueItem)) \ and cur_item.show == show and cur_item.segment == segment: return True return False @@ -90,7 +90,7 @@ def add_item(self, item): # daily searches generic_queue.GenericQueue.add_item(self, item) elif isinstance(item, (BacklogQueueItem, FailedQueueItem, - SnatchQueueItem, ForcedSearchQueueItem)) \ + SnatchQueueItem, ManualSearchQueueItem)) \ and not self.is_in_queue(item.show, item.segment): generic_queue.GenericQueue.add_item(self, item) else: @@ -105,12 +105,12 @@ def force_daily(self): class ForcedSearchQueue(generic_queue.GenericQueue): - """Search Queueu used for Forced Search, Failed Search.""" + """Search Queueu used for Manual, Failed Search.""" def __init__(self): """Initialize ForcedSearch Queue.""" generic_queue.GenericQueue.__init__(self) - self.queue_name = 'SEARCHQUEUE' + self.queue_name = 'FORCEDSEARCHQUEUE' def is_in_queue(self, show, segment): """Verify if the show and segment (episode or number of episodes) are scheduled.""" @@ -122,14 +122,14 @@ def is_in_queue(self, show, segment): def is_ep_in_queue(self, segment): """Verify if the show and segment (episode or number of episodes) are scheduled.""" for cur_item in self.queue: - if isinstance(cur_item, (ForcedSearchQueueItem, FailedQueueItem)) and cur_item.segment == segment: + if isinstance(cur_item, (BacklogQueueItem, FailedQueueItem, ManualSearchQueueItem)) and cur_item.segment == segment: return True return False def is_show_in_queue(self, show): """Verify if the show is queued in this queue as a ForcedSearchQueueItem or FailedQueueItem.""" for cur_item in self.queue: - if isinstance(cur_item, (ForcedSearchQueueItem, FailedQueueItem)) and cur_item.show.indexerid == show: + if isinstance(cur_item, (BacklogQueueItem, FailedQueueItem, ManualSearchQueueItem)) and cur_item.show.indexerid == show: return True return False @@ -143,7 +143,7 @@ def get_all_ep_from_queue(self, series_obj): """ ep_obj_list = [] for cur_item in self.queue: - if isinstance(cur_item, (ForcedSearchQueueItem, FailedQueueItem)): + if isinstance(cur_item, (BacklogQueueItem, FailedQueueItem, ManualSearchQueueItem)): if series_obj and cur_item.show.identifier != series_obj.identifier: continue ep_obj_list.append(cur_item) @@ -159,8 +159,11 @@ def is_backlog_paused(self): return self.min_priority >= generic_queue.QueuePriorities.NORMAL def is_forced_search_in_progress(self): - """Test of a forced search is currently running, it doesn't check what's in queue.""" - if isinstance(self.currentItem, (ForcedSearchQueueItem, FailedQueueItem)): + """Test of a forced search is currently running (can be backlog, manual or failed search). + + it doesn't check what's in queue. + """ + if isinstance(self.currentItem, (BacklogQueueItem, ManualSearchQueueItem, FailedQueueItem)): return True return False @@ -170,15 +173,15 @@ def queue_length(self): for cur_item in self.queue: if isinstance(cur_item, FailedQueueItem): length['failed'] += 1 - elif isinstance(cur_item, ForcedSearchQueueItem) and not cur_item.manual_search: - length['forced_search'] += 1 - elif isinstance(cur_item, ForcedSearchQueueItem) and cur_item.manual_search: + elif isinstance(cur_item, BacklogQueueItem) and not cur_item.manual_search: + length['backlog_search'] += 1 + elif isinstance(cur_item, ManualSearchQueueItem): length['manual_search'] += 1 return length def add_item(self, item): - """Add a new ForcedSearchQueueItem or FailedQueueItem to the ForcedSearchQueue.""" - if isinstance(item, (ForcedSearchQueueItem, FailedQueueItem)) and not self.is_ep_in_queue(item.segment): + """Add a new ManualSearchQueueItem or FailedQueueItem to the ForcedSearchQueue.""" + if isinstance(item, (ManualSearchQueueItem, FailedQueueItem, BacklogQueueItem)) and not self.is_ep_in_queue(item.segment): # manual, snatch and failed searches generic_queue.GenericQueue.add_item(self, item) else: @@ -435,7 +438,88 @@ def run(self): log.debug(traceback.format_exc()) # Keep a list with the 100 last executed searches - fifo(FORCED_SEARCH_HISTORY, self, FORCED_SEARCH_HISTORY_SIZE) + fifo(SEARCH_HISTORY, self, SEARCH_HISTORY_SIZE) + + if self.success is None: + self.success = False + + self.finish() + + +class ManualSearchQueueItem(generic_queue.QueueItem): + """Manual search queue item class.""" + + def __init__(self, show, segment, manual_search_type='episode'): + """ + Initialize class of a QueueItem used to queue forced and manual searches. + + :param show: A show object + :param segment: A list of episode objects. + :param manual_search_type: Used to switch between episode and season search. Options are 'episode' or 'season'. + :return: The run() method searches and snatches the episode(s) if possible or it only searches and saves results to cache tables. + """ + generic_queue.QueueItem.__init__(self, u'Manual Search', MANUAL_SEARCH) + self.priority = generic_queue.QueuePriorities.HIGH + self.name = '{search_type}-{indexerid}'.format( + search_type='MANUAL', + indexerid=show.indexerid + ) + + self.success = None + self.started = None + self.results = None + + self.show = show + self.segment = segment + self.manual_search_type = manual_search_type + + def run(self): + """Run forced search thread.""" + generic_queue.QueueItem.run(self) + self.started = True + + try: + log.info( + 'Beginning {search_type} {season_pack}search for: {ep}', { + 'search_type': 'manual', + 'season_pack': ('', 'season pack ')[bool(self.manual_search_type == 'season')], + 'ep': self.segment[0].pretty_name() + } + ) + + search_result = search_providers(self.show, self.segment, True, True, + True, self.manual_search_type) + + if search_result: + self.results = search_result + self.success = True + + if self.manual_search_type == 'season': + ui.notifications.message('We have found season packs for {show_name}' + .format(show_name=self.show.name), + 'These should become visible in the manual select page.') + else: + ui.notifications.message('We have found results for {ep}' + .format(ep=self.segment[0].pretty_name()), + 'These should become visible in the manual select page.') + + else: + ui.notifications.message('No results were found') + log.info( + 'Unable to find {search_type} {season_pack}results for: {ep}', { + 'search_type': ('forced', 'manual')[bool(self.manual_search)], + 'season_pack': ('', 'season pack ')[bool(self.manual_search_type == 'season')], + 'ep': self.segment[0].pretty_name() + } + ) + + # TODO: Remove catch all exception. + except Exception: + self.success = False + log.debug(traceback.format_exc()) + + # Keep a list with the 100 last executed searches + fifo(SEARCH_HISTORY, self, SEARCH_HISTORY_SIZE) if self.success is None: self.success = False @@ -693,7 +777,7 @@ def run(self): log.info(traceback.format_exc()) # ## Keep a list with the 100 last executed searches - fifo(FORCED_SEARCH_HISTORY, self, FORCED_SEARCH_HISTORY_SIZE) + fifo(SEARCH_HISTORY, self, SEARCH_HISTORY_SIZE) if self.success is None: self.success = False diff --git a/medusa/server/api/v2/search.py b/medusa/server/api/v2/search.py new file mode 100644 index 0000000000..414451d83d --- /dev/null +++ b/medusa/server/api/v2/search.py @@ -0,0 +1,248 @@ +# coding=utf-8 +"""Request handler for statistics.""" +from __future__ import unicode_literals + +from collections import defaultdict +from medusa import app +from medusa.common import ( + ARCHIVED, + DOWNLOADED, + FAILED, + SKIPPED, + SNATCHED, + SNATCHED_BEST, + SNATCHED_PROPER, + UNAIRED, + WANTED +) +from medusa.tv.episode import Episode, EpisodeNumber +from medusa.tv.series import Series, SeriesIdentifier +from medusa.search.manual import collect_episodes_from_search_thread +from medusa.search.queue import ( + BacklogQueueItem, + FailedQueueItem, + ManualSearchQueueItem, +) +from medusa.server.api.v2.base import BaseRequestHandler +from tornado.escape import json_decode + + +# log = BraceAdapter(logging.getLogger(__name__)) +# log.logger.addHandler(logging.NullHandler()) + + +class SearchHandler(BaseRequestHandler): + """Search queue request handler.""" + + #: resource name + name = 'search' + #: identifier + identifier = ('identifier', r'\w+') + #: path param + path_param = ('path_param', r'\w+') + #: allowed HTTP methods + allowed_methods = ('GET', 'POST') + + def http_get(self, identifier, path_param=None, *args, **kwargs): + """Query statistics. + + :param identifier: + :param path_param: + :type path_param: str + """ + if not identifier: + return self._bad_request('You need to add the show slug to the route') + + series = SeriesIdentifier.from_slug(identifier) + if not series: + return self._bad_request('Invalid series slug') + + series_obj = Series.find_by_identifier(series) + if not series_obj: + return self._not_found('Series not found') + + return { + 'results': collect_episodes_from_search_thread(series_obj) + } + + def http_post(self, identifier, path_param=None): + """Queue a backlog search for a range of episodes. + + :param identifier: + :param path_param: + :type path_param: str + "tvdb1234" : [ + "s01e01", + "s01e02", + "s03e03", + ] + + """ + data = json_decode(self.request.body) + + if identifier == 'backlog': + return self._search_backlog(data) + if identifier == 'daily': + return self._search_daily(data) + if identifier == 'failed': + return self._search_failed(data) + if identifier == 'manual': + return self._search_manual(data) + + return self._bad_request('{key} is a invalid path. You will need to add a search type to the path'.format(key=identifier)) + + def _search_backlog(self, data): + """Start a backlog search for results for the provided episodes. + + :param data: + :return: + """ + statuses = {} + + if all([not data.get('showslug'), not data.get('episodes')]): + # Trigger a full backlog search + if app.backlog_search_scheduler.forceRun(): + return self._created() + + return self._bad_request('Triggering a backlog search failed') + + if not data.get('showslug'): + return self._bad_request('For a backlog search you need to provide a showslug') + + if not data.get('episodes'): + return self._bad_request('For a backlog search you need to provide a list of episodes') + + identifier = SeriesIdentifier.from_slug(data['showslug']) + if not identifier: + return self._bad_request('Invalid series slug') + + series = Series.find_by_identifier(identifier) + if not series: + return self._not_found('Series not found') + + season_segments = defaultdict(list) + for episode_slug in data['episodes']: + episode_number = EpisodeNumber.from_slug(episode_slug) + if not episode_number: + statuses[episode_slug] = {'status': 400} + continue + + episode = Episode.find_by_series_and_episode(series, episode_number) + if not episode: + statuses[episode_slug] = {'status': 404} + continue + + season_segments[episode.season].append(episode) + + if not season_segments: + return self._not_found('No episodes passed to search for using the backlog search') + + for segment in season_segments.values(): + cur_backlog_queue_item = BacklogQueueItem(series, segment) + app.forced_search_queue_scheduler.action.add_item(cur_backlog_queue_item) + + return self._created() + + def _search_daily(self, data): + """Start a daily search. + + :param data: + :return: + """ + if app.daily_search_scheduler.forceRun(): + return self._created() + + return self._bad_request('Triggering a daily search failed') + + def _search_failed(self, data): + """Start a failed search. + + :param data: + :return: + """ + statuses = {} + + if not data.get('showslug'): + return self._bad_request('For a backlog search you need to provide a showslug') + + if not data.get('episodes'): + return self._bad_request('For a backlog search you need to provide a list of episodes') + + identifier = SeriesIdentifier.from_slug(data['showslug']) + if not identifier: + return self._bad_request('Invalid series slug') + + series = Series.find_by_identifier(identifier) + if not series: + return self._not_found('Series not found') + + season_segments = defaultdict(list) + for episode_slug in data['episodes']: + episode_number = EpisodeNumber.from_slug(episode_slug) + if not episode_number: + statuses[episode_slug] = {'status': 400} + continue + + episode = Episode.find_by_series_and_episode(series, episode_number) + if not episode: + statuses[episode_slug] = {'status': 404} + continue + + season_segments[episode.season].append(episode) + + if not season_segments: + return self._not_found('No episodes passed to search for using the backlog search') + + for segment in season_segments.values(): + cur_failed_queue_item = FailedQueueItem(series, segment) + app.forced_search_queue_scheduler.action.add_item(cur_failed_queue_item) + + return self._created() + + def _search_manual(self, data): + """Start a manual search for results for the provided episodes. + + :param data: + :return: + """ + statuses = {} + + if not data.get('showslug'): + return self._bad_request('For a backlog search you need to provide a showslug') + + if not data.get('episodes'): + return self._bad_request('For a backlog search you need to provide a list of episodes') + + identifier = SeriesIdentifier.from_slug(data['showslug']) + if not identifier: + return self._bad_request('Invalid series slug') + + series = Series.find_by_identifier(identifier) + if not series: + return self._not_found('Series not found') + + season_segments = defaultdict(list) + for episode_slug in data['episodes']: + episode_number = EpisodeNumber.from_slug(episode_slug) + if not episode_number: + statuses[episode_slug] = {'status': 400} + continue + + episode = Episode.find_by_series_and_episode(series, episode_number) + if not episode: + statuses[episode_slug] = {'status': 404} + continue + + season_segments[episode.season].append(episode) + + if not season_segments: + return self._not_found('No episodes passed to search for using the backlog search') + + # Retrieve the search type option (episode vs season) + search_type = data['options'].get('type', 'episode') + + for segment in season_segments.values(): + cur_manual_search_queue_item = ManualSearchQueueItem(series, segment, manual_search_type=search_type) + app.forced_search_queue_scheduler.action.add_item(cur_manual_search_queue_item) + + return self._created() diff --git a/medusa/server/core.py b/medusa/server/core.py index 9d25105b70..0c5e68312c 100644 --- a/medusa/server/core.py +++ b/medusa/server/core.py @@ -25,6 +25,7 @@ from medusa.server.api.v2.episodes import EpisodeHandler from medusa.server.api.v2.internal import InternalHandler from medusa.server.api.v2.log import LogHandler +from medusa.server.api.v2.search import SearchHandler from medusa.server.api.v2.series import SeriesHandler from medusa.server.api.v2.series_asset import SeriesAssetHandler from medusa.server.api.v2.series_legacy import SeriesLegacyHandler @@ -76,7 +77,12 @@ def clean_url_path(*args, **kwargs): def get_apiv2_handlers(base): """Return api v2 handlers.""" return [ + # Order: Most specific to most generic + + # /api/v2/search + SearchHandler.create_app_handler(base), + # /api/v2/series/tvdb1234/episode EpisodeHandler.create_app_handler(base), From cc8111d7a5ccdf26084d8aa4e2eae486409c87b1 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 25 May 2019 13:17:07 +0200 Subject: [PATCH 02/12] Removed the SearchHandler changes, as that's done in PR #6716 --- medusa/server/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/medusa/server/core.py b/medusa/server/core.py index 0c5e68312c..9d25105b70 100644 --- a/medusa/server/core.py +++ b/medusa/server/core.py @@ -25,7 +25,6 @@ from medusa.server.api.v2.episodes import EpisodeHandler from medusa.server.api.v2.internal import InternalHandler from medusa.server.api.v2.log import LogHandler -from medusa.server.api.v2.search import SearchHandler from medusa.server.api.v2.series import SeriesHandler from medusa.server.api.v2.series_asset import SeriesAssetHandler from medusa.server.api.v2.series_legacy import SeriesLegacyHandler @@ -77,12 +76,7 @@ def clean_url_path(*args, **kwargs): def get_apiv2_handlers(base): """Return api v2 handlers.""" return [ - # Order: Most specific to most generic - - # /api/v2/search - SearchHandler.create_app_handler(base), - # /api/v2/series/tvdb1234/episode EpisodeHandler.create_app_handler(base), From a2cc5b9e7e30350f1674f04879f7c013e9a9e424 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 25 May 2019 13:41:16 +0200 Subject: [PATCH 03/12] Remove the old FailedSearchQueueItem class. --- medusa/search/queue.py | 132 +---------------------------------------- 1 file changed, 2 insertions(+), 130 deletions(-) diff --git a/medusa/search/queue.py b/medusa/search/queue.py index 057ff60330..59ae9f5a16 100644 --- a/medusa/search/queue.py +++ b/medusa/search/queue.py @@ -127,7 +127,7 @@ def is_ep_in_queue(self, segment): return False def is_show_in_queue(self, show): - """Verify if the show is queued in this queue as a ForcedSearchQueueItem or FailedQueueItem.""" + """Verify if the show is queued in this queue as a BacklogQueueItem, ManualSearchQueueItem or FailedQueueItem.""" for cur_item in self.queue: if isinstance(cur_item, (BacklogQueueItem, FailedQueueItem, ManualSearchQueueItem)) and cur_item.show.indexerid == show: return True @@ -139,7 +139,7 @@ def get_all_ep_from_queue(self, series_obj): @param series_obj: Series object. - @return: A list of ForcedSearchQueueItem or FailedQueueItem items + @return: A list of BacklogQueueItem, FailedQueueItem or FailedQueueItem items """ ep_obj_list = [] for cur_item in self.queue: @@ -318,134 +318,6 @@ def run(self): self.finish() -class ForcedSearchQueueItem(generic_queue.QueueItem): - """Forced search queue item class.""" - - def __init__(self, show, segment, down_cur_quality=False, manual_search=False, manual_search_type='episode'): - """ - Initialize class of a QueueItem used to queue forced and manual searches. - - :param show: A show object - :param segment: A list of episode objects. - :param down_cur_quality: Not sure what it's used for. Maybe legacy. - :param manual_search: Passed as True (bool) when the search should be performed without snatching a result - :param manual_search_type: Used to switch between episode and season search. Options are 'episode' or 'season'. - :return: The run() method searches and snatches the episode(s) if possible or it only searches and saves results to cache tables. - """ - generic_queue.QueueItem.__init__(self, u'Forced Search', FORCED_SEARCH) - self.priority = generic_queue.QueuePriorities.HIGH - # SEARCHQUEUE-MANUAL-12345 - # SEARCHQUEUE-FORCED-12345 - self.name = '{search_type}-{indexerid}'.format( - search_type=('FORCED', 'MANUAL')[bool(manual_search)], - indexerid=show.indexerid - ) - - self.success = None - self.started = None - self.results = None - - self.show = show - self.segment = segment - self.down_cur_quality = down_cur_quality - self.manual_search = manual_search - self.manual_search_type = manual_search_type - - def run(self): - """Run forced search thread.""" - generic_queue.QueueItem.run(self) - self.started = True - - try: - log.info( - 'Beginning {search_type} {season_pack}search for: {ep}', { - 'search_type': ('forced', 'manual')[bool(self.manual_search)], - 'season_pack': ('', 'season pack ')[bool(self.manual_search_type == 'season')], - 'ep': self.segment[0].pretty_name() - } - ) - - search_result = search_providers(self.show, self.segment, True, self.down_cur_quality, - self.manual_search, self.manual_search_type) - - if not self.manual_search and search_result: - for result in search_result: - # Just use the first result for now - if result.seeders not in (-1, None) and result.leechers not in (-1, None): - log.info( - 'Downloading {name} with {seeders} seeders and {leechers} leechers ' - 'and size {size} from {provider}', { - 'name': result.name, - 'seeders': result.seeders, - 'leechers': result.leechers, - 'size': pretty_file_size(result.size), - 'provider': result.provider.name, - } - ) - else: - log.info( - 'Downloading {name} with size: {size} from {provider}', { - 'name': result.name, - 'size': pretty_file_size(result.size), - 'provider': result.provider.name, - } - ) - - # Set the search_type for the result. - result.search_type = SearchType.FORCED_SEARCH - - # Create the queue item - snatch_queue_item = SnatchQueueItem(result.series, result.episodes, result) - - # Add the queue item to the queue - app.manual_snatch_scheduler.action.add_item(snatch_queue_item) - - self.success = False - while snatch_queue_item.success is False: - if snatch_queue_item.started and snatch_queue_item.success: - self.success = True - time.sleep(1) - - # Give the CPU a break - time.sleep(common.cpu_presets[app.CPU_PRESET]) - - elif self.manual_search and search_result: - self.results = search_result - self.success = True - - if self.manual_search_type == 'season': - ui.notifications.message('We have found season packs for {show_name}' - .format(show_name=self.show.name), - 'These should become visible in the manual select page.') - else: - ui.notifications.message('We have found results for {ep}' - .format(ep=self.segment[0].pretty_name()), - 'These should become visible in the manual select page.') - - else: - ui.notifications.message('No results were found') - log.info( - 'Unable to find {search_type} {season_pack}results for: {ep}', { - 'search_type': ('forced', 'manual')[bool(self.manual_search)], - 'season_pack': ('', 'season pack ')[bool(self.manual_search_type == 'season')], - 'ep': self.segment[0].pretty_name() - } - ) - - # TODO: Remove catch all exception. - except Exception: - self.success = False - log.debug(traceback.format_exc()) - - # Keep a list with the 100 last executed searches - fifo(SEARCH_HISTORY, self, SEARCH_HISTORY_SIZE) - - if self.success is None: - self.success = False - - self.finish() - - class ManualSearchQueueItem(generic_queue.QueueItem): """Manual search queue item class.""" From 22df811c28901ceba4e390365db50f3292d249e8 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 25 May 2019 13:41:48 +0200 Subject: [PATCH 04/12] Fix import order. Fix unused assignment. --- medusa/search/manual.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/medusa/search/manual.py b/medusa/search/manual.py index db438b1eec..8831a9b15b 100644 --- a/medusa/search/manual.py +++ b/medusa/search/manual.py @@ -22,7 +22,7 @@ from medusa.helper.common import enabled_providers, pretty_file_size from medusa.logger.adapters.style import BraceAdapter from medusa.network_timezones import app_timezone -from medusa.search.queue import SEARCH_HISTORY, ManualSearchQueueItem +from medusa.search.queue import ManualSearchQueueItem, SEARCH_HISTORY from medusa.show.naming import contains_at_least_one_word, filter_bad_releases from medusa.show.show import Show @@ -181,7 +181,6 @@ def collect_episodes_from_search_thread(series_obj): def get_provider_cache_results(series_obj, show_all_results=None, perform_search=None, season=None, episode=None, manual_search_type=None, **search_show): """Check all provider cache tables for search results.""" - down_cur_quality = 0 preferred_words = series_obj.show_words().preferred_words undesired_words = series_obj.show_words().undesired_words ignored_words = series_obj.show_words().ignored_words From 48c49de9525afae4ff0fd1006c44e21358201224 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 25 May 2019 13:42:29 +0200 Subject: [PATCH 05/12] Replace ForcedSearchQueueItem in api\v1\core.py --- medusa/server/api/v1/core.py | 4 ++-- medusa/server/web/home/handler.py | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/medusa/server/api/v1/core.py b/medusa/server/api/v1/core.py index 811cdfbd1a..e29e2ca976 100644 --- a/medusa/server/api/v1/core.py +++ b/medusa/server/api/v1/core.py @@ -53,7 +53,7 @@ from medusa.media.fan_art import ShowFanArt from medusa.media.network_logo import ShowNetworkLogo from medusa.media.poster import ShowPoster -from medusa.search.queue import BacklogQueueItem, ForcedSearchQueueItem +from medusa.search.queue import BacklogQueueItem from medusa.show.coming_episodes import ComingEpisodes from medusa.show.history import History from medusa.show.show import Show @@ -795,7 +795,7 @@ def run(self): return _responds(RESULT_FAILURE, msg='Episode not found') # make a queue item for it and put it on the queue - ep_queue_item = ForcedSearchQueueItem(show_obj, [ep_obj]) + ep_queue_item = BacklogQueueItem(show_obj, [ep_obj]) app.forced_search_queue_scheduler.action.add_item(ep_queue_item) # @UndefinedVariable # wait until the queue item tells us whether it worked or not diff --git a/medusa/server/web/home/handler.py b/medusa/server/web/home/handler.py index 973b249bb8..ea37211bc7 100644 --- a/medusa/server/web/home/handler.py +++ b/medusa/server/web/home/handler.py @@ -92,7 +92,6 @@ from medusa.search.queue import ( BacklogQueueItem, FailedQueueItem, - ForcedSearchQueueItem, SnatchQueueItem, ) from medusa.server.web.core import ( @@ -1980,9 +1979,7 @@ def doRename(self, indexername=None, seriesid=None, eps=None): return self.redirect('/home/displayShow?indexername={series_obj.indexer_name}&seriesid={series_obj.series_id}'.format(series_obj=series_obj)) def searchEpisode(self, indexername=None, seriesid=None, season=None, episode=None, manual_search=None): - """Search a ForcedSearch single episode using providers which are backlog enabled.""" - down_cur_quality = 0 - + """Search for a single using a episode using a Backlog Search with providers that are backlog enabled.""" # retrieve the episode object and fail if we can't get one series_obj = Show.find_by_id(app.showList, indexer_name_to_id(indexername), seriesid) ep_obj = series_obj.get_episode(season, episode) @@ -1992,7 +1989,7 @@ def searchEpisode(self, indexername=None, seriesid=None, season=None, episode=No }) # make a queue item for it and put it on the queue - ep_queue_item = ForcedSearchQueueItem(ep_obj.series, [ep_obj], bool(int(down_cur_quality)), bool(manual_search)) + ep_queue_item = BacklogQueueItem(ep_obj.series, [ep_obj]) app.forced_search_queue_scheduler.action.add_item(ep_queue_item) From f0dea9d45f808ed76a38abeecf4231b307eaa9b5 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 11 Aug 2019 12:08:23 +0200 Subject: [PATCH 06/12] Fix loading displayShow using python3 when app.DOWNLOAD_URL is used. --- themes-default/slim/views/displayShow.mako | 10 ++++++++-- themes/dark/templates/displayShow.mako | 10 ++++++++-- themes/light/templates/displayShow.mako | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/themes-default/slim/views/displayShow.mako b/themes-default/slim/views/displayShow.mako index c5ab23c8a0..e09836c460 100644 --- a/themes-default/slim/views/displayShow.mako +++ b/themes-default/slim/views/displayShow.mako @@ -1,11 +1,17 @@ <%inherit file="/layouts/main.mako"/> <%! import datetime - import urllib from medusa import app, helpers, subtitles, sbdatetime, network_timezones from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, FAILED, DOWNLOADED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST from medusa.common import Quality, statusStrings, Overview from medusa.helper.common import pretty_file_size + + try: + # python 2 + from urllib import quote + except ImportError: + # python 3+ + from urllib.parse import quote %> <%block name="scripts">