Repository: pymedusa/Medusa

Issue:
Not finding torrent
**Describe the bug**
Taking example in the TV series "Arrow", medusa refuses to find "Arrow.S07E02.Custom.DKsubs.720p.WEB-DL.H.264-SUBSTANCE"
Where required word "Substance" is the only criteria for medusa. It easily find others (look below in log section)

This happends to all releases from "Substance" (Tracker: DanishBits) and "YesWeAre" (Tracker: NordicBits)

**To Reproduce**
Steps to reproduce the behavior:
Setup Medusa to do a search for "Arrow" with required word "Substance" on tracker DanishBits or "Supernatural" with required word "YesWeAre" on tracker NordicBits

I will gladly have you take a look from within my pc remotely, debug log says nothing else than normal log.

**Expected behavior**
Expected behavior is it should download and find this "Substance" and "YesWeAre" releases. Been trying to fix this for days now.
Please help :)

**Screenshots**
None that would help better.

**Medusa (please complete the following information):**
 - OS: Linux Synology running SickBeard with Medusa set as master Branch
 - Branch: master
 - Commit: Not sure what is needed here?

**Logs:**
Not finding "Substance" Arrow episode on DanishBits.

```
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [0c0a735] Unable to find forced results for: Arrow - S07E02 - The Longbow Hunters
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Ignoring Arrow.S07E02.1080p.HEVC.x265-MeGusta based on required words filter: [u'SUBSTANCE']
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Quality of Arrow.S07E02.1080p.HEVC.x265-MeGusta is 1080p HDTV
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Ignoring Arrow.S07E02.HDTV.x264-SVA based on required words filter: [u'SUBSTANCE']
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Quality of Arrow.S07E02.HDTV.x264-SVA is SDTV
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Ignoring Arrow.S07E02.iNTERNAL.720p.WEB.h264-BAMBOOZLE based on required words filter: [u'SUBSTANCE']
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Quality of Arrow.S07E02.iNTERNAL.720p.WEB.h264-BAMBOOZLE is 720p WEB-DL
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Ignoring Arrow.S07E02.1080p.WEB.h264-TBS based on required words filter: [u'SUBSTANCE']
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Quality of Arrow.S07E02.1080p.WEB.h264-TBS is 1080p WEB-DL
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Ignoring Arrow.S07E02.720p.HDTV.x264-SVA based on required words filter: [u'SUBSTANCE']
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Quality of Arrow.S07E02.720p.HDTV.x264-SVA is 720p HDTV
2018-11-01 12:27:07 INFO     SEARCHQUEUE-FORCED-257655 :: [Danishbits] :: [0c0a735] Performing episode search for Arrow
```
</details>

**Additional context**
Dont have more details.



Discussion:
Enable debug logs and post a full manual search.
I did enable debug, but "logs" dont show anything debug related. Been trying to find solution online too. What should I do ?

Even tried restarted Medusa after enable and try do manual search. Still shows no debug even tho I see the checkmark for "enabled"
You need to select debug from the drop down in view logs.
Stupid me.. Ok found debug. Look here please.
https://imgur.com/a/Jq9sIKP

