search.py 27.3 KB
Newer Older
1
# Author: echel0n <[email protected]>
2
3
# URL: https://sickrage.ca
# Git: https://git.sickrage.ca/SiCKRAGE/sickrage.git
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#
# This file is part of SickRage.
#
# SickRage is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickRage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickRage.  If not, see <http://www.gnu.org/licenses/>.

20
from __future__ import unicode_literals
21
22
23

import re
import threading
echel0n's avatar
echel0n committed
24
25
from datetime import date, timedelta

26
import sickrage
27
from sickrage.clients import getClientIstance
28
29
from sickrage.clients.nzbget import NZBGet
from sickrage.clients.sabnzbd import SabNZBd
30
from sickrage.core.common import Quality, SEASON_RESULT, SNATCHED_BEST, \
31
    SNATCHED_PROPER, SNATCHED, MULTI_EP_RESULT
32
from sickrage.core.exceptions import AuthException
echel0n's avatar
echel0n committed
33
from sickrage.core.helpers import show_names
34
35
from sickrage.core.nzbSplitter import splitNZBResult
from sickrage.core.tv.show.history import FailedHistory, History
36
from sickrage.notifiers import Notifiers
37
from sickrage.providers import NZBProvider, NewznabProvider, TorrentProvider, TorrentRssProvider
38

39
40
41
42
43
44

def snatchEpisode(result, endStatus=SNATCHED):
    """
    Contains the internal logic necessary to actually "snatch" a result that
    has been found.

45
46
47
    :param result: SearchResult instance to be snatched.
    :param endStatus: the episode status that should be used for the episode object once it's snatched.
    :return: boolean, True on success
48
49
50
51
52
53
    """

    if result is None:
        return False

    result.priority = 0  # -1 = low, 0 = normal, 1 = high
54
    if sickrage.app.config.allow_high_priority:
55
56
        # if it aired recently make it high priority
        for curEp in result.episodes:
echel0n's avatar
echel0n committed
57
            if date.today() - curEp.airdate <= timedelta(days=7):
58
                result.priority = 1
59

echel0n's avatar
echel0n committed
60
    if re.search(r'(^|[. _-])(proper|repack)([. _-]|$)', result.name, re.I) is not None:
61
62
        endStatus = SNATCHED_PROPER

echel0n's avatar
echel0n committed
63
    # get result content
echel0n's avatar
echel0n committed
64
    result.content = result.provider.get_content(result.url)
echel0n's avatar
echel0n committed
65

66
    dlResult = False
67
    if result.resultType in ("nzb", "nzbdata"):
68
        if sickrage.app.config.nzb_method == "blackhole":
echel0n's avatar
echel0n committed
69
            dlResult = result.provider.download_result(result)
70
        elif sickrage.app.config.nzb_method == "sabnzbd":
71
            dlResult = SabNZBd.sendNZB(result)
72
        elif sickrage.app.config.nzb_method == "nzbget":
73
            is_proper = True if endStatus == SNATCHED_PROPER else False
74
            dlResult = NZBGet.sendNZB(result, is_proper)
75
        else:
echel0n's avatar
echel0n committed
76
            sickrage.app.log.error("Unknown NZB action specified in config: " + sickrage.app.config.nzb_method)
77
    elif result.resultType in ("torrent", "torznab"):
echel0n's avatar
echel0n committed
78
79
80
81
        # add public trackers to torrent result
        if not result.provider.private:
            result = result.provider.add_trackers(result)

82
        if sickrage.app.config.torrent_method == "blackhole":
echel0n's avatar
echel0n committed
83
            dlResult = result.provider.download_result(result)
84
        else:
85
            if any([result.content, result.url.startswith('magnet:')]):
86
                client = getClientIstance(sickrage.app.config.torrent_method)()
echel0n's avatar
echel0n committed
87
                dlResult = client.send_torrent(result)
88
            else:
89
                sickrage.app.log.warning("Torrent file content is empty")
90
    else:
91
        sickrage.app.log.error("Unknown result type, unable to download it (%r)" % result.resultType)
92

93
    # no download results found
94
95
96
    if not dlResult:
        return False

97
    FailedHistory.logSnatch(result)
98

99
    sickrage.app.alerts.message(_('Episode snatched'), result.name)
100

101
    History.logSnatch(result)
102
103
104
105
106
107
108
109
110
111

    # don't notify when we re-download an episode
    trakt_data = []
    for curEpObj in result.episodes:
        with curEpObj.lock:
            if isFirstBestMatch(result):
                curEpObj.status = Quality.compositeStatus(SNATCHED_BEST, result.quality)
            else:
                curEpObj.status = Quality.compositeStatus(endStatus, result.quality)

echel0n's avatar
echel0n committed
112
113
            # save episode to DB
            curEpObj.saveToDB()
114
115

        if curEpObj.status not in Quality.DOWNLOADED:
116
            try:
echel0n's avatar
echel0n committed
117
                Notifiers.mass_notify_snatch(
118
                    curEpObj._format_pattern('%SN - %Sx%0E - %EN - %QN') + " from " + result.provider.name)
119
            except:
120
                sickrage.app.log.debug("Failed to send snatch notification")
121
122
123

            trakt_data.append((curEpObj.season, curEpObj.episode))

124
    data = sickrage.app.notifier_providers['trakt'].trakt_episode_data_generate(trakt_data)
125

126
    if sickrage.app.config.use_trakt and sickrage.app.config.trakt_sync_watchlist:
127
        sickrage.app.log.debug(
128
129
            "Add episodes, showid: indexerid " + str(result.show.indexerid) + ", Title " + str(
                result.show.name) + " to Traktv Watchlist")
130
        if data:
131
            sickrage.app.notifier_providers['trakt'].update_watchlist(result.show, data_episode=data, update="add")
132
133
134
135

    return True


136
def pickBestResult(results, show):
137
138
139
140
141
142
143
    """
    Find the best result out of a list of search results for a show

    :param results: list of result objects
    :param show: Shows we check for
    :return: best result object
    """
144
145
    results = results if isinstance(results, list) else [results]

146
    sickrage.app.log.debug("Picking the best result out of " + str([x.name for x in results]))
147
148
149
150
151
152
153
154
155

    bestResult = None

    # find the best result for the current episode
    for cur_result in results:
        if show and cur_result.show is not show:
            continue

        # build the black And white list
156
157
        if show.is_anime:
            if not show.release_groups.is_valid(cur_result):
158
159
                continue

160
        sickrage.app.log.info(
161
            "Quality of " + cur_result.name + " is " + Quality.qualityStrings[cur_result.quality])
162

163
164
165
        anyQualities, bestQualities = Quality.splitQuality(show.quality)

        if cur_result.quality not in anyQualities + bestQualities:
166
            sickrage.app.log.debug(cur_result.name + " is a quality we know we don't want, rejecting it")
167
168
            continue

echel0n's avatar
echel0n committed
169
170
171
        # check if seeders meet out minimum requirements, disgard result if it does not
        if hasattr(cur_result.provider, 'minseed') and cur_result.seeders not in (-1, None):
            if int(cur_result.seeders) < int(cur_result.provider.minseed):
echel0n's avatar
echel0n committed
172
173
                sickrage.app.log.info("Discarding torrent because it doesn't meet the minimum seeders: {}. Seeders:  "
                                      "{}".format(cur_result.name, cur_result.seeders))
echel0n's avatar
echel0n committed
174
175
176
177
178
                continue

        # check if leechers meet out minimum requirements, disgard result if it does not
        if hasattr(cur_result.provider, 'minleech') and cur_result.leechers not in (-1, None):
            if int(cur_result.leechers) < int(cur_result.provider.minleech):
echel0n's avatar
echel0n committed
179
180
                sickrage.app.log.info("Discarding torrent because it doesn't meet the minimum leechers: {}. Leechers:  "
                                      "{}".format(cur_result.name, cur_result.leechers))
echel0n's avatar
echel0n committed
181
                continue
echel0n's avatar
echel0n committed
182

183
184
        if show.rls_ignore_words and show_names.containsAtLeastOneWord(cur_result.name,
                                                                       cur_result.show.rls_ignore_words):
185
            sickrage.app.log.info(
186
                "Ignoring " + cur_result.name + " based on ignored words filter: " + show.rls_ignore_words)
187
188
            continue

189
190
        if show.rls_require_words and not show_names.containsAtLeastOneWord(cur_result.name,
                                                                            cur_result.show.rls_require_words):
191
            sickrage.app.log.info(
192
                "Ignoring " + cur_result.name + " based on required words filter: " + show.rls_require_words)
193
194
            continue

195
        if not show_names.filterBadReleases(cur_result.name, parse=False):
196
            sickrage.app.log.info(
echel0n's avatar
echel0n committed
197
                "Ignoring " + cur_result.name + " because its not a valid scene release that we want")
198
199
200
            continue

        if hasattr(cur_result, 'size'):
201
            if FailedHistory.hasFailed(cur_result.name, cur_result.size, cur_result.provider.name):
202
                sickrage.app.log.info(cur_result.name + " has previously failed, rejecting it")
203
204
                continue

205
206
            # quality definition video file size constraints check
            try:
207
208
209
210
211
212
213
214
                if cur_result.size:
                    quality_size = sickrage.app.config.quality_sizes[cur_result.quality]
                    file_size = float(cur_result.size / 1000000)
                    if file_size > quality_size:
                        raise Exception(
                            "Ignoring " + cur_result.name + " with size: {} based on quality size filter: {}".format(
                                file_size, quality_size)
                        )
215
            except Exception as e:
echel0n's avatar
echel0n committed
216
                sickrage.app.log.info(str(e))
217
                continue
218

echel0n's avatar
echel0n committed
219
        # verify result content
220
221
        if not cur_result.provider.private:
            if cur_result.resultType in ["nzb", "torrent"] and not cur_result.provider.get_content(cur_result.url):
222
223
224
225
226
227
228
                if sickrage.app.config.download_unverified_magnet_link and cur_result.url.startswith('magnet'):
                    # Attempt downloading unverified torrent magnet link
                    pass
                else:
                    sickrage.app.log.info(
                        "Ignoring {} because we are unable to verify the download url".format(cur_result.name))
                    continue
echel0n's avatar
echel0n committed
229

230
        if not bestResult:
231
            bestResult = cur_result
232
        elif cur_result.quality in bestQualities and (
echel0n's avatar
echel0n committed
233
                bestResult.quality < cur_result.quality or bestResult.quality not in bestQualities):
234
            bestResult = cur_result
235
        elif cur_result.quality in anyQualities and bestResult.quality not in bestQualities and bestResult.quality < cur_result.quality:
236
237
            bestResult = cur_result
        elif bestResult.quality == cur_result.quality:
238
239
240
241
242
            if "proper" in cur_result.name.lower() or "repack" in cur_result.name.lower():
                bestResult = cur_result
            elif "internal" in bestResult.name.lower() and "internal" not in cur_result.name.lower():
                bestResult = cur_result
            elif "xvid" in bestResult.name.lower() and "x264" in cur_result.name.lower():
243
                sickrage.app.log.info("Preferring " + cur_result.name + " (x264 over xvid)")
244
245
246
                bestResult = cur_result

    if bestResult:
247
        sickrage.app.log.debug("Picked " + bestResult.name + " as the best")
248
    else:
249
        sickrage.app.log.debug("No result picked.")
250
251
252
253
254
255
256
257
258
259
260
261

    return bestResult


def isFinalResult(result):
    """
    Checks if the given result is good enough quality that we can stop searching for other ones.

    If the result is the highest quality in both the any/best quality lists then this function
    returns True, if not then it's False
    """

262
    sickrage.app.log.debug("Checking if we should keep searching after we've found " + result.name)
263
264
265
266
267
268
269
270
271
272

    show_obj = result.episodes[0].show

    any_qualities, best_qualities = Quality.splitQuality(show_obj.quality)

    # if there is a redownload that's higher than this then we definitely need to keep looking
    if best_qualities and result.quality < max(best_qualities):
        return False

    # if it does not match the shows black and white list its no good
273
    elif show_obj.is_anime and show_obj.release_groups.is_valid(result):
274
275
276
277
278
279
280
        return False

    # if there's no redownload that's higher (above) and this is the highest initial download then we're good
    elif any_qualities and result.quality in any_qualities:
        return True

    elif best_qualities and result.quality == max(best_qualities):
281
        return True
282
283
284
285
286
287
288
289
290
291
292

    # if we got here than it's either not on the lists, they're empty, or it's lower than the highest required
    else:
        return False


def isFirstBestMatch(result):
    """
    Checks if the given result is a best quality match and if we want to archive the episode on first match.
    """

293
    sickrage.app.log.debug(
294
        "Checking if we should archive our first best quality match for episode " + result.name)
295
296
297
298
299

    show_obj = result.episodes[0].show

    any_qualities, best_qualities = Quality.splitQuality(show_obj.quality)

300
301
302
    # if there is a re-download that's a match to one of our best qualities and we want to skip downloaded then we
    # are done
    if best_qualities and show_obj.skip_downloaded and result.quality in best_qualities:
303
304
305
306
        return True

    return False

307

308
def searchProviders(show, episodes, manualSearch=False, downCurQuality=False, updateCache=True, cacheOnly=False):
309
310
311
312
313
314
315
316
317
    """
    Walk providers for information on shows

    :param show: Show we are looking for
    :param episodes: Episodes we hope to find
    :param manualSearch: Boolean, is this a manual search?
    :param downCurQuality: Boolean, should we redownload currently avaialble quality file
    :return: results for search
    """
echel0n's avatar
echel0n committed
318

319
    # build name cache for show
320
    sickrage.app.name_cache.build(show)
321

322
    origThreadName = threading.currentThread().getName()
323

324
    def perform_searches():
325
326
327
        search_results = {}
        found_results = {}
        final_results = []
328

329
        for providerID, providerObj in sickrage.app.search_providers.sort(
330
                randomize=sickrage.app.config.randomize_providers).items():
331

echel0n's avatar
echel0n committed
332
333
334
335
336
            # check if provider is enabled
            if not providerObj.isEnabled:
                continue

            # check provider type
337
            if not sickrage.app.config.use_nzbs and providerObj.type in [NZBProvider.type, NewznabProvider.type]:
338
                continue
339
            elif not sickrage.app.config.use_torrents and providerObj.type in [TorrentProvider.type,
340
                                                                               TorrentRssProvider.type]:
341
342
                continue

343
            if providerObj.anime_only and not show.is_anime:
344
                sickrage.app.log.debug("" + str(show.name) + " is not an anime, skiping")
345
                continue
346

347
            found_results[providerObj.name] = {}
348

echel0n's avatar
echel0n committed
349
            search_count = 0
350
            search_mode = providerObj.search_mode
351

352
353
354
            # Always search for episode when manually searching when in sponly
            if search_mode == 'sponly' and manualSearch == True:
                search_mode = 'eponly'
355

356
            while True:
echel0n's avatar
echel0n committed
357
                search_count += 1
358

359
                try:
360
361
                    threading.currentThread().setName(origThreadName + "::[" + providerObj.name + "]")

echel0n's avatar
echel0n committed
362
                    # update provider RSS cache
363
364
                    if sickrage.app.config.enable_rss_cache and updateCache:
                        providerObj.cache.update()
echel0n's avatar
echel0n committed
365

366
367
                    if len(episodes):
                        if search_mode == 'eponly':
368
                            sickrage.app.log.info("Performing episode search for " + show.name)
369
                        else:
370
                            sickrage.app.log.info("Performing season pack search for " + show.name)
echel0n's avatar
echel0n committed
371

echel0n's avatar
echel0n committed
372
                    # search provider for episodes
373
                    search_results = providerObj.findSearchResults(show,
374
375
376
377
378
                                                                   episodes,
                                                                   search_mode,
                                                                   manualSearch,
                                                                   downCurQuality,
                                                                   cacheOnly)
379
                except AuthException as e:
echel0n's avatar
echel0n committed
380
                    sickrage.app.log.warning("Authentication error: {}".format(e))
381
382
                    break
                except Exception as e:
echel0n's avatar
echel0n committed
383
                    sickrage.app.log.error("Error while searching " + providerObj.name + ", skipping: {}".format(e))
384
                    break
385
386
                finally:
                    threading.currentThread().setName(origThreadName)
387

388
                if len(search_results):
389
                    # make a list of all the results for this provider
390
391
392
                    for curEp in search_results:
                        if curEp in found_results:
                            found_results[providerObj.name][curEp] += search_results[curEp]
393
                        else:
394
                            found_results[providerObj.name][curEp] = search_results[curEp]
echel0n's avatar
echel0n committed
395
396
397

                        # Sort results by seeders if available
                        if providerObj.type == 'torrent' or getattr(providerObj, 'torznab', False):
398
                            found_results[providerObj.name][curEp].sort(key=lambda k: int(k.seeders), reverse=True)
echel0n's avatar
echel0n committed
399

400
                    break
echel0n's avatar
echel0n committed
401
                elif not providerObj.search_fallback or search_count == 2:
402
                    break
403

404
                if search_mode == 'sponly':
405
                    sickrage.app.log.debug("Fallback episode search initiated")
406
407
                    search_mode = 'eponly'
                else:
408
                    sickrage.app.log.debug("Fallback season pack search initiate")
409
                    search_mode = 'sponly'
410

411
            # skip to next provider if we have no results to process
412
            if not len(found_results[providerObj.name]):
413
                continue
414

415
416
            # pick the best season NZB
            bestSeasonResult = None
417
418
            if SEASON_RESULT in found_results[providerObj.name]:
                bestSeasonResult = pickBestResult(found_results[providerObj.name][SEASON_RESULT], show)
419

420
            highest_quality_overall = 0
421
422
            for cur_episode in found_results[providerObj.name]:
                for cur_result in found_results[providerObj.name][cur_episode]:
423
424
                    if cur_result.quality != Quality.UNKNOWN and cur_result.quality > highest_quality_overall:
                        highest_quality_overall = cur_result.quality
echel0n's avatar
echel0n committed
425

426
            sickrage.app.log.debug(
427
                "The highest quality of any match is " + Quality.qualityStrings[highest_quality_overall])
428

429
430
            # see if every episode is wanted
            if bestSeasonResult:
431
                searchedSeasons = {x.season for x in episodes}
432

433
434
                # get the quality of the season nzb
                seasonQual = bestSeasonResult.quality
435
                sickrage.app.log.debug(
436
437
438
                    "The quality of the season " + bestSeasonResult.provider.type + " is " +
                    Quality.qualityStrings[
                        seasonQual])
439

440
441
                allEps = [int(x["episode"]) for x in sickrage.app.main_db.get_many('tv_episodes', show.indexerid)
                          if x['season'] in searchedSeasons]
442

443
                sickrage.app.log.debug("Episode list: " + str(allEps))
444

445
446
447
448
449
450
                allWanted = True
                anyWanted = False
                for curEpNum in allEps:
                    for season in set([x.season for x in episodes]):
                        if not show.wantEpisode(season, curEpNum, seasonQual, downCurQuality):
                            allWanted = False
451
                        else:
452
                            anyWanted = True
453

454
455
                # 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)
456
                if allWanted and bestSeasonResult.quality == highest_quality_overall:
457
                    sickrage.app.log.info(
458
                        "Every ep in this season is needed, downloading the whole " + bestSeasonResult.provider.type + " " + bestSeasonResult.name)
echel0n's avatar
echel0n committed
459

460
461
462
463
                    epObjs = []
                    for curEpNum in allEps:
                        for season in set([x.season for x in episodes]):
                            epObjs.append(show.getEpisode(season, curEpNum))
echel0n's avatar
echel0n committed
464

465
466
                    bestSeasonResult.episodes = epObjs

467
                    return [bestSeasonResult]
468

469
                elif not anyWanted:
470
                    sickrage.app.log.debug(
471
                        "No eps from this season are wanted at this quality, ignoring the result of " + bestSeasonResult.name)
472
                else:
echel0n's avatar
echel0n committed
473
                    if bestSeasonResult.provider.type == NZBProvider.type:
474
                        sickrage.app.log.debug(
475
                            "Breaking apart the NZB and adding the individual ones to our results")
476
477
478
479

                        # if not, break it apart and add them as the lowest priority results
                        individualResults = splitNZBResult(bestSeasonResult)
                        for curResult in individualResults:
480
                            epNum = -1
481
482
483
484
485
                            if len(curResult.episodes) == 1:
                                epNum = curResult.episodes[0].episode
                            elif len(curResult.episodes) > 1:
                                epNum = MULTI_EP_RESULT

486
487
                            if epNum in found_results[providerObj.name]:
                                found_results[providerObj.name][epNum].append(curResult)
488
                            else:
489
                                found_results[providerObj.name][epNum] = [curResult]
490

491
                    # 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
492
                    else:
493
                        # Season result from Torrent Provider must be a full-season torrent, creating multi-ep result for it.
494
                        sickrage.app.log.info(
495
                            "Adding multi-ep result for full-season torrent. Set the episodes you don't want to 'don't download' in your torrent client if desired!")
echel0n's avatar
echel0n committed
496

497
498
499
500
501
502
                        epObjs = []
                        for curEpNum in allEps:
                            for season in set([x.season for x in episodes]):
                                epObjs.append(show.getEpisode(season, curEpNum))
                        bestSeasonResult.episodes = epObjs

