mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-05 17:43:37 +00:00
Complete re-write of backlog search.
Fixed air-by-date backlog searches.
This commit is contained in:
parent
1aff31eaec
commit
a39c881cb3
5 changed files with 83 additions and 183 deletions
|
@ -387,12 +387,8 @@ def searchProviders(queueItem, show, season, episodes, seasonSearch=False, manua
|
|||
else:
|
||||
anyWanted = True
|
||||
|
||||
# if we need every ep in the season check if single episode releases should be preferred over season releases (missing single episode releases will be picked individually from season release)
|
||||
preferSingleEpisodesOverSeasonReleases = sickbeard.PREFER_EPISODE_RELEASES
|
||||
logger.log(u"Prefer single episodes over season releases: " + str(preferSingleEpisodesOverSeasonReleases),
|
||||
logger.DEBUG)
|
||||
# if we need every ep in the season and there's nothing better then just download this and be done with it (unless single episodes are preferred)
|
||||
if allWanted and bestSeasonNZB.quality == highest_quality_overall and not preferSingleEpisodesOverSeasonReleases:
|
||||
if allWanted and bestSeasonNZB.quality == highest_quality_overall:
|
||||
logger.log(
|
||||
u"Every ep in this season is needed, downloading the whole " + bestSeasonNZB.provider.providerType + " " + bestSeasonNZB.name)
|
||||
epObjs = []
|
||||
|
|
|
@ -27,7 +27,7 @@ from sickbeard import db, scheduler
|
|||
from sickbeard import search_queue
|
||||
from sickbeard import logger
|
||||
from sickbeard import ui
|
||||
#from sickbeard.common import *
|
||||
from sickbeard import common
|
||||
|
||||
class BacklogSearchScheduler(scheduler.Scheduler):
|
||||
def forceSearch(self):
|
||||
|
@ -90,46 +90,17 @@ class BacklogSearcher:
|
|||
self.amActive = True
|
||||
self.amPaused = False
|
||||
|
||||
#myDB = db.DBConnection()
|
||||
#numSeasonResults = myDB.select("SELECT DISTINCT(season), showid FROM tv_episodes ep, tv_shows show WHERE season != 0 AND ep.showid = show.indexer_id AND show.paused = 0 AND ep.airdate > ?", [fromDate.toordinal()])
|
||||
|
||||
# get separate lists of the season/date shows
|
||||
#season_shows = [x for x in show_list if not x.air_by_date]
|
||||
air_by_date_shows = [x for x in show_list if x.air_by_date]
|
||||
|
||||
# figure out how many segments of air by date shows we're going to do
|
||||
air_by_date_segments = []
|
||||
for cur_id in [x.indexerid for x in air_by_date_shows]:
|
||||
air_by_date_segments += self._get_air_by_date_segments(cur_id, fromDate)
|
||||
|
||||
logger.log(u"Air-by-date segments: " + str(air_by_date_segments), logger.DEBUG)
|
||||
|
||||
#totalSeasons = float(len(numSeasonResults) + len(air_by_date_segments))
|
||||
#numSeasonsDone = 0.0
|
||||
|
||||
# go through non air-by-date shows and see if they need any episodes
|
||||
for curShow in show_list:
|
||||
|
||||
if curShow.paused:
|
||||
continue
|
||||
|
||||
if curShow.air_by_date:
|
||||
segments = [x[1] for x in self._get_air_by_date_segments(curShow.indexerid, fromDate)]
|
||||
else:
|
||||
segments = self._get_segments(curShow.indexerid, fromDate)
|
||||
segments = self._get_segments(curShow, fromDate)
|
||||
|
||||
for cur_segment in segments:
|
||||
|
||||
self.currentSearchInfo = {'title': curShow.name + " Season " + str(cur_segment)}
|
||||
|
||||
backlog_queue_item = search_queue.BacklogQueueItem(curShow, cur_segment)
|
||||
|
||||
if backlog_queue_item.wantedEpisodes:
|
||||
sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) #@UndefinedVariable
|
||||
else:
|
||||
logger.log(
|
||||
u"Nothing in season " + str(cur_segment) + " needs to be downloaded, skipping this season",
|
||||
logger.DEBUG)
|
||||
if len(segments):
|
||||
backlog_queue_item = search_queue.BacklogQueueItem(curShow, segments)
|
||||
sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) #@UndefinedVariable
|
||||
|
||||
# don't consider this an actual backlog search if we only did recent eps
|
||||
# or if we only did certain shows
|
||||
|
@ -158,34 +129,42 @@ class BacklogSearcher:
|
|||
self._lastBacklog = lastBacklog
|
||||
return self._lastBacklog
|
||||
|
||||
def _get_segments(self, indexer_id, fromDate):
|
||||
def _get_segments(self, show, fromDate):
|
||||
anyQualities, bestQualities = common.Quality.splitQuality(show.quality) #@UnusedVariable
|
||||
|
||||
myDB = db.DBConnection()
|
||||
sqlResults = myDB.select(
|
||||
"SELECT DISTINCT(season) as season FROM tv_episodes WHERE showid = ? AND season > 0 and airdate > ?",
|
||||
[indexer_id, fromDate.toordinal()])
|
||||
if show.air_by_date:
|
||||
sqlResults = myDB.select(
|
||||
"SELECT ep.status, ep.season, ep.episode FROM tv_episodes ep, tv_shows show WHERE season != 0 AND ep.showid = show.indexer_id AND show.paused = 0 ANd ep.airdate > ? AND ep.showid = ? AND show.air_by_date = 1",
|
||||
[fromDate.toordinal(), show.indexerid])
|
||||
else:
|
||||
sqlResults = myDB.select(
|
||||
"SELECT status, season, episode FROM tv_episodes WHERE showid = ? AND season > 0 and airdate > ?",
|
||||
[show.indexerid, fromDate.toordinal()])
|
||||
|
||||
return [int(x["season"]) for x in sqlResults]
|
||||
# check through the list of statuses to see if we want any
|
||||
wanted = {}
|
||||
for result in sqlResults:
|
||||
curCompositeStatus = int(result["status"])
|
||||
curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
|
||||
|
||||
def _get_air_by_date_segments(self, indexer_id, fromDate):
|
||||
# query the DB for all dates for this show
|
||||
myDB = db.DBConnection()
|
||||
num_air_by_date_results = myDB.select(
|
||||
"SELECT airdate, showid FROM tv_episodes ep, tv_shows show WHERE season != 0 AND ep.showid = show.indexer_id AND show.paused = 0 ANd ep.airdate > ? AND ep.showid = ? AND show.air_by_date = 1",
|
||||
[fromDate.toordinal(), indexer_id])
|
||||
if bestQualities:
|
||||
highestBestQuality = max(bestQualities)
|
||||
else:
|
||||
highestBestQuality = 0
|
||||
|
||||
# break them apart into month/year strings
|
||||
air_by_date_segments = []
|
||||
for cur_result in num_air_by_date_results:
|
||||
cur_date = datetime.date.fromordinal(int(cur_result["airdate"]))
|
||||
cur_date_str = str(cur_date)[:7]
|
||||
cur_indexer_id = int(cur_result["showid"])
|
||||
# if we need a better one then say yes
|
||||
if (curStatus in (common.DOWNLOADED, common.SNATCHED, common.SNATCHED_PROPER,
|
||||
common.SNATCHED_BEST) and curQuality < highestBestQuality) or curStatus == common.WANTED:
|
||||
|
||||
cur_result_tuple = (cur_indexer_id, cur_date_str)
|
||||
if cur_result_tuple not in air_by_date_segments:
|
||||
air_by_date_segments.append(cur_result_tuple)
|
||||
epObj = show.getEpisode(int(result["season"]), int(result["episode"]))
|
||||
|
||||
return air_by_date_segments
|
||||
if epObj.season in wanted:
|
||||
wanted[epObj.season].append(epObj)
|
||||
else:
|
||||
wanted[epObj.season] = [epObj]
|
||||
|
||||
return wanted
|
||||
|
||||
def _set_lastBacklog(self, when):
|
||||
|
||||
|
|
|
@ -42,16 +42,8 @@ class SearchQueue(generic_queue.GenericQueue):
|
|||
def is_in_queue(self, show, segment):
|
||||
queue = [x for x in self.queue.queue] + [self.currentItem]
|
||||
for cur_item in queue:
|
||||
with search_queue_lock:
|
||||
if isinstance(cur_item, BacklogQueueItem) and cur_item.show == show and cur_item.segment == segment:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_ep_in_queue(self, ep_obj):
|
||||
queue = [x for x in self.queue.queue] + [self.currentItem]
|
||||
for cur_item in queue:
|
||||
with search_queue_lock:
|
||||
if isinstance(cur_item, ManualSearchQueueItem) and cur_item.ep_obj == ep_obj:
|
||||
if cur_item:
|
||||
if cur_item.show == show and cur_item.segment == segment:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -76,9 +68,9 @@ class SearchQueue(generic_queue.GenericQueue):
|
|||
|
||||
if isinstance(item, BacklogQueueItem) and not self.is_in_queue(item.show, item.segment):
|
||||
generic_queue.GenericQueue.add_item(self, item)
|
||||
elif isinstance(item, ManualSearchQueueItem) and not self.is_ep_in_queue(item.ep_obj):
|
||||
elif isinstance(item, ManualSearchQueueItem) and not self.is_in_queue(item.show, item.segment):
|
||||
generic_queue.GenericQueue.add_item(self, item)
|
||||
elif isinstance(item, FailedQueueItem) and not self.is_in_queue(item.show, item.episodes):
|
||||
elif isinstance(item, FailedQueueItem) and not self.is_in_queue(item.show, item.segment):
|
||||
generic_queue.GenericQueue.add_item(self, item)
|
||||
else:
|
||||
logger.log(u"Not adding item, it's already in the queue", logger.DEBUG)
|
||||
|
@ -92,29 +84,29 @@ class SearchQueue(generic_queue.GenericQueue):
|
|||
generic_queue.QueueItem.finish(item)
|
||||
|
||||
class ManualSearchQueueItem(generic_queue.QueueItem):
|
||||
def __init__(self, ep_obj):
|
||||
def __init__(self, show, segment):
|
||||
generic_queue.QueueItem.__init__(self, 'Manual Search', MANUAL_SEARCH)
|
||||
self.priority = generic_queue.QueuePriorities.HIGH
|
||||
self.thread_name = 'MANUAL-' + str(ep_obj.show.indexerid) + '-'
|
||||
self.thread_name = 'MANUAL-' + str(show.indexerid) + '-'
|
||||
self.success = None
|
||||
self.show = ep_obj.show
|
||||
self.ep_obj = ep_obj
|
||||
self.show = show
|
||||
self.segment = segment
|
||||
self.results = []
|
||||
|
||||
def execute(self):
|
||||
generic_queue.QueueItem.execute(self)
|
||||
|
||||
try:
|
||||
logger.log("Beginning manual search for [" + self.ep_obj.prettyName() + "]")
|
||||
searchResult = search.searchProviders(self, self.show, self.ep_obj.season, [self.ep_obj],False,True)
|
||||
logger.log("Beginning manual search for [" + self.segment.prettyName() + "]")
|
||||
searchResult = search.searchProviders(self, self.show, self.segment.season, [self.segment],False,True)
|
||||
|
||||
if searchResult:
|
||||
SearchQueue().snatch_item(searchResult)
|
||||
else:
|
||||
ui.notifications.message('No downloads were found',
|
||||
"Couldn't find a download for <i>%s</i>" % self.ep_obj.prettyName())
|
||||
"Couldn't find a download for <i>%s</i>" % self.segment.prettyName())
|
||||
|
||||
logger.log(u"Unable to find a download for " + self.ep_obj.prettyName())
|
||||
logger.log(u"Unable to find a download for " + self.segment.prettyName())
|
||||
|
||||
except Exception:
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
|
@ -129,86 +121,41 @@ class BacklogQueueItem(generic_queue.QueueItem):
|
|||
self.success = None
|
||||
self.show = show
|
||||
self.segment = segment
|
||||
self.wantedEpisodes = []
|
||||
self.results = []
|
||||
|
||||
logger.log(u"Seeing if we need any episodes from " + self.show.name + " season " + str(self.segment))
|
||||
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# see if there is anything in this season worth searching for
|
||||
if not self.show.air_by_date:
|
||||
statusResults = myDB.select("SELECT status, episode FROM tv_episodes WHERE showid = ? AND season = ?",
|
||||
[self.show.indexerid, self.segment])
|
||||
else:
|
||||
season_year, season_month = map(int, self.segment.split('-'))
|
||||
min_date = datetime.date(season_year, season_month, 1)
|
||||
|
||||
# it's easier to just hard code this than to worry about rolling the year over or making a month length map
|
||||
if season_month == 12:
|
||||
max_date = datetime.date(season_year, 12, 31)
|
||||
else:
|
||||
max_date = datetime.date(season_year, season_month + 1, 1) - datetime.timedelta(days=1)
|
||||
|
||||
statusResults = myDB.select(
|
||||
"SELECT status, episode FROM tv_episodes WHERE showid = ? AND airdate >= ? AND airdate <= ?",
|
||||
[self.show.indexerid, min_date.toordinal(), max_date.toordinal()])
|
||||
|
||||
anyQualities, bestQualities = common.Quality.splitQuality(self.show.quality) #@UnusedVariable
|
||||
self.wantedEpisodes = self._need_any_episodes(statusResults, bestQualities)
|
||||
|
||||
def execute(self):
|
||||
generic_queue.QueueItem.execute(self)
|
||||
|
||||
# check if we want to search for season packs instead of just season/episode
|
||||
seasonSearch = False
|
||||
seasonEps = self.show.getAllEpisodes(self.segment)
|
||||
if len(seasonEps) == len(self.wantedEpisodes):
|
||||
seasonSearch = True
|
||||
for season in self.segment:
|
||||
wantedEps = self.segment[season]
|
||||
|
||||
try:
|
||||
logger.log("Beginning backlog search for episodes from [" + self.show.name + "] - Season[" + str(self.segment) + "]")
|
||||
searchResult = search.searchProviders(self, self.show, self.segment, self.wantedEpisodes, seasonSearch, False)
|
||||
# check if we want to search for season packs instead of just season/episode
|
||||
seasonSearch = False
|
||||
seasonEps = self.show.getAllEpisodes(season)
|
||||
if len(seasonEps) == len(wantedEps) and not sickbeard.PREFER_EPISODE_RELEASES:
|
||||
seasonSearch = True
|
||||
|
||||
if searchResult:
|
||||
SearchQueue().snatch_item(searchResult)
|
||||
else:
|
||||
logger.log(u"No needed episodes found during backlog search")
|
||||
try:
|
||||
logger.log("Beginning backlog search for episodes from [" + self.show.name + "] - Season[" + str(season) + "]")
|
||||
searchResult = search.searchProviders(self, self.show, season, wantedEps, seasonSearch, False)
|
||||
|
||||
except Exception:
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
if searchResult:
|
||||
SearchQueue().snatch_item(searchResult)
|
||||
else:
|
||||
logger.log(u"No needed episodes found during backlog search")
|
||||
|
||||
except Exception:
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
|
||||
self.finish()
|
||||
|
||||
def _need_any_episodes(self, statusResults, bestQualities):
|
||||
wantedEpisodes = []
|
||||
|
||||
# check through the list of statuses to see if we want any
|
||||
for curStatusResult in statusResults:
|
||||
curCompositeStatus = int(curStatusResult["status"])
|
||||
curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
|
||||
episode = int(curStatusResult["episode"])
|
||||
|
||||
if bestQualities:
|
||||
highestBestQuality = max(bestQualities)
|
||||
else:
|
||||
highestBestQuality = 0
|
||||
|
||||
# if we need a better one then say yes
|
||||
if (curStatus in (common.DOWNLOADED, common.SNATCHED, common.SNATCHED_PROPER,
|
||||
common.SNATCHED_BEST) and curQuality < highestBestQuality) or curStatus == common.WANTED:
|
||||
epObj = self.show.getEpisode(self.segment, episode)
|
||||
wantedEpisodes.append(epObj)
|
||||
|
||||
return wantedEpisodes
|
||||
|
||||
class FailedQueueItem(generic_queue.QueueItem):
|
||||
def __init__(self, show, episodes):
|
||||
def __init__(self, show, segment):
|
||||
generic_queue.QueueItem.__init__(self, 'Retry', FAILED_SEARCH)
|
||||
self.priority = generic_queue.QueuePriorities.HIGH
|
||||
self.thread_name = 'RETRY-' + str(show.indexerid) + '-'
|
||||
self.show = show
|
||||
self.episodes = episodes
|
||||
self.segment = segment
|
||||
self.success = None
|
||||
self.results = []
|
||||
|
||||
|
@ -216,7 +163,9 @@ class FailedQueueItem(generic_queue.QueueItem):
|
|||
generic_queue.QueueItem.execute(self)
|
||||
|
||||
failed_episodes = []
|
||||
for i, epObj in enumerate(self.episodes):
|
||||
for season in self.segment:
|
||||
epObj = self.segment[season]
|
||||
|
||||
(release, provider) = failed_history.findRelease(epObj)
|
||||
if release:
|
||||
logger.log(u"Marking release as bad: " + release)
|
||||
|
@ -226,11 +175,11 @@ class FailedQueueItem(generic_queue.QueueItem):
|
|||
failed_history.revertEpisode(epObj)
|
||||
failed_episodes.append(epObj)
|
||||
|
||||
logger.log(
|
||||
"Beginning failed download search for [" + epObj.prettyName() + "]")
|
||||
|
||||
if len(failed_episodes):
|
||||
try:
|
||||
logger.log(
|
||||
"Beginning failed download search for episodes from Season [" + str(self.episodes[0].season) + "]")
|
||||
|
||||
searchResult = search.searchProviders(self, self.show, failed_episodes[0].season, failed_episodes, False, True)
|
||||
|
||||
if searchResult:
|
||||
|
|
|
@ -140,20 +140,7 @@ class TVShow(object):
|
|||
sql_selection = sql_selection + " FROM tv_episodes tve WHERE showid = " + str(self.indexerid)
|
||||
|
||||
if season is not None:
|
||||
if not self.air_by_date:
|
||||
sql_selection = sql_selection + " AND season = " + str(season)
|
||||
else:
|
||||
segment_year, segment_month = map(int, str(season).split('-'))
|
||||
min_date = datetime.date(segment_year, segment_month, 1)
|
||||
|
||||
# it's easier to just hard code this than to worry about rolling the year over or making a month length map
|
||||
if segment_month == 12:
|
||||
max_date = datetime.date(segment_year, 12, 31)
|
||||
else:
|
||||
max_date = datetime.date(segment_year, segment_month + 1, 1) - datetime.timedelta(days=1)
|
||||
|
||||
sql_selection = sql_selection + " AND airdate >= " + str(
|
||||
min_date.toordinal()) + " AND airdate <= " + str(max_date.toordinal())
|
||||
sql_selection = sql_selection + " AND season = " + str(season)
|
||||
|
||||
if has_location:
|
||||
sql_selection = sql_selection + " AND location != '' "
|
||||
|
|
|
@ -3227,8 +3227,7 @@ class Home:
|
|||
else:
|
||||
return _genericMessage("Error", errMsg)
|
||||
|
||||
wanted_segments = []
|
||||
failed_segments = {}
|
||||
segments = {}
|
||||
|
||||
if eps is not None:
|
||||
|
||||
|
@ -3244,22 +3243,12 @@ class Home:
|
|||
if epObj is None:
|
||||
return _genericMessage("Error", "Episode couldn't be retrieved")
|
||||
|
||||
if int(status) == WANTED:
|
||||
if int(status) in [WANTED, FAILED]:
|
||||
# figure out what episodes are wanted so we can backlog them
|
||||
if epObj.show.air_by_date or epObj.show.sports:
|
||||
segment = str(epObj.airdate)[:7]
|
||||
if epObj in segments:
|
||||
segments[epObj.season].append(epObj)
|
||||
else:
|
||||
segment = epObj.season
|
||||
|
||||
if segment not in wanted_segments:
|
||||
wanted_segments.append(segment)
|
||||
|
||||
elif int(status) == FAILED:
|
||||
# figure out what episodes failed so we can retry them
|
||||
if epObj.season not in failed_segments:
|
||||
failed_segments[epObj.season] = []
|
||||
if epObj.episode not in failed_segments[epObj.season]:
|
||||
failed_segments[epObj.season].append(epObj.episode)
|
||||
segments[epObj.season] = [epObj]
|
||||
|
||||
with epObj.lock:
|
||||
# don't let them mess up UNAIRED episodes
|
||||
|
@ -3294,7 +3283,7 @@ class Home:
|
|||
|
||||
if int(status) == WANTED:
|
||||
msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />"
|
||||
for cur_segment in wanted_segments:
|
||||
for cur_segment in segments:
|
||||
msg += "<li>Season " + str(cur_segment) + "</li>"
|
||||
logger.log(u"Sending backlog for " + showObj.name + " season " + str(
|
||||
cur_segment) + " because some eps were set to wanted")
|
||||
|
@ -3302,12 +3291,12 @@ class Home:
|
|||
sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) # @UndefinedVariable
|
||||
msg += "</ul>"
|
||||
|
||||
if wanted_segments:
|
||||
if segments:
|
||||
ui.notifications.message("Backlog started", msg)
|
||||
|
||||
if int(status) == FAILED:
|
||||
msg = "Retrying Search was automatically started for the following season of <b>" + showObj.name + "</b>:<br />"
|
||||
for cur_segment in failed_segments:
|
||||
for cur_segment in segments:
|
||||
msg += "<li>Season " + str(cur_segment) + "</li>"
|
||||
logger.log(u"Retrying Search for " + showObj.name + " season " + str(
|
||||
cur_segment) + " because some eps were set to failed")
|
||||
|
@ -3315,7 +3304,7 @@ class Home:
|
|||
sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item) # @UndefinedVariable
|
||||
msg += "</ul>"
|
||||
|
||||
if failed_segments:
|
||||
if segments:
|
||||
ui.notifications.message("Retry Search started", msg)
|
||||
|
||||
if direct:
|
||||
|
@ -3427,7 +3416,7 @@ class Home:
|
|||
return json.dumps({'result': 'failure'})
|
||||
|
||||
# make a queue item for it and put it on the queue
|
||||
ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj)
|
||||
ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj.show, ep_obj)
|
||||
sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable
|
||||
|
||||
# wait until the queue item tells us whether it worked or not
|
||||
|
|
Loading…
Reference in a new issue