![debug](https://user-images.githubusercontent.com/18222939/47851563-4b8d6700-ddd9-11e8-8efc-befb3c1b88d0.png)

Better picture here:
![debug2](https://user-images.githubusercontent.com/18222939/47851837-1b929380-ddda-11e8-8e9b-5f36c00649c2.png)

Copy the logs to gist.github.com. Past gist link here
https://gist.github.com/Masterditto/218b55c0bfb002c623c4eee6985eb067
Now do the same with a manual search.
https://gist.github.com/Masterditto/8a6de8873c5ad86f151fba3a28cf19b1
That's still not a manual search. A manual search starts with `SEARCHQUEUE-MANUAL-`.
Sorry! got it.. but clicking it returns nothing in DEBUG log other than pushing me to this website:
![debug4](https://user-images.githubusercontent.com/18222939/47856234-fce6c980-dde6-11e8-9ac3-08b20ed8a996.jpg)
Click the force search button
It will star a new search.
Also you have SUSTANCE as a required word? Why. 
Ok that made it appear in the list!
![debug5](https://user-images.githubusercontent.com/18222939/47857856-02461300-ddeb-11e8-94de-2f7d51c50564.jpg)

Log here:
*DELETED*

I have required word "Substance" becuz its the only sub group I wanna download from in this paticular TV show. Could I do this smarter somehow?
Ok then it will only download those.
It should have that picked up in a backlog search
It downloaded the Episode 02, but how can I tell it to look for these Substance? without me requiring to do a manual search everytime.
Tried going to "Black Lightning" TV show. Same problem - This time "DBRETAiL" required word

Did a Force manual search on Episode 2 and it found DBRETAiL.

Tried to make it auto download episode 3, but had to do force manual search again?
Didn't read the whole thread but results with `dk` subs are ignored by default. Try removing `dk` from the list of ignored subtitled results on the **Search Settings** config page.
Option is titled **Ignore language names in subbed results**
I already did a full clear there:
![search filters](https://user-images.githubusercontent.com/18222939/47859164-fb6ccf80-dded-11e8-984b-d8ed9037a8e3.png)

Just wonder why it does not pick these up automaticly. Really dont wanna do a "Force manual search" every episode :(
Which is the main purpose/job for Medusa to handle.

Hope someone has a fix for me :O
So I've now did what u asked. And surely enough a "Forced manuel search" does find Episode 2 here. Look at first picture, but why should I do a manual search on ALL episodes?
Going into episode 3 (2. picture below) I again have to do manuel search ?

This is most likely a bug or?

![2](https://user-images.githubusercontent.com/18222939/47953602-3a328f00-df80-11e8-88a5-63eb56ea0849.jpg)

![1](https://user-images.githubusercontent.com/18222939/47953605-3ef74300-df80-11e8-94de-4e9d337a367b.jpg)

All TV series mentioned below is from "substance" group and from same tracker (DanishBits)
"Blindspot" and "The last ship" was found automaticly without a problem though...
Blindspot and The Flash was released same day.
"The Flash", "Arrow", "Mr. Mercedes", "The Walking Dead" was not found..

![4](https://user-images.githubusercontent.com/18222939/47953907-aebafd00-df83-11e8-997d-b63cf11b2105.jpg)

Can't seem to find the problem here. Help?

![3](https://user-images.githubusercontent.com/18222939/47953707-9944d380-df81-11e8-8bbb-20b4f24d2b2c.jpg)


Please help me fix this! Love Medusa over old SickRage!
You most likely only want releases with danish subs, right? In that case you should just add `DKsubs` to required words. Regarding the issue you seem to have, without a full debug log (talking about at least 24h from when the episode was released) it is really hard to help you further.
Hi medaiox

Been trying having no required word for the show "Arrow" and it does not find the releases from "Substance" automaticly. I have to do a "Force manual search" before it shows up in the list - Like I talked about earlier. Take a look here:


BEFORE "Force Manual Search"
![arrow](https://user-images.githubusercontent.com/18222939/48354815-ebc57480-e692-11e8-8511-ca92df803810.jpg)


AFTER "Force Manual Search"
![arrow2](https://user-images.githubusercontent.com/18222939/48355452-5b882f00-e694-11e8-9cad-bdfe052027ed.jpg)


My debug log didn't go further than this in the web-interface:
https://gist.github.com/Masterditto/51b78727b75c096f4ad5d79f8217e7bf

"Danishbits" search provider does work, because its grabbing 90% of the content I also have MEDUSA search for.
Recently this was taken without any problems:
Kidding.S01E10.NORDiC.720p.WEB-DL.DDP5.1.H.264-DBRETAiL (2 hours ago - required word: DBRETAiL)
DCs.Legends.of.Tomorrow.S04E03.NORDiC.720p.WEB-DL.DD5.1.H.264-DBRETAiL (19 hours ago - required word: DBRETAiL)
The.Big.Bang.Theory.S12E08.Custom.DKsubs.720p.WEB-DL.x264-ENiGMA (2 days ago - required word: ENiGMA)

To me it could be fixed if Medusa could do its own "manuel force search" automaticly?
Makes no sense users should do this on every new episode :)
Hope you guys can fix this.

Regards


EDIT1:
Medusa has just found this torrent with required word "substance"
"The.Walking.Dead.S09E06.Custom.DKsubs.720p.WEB-DL.H264-SUBSTANCE"
But still not arrow? cant find the reason behind it :( hmm....

EDIT2:
"The.Flash.S05E05.Custom.DKsubs.720p.WEB-DL.H.264-SUBSTANCE" was not found unless I did a "Manual force search" on the episode.

Before
![before](https://user-images.githubusercontent.com/18222939/48657239-c8058400-ea2e-11e8-8394-298866cccd36.jpg)

After
![after](https://user-images.githubusercontent.com/18222939/48657243-d05dbf00-ea2e-11e8-9577-e048d8873e1b.jpg)

EDIT3:
Blindspot.S04E05.Custom.DKsubs.720p.WEB-DL.H.264-SUBSTANCE - Was found
Lethal.Weapon.S03\Lethal.Weapon.S03E07.NORDiC.720p.WEB-DL.DDP5.1.H.264-DBRETAiL - Was found

EDIT4:
The.Big.Bang.Theory.S12E07.Custom.DKsubs.720p.WEB-DL.x264-ENiGMA
The.Big.Bang.Theory.S12E09.Custom.DKsubs.720p.WEB-DL.x264-ENiGMA
This was not found - Had to do manual force search again here:
![big bang](https://user-images.githubusercontent.com/18222939/48664920-713b9100-eaa6-11e8-9732-98a58c84d62d.jpg)

EDIT5:
Black.Lightning.S02E06.NORDiC.720p.WEB-DL.DDP5.1.H.264-DBRETAiL
![black lightning](https://user-images.githubusercontent.com/18222939/49129869-bbf0b080-f2d1-11e8-94e1-83b0bcd0b2b9.PNG)
![black lightning2](https://user-images.githubusercontent.com/18222939/49129884-c57a1880-f2d1-11e8-92ca-f11efbd69beb.PNG)

And this
Gold.Rush.S09E07.NORDiC.720p.WEB-DL.AAC2.0.H.264-DBRETAiL
![gold rush](https://user-images.githubusercontent.com/18222939/49129972-0a05b400-f2d2-11e8-926d-c97aa07b917e.PNG)
![gold rush2](https://user-images.githubusercontent.com/18222939/49129981-0f62fe80-f2d2-11e8-9795-a347f68f8dc5.PNG)

And this
DCs.Legends.of.Tomorrow.S04E05.NORDiC.720p.WEB-DL.DD5.1.H.264-DBRETAiL
![dc legends of tomrrow](https://user-images.githubusercontent.com/18222939/49130216-281fe400-f2d3-11e8-93f4-e827303a0e04.PNG)
![dc legends of tomrrow2](https://user-images.githubusercontent.com/18222939/49130223-30781f00-f2d3-11e8-8c9b-27328c36b6f4.PNG)



__ALL EDITS WORKS WHEN DOING "FORCE MANUAL SEARCH"__ help???
I have the same problem with Medusa and Danishbits. Any news on a solution?
@Masterditto still an issue?
Normally these episodes should be found 1. through daily (rss) search and then 2. backlog search. We'll need debug logs for these. A `force manual search` is the same as a backlog search but for one specific episode. All results from a backlog search, should also be added to cache.
Yes im still doing "Force manual search" on many episodes. Waiting for u guys for a fix (I hope)

What more information can I provide u to help fix this?
If your missing a result. Manual start a backlog search. And share the debug log.
"Force Manual Search" is not a backlog search? Can u tell me where that is?
Manage - manage searches
You can trigger a backlog search there.
Hi p0psicles - Had to wait for a release to test :) here is one with full information.

The.Last.Ship.S05E10.Custom.DKsubs.720p.WEB-DL.H.264-SUBSTANCE
Was released 14 days ago on my DanishBits tracker (I missed it) so changed backlog search to 30 days for this.

![thelastship1](https://user-images.githubusercontent.com/18222939/49337629-76f0b500-f616-11e8-8b11-675b1d490874.jpg)

![thelastship2](https://user-images.githubusercontent.com/18222939/49337630-7c4dff80-f616-11e8-89fb-48372e9a5a5b.jpg)

![thelastship3](https://user-images.githubusercontent.com/18222939/49337631-7fe18680-f616-11e8-9f3e-faa258179321.jpg)

And debug log file AFTER doing "Manage - manage searches You can trigger a backlog search there."
https://gist.github.com/Masterditto/072a2d331182bbcd7d33f693c5a57f15

AFTER CLICKING "FORCE SEARCH":
![thelastship4](https://user-images.githubusercontent.com/18222939/49337680-29287c80-f617-11e8-8652-ec33117203c6.jpg)


@pymedusa/developers the backlog search works like this:
1. Check cache for a specific show/episode
2. if it finds results, it is satisfied with that, and moves to the next wanted ep.
3. if it doesn't find results, it will search the provider.

Meaning that we're pretty much depending heavily on the RSS search. As that makes sure that "all" eps are added to cache.

The issue here is that *for some reason* the DKsubs releases are not added through the RSS search.
But other releases are added. Then the backlog only finds the releases that were added and doesn't bother searching further.

So there are two issues here:
1. RSS search doesn't pick up all releases
2. Backlog doesn't search the provider if it has >= 1 release in cache.
@Masterditto For the two issues 2 is really hard to do something about short term. But we can focus for now on why the specific release is not added through the RSS. But for that, you need to keep an eye on the logs for when an episode is released. You can filter your logs with `SEARCHQUEUE-DAILY-SEARCH`.
@Masterditto it could also help if I get temporary access. But that's up to you.

Resolving patch:
diff --git a/medusa/providers/generic_provider.py b/medusa/providers/generic_provider.py
--- a/medusa/providers/generic_provider.py
+++ b/medusa/providers/generic_provider.py
@@ -238,9 +238,33 @@ def remove_duplicate_mappings(items, pk='link'):
             ))
         )
 
+    def search_results_in_cache(self, episodes):
+        """
+        Search episodes based on param in cache.
+
+        Search the cache (db) for this provider
+        :param episodes: List of Episode objects
+
+        :return: A dict of search results, ordered by episode number
+        """
+        return self.cache.find_episodes(episodes)
+
     def find_search_results(self, series, episodes, search_mode, forced_search=False, download_current_quality=False,
                             manual_search=False, manual_search_type='episode'):
-        """Search episodes based on param."""
+        """
+        Search episodes based on param.
+
+        Search the provider using http queries.
+        :param series: Series object
+        :param episodes: List of Episode objects
+        :param search_mode: 'eponly' or 'sponly'
+        :param forced_search: Flag if the search was triggered by a forced search
+        :param download_current_quality: Flag if we want to include an already downloaded quality in the new search
+        :param manual_search: Flag if the search was triggered by a manual search
+        :param manual_search_type: How the manual search was started: For example an 'episode' or 'season'
+
+        :return: A dict of search results, ordered by episode number.
+        """
         self._check_auth()
         self.series = series
 
@@ -249,18 +273,6 @@ def find_search_results(self, series, episodes, search_mode, forced_search=False
         season_search = (len(episodes) > 1 or manual_search_type == 'season') and search_mode == 'sponly'
 
         for episode in episodes:
-            if not manual_search:
-                cache_results = self.cache.find_needed_episodes(
-                    episode, forced_search=forced_search, down_cur_quality=download_current_quality
-                )
-                if cache_results:
-                    for episode_no in cache_results:
-                        if episode_no not in results:
-                            results[episode_no] = cache_results[episode_no]
-                        else:
-                            results[episode_no] += cache_results[episode_no]
-                    continue
-
             search_strings = []
             if season_search:
                 search_strings = self._get_season_search_strings(episode)
@@ -471,6 +483,7 @@ def find_search_results(self, series, episodes, search_mode, forced_search=False
                           ', '.join(map(str, search_result.parsed_result.episode_numbers)),
                           search_result.name,
                           search_result.url)
+
             if episode_number not in results:
                 results[episode_number] = [search_result]
             else:
diff --git a/medusa/search/core.py b/medusa/search/core.py
--- a/medusa/search/core.py
+++ b/medusa/search/core.py
@@ -647,6 +647,8 @@ def search_providers(series_obj, episodes, forced_search=False, down_cur_quality
     manual_search_results = []
     multi_results = []
     single_results = []
+    cache_multi_results = []
+    cache_single_results = []
 
     # build name cache for show
     name_cache.build_name_cache(series_obj)
@@ -667,14 +669,15 @@ def search_providers(series_obj, episodes, forced_search=False, down_cur_quality
     threading.currentThread().name = original_thread_name
 
     for cur_provider in providers:
-        threading.currentThread().name = original_thread_name + u' :: [' + cur_provider.name + u']'
+        threading.currentThread().name = '{original_thread_name} :: [{provider}]'.format(
+            original_thread_name=original_thread_name, provider=cur_provider.name
+        )
 
         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
 
@@ -694,24 +697,41 @@ def search_providers(series_obj, episodes, forced_search=False, down_cur_quality
                 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)
+                search_results = []
+                cache_search_results = []
+                cache_multi = []
+                cache_single = []
+
+                if not manual_search:
+                    cache_search_results = cur_provider.search_results_in_cache(episodes)
+                    if cache_search_results:
+                        # From our provider multi_episode and single_episode results, collect candidates.
+                        found_cache_results = list_results_for_provider(cache_search_results, found_results, cur_provider)
+                        # We're passing the empty lists, because we don't want to include previous candidates
+                        cache_multi, cache_single = collect_candidates(found_cache_results, cur_provider, cache_multi,
+                                                                       cache_single, series_obj, down_cur_quality)
+
+                        # Check if we got any candidates from cache add add them to the list.
+                        # If we found candidates in cache, we don't need to search the provider.
+                        if cache_multi:
+                            cache_multi_results += cache_multi
+                        if cache_single:
+                            cache_single_results += cache_single
+
+                # For now we only search if we didn't get any results back from cache,
+                # but we might wanna check if there was something useful in cache.
+                if not (cache_multi or cache_single):
+                    log.debug(u'Could not find any candidates in cache, searching provider.')
+                    search_results = cur_provider.find_search_results(series_obj, episodes, search_mode, forced_search,
+                                                                      down_cur_quality, manual_search, manual_search_type)
+                    # Update the list found_results
+                    found_results = list_results_for_provider(search_results, found_results, cur_provider)
+
             except AuthException as error:
-                log.error(u'Authentication error: {0}', ex(error))
+                log.error(u'Authentication error: {0!r}', 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)
-
+            if search_results or cache_search_results:
                 break
             elif not cur_provider.search_fallback or search_count == 2:
                 break
@@ -747,16 +767,13 @@ def search_providers(series_obj, episodes, forced_search=False, down_cur_quality
             # Continue because we don't want to pick best results as we are running a manual search by user
             continue
 
-        # Collect candidates for multi-episode or season results
-        candidates = (candidate for result, candidate in iteritems(found_results[cur_provider.name])
-                      if result in (SEASON_RESULT, MULTI_EP_RESULT))
-        candidates = list(itertools.chain(*candidates))
-        if candidates:
-            multi_results += collect_multi_candidates(candidates, series_obj, episodes, down_cur_quality)
-
-        # Collect candidates for single-episode results
-        single_results = collect_single_candidates(found_results[cur_provider.name],
-                                                   single_results)
+        # From our providers multi_episode and single_episode results, collect candidates.
+        # Only collect the candidates if we didn't get any from cache.
+        if not (cache_multi_results or cache_single_results):
+            multi_results, single_results = collect_candidates(found_results, cur_provider, multi_results,
+                                                               single_results, series_obj, down_cur_quality)
+        else:
+            multi_results, single_results = cache_multi_results, cache_single_results
 
     # Remove provider from thread name before return results
     threading.currentThread().name = original_thread_name
@@ -768,6 +785,43 @@ def search_providers(series_obj, episodes, forced_search=False, down_cur_quality
         return combine_results(multi_results, single_results)
 
 
+def collect_candidates(found_results, provider, multi_results, single_results, series_obj, down_cur_quality):
+    """Collect candidates for episode, multi-episode or season results."""
+    candidates = (candidate for result, candidate in iteritems(found_results[provider.name])
+                  if result in (SEASON_RESULT, MULTI_EP_RESULT))
+    candidates = list(itertools.chain(*candidates))
+    if candidates:
+        multi_results += collect_multi_candidates(candidates, series_obj, down_cur_quality)
+
+    # Collect candidates for single-episode results
+    single_results = collect_single_candidates(found_results[provider.name], single_results)
+
+    return multi_results, single_results
+
+
+def list_results_for_provider(search_results, found_results, provider):
+    """
+    Add results for this provider to the search_results dict.
+
+    The structure is based on [provider_name][episode_number][search_result]
+    :param search_results: New dictionary with search results for this provider
+    :param found_results: Dictionary with existing per provider search results
+    :param provider: Provider object
+    :return: Updated dict found_results
+    """
+    for cur_ep in search_results:
+        if cur_ep in found_results[provider.name]:
+            found_results[provider.name][cur_ep] += search_results[cur_ep]
+        else:
+            found_results[provider.name][cur_ep] = search_results[cur_ep]
+
+        # Sort the list by seeders if possible
+        if provider.provider_type == u'torrent' or getattr(provider, u'torznab', None):
+            found_results[provider.name][cur_ep].sort(key=lambda d: int(d.seeders), reverse=True)
+
+    return found_results
+
+
 def collect_single_candidates(candidates, results):
     """Collect single-episode result candidates."""
     single_candidates = list(results)
@@ -798,7 +852,7 @@ def collect_single_candidates(candidates, results):
     return single_candidates + new_candidates
 
 
-def collect_multi_candidates(candidates, series_obj, episodes, down_cur_quality):
+def collect_multi_candidates(candidates, series_obj, down_cur_quality):
     """Collect mutli-episode and season result candidates."""
     multi_candidates = []
 
diff --git a/medusa/tv/cache.py b/medusa/tv/cache.py
--- a/medusa/tv/cache.py
+++ b/medusa/tv/cache.py
@@ -9,6 +9,7 @@
 import traceback
 from builtins import object
 from builtins import str
+from collections import defaultdict
 from time import time
 
 from medusa import (
@@ -31,7 +32,7 @@
 from medusa.show import naming
 from medusa.show.show import Show
 
-from six import text_type
+from six import text_type, viewitems
 
 log = BraceAdapter(logging.getLogger(__name__))
 log.logger.addHandler(logging.NullHandler())
@@ -454,38 +455,101 @@ def item_in_cache(self, url):
             'WHERE url=?'.format(provider=self.provider_id), [url]
         )[0]['count']
 
-    def find_needed_episodes(self, episode, forced_search=False,
-                             down_cur_quality=False):
-        """Find needed episodes."""
-        needed_eps = {}
+    def find_needed_episodes(self, episodes, forced_search=False, down_cur_quality=False):
+        """
+        Search cache for needed episodes.
+
+        NOTE: This is currently only used by the Daily Search.
+        The following checks are performed on the cache results:
+        * Use the episodes current quality / wanted quality to decide if we want it
+        * Filtered on ignored/required words, and non-tv junk
+        * Filter out non-anime results on Anime only providers
+        * Check if the series is still in our library
+
+        :param episodes: Single or list of episode object(s)
+        :param forced_search: Flag to mark that this is searched through a forced search
+        :param down_cur_quality: Flag to mark that we want to include the episode(s) current quality
+
+        :return dict(episode: [list of SearchResult objects]).
+        """
+        results = defaultdict(list)
+        cache_results = self.find_episodes(episodes)
+
+        for episode_number, search_results in viewitems(cache_results):
+            for search_result in search_results:
+
+                # ignored/required words, and non-tv junk
+                if not naming.filter_bad_releases(search_result.name):
+                    continue
+
+                all_wanted = True
+                for cur_ep in search_result.actual_episodes:
+                    # if the show says we want that episode then add it to the list
+                    if not search_result.series.want_episode(search_result.actual_season, cur_ep, search_result.quality,
+                                                             forced_search, down_cur_quality):
+                        log.debug('Ignoring {0} because one or more episodes are unwanted', search_result.name)
+                        all_wanted = False
+                        break
+
+                if not all_wanted:
+                    continue
+
+                log.debug(
+                    '{id}: Using cached results from {provider} for series {show_name!r} episode {ep}', {
+                        'id': search_result.series.series_id,
+                        'provider': self.provider.name,
+                        'show_name': search_result.series.name,
+                        'ep': episode_num(search_result.episodes[0].season, search_result.episodes[0].episode),
+                    }
+                )
+
+                # FIXME: Should be changed to search_result.search_type
+                search_result.forced_search = forced_search
+                search_result.download_current_quality = down_cur_quality
+
+                # add it to the list
+                results[episode_number].append(search_result)
+
+        return results
+
+    def find_episodes(self, episodes):
+        """
+        Search cache for episodes.
+
+        NOTE: This is currently only used by the Backlog/Forced Search. As we determine the candidates there.
+        The following checks are performed on the cache results:
+        * Filter out non-anime results on Anime only providers
+        * Check if the series is still in our library
+        :param episodes: Single or list of episode object(s)
+
+        :return list of SearchResult objects.
+        """
+        cache_results = defaultdict(list)
         results = []
 
         cache_db_con = self._get_db()
-        if not episode:
+        if not episodes:
             sql_results = cache_db_con.select(
                 'SELECT * FROM [{name}]'.format(name=self.provider_id))
-        elif not isinstance(episode, list):
+        elif not isinstance(episodes, list):
             sql_results = cache_db_con.select(
                 'SELECT * FROM [{name}] '
-                'WHERE indexer = ? AND'
-                '      indexerid = ? AND'
-                '      season = ? AND'
-                '      episodes LIKE ?'.format(name=self.provider_id),
-                [episode.series.indexer, episode.series.series_id, episode.season,
-                 '%|{0}|%'.format(episode.episode)]
+                'WHERE indexer = ? AND '
+                'indexerid = ? AND '
+                'season = ? AND '
+                'episodes LIKE ?'.format(name=self.provider_id),
+                [episodes.series.indexer, episodes.series.series_id, episodes.season,
+                 '%|{0}|%'.format(episodes.episode)]
             )
         else:
-            for ep_obj in episode:
+            for ep_obj in episodes:
                 results.append([
                     'SELECT * FROM [{name}] '
                     'WHERE indexer = ? AND '
-                    '      indexerid = ? AND'
-                    '      season = ? AND'
-                    '      episodes LIKE ? AND '
-                    '      quality IN ({qualities})'.format(
-                        name=self.provider_id,
-                        qualities=','.join((str(x)
-                                            for x in ep_obj.wanted_quality))
+                    'indexerid = ? AND '
+                    'season = ? AND '
+                    'episodes LIKE ?'.format(
+                        name=self.provider_id
                     ),
                     [ep_obj.series.indexer, ep_obj.series.series_id, ep_obj.season,
                      '%|{0}|%'.format(ep_obj.episode)]]
@@ -499,10 +563,10 @@ def find_needed_episodes(self, episode, forced_search=False,
                 sql_results = []
                 log.debug(
                     '{id}: No cached results in {provider} for series {show_name!r} episode {ep}', {
-                        'id': episode[0].series.series_id,
+                        'id': episodes[0].series.series_id,
                         'provider': self.provider.name,
-                        'show_name': episode[0].series.name,
-                        'ep': episode_num(episode[0].season, episode[0].episode),
+                        'show_name': episodes[0].series.name,
+                        'ep': episode_num(episodes[0].season, episodes[0].episode),
                     }
                 )
 
@@ -515,10 +579,6 @@ def find_needed_episodes(self, episode, forced_search=False,
 
             search_result = self.provider.get_result()
 
-            # ignored/required words, and non-tv junk
-            if not naming.filter_bad_releases(cur_result['name']):
-                continue
-
             # get the show, or ignore if it's not one of our shows
             series_obj = Show.find_by_id(app.showList, int(cur_result['indexer']), int(cur_result['indexerid']))
             if not series_obj:
@@ -535,49 +595,32 @@ def find_needed_episodes(self, episode, forced_search=False,
             search_result.version = cur_result['version']
             search_result.name = cur_result['name']
             search_result.url = cur_result['url']
-            search_result.season = int(cur_result['season'])
-            search_result.actual_season = search_result.season
+            search_result.actual_season = int(cur_result['season'])
 
-            sql_episodes = cur_result['episodes'].strip('|')
             # TODO: Add support for season results
+            sql_episodes = cur_result['episodes'].strip('|')
             # Season result
             if not sql_episodes:
-                ep_objs = series_obj.get_all_episodes(search_result.season)
+                ep_objs = series_obj.get_all_episodes(search_result.actual_season)
+                if not ep_objs:
+                    # We couldn't get any episodes for this season, which is odd, skip the result.
+                    log.debug("We couldn't get any episodes for season {0} of {1}, skipping",
+                              search_result.actual_season, search_result.name)
+                    continue
                 actual_episodes = [ep.episode for ep in ep_objs]
                 episode_number = SEASON_RESULT
             # Multi or single episode result
             else:
                 actual_episodes = [int(ep) for ep in sql_episodes.split('|')]
-                ep_objs = [series_obj.get_episode(search_result.season, ep) for ep in actual_episodes]
+                ep_objs = [series_obj.get_episode(search_result.actual_season, ep) for ep in actual_episodes]
                 if len(actual_episodes) == 1:
                     episode_number = actual_episodes[0]
                 else:
                     episode_number = MULTI_EP_RESULT
 
-            all_wanted = True
-            for cur_ep in actual_episodes:
-                # if the show says we want that episode then add it to the list
-                if not series_obj.want_episode(search_result.season, cur_ep, search_result.quality,
-                                               forced_search, down_cur_quality):
-                    log.debug('Ignoring {0} because one or more episodes are unwanted', cur_result['name'])
-                    all_wanted = False
-                    break
-
-            if not all_wanted:
-                continue
-
             search_result.episodes = ep_objs
             search_result.actual_episodes = actual_episodes
 
-            log.debug(
-                '{id}: Using cached results from {provider} for series {show_name!r} episode {ep}', {
-                    'id': search_result.episodes[0].series.series_id,
-                    'provider': self.provider.name,
-                    'show_name': search_result.episodes[0].series.name,
-                    'ep': episode_num(search_result.episodes[0].season, search_result.episodes[0].episode),
-                }
-            )
-
             # Map the remaining attributes
             search_result.series = series_obj
             search_result.seeders = cur_result['seeders']
@@ -587,17 +630,10 @@ def find_needed_episodes(self, episode, forced_search=False,
             search_result.proper_tags = cur_result['proper_tags'].split('|') if cur_result['proper_tags'] else ''
             search_result.content = None
 
-            # FIXME: Should be changed to search_result.search_type
-            search_result.forced_search = forced_search
-            search_result.download_current_quality = down_cur_quality
-
             # add it to the list
-            if episode_number not in needed_eps:
-                needed_eps[episode_number] = [search_result]
-            else:
-                needed_eps[episode_number].append(search_result)
+            cache_results[episode_number].append(search_result)
 
         # datetime stamp this search so cache gets cleared
         self.searched = time()
 
-        return needed_eps
+        return cache_results


Test patch: