Change only show unaired episodes on Manage/Backlog Overview and Manage/Episode Status Management where relevant.

Change display show page, can mark unaired items with a date to "Wanted" to trigger a manual active backlog search provided search setting "Unaired episodes" is enabled.
This commit is contained in:
JackDandy 2016-09-24 12:23:22 +01:00
parent 4a4afdaa01
commit 145301e275
8 changed files with 130 additions and 104 deletions

View file

@ -166,6 +166,9 @@
* Remove redundant config/general/"Allow incomplete show data"
* Fix status reset of a snatched, downloaded, or archived episode when its date is set to never (no date) on the info
source and there is no media file
* Change only show unaired episodes on Manage/Backlog Overview and Manage/Episode Status Management where relevant
* Change display show page, can mark unaired items with a date to "Wanted" to trigger a manual active backlog search
provided search setting "Unaired episodes" is enabled
[develop changelog]
* Change send nzb data to NZBGet for Anizb instead of url

View file

@ -483,7 +483,7 @@
<tr class="#echo ($Overview.overviewStrings[$epCats[$epStr]], 'airdate-never')[$never_aired]##echo ('', ' archived')[ARCHIVED == int($epResult['status'])]# season-$curSeason seasonstyle">
<td class="col-checkbox">
#if $UNAIRED != int($epResult['status']) and not $never_aired
#if ($UNAIRED != int($epResult['status']) or $sickbeard.SEARCH_UNAIRED) and not $never_aired
<input type="checkbox" class="epCheck" id="#echo $epStr#" name="#echo $epStr#">
#end if
</td>

View file

@ -175,7 +175,7 @@
<span class="component-desc">
<input type="checkbox" name="dvdorder" id="dvdorder"#echo ('', $html_checked)[$show.dvdorder]#>
<p>#echo ('enable to use DVD title and episode ordering', 'disable to use TV network title, number and aired order')[$show.dvdorder]#.&nbsp;
After changing this setting, a "force full update" is essential, and existing episodes must be manually renamed</p>
After changing this setting, a "force full update" is essential, and existing episodes should be manually renamed or replaced with #echo ('DVD', 'network')[$show.dvdorder]# numbered releases</p>
</span>
</label>
</div>

View file

@ -52,69 +52,73 @@
#set $totalQual += $showCounts[$curShow.indexerid][$Overview.QUAL]
#end for
##
<div class="h2footer pull-right">
<span class="listing-key wanted">Wanted: <b>$totalWanted</b></span>
<span class="listing-key qual">Low Quality: <b>$totalQual</b></span>
</div>
<br/>
<div class="h2footer pull-right">
<span class="listing-key wanted">Wanted: <b>$totalWanted</b></span>
<span class="listing-key qual">Low Quality: <b>$totalQual</b></span>
</div>
<br/>
<div class="pull-left">
Jump to Show
<select id="pickShow" class="form-control form-control-inline input-sm">
#for $curShow in sorted($sickbeard.showList, key = operator.attrgetter('name')):
#if 0 != $showCounts[$curShow.indexerid][$Overview.QUAL] + $showCounts[$curShow.indexerid][$Overview.WANTED]:
<option value="$curShow.indexerid">$curShow.name</option>
#end if
#end for
</select>
</div>
#if not $totalWanted
<h3>no shows require a <span class="grey-text">backlog search</span></h3>
#else
<div class="pull-left">
Jump to Show
<select id="pickShow" class="form-control form-control-inline input-sm">
#for $curShow in sorted($sickbeard.showList, key = operator.attrgetter('name')):
#if 0 != $showCounts[$curShow.indexerid][$Overview.QUAL] + $showCounts[$curShow.indexerid][$Overview.WANTED]:
<option value="$curShow.indexerid">$curShow.name</option>
#end if
#end for
</select>
</div>
#end if
<table class="sickbeardTable" border="0">
#for $curShow in sorted($sickbeard.showList, key = operator.attrgetter('name')):
##
#if 0 == $showCounts[$curShow.indexerid][$Overview.QUAL] + $showCounts[$curShow.indexerid][$Overview.WANTED]:
#continue
#end if
<tr class="seasonheader" id="show-$curShow.indexerid">
<td colspan="3" class="text-left">
<br/>
<h2><a href="$sbRoot/home/displayShow?show=$curShow.indexerid">$curShow.name</a></h2>
<div class="pull-right">
<span class="listing-key wanted">Wanted: <b>$showCounts[$curShow.indexerid][$Overview.WANTED]</b></span>
<span class="listing-key qual">Low Quality: <b>$showCounts[$curShow.indexerid][$Overview.QUAL]</b></span>
#if not $curShow.paused:
<a class="btn btn-inline forceBacklog" href="$sbRoot/manage/backlogShow?indexer_id=$curShow.indexerid"><i class="sgicon-play"></i> Force Backlog</a>
#else
<span class="quality SD btn-inline forceBacklog" style="padding:4px 10px; margin-bottom:1px"><i class="sgicon-pause"></i> Paused</span>
#end if
</div>
</td>
</tr>
<tr class="seasoncols"><th>Episode</th><th class="text-left">Name</th><th class="text-nowrap">Airdate</th></tr>
##
#for $curResult in $showSQLResults[$curShow.indexerid]:
#set $whichStr = $str($curResult['season']) + 'x' + $str($curResult['episode'])
#try:
#set $overview = $showCats[$curShow.indexerid][$whichStr]
#except Exception
#continue
#end try
<table class="sickbeardTable" border="0">
#for $curShow in sorted($sickbeard.showList, key = operator.attrgetter('name')):
##
#if $overview not in ($Overview.QUAL, $Overview.WANTED):
#if 0 == $showCounts[$curShow.indexerid][$Overview.QUAL] + $showCounts[$curShow.indexerid][$Overview.WANTED]:
#continue
#end if
<tr class="seasonstyle $Overview.overviewStrings[$showCats[$curShow.indexerid][$whichStr]]">
<td>$whichStr</td>
<td class="text-left">$curResult["name"]</td>
<td class="text-nowrap"><div class="${fuzzydate}">#if int($curResult['airdate']) == 1 then 'never' else $sbdatetime.sbdatetime.sbfdate($sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($curResult['airdate'],$curShow.airs,$curShow.network)))#</div></td>
</tr>
#end for
#end for
<tr class="seasonheader" id="show-$curShow.indexerid">
<td colspan="3" class="text-left">
<br/>
<h2><a href="$sbRoot/home/displayShow?show=$curShow.indexerid">$curShow.name</a></h2>
<div class="pull-right">
<span class="listing-key wanted">Wanted: <b>$showCounts[$curShow.indexerid][$Overview.WANTED]</b></span>
<span class="listing-key qual">Low Quality: <b>$showCounts[$curShow.indexerid][$Overview.QUAL]</b></span>
#if not $curShow.paused:
<a class="btn btn-inline forceBacklog" href="$sbRoot/manage/backlogShow?indexer_id=$curShow.indexerid"><i class="sgicon-play"></i> Force Backlog</a>
#else
<span class="quality SD btn-inline forceBacklog" style="padding:4px 10px; margin-bottom:1px"><i class="sgicon-pause"></i> Paused</span>
#end if
</div>
</td>
</tr>
</table>
<tr class="seasoncols"><th style="width:10%">Episode</th><th class="text-left">Name</th><th class="text-nowrap">Airdate</th></tr>
##
#for $curResult in $showSQLResults[$curShow.indexerid]:
#set $whichStr = '%sx%s' % ($str($curResult['season']), $str($curResult['episode']))
#try:
#set $overview = $showCats[$curShow.indexerid][$whichStr]
#except Exception
#continue
#end try
##
#if $overview in ($Overview.QUAL, $Overview.WANTED) or ($sickbeard.SEARCH_UNAIRED and $Overview.UNAIRED == $overview and 0 < $curResult['season'])
#
<tr class="seasonstyle $Overview.overviewStrings[$showCats[$curShow.indexerid][$whichStr]]">
<td>$whichStr</td>
<td class="text-left">$curResult['name']</td>
<td class="text-nowrap"><div class="${fuzzydate}">#if 1 == int($curResult['airdate']) then 'never' else $sbdatetime.sbdatetime.sbfdate($sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($curResult['airdate'], $curShow.airs,$curShow.network)))#</div></td>
</tr>
#
#end if
#end for
#end for
</table>
</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -35,12 +35,10 @@ from tvdb_ui import BaseUI, ConsoleUI
from tvdb_exceptions import (tvdb_error, tvdb_shownotfound,
tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound)
def log():
return logging.getLogger('tvdb_api')
from sickbeard import logger
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logr=None):
"""Retry calling the decorated function using an exponential backoff.
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
@ -56,8 +54,8 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
:param backoff: backoff multiplier e.g. value of 2 will double the delay
each retry
:type backoff: int
:param logger: logger to use. If None, print
:type logger: logging.Logger instance
:param logr: logger to use. If None, print
:type logr: logging.Logger instance
"""
def deco_retry(f):
@ -69,9 +67,9 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
try:
return f(*args, **kwargs)
except ExceptionToCheck, e:
msg = '%s, Retrying in %d seconds...' % (str(e), mdelay)
if logger:
logger.warning(msg)
msg = 'TVDB_API :: %s, Retrying in %d seconds...' % (str(e), mdelay)
if logr:
logger.log(msg, logger.WARNING)
else:
print msg
time.sleep(mdelay)
@ -168,9 +166,9 @@ class Show(dict):
Search terms are converted to lower case (unicode) strings.
# Examples
These examples assume t is an instance of Tvdb():
>> t = Tvdb()
>>
@ -518,6 +516,9 @@ class Tvdb:
self.config['url_seriesBanner'] = u'%(base_url)s/api/%(apikey)s/series/%%s/banners.xml' % self.config
self.config['url_artworkPrefix'] = u'%(base_url)s/banners/%%s' % self.config
def log(self, msg, log_level=logger.DEBUG):
logger.log('TVDB_API :: %s' % (msg.replace(self.config['apikey'], '<apikey>')), logLevel=log_level)
@staticmethod
def _get_temp_dir():
"""Returns the [system temp dir]/tvdb_api-u501 (or
@ -536,7 +537,7 @@ class Tvdb:
@retry(tvdb_error)
def _load_url(self, url, params=None, language=None):
log().debug('Retrieving URL %s' % url)
self.log('Retrieving URL %s' % url)
session = requests.session()
@ -544,7 +545,7 @@ class Tvdb:
session = CacheControl(session, cache=caches.FileCache(self.config['cache_location']))
if self.config['proxy']:
log().debug('Using proxy for URL: %s' % url)
self.log('Using proxy for URL: %s' % url)
session.proxies = {'http': self.config['proxy'], 'https': self.config['proxy']}
session.headers.update({'Accept-Encoding': 'gzip,deflate'})
@ -576,7 +577,7 @@ class Tvdb:
if 'application/zip' in resp.headers.get('Content-Type', ''):
try:
# TODO: The zip contains actors.xml and banners.xml, which are currently ignored [GH-20]
log().debug('We recived a zip file unpacking now ...')
self.log('We received a zip file unpacking now ...')
zipdata = StringIO.StringIO()
zipdata.write(resp.content)
myzipfile = zipfile.ZipFile(zipdata)
@ -642,7 +643,7 @@ class Tvdb:
and returns the result list
"""
series = series.encode('utf-8')
log().debug('Searching for show %s' % series)
self.log('Searching for show %s' % series)
self.config['params_get_series']['seriesname'] = series
try:
@ -666,19 +667,19 @@ class Tvdb:
all_series = [all_series]
if 0 == len(all_series):
log().debug('Series result returned zero')
self.log('Series result returned zero')
raise tvdb_shownotfound('Show-name search returned zero results (cannot find show on TVDB)')
if None is not self.config['custom_ui']:
log().debug('Using custom UI %s' % (repr(self.config['custom_ui'])))
self.log('Using custom UI %s' % (repr(self.config['custom_ui'])))
custom_ui = self.config['custom_ui']
ui = custom_ui(config=self.config)
else:
if not self.config['interactive']:
log().debug('Auto-selecting first search result using BaseUI')
self.log('Auto-selecting first search result using BaseUI')
ui = BaseUI(config=self.config)
else:
log().debug('Interactively selecting show using ConsoleUI')
self.log('Interactively selecting show using ConsoleUI')
ui = ConsoleUI(config=self.config)
return ui.selectSeries(all_series)
@ -701,7 +702,7 @@ class Tvdb:
This interface will be improved in future versions.
"""
log().debug('Getting season banners for %s' % sid)
self.log('Getting season banners for %s' % sid)
banners_et = self._getetsrc(self.config['url_seriesBanner'] % sid)
banners = {}
@ -729,7 +730,7 @@ class Tvdb:
for k, v in banners[btype][btype2][bid].items():
if k.endswith('path'):
new_key = '_%s' % k
log().debug('Transforming %s to %s' % (k, new_key))
self.log('Transforming %s to %s' % (k, new_key))
new_url = self.config['url_artworkPrefix'] % v
banners[btype][btype2][bid][new_key] = new_url
except:
@ -761,7 +762,7 @@ class Tvdb:
Any key starting with an underscore has been processed (not the raw
data from the XML)
"""
log().debug('Getting actors for %s' % sid)
self.log('Getting actors for %s' % sid)
actors_et = self._getetsrc(self.config['url_actorsInfo'] % sid)
cur_actors = Actors()
@ -789,16 +790,16 @@ class Tvdb:
"""
if None is self.config['language']:
log().debug('Config language is none, using show language')
self.log('Config language is none, using show language')
if None is language:
raise tvdb_error('config[\'language\'] was None, this should not happen')
get_show_in_language = language
else:
log().debug('Configured language %s override show language of %s' % (self.config['language'], language))
self.log('Configured language %s override show language of %s' % (self.config['language'], language))
get_show_in_language = self.config['language']
# Parse show information
log().debug('Getting all series data for %s' % sid)
self.log('Getting all series data for %s' % sid)
url = (self.config['url_seriesInfo'] % (sid, language), self.config['url_epInfo%s' % ('', '_zip')[self.config['useZip']]] % (sid, language))[get_ep_info]
show_data = self._getetsrc(url, language=get_show_in_language)
@ -825,7 +826,7 @@ class Tvdb:
self._parse_actors(sid)
# Parse episode data
log().debug('Getting all episodes of %s' % sid)
self.log('Getting all episodes of %s' % sid)
if 'Episode' not in show_data:
return False
@ -834,10 +835,10 @@ class Tvdb:
if not isinstance(episodes, list):
episodes = [episodes]
dvd_order = {'dvd': [], 'network': []}
for cur_ep in episodes:
if self.config['dvdorder']:
log().debug('Using DVD ordering.')
use_dvd = None is not cur_ep['DVD_season'] and None is not cur_ep['DVD_episodenumber']
use_dvd = cur_ep['DVD_season'] not in (None, '') and cur_ep['DVD_episodenumber'] not in (None, '')
else:
use_dvd = False
@ -847,14 +848,17 @@ class Tvdb:
elem_seasnum, elem_epno = cur_ep['SeasonNumber'], cur_ep['EpisodeNumber']
if None is elem_seasnum or None is elem_epno:
log().warning('An episode has incomplete season/episode number (season: %r, episode: %r)' % (
elem_seasnum, elem_epno))
self.log('An episode has incomplete season/episode number (season: %r, episode: %r)' % (
elem_seasnum, elem_epno), logger.WARNING)
continue # Skip to next episode
# float() is because https://github.com/dbr/tvnamer/issues/95 - should probably be fixed in TVDB data
seas_no = int(float(elem_seasnum))
ep_no = int(float(elem_epno))
if self.config['dvdorder']:
dvd_order[('network', 'dvd')[use_dvd]] += ['S%02dE%02d' % (seas_no, ep_no)]
for k, v in cur_ep.items():
k = k.lower()
@ -866,6 +870,15 @@ class Tvdb:
self._set_item(sid, seas_no, ep_no, k, v)
if self.config['dvdorder']:
num_dvd, num_network = [len(dvd_order[x]) for x in 'dvd', 'network']
num_all = num_dvd + num_network
if num_all:
self.log('Of %s episodes, %s use the DVD order, and %s use the network aired order' % (
num_all, num_dvd, num_network))
for ep_numbers in [', '.join(dvd_order['dvd'][i:i + 5]) for i in xrange(0, num_dvd, 5)]:
self.log('Using DVD order: %s' % ep_numbers)
return True
def _name_to_sid(self, name):
@ -874,10 +887,10 @@ class Tvdb:
the correct SID.
"""
if name in self.corrections:
log().debug('Correcting %s to %s' % (name, self.corrections[name]))
self.log('Correcting %s to %s' % (name, self.corrections[name]))
return self.corrections[name]
else:
log().debug('Getting show %s' % name)
self.log('Getting show %s' % name)
selected_series = self._get_series(name)
if isinstance(selected_series, dict):
selected_series = [selected_series]

View file

@ -743,7 +743,7 @@ class TorrentProvider(object, GenericProvider):
@property
def url(self):
if None is self._url or (hasattr(self, 'url_tmpl') and not self.urls):
self._url = self._valid_home()
self._url = self._valid_home(False)
self._valid_url()
return self._url
@ -873,7 +873,7 @@ class TorrentProvider(object, GenericProvider):
return data and re.search(r'(?sim)<input[^<]+name="password"', data) and \
re.search(r'(?sim)<input[^<]+name="username"', data)
def _valid_home(self):
def _valid_home(self, attempt_fetch=True):
"""
:return: signature verified home url else None if validation fail
"""
@ -910,7 +910,7 @@ class TorrentProvider(object, GenericProvider):
logger.log('Failed to identify a "%s" page with %s %s (local network issue, site down, or ISP blocked) ' %
(self.name, len(url_list), ('URL', 'different URLs')[1 < len(url_list)]) +
'Suggest; 1) Disable "%s" 2) Use a proxy/VPN' % self.get_id(),
(attempt_fetch and ('Suggest; 1) Disable "%s" 2) Use a proxy/VPN' % self.get_id()) or ''),
(logger.WARNING, logger.ERROR)[self.enabled])
self.urls = {}
sickbeard.PROVIDER_HOMES[self.get_id()] = ('site down', int(time.time()) + (5 * 60))

View file

@ -1076,15 +1076,15 @@ class TVShow(object):
for path, dirs, files in ek.ek(os.walk, image_cache_dir):
for filename in ek.ek(fnmatch.filter, files, '%s.*' % self.indexerid):
cache_file = ek.ek(os.path.join, path, filename)
logger.log('Attempt to %s cache file %s' % (action, cache_file))
try:
if sickbeard.TRASH_REMOVE_SHOW:
send2trash(cache_file)
else:
os.remove(cache_file)
logger.log('Attempt to %s cache file %s' % (action, cache_file))
try:
if sickbeard.TRASH_REMOVE_SHOW:
send2trash(cache_file)
else:
os.remove(cache_file)
except OSError as e:
logger.log('Unable to %s %s: %s / %s' % (action, cache_file, repr(e), str(e)), logger.WARNING)
except OSError as e:
logger.log('Unable to %s %s: %s / %s' % (action, cache_file, repr(e), str(e)), logger.WARNING)
# remove entire show folder
if full:

View file

@ -1871,7 +1871,7 @@ class Home(MainHandler):
with epObj.lock:
# don't let them mess up UNAIRED episodes
if epObj.status == UNAIRED:
if epObj.status == UNAIRED and not sickbeard.SEARCH_UNAIRED:
logger.log(u'Refusing to change status of ' + curEp + ' because it is UNAIRED', logger.ERROR)
continue
@ -3392,13 +3392,15 @@ class Manage(MainHandler):
result = {}
for cur_result in cur_show_results:
if not sickbeard.SEARCH_UNAIRED and 1000 > cur_result['airdate']:
continue
cur_season = int(cur_result['season'])
cur_episode = int(cur_result['episode'])
if cur_season not in result:
result[cur_season] = {}
result[cur_season][cur_episode] = {'name': cur_result['name'], 'airdate_never': (True, False)[1000 < int(cur_result['airdate'])]}
result[cur_season][cur_episode] = {'name': cur_result['name'], 'airdate_never': 1000 > int(cur_result['airdate'])}
return json.dumps(result)
@ -3438,6 +3440,8 @@ class Manage(MainHandler):
show_names = {}
sorted_show_ids = []
for cur_status_result in status_results:
if not sickbeard.SEARCH_UNAIRED and 1000 > cur_status_result['airdate']:
continue
cur_indexer_id = int(cur_status_result['indexer_id'])
if cur_indexer_id not in ep_counts:
ep_counts[cur_indexer_id] = 1
@ -3640,6 +3644,8 @@ class Manage(MainHandler):
[curShow.indexerid])
for curResult in sqlResults:
if not sickbeard.SEARCH_UNAIRED and 1 == curResult['airdate']:
continue
curEpCat = curShow.getOverview(int(curResult['status']))
if curEpCat:
epCats[str(curResult['season']) + 'x' + str(curResult['episode'])] = curEpCat