Add option "Search for unaired episodes" to config/Search Settings

* Change reduce time to search recent result list by searching only once for a best result
* Fix replacing episodes that have a lower quality than what is selected in the initial and archive quality list
* Fix to include episodes marked Failed in the recent and backlog search processes
* Fix display of search status for an alternative release after episode is manually set to "Failed" on the Display Show page
* Change handle more varieties of media quality
* Change to prevent another scheduled search when one of the same type is already running
This commit is contained in:
Prinz23 2015-04-05 20:12:15 +02:00
parent 7cdcbaf241
commit 4ae30ab6ad
11 changed files with 146 additions and 108 deletions

View file

@ -1,9 +1,10 @@
### 0.x.x (2015-xx-xx xx:xx:xx UTC) ### 0.x.x (2015-xx-xx xx:xx:xx UTC)
* Add ToTV provider * Add ToTV provider
* Fix Backlog scheduler initialization and change backlog frequency from minutes to days * Fix Backlog scheduler initialization and change backlog frequency from minutes to days
* Change to consolidate and tidy some provider code * Change to consolidate and tidy some provider code
* Fix restore table row colours on the Manage/Episode Status Management page * Fix restore table row colours on the Manage/Episode Status Management page
* Add option "Search for unaired episodes" to config/Search Settings
### 0.8.2 (2015-04-19 06:45:00 UTC) ### 0.8.2 (2015-04-19 06:45:00 UTC)

View file

@ -100,6 +100,15 @@
</label> </label>
</div> </div>
<div class="field-pair">
<label for="search_unaired">
<span class="component-title">Search for unaired episodes</span>
<span class="component-desc">
<input type="checkbox" name="search_unaired" id="search_unaired" class="enabler"<%= html_checked if sickbeard.SEARCH_UNAIRED == True else '' %>>
</span>
</label>
</div>
<div class="field-pair"> <div class="field-pair">
<label> <label>
<span class="component-title">Usenet retention</span> <span class="component-title">Usenet retention</span>

View file

@ -31,6 +31,7 @@ import sys
import os.path import os.path
import uuid import uuid
import base64 import base64
import sickbeard
sys.path.append(os.path.abspath('../lib')) sys.path.append(os.path.abspath('../lib'))
from sickbeard import providers, metadata, config, webserveInit from sickbeard import providers, metadata, config, webserveInit
from sickbeard.providers.generic import GenericProvider from sickbeard.providers.generic import GenericProvider
@ -225,6 +226,7 @@ MAX_BACKLOG_FREQUENCY = 35
MIN_UPDATE_FREQUENCY = 1 MIN_UPDATE_FREQUENCY = 1
BACKLOG_DAYS = 7 BACKLOG_DAYS = 7
SEARCH_UNAIRED = False
ADD_SHOWS_WO_DIR = False ADD_SHOWS_WO_DIR = False
CREATE_MISSING_SHOW_DIRS = False CREATE_MISSING_SHOW_DIRS = False
@ -528,7 +530,7 @@ def initialize(consoleLogging=True):
USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, PROXY_INDEXERS, \ USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, PROXY_INDEXERS, \
AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \ AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \
ANIME_DEFAULT, NAMING_ANIME, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \ ANIME_DEFAULT, NAMING_ANIME, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \
ANIME_SPLIT_HOME, SCENE_DEFAULT, BACKLOG_DAYS, ANIME_TREAT_AS_HDTV, \ ANIME_SPLIT_HOME, SCENE_DEFAULT, BACKLOG_DAYS, SEARCH_UNAIRED, ANIME_TREAT_AS_HDTV, \
COOKIE_SECRET, USE_IMDB_INFO, DISPLAY_BACKGROUND, DISPLAY_BACKGROUND_TRANSPARENT, DISPLAY_ALL_SEASONS COOKIE_SECRET, USE_IMDB_INFO, DISPLAY_BACKGROUND, DISPLAY_BACKGROUND_TRANSPARENT, DISPLAY_ALL_SEASONS
if __INITIALIZED__: if __INITIALIZED__:
@ -736,6 +738,7 @@ def initialize(consoleLogging=True):
UPDATE_FREQUENCY = MIN_UPDATE_FREQUENCY UPDATE_FREQUENCY = MIN_UPDATE_FREQUENCY
BACKLOG_DAYS = check_setting_int(CFG, 'General', 'backlog_days', 7) BACKLOG_DAYS = check_setting_int(CFG, 'General', 'backlog_days', 7)
SEARCH_UNAIRED = bool(check_setting_int(CFG, 'General', 'search_unaired', 0))
NZB_DIR = check_setting_str(CFG, 'Blackhole', 'nzb_dir', '') NZB_DIR = check_setting_str(CFG, 'Blackhole', 'nzb_dir', '')
TORRENT_DIR = check_setting_str(CFG, 'Blackhole', 'torrent_dir', '') TORRENT_DIR = check_setting_str(CFG, 'Blackhole', 'torrent_dir', '')
@ -1170,13 +1173,15 @@ def initialize(consoleLogging=True):
cycleTime=update_interval, cycleTime=update_interval,
threadName="RECENTSEARCHER", threadName="RECENTSEARCHER",
run_delay=update_now if RECENTSEARCH_STARTUP run_delay=update_now if RECENTSEARCH_STARTUP
else datetime.timedelta(minutes=5)) else datetime.timedelta(minutes=5),
prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_recentsearch_in_progress)
backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(), backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(),
cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()), cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()),
threadName="BACKLOG", threadName="BACKLOG",
run_delay=update_now if BACKLOG_STARTUP run_delay=update_now if BACKLOG_STARTUP
else datetime.timedelta(minutes=10)) else datetime.timedelta(minutes=10),
prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_standard_backlog_in_progress)
search_intervals = {'15m': 15, '45m': 45, '90m': 90, '4h': 4 * 60, 'daily': 24 * 60} search_intervals = {'15m': 15, '45m': 45, '90m': 90, '4h': 4 * 60, 'daily': 24 * 60}
if CHECK_PROPERS_INTERVAL in search_intervals: if CHECK_PROPERS_INTERVAL in search_intervals:
@ -1495,6 +1500,7 @@ def save_config():
new_config['General']['metadata_kodi'] = METADATA_KODI new_config['General']['metadata_kodi'] = METADATA_KODI
new_config['General']['backlog_days'] = int(BACKLOG_DAYS) new_config['General']['backlog_days'] = int(BACKLOG_DAYS)
new_config['General']['search_unaired'] = int(SEARCH_UNAIRED)
new_config['General']['cache_dir'] = ACTUAL_CACHE_DIR if ACTUAL_CACHE_DIR else 'cache' new_config['General']['cache_dir'] = ACTUAL_CACHE_DIR if ACTUAL_CACHE_DIR else 'cache'
new_config['General']['root_dirs'] = ROOT_DIRS if ROOT_DIRS else '' new_config['General']['root_dirs'] = ROOT_DIRS if ROOT_DIRS else ''

View file

@ -206,26 +206,26 @@ class Quality:
return Quality.UNKNOWN return Quality.UNKNOWN
if checkName(['(pdtv|hdtv|dsr|tvrip).(xvid|x264|h.?264)'], all) and not checkName(['(720|1080)[pi]'], all) \ if checkName(['(pdtv|hdtv|dsr|tvrip).(xvid|x264|h.?264)'], all) and not checkName(['(720|1080)[pi]'], all) \
and not checkName(['hr.ws.pdtv.x264'], any): and not checkName(['hr.ws.pdtv.(x264|h.?264)'], any):
return Quality.SDTV return Quality.SDTV
elif checkName(['web.dl|webrip', 'xvid|x264|h.?264'], all) and not checkName(['(720|1080)[pi]'], all): elif checkName(['web.?dl|web.?rip', 'xvid|x264|h.?264'], all) and not checkName(['(720|1080)[pi]'], all):
return Quality.SDTV return Quality.SDTV
elif checkName(['(dvdrip|b[r|d]rip)(.ws)?.(xvid|divx|x264)'], any) and not checkName(['(720|1080)[pi]'], all): elif checkName(['(dvd.?rip|b[r|d]rip)(.ws)?(.(xvid|divx|x264|h.?264))?'], any) and not checkName(['(720|1080)[pi]'], all):
return Quality.SDDVD return Quality.SDDVD
elif checkName(['720p', 'hdtv', 'x264'], all) or checkName(['hr.ws.pdtv.x264'], any) \ elif checkName(['720p', 'hdtv', 'x264|h.?264'], all) or checkName(['hr.ws.pdtv.(x264|h.?264)'], any) \
and not checkName(['(1080)[pi]'], all): and not checkName(['(1080)[pi]'], all):
return Quality.HDTV return Quality.HDTV
elif checkName(['720p|1080i', 'hdtv', 'mpeg-?2'], all) or checkName(['1080[pi].hdtv', 'h.?264'], all): elif checkName(['720p|1080i', 'hdtv', 'mpeg-?2'], all) or checkName(['1080[pi].hdtv', 'h.?264'], all):
return Quality.RAWHDTV return Quality.RAWHDTV
elif checkName(['1080p', 'hdtv', 'x264'], all): elif checkName(['1080p', 'hdtv', 'x264'], all):
return Quality.FULLHDTV return Quality.FULLHDTV
elif checkName(['720p', 'web.dl|webrip'], all) or checkName(['720p', 'itunes', 'h.?264'], all): elif checkName(['720p', 'web.?dl|web.?rip'], all) or checkName(['720p', 'itunes', 'x264|h.?264'], all):
return Quality.HDWEBDL return Quality.HDWEBDL
elif checkName(['1080p', 'web.dl|webrip'], all) or checkName(['1080p', 'itunes', 'h.?264'], all): elif checkName(['1080p', 'web.?dl|web.?rip'], all) or checkName(['1080p', 'itunes', 'x264|h.?264'], all):
return Quality.FULLHDWEBDL return Quality.FULLHDWEBDL
elif checkName(['720p', 'bluray|hddvd|b[r|d]rip', 'x264'], all): elif checkName(['720p', 'blu.?ray|hddvd|b[r|d]rip', 'x264|h.?264'], all):
return Quality.HDBLURAY return Quality.HDBLURAY
elif checkName(['1080p', 'bluray|hddvd|b[r|d]rip', 'x264'], all): elif checkName(['1080p', 'blu.?ray|hddvd|b[r|d]rip', 'x264|h.?264'], all):
return Quality.FULLHDBLURAY return Quality.FULLHDBLURAY
else: else:
return Quality.UNKNOWN return Quality.UNKNOWN

View file

@ -27,13 +27,14 @@ from sickbeard.exceptions import ex
class Scheduler(threading.Thread): class Scheduler(threading.Thread):
def __init__(self, action, cycleTime=datetime.timedelta(minutes=10), run_delay=datetime.timedelta(minutes=0), def __init__(self, action, cycleTime=datetime.timedelta(minutes=10), run_delay=datetime.timedelta(minutes=0),
start_time=None, threadName="ScheduledThread", silent=True): start_time=None, threadName="ScheduledThread", silent=True, prevent_cycle_run=None):
super(Scheduler, self).__init__() super(Scheduler, self).__init__()
self.lastRun = datetime.datetime.now() + run_delay - cycleTime self.lastRun = datetime.datetime.now() + run_delay - cycleTime
self.action = action self.action = action
self.cycleTime = cycleTime self.cycleTime = cycleTime
self.start_time = start_time self.start_time = start_time
self.prevent_cycle_run = prevent_cycle_run
self.name = threadName self.name = threadName
self.silent = silent self.silent = silent
@ -70,6 +71,12 @@ class Scheduler(threading.Thread):
else: else:
should_run = True should_run = True
if should_run and self.prevent_cycle_run is not None and self.prevent_cycle_run():
logger.log(u'%s skipping this cycleTime' % self.name, logger.WARNING)
# set lastRun to only check start_time after another cycleTime
self.lastRun = current_time
should_run = False
if should_run: if should_run:
self.lastRun = current_time self.lastRun = current_time

View file

@ -317,49 +317,78 @@ def isFirstBestMatch(result):
return False return False
def wantedEpisodes(show, fromDate): def wantedEpisodes(show, fromDate, make_dict=False):
anyQualities, bestQualities = common.Quality.splitQuality(show.quality) # @UnusedVariable initialQualities, archiveQualities = common.Quality.splitQuality(show.quality)
allQualities = list(set(anyQualities + bestQualities)) allQualities = list(set(initialQualities + archiveQualities))
myDB = db.DBConnection() myDB = db.DBConnection()
if show.air_by_date: if show.air_by_date:
sqlResults = myDB.select( sqlString = 'SELECT ep.status, ep.season, ep.episode, ep.airdate FROM [tv_episodes] AS ep, [tv_shows] AS show WHERE season != 0 AND ep.showid = show.indexer_id AND show.paused = 0 AND ep.showid = ? AND show.air_by_date = 1'
"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: else:
sqlResults = myDB.select( sqlString = 'SELECT status, season, episode, airdate FROM [tv_episodes] WHERE showid = ? AND season > 0'
"SELECT status, season, episode FROM tv_episodes WHERE showid = ? AND season > 0 and airdate > ?",
[show.indexerid, fromDate.toordinal()]) if sickbeard.SEARCH_UNAIRED:
statusList = [common.WANTED, common.FAILED, common.UNAIRED]
sqlString += ' AND ( airdate > ? OR airdate = 1 )'
else:
statusList = [common.WANTED, common.FAILED]
sqlString += ' AND airdate > ?'
sqlResults = myDB.select(sqlString, [show.indexerid, fromDate.toordinal()])
# check through the list of statuses to see if we want any # check through the list of statuses to see if we want any
wanted = [] if make_dict:
total_wanted = total_replacing = 0 wanted = {}
else:
wanted = []
total_wanted = total_replacing = total_unaired = 0
downloadedStatusList = (common.DOWNLOADED, common.SNATCHED, common.SNATCHED_PROPER, common.SNATCHED_BEST)
for result in sqlResults: for result in sqlResults:
not_downloaded = True
curCompositeStatus = int(result["status"]) curCompositeStatus = int(result["status"])
curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus) curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
if bestQualities: if show.archive_firstmatch and curStatus in downloadedStatusList and curQuality in archiveQualities:
highestBestQuality = max(allQualities) continue
# special case: already downloaded quality is not in any of the wanted Qualities
other_quality_downloaded = False
if curStatus in downloadedStatusList and curQuality not in allQualities:
other_quality_downloaded = True
wantedQualities = allQualities
else: else:
highestBestQuality = 0 wantedQualities = archiveQualities
if archiveQualities:
highestWantedQuality = max(wantedQualities)
else:
if other_quality_downloaded:
highestWantedQuality = max(initialQualities)
else:
highestWantedQuality = 0
# if we need a better one then say yes # if we need a better one then say yes
if (curStatus in (common.DOWNLOADED, common.SNATCHED, common.SNATCHED_PROPER, if (curStatus in downloadedStatusList and curQuality < highestWantedQuality) or curStatus in statusList or (sickbeard.SEARCH_UNAIRED and result['airdate'] == 1 and curStatus in (common.SKIPPED, common.IGNORED, common.UNAIRED, common.UNKNOWN, common.FAILED)):
common.SNATCHED_BEST) and curQuality < highestBestQuality) or curStatus == common.WANTED:
if curStatus == common.WANTED: if curStatus in (common.WANTED, common.FAILED):
total_wanted += 1 total_wanted += 1
elif curStatus in (common.UNAIRED, common.SKIPPED, common.IGNORED, common.UNKNOWN):
total_unaired += 1
else: else:
total_replacing += 1 total_replacing += 1
not_downloaded = False
epObj = show.getEpisode(int(result["season"]), int(result["episode"])) epObj = show.getEpisode(int(result["season"]), int(result["episode"]))
epObj.wantedQuality = [i for i in allQualities if (i > curQuality and i != common.Quality.UNKNOWN)] if make_dict:
wanted.append(epObj) wanted.setdefault(epObj.season, []).append(epObj)
else:
epObj.wantedQuality = [i for i in (initialQualities if not_downloaded else wantedQualities) if (i > curQuality and i != common.Quality.UNKNOWN)]
wanted.append(epObj)
if 0 < total_wanted + total_replacing: if 0 < total_wanted + total_replacing + total_unaired:
actions = [] actions = []
for msg, total in ['%d episode%s', total_wanted], ['to upgrade %d episode%s', total_replacing]: for msg, total in ['%d episode%s', total_wanted], ['to upgrade %d episode%s', total_replacing], ['%d unaired episode%s', total_unaired]:
if 0 < total: if 0 < total:
actions.append(msg % (total, helpers.maybe_plural(total))) actions.append(msg % (total, helpers.maybe_plural(total)))
logger.log(u'We want %s for %s' % (' and '.join(actions), show.name)) logger.log(u'We want %s for %s' % (' and '.join(actions), show.name))
@ -393,11 +422,6 @@ def searchForNeededEpisodes(episodes):
continue continue
# find the best result for the current episode # find the best result for the current episode
bestResult = None
for curResult in curFoundResults[curEp]:
if not bestResult or bestResult.quality < curResult.quality:
bestResult = curResult
bestResult = pickBestResult(curFoundResults[curEp], curEp.show) bestResult = pickBestResult(curFoundResults[curEp], curEp.show)
# if all results were rejected move on to the next episode # if all results were rejected move on to the next episode

View file

@ -28,6 +28,7 @@ from sickbeard import search_queue
from sickbeard import logger from sickbeard import logger
from sickbeard import ui from sickbeard import ui
from sickbeard import common from sickbeard import common
from sickbeard.search import wantedEpisodes
class BacklogSearchScheduler(scheduler.Scheduler): class BacklogSearchScheduler(scheduler.Scheduler):
@ -78,8 +79,10 @@ class BacklogSearcher:
if which_shows: if which_shows:
show_list = which_shows show_list = which_shows
standard_backlog = False
else: else:
show_list = sickbeard.showList show_list = sickbeard.showList
standard_backlog = True
self._get_lastBacklog() self._get_lastBacklog()
@ -99,12 +102,12 @@ class BacklogSearcher:
if curShow.paused: if curShow.paused:
continue continue
segments = self._get_segments(curShow, fromDate) segments = wantedEpisodes(curShow, fromDate, make_dict=True)
for season, segment in segments.items(): for season, segment in segments.items():
self.currentSearchInfo = {'title': curShow.name + " Season " + str(season)} self.currentSearchInfo = {'title': curShow.name + " Season " + str(season)}
backlog_queue_item = search_queue.BacklogQueueItem(curShow, segment) backlog_queue_item = search_queue.BacklogQueueItem(curShow, segment, standard_backlog=standard_backlog)
sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) # @UndefinedVariable sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) # @UndefinedVariable
else: else:
logger.log(u'Nothing needs to be downloaded for %s, skipping' % str(curShow.name), logger.DEBUG) logger.log(u'Nothing needs to be downloaded for %s, skipping' % str(curShow.name), logger.DEBUG)
@ -136,55 +139,6 @@ class BacklogSearcher:
self._lastBacklog = lastBacklog self._lastBacklog = lastBacklog
return self._lastBacklog return self._lastBacklog
def _get_segments(self, show, fromDate):
anyQualities, bestQualities = common.Quality.splitQuality(show.quality) # @UnusedVariable
myDB = db.DBConnection()
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()])
# check through the list of statuses to see if we want any
wanted = {}
total_wanted = total_replacing = 0
for result in sqlResults:
curCompositeStatus = int(result["status"])
curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
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:
if curStatus == common.WANTED:
total_wanted += 1
else:
total_replacing += 1
epObj = show.getEpisode(int(result["season"]), int(result["episode"]))
if epObj.season not in wanted:
wanted[epObj.season] = [epObj]
else:
wanted[epObj.season].append(epObj)
if 0 < total_wanted + total_replacing:
actions = []
for msg, total in ['%d episode%s', total_wanted], ['to upgrade %d episode%s', total_replacing]:
if 0 < total:
actions.append(msg % (total, helpers.maybe_plural(total)))
logger.log(u'We want %s for %s' % (' and '.join(actions), show.name))
return wanted
def _set_lastBacklog(self, when): def _set_lastBacklog(self, when):
logger.log(u"Setting the last backlog in the DB to " + str(when), logger.DEBUG) logger.log(u"Setting the last backlog in the DB to " + str(when), logger.DEBUG)

View file

@ -94,6 +94,12 @@ class SearchQueue(generic_queue.GenericQueue):
return True return True
return False return False
def is_standard_backlog_in_progress(self):
for cur_item in self.queue + [self.currentItem]:
if isinstance(cur_item, BacklogQueueItem) and cur_item.standard_backlog:
return True
return False
def is_recentsearch_in_progress(self): def is_recentsearch_in_progress(self):
for cur_item in self.queue + [self.currentItem]: for cur_item in self.queue + [self.currentItem]:
if isinstance(cur_item, RecentSearchQueueItem): if isinstance(cur_item, RecentSearchQueueItem):
@ -308,13 +314,14 @@ class ManualSearchQueueItem(generic_queue.QueueItem):
class BacklogQueueItem(generic_queue.QueueItem): class BacklogQueueItem(generic_queue.QueueItem):
def __init__(self, show, segment): def __init__(self, show, segment, standard_backlog=False):
generic_queue.QueueItem.__init__(self, 'Backlog', BACKLOG_SEARCH) generic_queue.QueueItem.__init__(self, 'Backlog', BACKLOG_SEARCH)
self.priority = generic_queue.QueuePriorities.LOW self.priority = generic_queue.QueuePriorities.LOW
self.name = 'BACKLOG-' + str(show.indexerid) self.name = 'BACKLOG-' + str(show.indexerid)
self.success = None self.success = None
self.show = show self.show = show
self.segment = segment self.segment = segment
self.standard_backlog = standard_backlog
def run(self): def run(self):
generic_queue.QueueItem.run(self) generic_queue.QueueItem.run(self)

View file

@ -1243,11 +1243,12 @@ class TVShow(object):
Quality.qualityStrings[quality], logger.DEBUG) Quality.qualityStrings[quality], logger.DEBUG)
# if the quality isn't one we want under any circumstances then just say no # if the quality isn't one we want under any circumstances then just say no
anyQualities, bestQualities = Quality.splitQuality(self.quality) initialQualities, archiveQualities = Quality.splitQuality(self.quality)
logger.log(u"any,best = " + str(anyQualities) + " " + str(bestQualities) + " and found " + str(quality), allQualities = list(set(initialQualities + archiveQualities))
logger.log(u"initial + archive = (" + ",".join([Quality.qualityStrings[qual] for qual in initialQualities]) + ") + (" + ",".join([Quality.qualityStrings[qual] for qual in archiveQualities]) + ") and found " + Quality.qualityStrings[quality],
logger.DEBUG) logger.DEBUG)
if quality not in anyQualities + bestQualities: if quality not in allQualities:
logger.log(u"Don't want this quality, ignoring found episode", logger.DEBUG) logger.log(u"Don't want this quality, ignoring found episode", logger.DEBUG)
return False return False
@ -1270,9 +1271,9 @@ class TVShow(object):
return False return False
# if it's one of these then we want it as long as it's in our allowed initial qualities # if it's one of these then we want it as long as it's in our allowed initial qualities
if quality in anyQualities + bestQualities: if quality in allQualities:
if epStatus in (WANTED, UNAIRED, SKIPPED): if epStatus in (WANTED, UNAIRED, SKIPPED, FAILED):
logger.log(u"Existing episode status is wanted/unaired/skipped, getting found episode", logger.DEBUG) logger.log(u"Existing episode status is wanted/unaired/skipped/failed, getting found episode", logger.DEBUG)
return True return True
elif manualSearch: elif manualSearch:
logger.log( logger.log(
@ -1284,9 +1285,15 @@ class TVShow(object):
logger.DEBUG) logger.DEBUG)
curStatus, curQuality = Quality.splitCompositeStatus(epStatus) curStatus, curQuality = Quality.splitCompositeStatus(epStatus)
downloadedStatusList = (DOWNLOADED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST)
# special case: already downloaded quality is not in any of the wanted Qualities
if curStatus in downloadedStatusList and curQuality not in allQualities:
wantedQualities = allQualities
else:
wantedQualities = archiveQualities
# if we are re-downloading then we only want it if it's in our bestQualities list and better than what we have # if we are re-downloading then we only want it if it's in our archiveQualities list and better than what we have
if curStatus in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.SNATCHED_BEST and quality in bestQualities and quality > curQuality: if curStatus in downloadedStatusList and quality in wantedQualities and quality > curQuality:
logger.log(u"Episode already exists but the found episode has better quality, getting found episode", logger.log(u"Episode already exists but the found episode has better quality, getting found episode",
logger.DEBUG) logger.DEBUG)
return True return True
@ -1767,7 +1774,7 @@ class TVEpisode(object):
# if it hasn't aired yet set the status to UNAIRED # if it hasn't aired yet set the status to UNAIRED
if self.airdate >= datetime.date.today() and self.status in [SKIPPED, UNAIRED, UNKNOWN, WANTED]: if self.airdate >= datetime.date.today() and self.status in [SKIPPED, UNAIRED, UNKNOWN, WANTED]:
logger.log(u"Episode airs in the future, marking it " + str(UNAIRED), logger.DEBUG) logger.log(u"Episode airs in the future, marking it " + statusStrings[UNAIRED], logger.DEBUG)
self.status = UNAIRED self.status = UNAIRED
# if there's no airdate then set it to skipped (and respect ignored) # if there's no airdate then set it to skipped (and respect ignored)

View file

@ -1614,7 +1614,7 @@ class Home(MainHandler):
msg += '<ul>' msg += '<ul>'
for season, segment in segments.items(): for season, segment in segments.items():
cur_failed_queue_item = search_queue.FailedQueueItem(showObj, [segment]) cur_failed_queue_item = search_queue.FailedQueueItem(showObj, segment)
sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item) # @UndefinedVariable sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item) # @UndefinedVariable
msg += '<li>Season ' + str(season) + '</li>' msg += '<li>Season ' + str(season) + '</li>'
@ -1789,12 +1789,21 @@ class Home(MainHandler):
searchstatus = 'finished' searchstatus = 'finished'
else: else:
searchstatus = 'searching' searchstatus = 'searching'
episodes.append({'episode': searchThread.segment.episode, if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem):
'episodeindexid': searchThread.segment.indexerid, episodes.append({'episode': searchThread.segment.episode,
'season' : searchThread.segment.season, 'episodeindexid': searchThread.segment.indexerid,
'searchstatus' : searchstatus, 'season' : searchThread.segment.season,
'status' : statusStrings[searchThread.segment.status], 'searchstatus' : searchstatus,
'quality': self.getQualityClass(searchThread.segment)}) 'status' : statusStrings[searchThread.segment.status],
'quality': self.getQualityClass(searchThread.segment)})
else:
for epObj in searchThread.segment:
episodes.append({'episode': epObj.episode,
'episodeindexid': epObj.indexerid,
'season' : epObj.season,
'searchstatus' : searchstatus,
'status' : statusStrings[epObj.status],
'quality': self.getQualityClass(epObj)})
if finishedManualSearchThreadItems: if finishedManualSearchThreadItems:
for searchThread in finishedManualSearchThreadItems: for searchThread in finishedManualSearchThreadItems:
@ -3554,7 +3563,7 @@ class ConfigSearch(Config):
def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None, def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None,
sab_apikey=None, sab_category=None, sab_host=None, nzbget_username=None, nzbget_password=None, sab_apikey=None, sab_category=None, sab_host=None, nzbget_username=None, nzbget_password=None,
nzbget_category=None, nzbget_priority=None, nzbget_host=None, nzbget_use_https=None, nzbget_category=None, nzbget_priority=None, nzbget_host=None, nzbget_use_https=None,
backlog_days=None, backlog_frequency=None, recentsearch_frequency=None, backlog_days=None, backlog_frequency=None, search_unaired=None, recentsearch_frequency=None,
nzb_method=None, torrent_method=None, usenet_retention=None, nzb_method=None, torrent_method=None, usenet_retention=None,
download_propers=None, check_propers_interval=None, allow_high_priority=None, download_propers=None, check_propers_interval=None, allow_high_priority=None,
torrent_dir=None, torrent_username=None, torrent_password=None, torrent_host=None, torrent_dir=None, torrent_username=None, torrent_password=None, torrent_host=None,
@ -3587,6 +3596,8 @@ class ConfigSearch(Config):
sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers) sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers)
sickbeard.CHECK_PROPERS_INTERVAL = check_propers_interval sickbeard.CHECK_PROPERS_INTERVAL = check_propers_interval
sickbeard.SEARCH_UNAIRED = config.checkbox_to_value(search_unaired)
sickbeard.ALLOW_HIGH_PRIORITY = config.checkbox_to_value(allow_high_priority) sickbeard.ALLOW_HIGH_PRIORITY = config.checkbox_to_value(allow_high_priority)
sickbeard.SAB_USERNAME = sab_username sickbeard.SAB_USERNAME = sab_username

View file

@ -22,6 +22,7 @@ class QualityTests(unittest.TestCase):
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.TVRip.x264-GROUP")) self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.TVRip.x264-GROUP"))
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEBRip.XViD-GROUP")) self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEBRip.XViD-GROUP"))
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEBRip.x264-GROUP")) self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEBRip.x264-GROUP"))
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.Web-Rip.x264.GROUP"))
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEB-DL.x264-GROUP")) self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEB-DL.x264-GROUP"))
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEB-DL.AAC2.0.H.264-GROUP")) self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02.WEB-DL.AAC2.0.H.264-GROUP"))
self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02 WEB-DL H 264-GROUP")) self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test.Show.S01E02 WEB-DL H 264-GROUP"))
@ -35,6 +36,7 @@ class QualityTests(unittest.TestCase):
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.DVDRip.WS.XViD-GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.DVDRip.WS.XViD-GROUP"))
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.DVDRip.WS.DiVX-GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.DVDRip.WS.DiVX-GROUP"))
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.DVDRip.WS.x264-GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.DVDRip.WS.x264-GROUP"))
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show-S01E02-Test.Dvd Rip"))
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.BDRIP.XViD-GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.BDRIP.XViD-GROUP"))
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.BDRIP.DiVX-GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.BDRIP.DiVX-GROUP"))
self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.BDRIP.x264-GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test.Show.S01E02.BDRIP.x264-GROUP"))
@ -65,6 +67,10 @@ class QualityTests(unittest.TestCase):
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test_Show.S01E02_720p_WEB-DL_AAC2.0_H264-GROUP")) self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test_Show.S01E02_720p_WEB-DL_AAC2.0_H264-GROUP"))
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.720p.WEB-DL.AAC2.0.H264-GROUP")) self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.720p.WEB-DL.AAC2.0.H264-GROUP"))
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.720p.iTunes.Rip.H264.AAC-GROUP")) self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.720p.iTunes.Rip.H264.AAC-GROUP"))
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test.Show.s01e02.WEBDL.720p.GROUP"))
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test Show s01e02 WEBDL 720p GROUP"))
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test Show S01E02 720p WEB-DL AVC-GROUP"))
self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.WEB-RIP.720p.GROUP"))
def test_FULLHDWEBDL(self): def test_FULLHDWEBDL(self):
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.1080p.WEB-DL-GROUP")) self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.1080p.WEB-DL-GROUP"))
@ -74,14 +80,20 @@ class QualityTests(unittest.TestCase):
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.1080p.iTunes.H.264.AAC-GROUP")) self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.1080p.iTunes.H.264.AAC-GROUP"))
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test Show S01E02 1080p iTunes H 264 AAC-GROUP")) self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test Show S01E02 1080p iTunes H 264 AAC-GROUP"))
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test_Show_S01E02_1080p_iTunes_H_264_AAC-GROUP")) self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test_Show_S01E02_1080p_iTunes_H_264_AAC-GROUP"))
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test.Show.s01e02.WEBDL.1080p.GROUP"))
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test Show s01e02 WEBDL 1080p GROUP"))
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test Show S01E02 1080p WEB-DL AVC-GROUP"))
self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test.Show.S01E02.WEB-RIP.1080p.GROUP"))
def test_HDBLURAY(self): def test_HDBLURAY(self):
self.assertEqual(common.Quality.HDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.720p.BluRay.x264-GROUP")) self.assertEqual(common.Quality.HDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.720p.BluRay.x264-GROUP"))
self.assertEqual(common.Quality.HDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.720p.HDDVD.x264-GROUP")) self.assertEqual(common.Quality.HDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.720p.HDDVD.x264-GROUP"))
self.assertEqual(common.Quality.HDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.720p.Blu-ray.x264-GROUP"))
def test_FULLHDBLURAY(self): def test_FULLHDBLURAY(self):
self.assertEqual(common.Quality.FULLHDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.1080p.BluRay.x264-GROUP")) self.assertEqual(common.Quality.FULLHDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.1080p.BluRay.x264-GROUP"))
self.assertEqual(common.Quality.FULLHDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.1080p.HDDVD.x264-GROUP")) self.assertEqual(common.Quality.FULLHDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.1080p.HDDVD.x264-GROUP"))
self.assertEqual(common.Quality.FULLHDBLURAY, common.Quality.nameQuality("Test.Show.S01E02.1080p.Blu-ray.x264-GROUP"))
def test_UNKNOWN(self): def test_UNKNOWN(self):
self.assertEqual(common.Quality.UNKNOWN, common.Quality.nameQuality("Test.Show.S01E02-SiCKBEARD")) self.assertEqual(common.Quality.UNKNOWN, common.Quality.nameQuality("Test.Show.S01E02-SiCKBEARD"))