def collect_multi_candidates(candidates, series_obj, episodes, down_cur_quality): """Collect mutli-episode and season result candidates.""" multi_candidates = [] single_candidates = [] wanted_candidates = filter_results(candidates) if not wanted_candidates: return multi_candidates, single_candidates for candidate in wanted_candidates: wanted_episodes = (series_obj.want_episode(ep_obj.season, ep_obj.episode, candidate.quality, down_cur_quality) for ep_obj in candidate.episodes) if all(wanted_episodes): log.info(u'All episodes in this season are needed, adding {0} {1}', candidate.provider.provider_type, candidate.name) # Skip the result if search delay is enabled for the provider if not delay_search(candidate): multi_candidates.append(candidate) elif not any(wanted_episodes): log.debug( u'No episodes in this season are needed at this quality, ignoring {0} {1}', candidate.provider.provider_type, candidate.name) continue else: # Some NZB providers (e.g. Jackett) can also download torrents, # but torrents cannot be split like NZBs if (candidate.provider.provider_type == GenericProvider.NZB and not candidate.url.endswith(GenericProvider.TORRENT)): log.debug( u'Breaking apart the NZB and adding the individual ones to our results' ) individual_results = nzb_splitter.split_result(candidate) for cur_result in individual_results: if len(cur_result.episodes) == 1: ep_number = cur_result.episodes[0].episode single_candidates.append((ep_number, cur_result)) elif len(cur_result.episodes) > 1: multi_candidates.append(cur_result) return multi_candidates, single_candidates
def search_providers(series_obj, episodes, forced_search=False, down_cur_quality=False, manual_search=False, manual_search_type=u'episode'): """ Walk providers for information on shows. :param series_obj: Show we are looking for :param episodes: List, episodes we hope to find :param forced_search: Boolean, is this a forced search? :param down_cur_quality: Boolean, should we re-download currently available quality file :param manual_search: Boolean, should we choose what to download? :param manual_search_type: Episode or Season search :return: results for search """ found_results = {} final_results = [] manual_search_results = [] # build name cache for show name_cache.build_name_cache(series_obj) original_thread_name = threading.currentThread().name if manual_search: log.info(u'Using manual search providers') providers = enabled_providers(u'manualsearch') else: log.info(u'Using backlog search providers') providers = enabled_providers(u'backlog') if not providers: log.warning( u'No NZB/Torrent providers found or enabled in the application config for {0} searches.' u' Please check your settings', 'manual' if manual_search else 'backlog') threading.currentThread().name = original_thread_name for cur_provider in providers: threading.currentThread( ).name = original_thread_name + u' :: [' + cur_provider.name + u']' if cur_provider.anime_only and not series_obj.is_anime: log.debug(u'{0} is not an anime, skipping', series_obj.name) continue found_results[cur_provider.name] = {} search_count = 0 search_mode = cur_provider.search_mode # Always search for episode when manually searching when in sponly if search_mode == u'sponly' and (forced_search or manual_search): search_mode = u'eponly' if manual_search and manual_search_type == u'season': search_mode = u'sponly' while True: search_count += 1 if search_mode == u'eponly': log.info(u'Performing episode search for {0}', series_obj.name) else: log.info(u'Performing season pack search for {0}', series_obj.name) try: search_results = cur_provider.find_search_results( series_obj, episodes, search_mode, forced_search, down_cur_quality, manual_search, manual_search_type) except AuthException as error: log.error(u'Authentication error: {0}', ex(error)) break if search_results: # make a list of all the results for this provider for cur_ep in search_results: if cur_ep in found_results[cur_provider.name]: found_results[cur_provider. name][cur_ep] += search_results[cur_ep] else: found_results[ cur_provider.name][cur_ep] = search_results[cur_ep] # Sort the list by seeders if possible if cur_provider.provider_type == u'torrent' or getattr( cur_provider, u'torznab', None): found_results[cur_provider.name][cur_ep].sort( key=lambda d: int(d.seeders), reverse=True) break elif not cur_provider.search_fallback or search_count == 2: break # Don't fallback when doing manual season search if manual_search_type == u'season': break if search_mode == u'sponly': log.debug(u'Fallback episode search initiated') search_mode = u'eponly' else: log.debug(u'Fallback season pack search initiate') search_mode = u'sponly' # skip to next provider if we have no results to process if not found_results[cur_provider.name]: continue # Update the cache if a manual search is being run if manual_search: # Let's create a list with episodes that we where looking for if manual_search_type == u'season': # If season search type, we only want season packs searched_episode_list = [SEASON_RESULT] else: searched_episode_list = [ episode_obj.episode for episode_obj in episodes ] + [MULTI_EP_RESULT] for searched_episode in searched_episode_list: if (searched_episode in search_results and cur_provider.cache.update_cache_manual_search( search_results[searched_episode])): # If we have at least a result from one provider, it's good enough to be marked as result manual_search_results.append(True) # Continue because we don't want to pick best results as we are running a manual search by user continue # pick the best season NZB best_season_result = None if SEASON_RESULT in found_results[cur_provider.name]: best_season_result = pick_best_result( found_results[cur_provider.name][SEASON_RESULT]) highest_quality_overall = 0 for cur_episode in found_results[cur_provider.name]: for cur_result in found_results[cur_provider.name][cur_episode]: if cur_result.quality > highest_quality_overall: highest_quality_overall = cur_result.quality log.debug(u'The highest quality of any match is {0}', Quality.qualityStrings[highest_quality_overall]) # see if every episode is wanted if best_season_result: searched_seasons = {str(x.season) for x in episodes} # get the quality of the season nzb season_quality = best_season_result.quality log.debug(u'The quality of the season {0} is {1}', best_season_result.provider.provider_type, Quality.qualityStrings[season_quality]) main_db_con = db.DBConnection() selection = main_db_con.select( 'SELECT episode ' 'FROM tv_episodes ' 'WHERE indexer = ?' ' AND showid = ?' ' AND ( season IN ( {0} ) )'.format( ','.join(searched_seasons)), [series_obj.indexer, series_obj.series_id]) all_eps = [int(x[b'episode']) for x in selection] log.debug(u'Episode list: {0}', all_eps) all_wanted = True any_wanted = False for cur_ep_num in all_eps: for season in {x.season for x in episodes}: if not series_obj.want_episode(season, cur_ep_num, season_quality, down_cur_quality): all_wanted = False else: any_wanted = True # if we need every ep in the season and there's nothing better then # just download this and be done with it (unless single episodes are preferred) if all_wanted and best_season_result.quality == highest_quality_overall: log.info( u'All episodes in this season are needed, downloading {0} {1}', best_season_result.provider.provider_type, best_season_result.name) ep_objs = [] for cur_ep_num in all_eps: for season in {x.season for x in episodes}: ep_objs.append( series_obj.get_episode(season, cur_ep_num)) best_season_result.episodes = ep_objs # Remove provider from thread name before return results threading.currentThread().name = original_thread_name return [best_season_result] elif not any_wanted: log.debug( u'No episodes in this season are needed at this quality, ignoring {0} {1}', best_season_result.provider.provider_type, best_season_result.name) else: # Some NZB providers (e.g. Jackett) can also download torrents, but torrents cannot be split like NZB if (best_season_result.provider.provider_type == GenericProvider.NZB and not best_season_result.url.endswith( GenericProvider.TORRENT)): log.debug( u'Breaking apart the NZB and adding the individual ones to our results' ) # if not, break it apart and add them as the lowest priority results individual_results = nzb_splitter.split_result( best_season_result) for cur_result in individual_results: if len(cur_result.episodes) == 1: ep_number = cur_result.episodes[0].episode elif len(cur_result.episodes) > 1: ep_number = MULTI_EP_RESULT if ep_number in found_results[cur_provider.name]: found_results[cur_provider.name][ep_number].append( cur_result) else: found_results[cur_provider.name][ep_number] = [ cur_result ] # If this is a torrent all we can do is leech the entire torrent, # user will have to select which eps not do download in his torrent client else: # Season result from Torrent Provider must be a full-season torrent, # creating multi-ep result for it. log.info( u'Adding multi-ep result for full-season torrent.' u' Undesired episodes can be skipped in torrent client if desired!' ) ep_objs = [] for cur_ep_num in all_eps: for season in {x.season for x in episodes}: ep_objs.append( series_obj.get_episode(season, cur_ep_num)) best_season_result.episodes = ep_objs if MULTI_EP_RESULT in found_results[cur_provider.name]: found_results[cur_provider.name][ MULTI_EP_RESULT].append(best_season_result) else: found_results[cur_provider.name][MULTI_EP_RESULT] = [ best_season_result ] # go through multi-ep results and see if we really want them or not, get rid of the rest multi_results = {} if MULTI_EP_RESULT in found_results[cur_provider.name]: for _multi_result in found_results[ cur_provider.name][MULTI_EP_RESULT]: log.debug( u'Seeing if we want to bother with multi-episode result {0}', _multi_result.name) # Filter result by ignore/required/whitelist/blacklist/quality, etc multi_result = pick_best_result(_multi_result) if not multi_result: continue # see how many of the eps that this result covers aren't covered by single results needed_eps = [] not_needed_eps = [] for ep_obj in multi_result.episodes: # if we have results for the episode if ep_obj.episode in found_results[cur_provider.name] and \ len(found_results[cur_provider.name][ep_obj.episode]) > 0: not_needed_eps.append(ep_obj.episode) else: needed_eps.append(ep_obj.episode) log.debug( u'Single-ep check result is needed_eps: {0}, not_needed_eps: {1}', needed_eps, not_needed_eps) if not needed_eps: log.debug( u'All of these episodes were covered by single episode results,' u' ignoring this multi-episode result') continue # check if these eps are already covered by another multi-result multi_needed_eps = [] multi_not_needed_eps = [] for ep_obj in multi_result.episodes: if ep_obj.episode in multi_results: multi_not_needed_eps.append(ep_obj.episode) else: multi_needed_eps.append(ep_obj.episode) log.debug( u'Multi-ep check result is multi_needed_eps: {0}, multi_not_needed_eps: {1}', multi_needed_eps, multi_not_needed_eps) if not multi_needed_eps: log.debug( u'All of these episodes were covered by another multi-episode nzb, ' u'ignoring this multi-ep result') continue # don't bother with the single result if we're going to get it with a multi result for ep_obj in multi_result.episodes: multi_results[ep_obj.episode] = multi_result if ep_obj.episode in found_results[cur_provider.name]: log.debug( u'A needed multi-episode result overlaps with a single-episode result for episode {0},' u' removing the single-episode results from the list', ep_obj.episode, ) del found_results[cur_provider.name][ep_obj.episode] # of all the single ep results narrow it down to the best one for each episode final_results += set(multi_results.values()) for cur_ep in found_results[cur_provider.name]: if cur_ep in (MULTI_EP_RESULT, SEASON_RESULT): continue if not found_results[cur_provider.name][cur_ep]: continue # if all results were rejected move on to the next episode best_result = pick_best_result( found_results[cur_provider.name][cur_ep]) if not best_result: continue # add result if its not a duplicate and found = False for i, result in enumerate(final_results): for best_resultEp in best_result.episodes: if best_resultEp in result.episodes: if result.quality < best_result.quality: final_results.pop(i) else: found = True if not found: # Skip the result if search delay is enabled for the provider. if not delay_search(best_result): final_results += [best_result] # Remove provider from thread name before return results threading.currentThread().name = original_thread_name if manual_search: # If results in manual search return True, else False return any(manual_search_results) else: return final_results
def collect_multi_candidates(candidates, series_obj, episodes, down_cur_quality): """Collect mutli-episode and season result candidates.""" multi_candidates = [] single_candidates = [] wanted_candidates = filter_results(candidates) if not wanted_candidates: return multi_candidates, single_candidates searched_seasons = {str(x.season) for x in episodes} main_db_con = db.DBConnection() selection = main_db_con.select( 'SELECT episode ' 'FROM tv_episodes ' 'WHERE indexer = ?' ' AND showid = ?' ' AND ( season IN ( {0} ) )'.format(','.join(searched_seasons)), [series_obj.indexer, series_obj.series_id]) all_eps = [int(x[b'episode']) for x in selection] log.debug(u'Episodes list: {0}', all_eps) for candidate in wanted_candidates: season_quality = candidate.quality all_wanted = True any_wanted = False for cur_ep_num in all_eps: for season in {x.season for x in episodes}: if not series_obj.want_episode( season, cur_ep_num, season_quality, down_cur_quality): all_wanted = False else: any_wanted = True if all_wanted: log.info(u'All episodes in this season are needed, adding {0} {1}', candidate.provider.provider_type, candidate.name) ep_objs = [] for cur_ep_num in all_eps: for season in {x.season for x in episodes}: ep_objs.append(series_obj.get_episode(season, cur_ep_num)) candidate.episodes = ep_objs # Skip the result if search delay is enabled for the provider if not delay_search(candidate): multi_candidates.append(candidate) elif not any_wanted: log.debug( u'No episodes in this season are needed at this quality, ignoring {0} {1}', candidate.provider.provider_type, candidate.name) continue else: # Some NZB providers (e.g. Jackett) can also download torrents, # but torrents cannot be split like NZBs if (candidate.provider.provider_type == GenericProvider.NZB and not candidate.url.endswith(GenericProvider.TORRENT)): log.debug( u'Breaking apart the NZB and adding the individual ones to our results' ) individual_results = nzb_splitter.split_result(candidate) for cur_result in individual_results: if len(cur_result.episodes) == 1: ep_number = cur_result.episodes[0].episode single_candidates.append((ep_number, cur_result)) elif len(cur_result.episodes) > 1: multi_candidates.append(cur_result) # If this is a torrent all we can do is get the entire torrent, # user will have to select which eps not do download in his torrent client else: log.info( u'Adding multi-episode result for full-season torrent.' u' Undesired episodes can be skipped in the torrent client if desired!' ) ep_objs = [] for cur_ep_num in all_eps: for season in {x.season for x in episodes}: ep_objs.append( series_obj.get_episode(season, cur_ep_num)) candidate.episodes = ep_objs multi_candidates.append(candidate) return multi_candidates, single_candidates