From 0aa08edd84c770310f28a7d38853f6ac65b262cb Mon Sep 17 00:00:00 2001 From: Sarakha63 <sarakha_ludovic@yahoo.fr> Date: Sat, 18 May 2013 13:04:51 +0200 Subject: [PATCH] first version : History download gestion a second search will automatically skip the first downloaded episode unless ou clean this episode download history also : changed the way backlog is done when only some episodes were wanted in a season (avoiding having a full season search if only some eps were wanted) --- data/images/corbeille.png | Bin 0 -> 16531 bytes data/interfaces/default/displayShow.tmpl | 8 +- data/js/ajaxHisttrunc.js | 45 ++ data/js/displayShow.js | 1 + sickbeard/databases/mainDB.py | 13 +- sickbeard/nzbSplitter.py | 2 +- sickbeard/providers/binnewz/nzbindex.py | 2 +- sickbeard/providers/cpasbien.py | 2 +- sickbeard/providers/gks.py | 2 +- sickbeard/providers/t411.py | 2 +- sickbeard/search.py | 546 +++++++++++++---------- sickbeard/webserve.py | 10 + 12 files changed, 381 insertions(+), 252 deletions(-) create mode 100644 data/images/corbeille.png create mode 100644 data/js/ajaxHisttrunc.js diff --git a/data/images/corbeille.png b/data/images/corbeille.png new file mode 100644 index 0000000000000000000000000000000000000000..d057ce92d6a136700cbc91f37e9e76660fba1b30 GIT binary patch literal 16531 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 literal 0 HcmV?d00001 diff --git a/data/interfaces/default/displayShow.tmpl b/data/interfaces/default/displayShow.tmpl index edfb26c58..608f63fb5 100644 --- a/data/interfaces/default/displayShow.tmpl +++ b/data/interfaces/default/displayShow.tmpl @@ -89,6 +89,7 @@ <script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/ajaxEpSubtitles.js?$sbPID"></script> +<script type="text/javascript" src="$sbRoot/js/ajaxhisttrunc.js?$sbPID"></script> <div class="align-left"><b>Change Show:</b> <div class="navShow"><img id="prevShow" width="16" height="18" src="$sbRoot/images/prev.gif" alt="<<" title="Prev Show" /></div> @@ -209,13 +210,13 @@ Change Audio of selected episodes to #for $epResult in $sqlResults: #if int($epResult["season"]) != $curSeason: - <tr><td colspan="10"><a name="season-$epResult["season"]"></a></td></tr> + <tr><td colspan="11"><a name="season-$epResult["season"]"></a></td></tr> <tr class="seasonheader" id="season-$epResult["season"]"> <td colspan="9"> <h2>#if int($epResult["season"]) == 0 then "Specials" else "Season "+str($epResult["season"])#</h2> </td> </tr> - <tr id="season-$epResult["season"]-cols"><th width="1%"><input type="checkbox" class="seasonCheck" id="$epResult["season"]" /></th><th>NFO</th><th>TBN</th><th>Episode</th><th>Name</th><th class="nowrap">Airdate</th><th>Filename</th><th>Audio</th>#if $sickbeard.USE_SUBTITLES and $show.subtitles then "<th>Subs</th>" else ""#<th>Status</th><th>Search</th></tr> + <tr id="season-$epResult["season"]-cols"><th width="1%"><input type="checkbox" class="seasonCheck" id="$epResult["season"]" /></th><th>NFO</th><th>TBN</th><th>Episode</th><th>Name</th><th class="nowrap">Airdate</th><th>Filename</th><th>Audio</th>#if $sickbeard.USE_SUBTITLES and $show.subtitles then "<th>Subs</th>" else ""#<th>Status</th><th>Search</th><th>Hist</th></tr> #set $curSeason = int($epResult["season"]) #end if @@ -280,6 +281,9 @@ $epLoc <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=$show.tvdbid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a> #end if </td> + <td align="center"> + <a class="histtrunc" href="trunchistory?epid=$epResult["episode_id"]"><img src="$sbRoot/images/corbeille.png" height="16" alt="trunc" title="Trunc Downloaded links History" /></a> + </td> </tr> #end for diff --git a/data/js/ajaxHisttrunc.js b/data/js/ajaxHisttrunc.js new file mode 100644 index 000000000..82db735ed --- /dev/null +++ b/data/js/ajaxHisttrunc.js @@ -0,0 +1,45 @@ +(function () { + + $.ajaxHisttrunc = { + defaults: { + size: 16, + colorRow: false, + loadingImage: 'loading16_dddddd.gif', + noImage: 'no16.png', + yesImage: 'yes16.png' + } + }; + + $.fn.ajaxHisttrunc = function (options) { + options = $.extend({}, $.ajaxHisttrunc.defaults, options); + + $('.histtrunc').click(function () { + var parent = $(this).parent(); + + // put the ajax spinner (for non white bg) placeholder while we wait + parent.empty(); + parent.append($("<img/>").attr({"src": sbRoot + "/images/" + options.loadingImage, "height": options.size, "alt": "", "title": "loading"})); + + $.getJSON($(this).attr('href'), function (data) { + // if they failed then just put the red X + if (data.result == 'failure') { + img_name = options.noImage; + img_result = 'failed'; + + // if the snatch was successful then apply the corresponding class and fill in the row appropriately + } else { + img_name = options.yesImage; + img_result = 'success'; + + } + + // put the corresponding image as the result for the the row + parent.empty(); + parent.append($("<img/>").attr({"src": sbRoot + "/images/" + img_name, "height": options.size, "alt": img_result, "title": img_result})); + }); + + // fon't follow the link + return false; + }); + }; +})(); diff --git a/data/js/displayShow.js b/data/js/displayShow.js index 7dc7a9375..30869610e 100644 --- a/data/js/displayShow.js +++ b/data/js/displayShow.js @@ -2,6 +2,7 @@ $(document).ready(function(){ $('#sbRoot').ajaxEpSearch({'colorRow': true}); $('#sbRoot').ajaxEpSubtitlesSearch({'colorRow': true}); + $('#sbRoot').ajaxHisttrunc({'colorRow': true}); $('#seasonJump').change(function() { var id = $(this).val(); diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py index 3dc8d4b4c..22a250bcb 100644 --- a/sickbeard/databases/mainDB.py +++ b/sickbeard/databases/mainDB.py @@ -25,7 +25,7 @@ from sickbeard.providers.generic import GenericProvider from sickbeard import encodingKludge as ek from sickbeard.name_parser.parser import NameParser, InvalidNameException -MAX_DB_VERSION = 13 +MAX_DB_VERSION = 14 class MainSanityCheck(db.DBSanityCheck): @@ -100,7 +100,8 @@ class InitialSchema (db.SchemaUpgrade): "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, seasonfolders NUMERIC, paused NUMERIC, startyear NUMERIC);", "CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, tvdbid NUMERIC, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT);", "CREATE TABLE info (last_backlog NUMERIC, last_tvdb NUMERIC);", - "CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider NUMERIC);" + "CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider NUMERIC);", + "CREATE TABLE episode_links (episode_id INTEGER, link TEXT);" ] for query in queries: self.connection.action(query) @@ -697,3 +698,11 @@ class AddSubtitlesSupport(Add1080pAndRawHDQualities): self.addColumn("tv_episodes", "subtitles_searchcount") self.addColumn("tv_episodes", "subtitles_lastsearch", "TIMESTAMP", str(datetime.datetime.min)) self.incDBVersion() + +class AddSubtitlesSupport(AddSubtitlesSupport): + def test(self): + return self.checkDBVersion() >= 14 + + def execute(self): + self.connection.action("CREATE TABLE episode_links (episode_id INTEGER, link TEXT)") + self.incDBVersion() diff --git a/sickbeard/nzbSplitter.py b/sickbeard/nzbSplitter.py index 048c8a59e..ab7eeba12 100644 --- a/sickbeard/nzbSplitter.py +++ b/sickbeard/nzbSplitter.py @@ -32,7 +32,7 @@ def getSeasonNZBs(name, urlData, season): try: showXML = etree.ElementTree(etree.XML(urlData)) except SyntaxError: - logger.log(u"Unable to parse the XML of "+name+", not splitting it", logger.ERROR) + logger.log(u"Unable to parse the XML of "+name+", not splitting it", logger.DEBUG) return ({},'') filename = name.replace(".nzb", "") diff --git a/sickbeard/providers/binnewz/nzbindex.py b/sickbeard/providers/binnewz/nzbindex.py index b340c99a9..d2a0547b1 100644 --- a/sickbeard/providers/binnewz/nzbindex.py +++ b/sickbeard/providers/binnewz/nzbindex.py @@ -44,4 +44,4 @@ class NZBIndex(NZBDownloader): for tr in results: nzblink = tr.find("a", text="Download") - return NZBGetURLSearchResult(self, nzblink.get("href"), None, refererURL) + return NZBGetURLSearchResult(self, nzblink.get("href"), None, nzblink.get("href")) diff --git a/sickbeard/providers/cpasbien.py b/sickbeard/providers/cpasbien.py index 9bfc47c36..0794f087e 100644 --- a/sickbeard/providers/cpasbien.py +++ b/sickbeard/providers/cpasbien.py @@ -143,7 +143,7 @@ class CpasbienSearchResult: self.title = title self.url = url self.quality = quality - self.audio_langs=[audio_langs] + self.audio_langs=audio_langs def getNZB(self): return self.opener.open( self.url , 'wb').read() diff --git a/sickbeard/providers/gks.py b/sickbeard/providers/gks.py index 32ac1a7b7..345a161a8 100644 --- a/sickbeard/providers/gks.py +++ b/sickbeard/providers/gks.py @@ -134,7 +134,7 @@ class GksSearchResult: self.title = title self.url = url self.quality = quality - self.audio_langs=[audio_langs] + self.audio_langs=audio_langs def getNZB(self): return self.opener.open( self.url , 'wb').read() diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py index 5113e9a31..3faa8533c 100644 --- a/sickbeard/providers/t411.py +++ b/sickbeard/providers/t411.py @@ -139,7 +139,7 @@ class T411SearchResult: self.title = title self.url = url self.quality = quality - self.audio_langs=[audio_langs] + self.audio_langs=audio_langs def getNZB(self): return self.opener.open( self.url , 'wb').read() diff --git a/sickbeard/search.py b/sickbeard/search.py index cfc541c34..f6d87388b 100644 --- a/sickbeard/search.py +++ b/sickbeard/search.py @@ -23,8 +23,7 @@ import traceback import sickbeard -from common import SNATCHED, Quality, SEASON_RESULT, MULTI_EP_RESULT - +from common import SNATCHED, Quality, SEASON_RESULT, MULTI_EP_RESULT,ARCHIVED, IGNORED, UNAIRED, WANTED, SKIPPED from sickbeard import logger, db, show_name_helpers, exceptions, helpers from sickbeard import sab from sickbeard import nzbget @@ -124,48 +123,51 @@ def snatchEpisode(result, endStatus=SNATCHED): """ # NZBs can be sent straight to SAB or saved to disk - if result.resultType in ("nzb", "nzbdata"): - if sickbeard.NZB_METHOD == "blackhole": - dlResult = _downloadResult(result) - elif sickbeard.NZB_METHOD == "sabnzbd": - dlResult = sab.sendNZB(result) - elif sickbeard.NZB_METHOD == "nzbget": - dlResult = nzbget.sendNZB(result) + if hasattr(result,'resultType'): + if result.resultType in ("nzb", "nzbdata"): + if sickbeard.NZB_METHOD == "blackhole": + dlResult = _downloadResult(result) + elif sickbeard.NZB_METHOD == "sabnzbd": + dlResult = sab.sendNZB(result) + elif sickbeard.NZB_METHOD == "nzbget": + dlResult = nzbget.sendNZB(result) + else: + logger.log(u"Unknown NZB action specified in config: " + sickbeard.NZB_METHOD, logger.ERROR) + dlResult = False + + # TORRENTs can be sent to clients or saved to disk + elif result.resultType in ("torrent", "torrentdata"): + # torrents are saved to disk when blackhole mode + if sickbeard.TORRENT_METHOD == "blackhole": + dlResult = _downloadResult(result) + else: + client = clients.getClientIstance(sickbeard.TORRENT_METHOD)() + if hasattr(result,'extraInfo') and result.resultType=="torrentdata": + result.content=result.extraInfo[0] + dlResult = client.sendTORRENT(result) else: - logger.log(u"Unknown NZB action specified in config: " + sickbeard.NZB_METHOD, logger.ERROR) + logger.log(u"Unknown result type, unable to download it", logger.ERROR) dlResult = False - - # TORRENTs can be sent to clients or saved to disk - elif result.resultType in ("torrent", "torrentdata"): - # torrents are saved to disk when blackhole mode - if sickbeard.TORRENT_METHOD == "blackhole": - dlResult = _downloadResult(result) - else: - client = clients.getClientIstance(sickbeard.TORRENT_METHOD)() - if hasattr(result,'extraInfo') and result.resultType=="torrentdata": - result.content=result.extraInfo[0] - dlResult = client.sendTORRENT(result) + + if dlResult == False: + return False + + history.logSnatch(result) + + # don't notify when we re-download an episode + for curEpObj in result.episodes: + with curEpObj.lock: + curEpObj.status = Quality.compositeStatus(endStatus, result.quality) + curEpObj.audio_langs = result.audio_lang + curEpObj.saveToDB() + + if curEpObj.status not in Quality.DOWNLOADED: + notifiers.notify_snatch(curEpObj.prettyName()) + + return True else: - logger.log(u"Unknown result type, unable to download it", logger.ERROR) - dlResult = False - - if dlResult == False: return False - history.logSnatch(result) - - # don't notify when we re-download an episode - for curEpObj in result.episodes: - with curEpObj.lock: - curEpObj.status = Quality.compositeStatus(endStatus, result.quality) - curEpObj.audio_langs = result.audio_lang - curEpObj.saveToDB() - - if curEpObj.status not in Quality.DOWNLOADED: - notifiers.notify_snatch(curEpObj.prettyName()) - - return True - def searchForNeededEpisodes(): logger.log(u"Searching all providers for any needed episodes") @@ -221,32 +223,68 @@ def searchForNeededEpisodes(): return foundResults.values() -def pickBestResult(results, quality_list=None): +def pickBestResult(results, quality_list=None, episode=None): logger.log(u"Picking the best result out of "+str([x.name for x in results]), logger.DEBUG) - + links=[] + myDB = db.DBConnection() + for eps in episode.values(): + if hasattr(eps,'tvdbid'): + epidr=myDB.select("SELECT episode_id from tv_episodes where tvdbid=?",[eps.tvdbid]) + listlink=myDB.select("SELECT link from episode_links where episode_id=?",[epidr[0][0]]) + for dlink in listlink: + links.append(dlink[0]) # find the best result for the current episode - bestResult = None - for cur_result in results: - logger.log("Quality of "+cur_result.name+" is "+Quality.qualityStrings[cur_result.quality]) + bestResult = None + for cur_result in results: + if hasattr(cur_result,'item'): + if hasattr(cur_result.item,'nzburl'): + eplink=cur_result.item.nzburl + else: + eplink=cur_result.item.url + else: + if hasattr(cur_result,'nzburl'): + eplink=cur_result.nzburl + else: + eplink=cur_result.url + logger.log("Quality of "+cur_result.name+" is "+Quality.qualityStrings[cur_result.quality]) - if quality_list and cur_result.quality not in quality_list: - logger.log(cur_result.name+" is a quality we know we don't want, rejecting it", logger.DEBUG) - continue + if quality_list and cur_result.quality not in quality_list: + logger.log(cur_result.name+" is a quality we know we don't want, rejecting it", logger.DEBUG) + continue - if not bestResult or bestResult.quality < cur_result.quality and cur_result.quality != Quality.UNKNOWN: - bestResult = cur_result - elif bestResult.quality == cur_result.quality: - 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(): + if eplink in links: + logger.log(eplink +" was already downloaded so let's skip it assuming the download failed, you can erase the downloaded links for that episode if you want", logger.DEBUG) + continue + + if not bestResult or bestResult.quality < cur_result.quality and cur_result.quality != Quality.UNKNOWN: bestResult = cur_result - - if bestResult: - logger.log(u"Picked "+bestResult.name+" as the best", logger.DEBUG) - else: - logger.log(u"No result picked.", logger.DEBUG) - + + elif bestResult.quality == cur_result.quality: + 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 + + if bestResult: + logger.log(u"Picked "+bestResult.name+" as the best", logger.DEBUG) + + if hasattr(bestResult,'item'): + if hasattr(bestResult.item,'nzburl'): + eplink=bestResult.item.nzburl + else: + eplink=bestResult.item.url + else: + if hasattr(bestResult,'nzburl'): + eplink=bestResult.nzburl + else: + eplink=bestResult.url + count=myDB.select("SELECT count(*) from episode_links where episode_id=? and link=?",[epidr[0][0],eplink]) + if count[0][0]==0: + myDB.action("INSERT INTO episode_links (episode_id, link) VALUES (?,?)",[epidr[0][0],eplink]) + else: + logger.log(u"No result picked.", logger.DEBUG) + return bestResult def isFinalResult(result): @@ -331,202 +369,224 @@ def findEpisode(episode, manualSearch=False): if not didSearch: logger.log(u"No NZB/Torrent providers found or enabled in the sickbeard config. Please check your settings.", logger.ERROR) - - bestResult = pickBestResult(foundResults) - + epi={} + epi[1]=episode + bestResult = pickBestResult(foundResults,episode=epi) return bestResult def findSeason(show, season): - logger.log(u"Searching for stuff we need from "+show.name+" season "+str(season)) - - foundResults = {} - - didSearch = False - - for curProvider in providers.sortedProviderList(): - - if not curProvider.isActive(): - continue - - try: - curResults = curProvider.findSeasonResults(show, season) - - # make a list of all the results for this provider - for curEp in curResults: - - # skip non-tv crap - curResults[curEp] = filter(lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name, show), curResults[curEp]) - - if curEp in foundResults: - foundResults[curEp] += curResults[curEp] - else: - foundResults[curEp] = curResults[curEp] - - except exceptions.AuthException, e: - logger.log(u"Authentication error: "+ex(e), logger.ERROR) - continue - except Exception, e: - logger.log(u"Error while searching "+curProvider.name+", skipping: "+ex(e), logger.ERROR) - logger.log(traceback.format_exc(), logger.DEBUG) - continue - - didSearch = True - - if not didSearch: - logger.log(u"No NZB/Torrent providers found or enabled in the sickbeard config. Please check your settings.", logger.ERROR) + myDB = db.DBConnection() + allEps = [int(x["episode"]) for x in myDB.select("SELECT episode FROM tv_episodes WHERE showid = ? AND season = ?", [show.tvdbid, season])] + logger.log(u"Episode list: "+str(allEps), logger.DEBUG) + + reallywanted=[] + notwanted=[] finalResults = [] - - anyQualities, bestQualities = Quality.splitQuality(show.quality) - - # pick the best season NZB - bestSeasonNZB = None - if SEASON_RESULT in foundResults: - bestSeasonNZB = pickBestResult(foundResults[SEASON_RESULT], anyQualities+bestQualities) - - highest_quality_overall = 0 - for cur_season in foundResults: - for cur_result in foundResults[cur_season]: - if cur_result.quality != Quality.UNKNOWN and cur_result.quality > highest_quality_overall: - highest_quality_overall = cur_result.quality - logger.log(u"The highest quality of any match is "+Quality.qualityStrings[highest_quality_overall], logger.DEBUG) - - # see if every episode is wanted - if bestSeasonNZB: - - # get the quality of the season nzb - seasonQual = Quality.nameQuality(bestSeasonNZB.name) - seasonQual = bestSeasonNZB.quality - logger.log(u"The quality of the season NZB is "+Quality.qualityStrings[seasonQual], logger.DEBUG) - - myDB = db.DBConnection() - allEps = [int(x["episode"]) for x in myDB.select("SELECT episode FROM tv_episodes WHERE showid = ? AND season = ?", [show.tvdbid, season])] - logger.log(u"Episode list: "+str(allEps), logger.DEBUG) - - allWanted = True - anyWanted = False - for curEpNum in allEps: - if not show.wantEpisode(season, curEpNum, seasonQual): - allWanted = False - else: - anyWanted = True - - # if we need every ep in the season and there's nothing better then just download this and be done with it - if allWanted and bestSeasonNZB.quality == highest_quality_overall: - logger.log(u"Every ep in this season is needed, downloading the whole NZB "+bestSeasonNZB.name) - epObjs = [] - for curEpNum in allEps: - epObjs.append(show.getEpisode(season, curEpNum)) - bestSeasonNZB.episodes = epObjs - return [bestSeasonNZB] - - elif not anyWanted: - logger.log(u"No eps from this season are wanted at this quality, ignoring the result of "+bestSeasonNZB.name, logger.DEBUG) - + for curEpNum in allEps: + sqlResults = myDB.select("SELECT status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?", [show.tvdbid, season, curEpNum]) + epStatus = int(sqlResults[0]["status"]) + if epStatus ==3: + reallywanted.append(curEpNum) else: - - if bestSeasonNZB.provider.providerType == GenericProvider.NZB: - logger.log(u"Breaking apart the NZB and adding the individual ones to our results", logger.DEBUG) - - # if not, break it apart and add them as the lowest priority results - individualResults = nzbSplitter.splitResult(bestSeasonNZB) - - individualResults = filter(lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name, show), individualResults) - - for curResult in individualResults: - if len(curResult.episodes) == 1: - epNum = curResult.episodes[0].episode - elif len(curResult.episodes) > 1: - epNum = MULTI_EP_RESULT - - if epNum in foundResults: - foundResults[epNum].append(curResult) + notwanted.append(curEpNum) + if notwanted != []: + for EpNum in reallywanted: + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, show.tvdbid) + episode = showObj.getEpisode(season, EpNum) + finalResults.append(findEpisode(episode, manualSearch=True)) + return finalResults + else: + logger.log(u"Searching for stuff we need from "+show.name+" season "+str(season)) + + foundResults = {} + + didSearch = False + + for curProvider in providers.sortedProviderList(): + + if not curProvider.isActive(): + continue + + try: + curResults = curProvider.findSeasonResults(show, season) + + # make a list of all the results for this provider + for curEp in curResults: + + # skip non-tv crap + curResults[curEp] = filter(lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name, show), curResults[curEp]) + + if curEp in foundResults: + foundResults[curEp] += curResults[curEp] else: - foundResults[epNum] = [curResult] - - # 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 BTN must be a full-season torrent, creating multi-ep result for it. - logger.log(u"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!") + foundResults[curEp] = curResults[curEp] + + except exceptions.AuthException, e: + logger.log(u"Authentication error: "+ex(e), logger.ERROR) + continue + except Exception, e: + logger.log(u"Error while searching "+curProvider.name+", skipping: "+ex(e), logger.DEBUG) + logger.log(traceback.format_exc(), logger.DEBUG) + continue + + didSearch = True + + if not didSearch: + logger.log(u"No NZB/Torrent providers found or enabled in the sickbeard config. Please check your settings.", logger.ERROR) + + finalResults = [] + + anyQualities, bestQualities = Quality.splitQuality(show.quality) + + # pick the best season NZB + bestSeasonNZB = None + if SEASON_RESULT in foundResults: + bestSeasonNZB = pickBestResult(foundResults[SEASON_RESULT], anyQualities+bestQualities,episode=show.episodes[1]) + + highest_quality_overall = 0 + for cur_season in foundResults: + for cur_result in foundResults[cur_season]: + if cur_result.quality != Quality.UNKNOWN and cur_result.quality > highest_quality_overall: + highest_quality_overall = cur_result.quality + logger.log(u"The highest quality of any match is "+Quality.qualityStrings[highest_quality_overall], logger.DEBUG) + + # see if every episode is wanted + if bestSeasonNZB: + + # get the quality of the season nzb + seasonQual = Quality.nameQuality(bestSeasonNZB.name) + seasonQual = bestSeasonNZB.quality + logger.log(u"The quality of the season NZB is "+Quality.qualityStrings[seasonQual], logger.DEBUG) + + myDB = db.DBConnection() + allEps = [int(x["episode"]) for x in myDB.select("SELECT episode FROM tv_episodes WHERE showid = ? AND season = ?", [show.tvdbid, season])] + logger.log(u"Episode list: "+str(allEps), logger.DEBUG) + + allWanted = True + anyWanted = False + for curEpNum in allEps: + if not show.wantEpisode(season, curEpNum, seasonQual): + allWanted = False + else: + anyWanted = True + + # if we need every ep in the season and there's nothing better then just download this and be done with it + if allWanted and bestSeasonNZB.quality == highest_quality_overall: + logger.log(u"Every ep in this season is needed, downloading the whole NZB "+bestSeasonNZB.name) epObjs = [] for curEpNum in allEps: epObjs.append(show.getEpisode(season, curEpNum)) bestSeasonNZB.episodes = epObjs - - epNum = MULTI_EP_RESULT - if epNum in foundResults: - foundResults[epNum].append(bestSeasonNZB) - else: - foundResults[epNum] = [bestSeasonNZB] - - # go through multi-ep results and see if we really want them or not, get rid of the rest - multiResults = {} - if MULTI_EP_RESULT in foundResults: - for multiResult in foundResults[MULTI_EP_RESULT]: - - logger.log(u"Seeing if we want to bother with multi-episode result "+multiResult.name, logger.DEBUG) - - # see how many of the eps that this result covers aren't covered by single results - neededEps = [] - notNeededEps = [] - for epObj in multiResult.episodes: - epNum = epObj.episode - # if we have results for the episode - if epNum in foundResults and len(foundResults[epNum]) > 0: - # but the multi-ep is worse quality, we don't want it - # TODO: wtf is this False for - #if False and multiResult.quality <= pickBestResult(foundResults[epNum]): - # notNeededEps.append(epNum) - #else: - neededEps.append(epNum) + return [bestSeasonNZB] + + elif not anyWanted: + logger.log(u"No eps from this season are wanted at this quality, ignoring the result of "+bestSeasonNZB.name, logger.DEBUG) + + else: + + if bestSeasonNZB.provider.providerType == GenericProvider.NZB: + logger.log(u"Breaking apart the NZB and adding the individual ones to our results", logger.DEBUG) + + # if not, break it apart and add them as the lowest priority results + individualResults = nzbSplitter.splitResult(bestSeasonNZB) + + individualResults = filter(lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name, show), individualResults) + + for curResult in individualResults: + if len(curResult.episodes) == 1: + epNum = curResult.episodes[0].episode + elif len(curResult.episodes) > 1: + epNum = MULTI_EP_RESULT + + if epNum in foundResults: + foundResults[epNum].append(curResult) + else: + foundResults[epNum] = [curResult] + + # 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: - neededEps.append(epNum) - - logger.log(u"Single-ep check result is neededEps: "+str(neededEps)+", notNeededEps: "+str(notNeededEps), logger.DEBUG) - - if not neededEps: - logger.log(u"All of these episodes were covered by single nzbs, ignoring this multi-ep result", logger.DEBUG) + + # Season result from BTN must be a full-season torrent, creating multi-ep result for it. + logger.log(u"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!") + epObjs = [] + for curEpNum in allEps: + epObjs.append(show.getEpisode(season, curEpNum)) + bestSeasonNZB.episodes = epObjs + + epNum = MULTI_EP_RESULT + if epNum in foundResults: + foundResults[epNum].append(bestSeasonNZB) + else: + foundResults[epNum] = [bestSeasonNZB] + + # go through multi-ep results and see if we really want them or not, get rid of the rest + multiResults = {} + if MULTI_EP_RESULT in foundResults: + for multiResult in foundResults[MULTI_EP_RESULT]: + + logger.log(u"Seeing if we want to bother with multi-episode result "+multiResult.name, logger.DEBUG) + + # see how many of the eps that this result covers aren't covered by single results + neededEps = [] + notNeededEps = [] + for epObj in multiResult.episodes: + epNum = epObj.episode + # if we have results for the episode + if epNum in foundResults and len(foundResults[epNum]) > 0: + # but the multi-ep is worse quality, we don't want it + # TODO: wtf is this False for + #if False and multiResult.quality <= pickBestResult(foundResults[epNum]): + # notNeededEps.append(epNum) + #else: + neededEps.append(epNum) + else: + neededEps.append(epNum) + + logger.log(u"Single-ep check result is neededEps: "+str(neededEps)+", notNeededEps: "+str(notNeededEps), logger.DEBUG) + + if not neededEps: + logger.log(u"All of these episodes were covered by single nzbs, ignoring this multi-ep result", logger.DEBUG) + continue + + # check if these eps are already covered by another multi-result + multiNeededEps = [] + multiNotNeededEps = [] + for epObj in multiResult.episodes: + epNum = epObj.episode + if epNum in multiResults: + multiNotNeededEps.append(epNum) + else: + multiNeededEps.append(epNum) + + logger.log(u"Multi-ep check result is multiNeededEps: "+str(multiNeededEps)+", multiNotNeededEps: "+str(multiNotNeededEps), logger.DEBUG) + + if not multiNeededEps: + logger.log(u"All of these episodes were covered by another multi-episode nzbs, ignoring this multi-ep result", logger.DEBUG) + continue + + # if we're keeping this multi-result then remember it + for epObj in multiResult.episodes: + multiResults[epObj.episode] = multiResult + + # don't bother with the single result if we're going to get it with a multi result + for epObj in multiResult.episodes: + epNum = epObj.episode + if epNum in foundResults: + logger.log(u"A needed multi-episode result overlaps with a single-episode result for ep #"+str(epNum)+", removing the single-episode results from the list", logger.DEBUG) + del foundResults[epNum] + + finalResults += set(multiResults.values()) + + # of all the single ep results narrow it down to the best one for each episode + for curEp in foundResults: + if curEp in (MULTI_EP_RESULT, SEASON_RESULT): continue - - # check if these eps are already covered by another multi-result - multiNeededEps = [] - multiNotNeededEps = [] - for epObj in multiResult.episodes: - epNum = epObj.episode - if epNum in multiResults: - multiNotNeededEps.append(epNum) - else: - multiNeededEps.append(epNum) - - logger.log(u"Multi-ep check result is multiNeededEps: "+str(multiNeededEps)+", multiNotNeededEps: "+str(multiNotNeededEps), logger.DEBUG) - - if not multiNeededEps: - logger.log(u"All of these episodes were covered by another multi-episode nzbs, ignoring this multi-ep result", logger.DEBUG) + + if len(foundResults[curEp]) == 0: continue - - # if we're keeping this multi-result then remember it - for epObj in multiResult.episodes: - multiResults[epObj.episode] = multiResult - - # don't bother with the single result if we're going to get it with a multi result - for epObj in multiResult.episodes: - epNum = epObj.episode - if epNum in foundResults: - logger.log(u"A needed multi-episode result overlaps with a single-episode result for ep #"+str(epNum)+", removing the single-episode results from the list", logger.DEBUG) - del foundResults[epNum] - - finalResults += set(multiResults.values()) - - # of all the single ep results narrow it down to the best one for each episode - for curEp in foundResults: - if curEp in (MULTI_EP_RESULT, SEASON_RESULT): - continue - - if len(foundResults[curEp]) == 0: - continue - - finalResults.append(pickBestResult(foundResults[curEp])) - - return finalResults + print curEp + finalResults.append(pickBestResult(foundResults[curEp],None,episode=show.episodes[1])) + + return finalResults diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index deaa2cc2d..65a92316f 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -3162,6 +3162,16 @@ class Home: redirect("/home/displayShow?show=" + show) + @cherrypy.expose + def trunchistory(self, epid): + + myDB = db.DBConnection() + nbep = myDB.select("Select count(*) from episode_links where episode_id=?",[epid]) + myDB.action("DELETE from episode_links where episode_id=?",[epid]) + messnum = str(nbep[0][0]) + ' history links deleted' + ui.notifications.message('Episode History Truncated' , messnum) + return json.dumps({'result': 'ok'}) + @cherrypy.expose def searchEpisode(self, show=None, season=None, episode=None): -- GitLab