503
504
                        if MULTI_EP_RESULT in found_results[providerObj.name]:
                            found_results[providerObj.name][MULTI_EP_RESULT].append(bestSeasonResult)
505
                        else:
506
                            found_results[providerObj.name][MULTI_EP_RESULT] = [bestSeasonResult]
507
508
509

            # go through multi-ep results and see if we really want them or not, get rid of the rest
            multiResults = {}
510
511
            if MULTI_EP_RESULT in found_results[providerObj.name]:
                for _multiResult in found_results[providerObj.name][MULTI_EP_RESULT]:
512

513
                    sickrage.app.log.debug(
514
                        "Seeing if we want to bother with multi-episode result " + _multiResult.name)
515
516
517
518
519
520
521
522
523
524
525

                    # Filter result by ignore/required/whitelist/blacklist/quality, etc
                    multiResult = pickBestResult(_multiResult, show)
                    if not multiResult:
                        continue

                    # see how many of the eps that this result covers aren't covered by single results
                    neededEps = []
                    notNeededEps = []
                    for epObj in multiResult.episodes:
                        # if we have results for the episode
526
527
                        if epObj.episode in found_results[providerObj.name] and len(
                                found_results[providerObj.name][epObj.episode]) > 0:
528
529
530
                            notNeededEps.append(epObj.episode)
                        else:
                            neededEps.append(epObj.episode)
531

532
                    sickrage.app.log.debug(
533
534
                        "Single-ep check result is neededEps: " + str(neededEps) + ", notNeededEps: " + str(
                            notNeededEps))
535

536
                    if not neededEps:
537
                        sickrage.app.log.debug(
538
                            "All of these episodes were covered by single episode results, ignoring this multi-episode result")
539
540
541
542
543
544
545
546
547
548
                        continue

                    # check if these eps are already covered by another multi-result
                    multiNeededEps = []
                    multiNotNeededEps = []
                    for epObj in multiResult.episodes:
                        if epObj.episode in multiResults:
                            multiNotNeededEps.append(epObj.episode)
                        else:
                            multiNeededEps.append(epObj.episode)
549

550
                    sickrage.app.log.debug(
551
552
553
                        "Multi-ep check result is multiNeededEps: " + str(
                            multiNeededEps) + ", multiNotNeededEps: " + str(
                            multiNotNeededEps))
554

555
                    if not multiNeededEps:
556
                        sickrage.app.log.debug(
557
                            "All of these episodes were covered by another multi-episode nzbs, ignoring this multi-ep result")
558
559
560
561
562
                        continue

                    # don't bother with the single result if we're going to get it with a multi result
                    for epObj in multiResult.episodes:
                        multiResults[epObj.episode] = multiResult
563
                        if epObj.episode in found_results[providerObj.name]:
564
                            sickrage.app.log.debug(
565
566
                                "A needed multi-episode result overlaps with a single-episode result for ep #" + str(
                                    epObj.episode) + ", removing the single-episode results from the list")
567
                            del found_results[providerObj.name][epObj.episode]
568
569

            # of all the single ep results narrow it down to the best one for each episode
570
571
            final_results += set(multiResults.values())
            for curEp in found_results[providerObj.name]:
572
573
                if curEp in (MULTI_EP_RESULT, SEASON_RESULT):
                    continue
574

575
                if not len(found_results[providerObj.name][curEp]) > 0:
576
                    continue
577

578
                # if all results were rejected move on to the next episode
579
                bestResult = pickBestResult(found_results[providerObj.name][curEp], show)
580
581
                if not bestResult:
                    continue
582

583
584
                # add result if its not a duplicate and
                found = False
585
                for i, result in enumerate(final_results):
586
587
588
                    for bestResultEp in bestResult.episodes:
                        if bestResultEp in result.episodes:
                            if result.quality < bestResult.quality:
589
                                final_results.pop(i)
590
591
592
                            else:
                                found = True
                if not found:
593
                    final_results += [bestResult]
594
595
596
597

            # check that we got all the episodes we wanted first before doing a match and snatch
            wantedEpCount = 0
            for wantedEp in episodes:
598
                for result in final_results:
599
600
601
602
603
604
605
                    if wantedEp in result.episodes and isFinalResult(result):
                        wantedEpCount += 1

            # make sure we search every provider for results unless we found everything we wanted
            if wantedEpCount == len(episodes):
                break

606
        return final_results
607
608

    return perform_searches()