diff --git a/CHANGES.md b/CHANGES.md index fd16d7e5..788e4c9f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl index f1c54fc1..0740cbac 100644 --- a/gui/slick/interfaces/default/displayShow.tmpl +++ b/gui/slick/interfaces/default/displayShow.tmpl @@ -483,7 +483,7 @@ - #if $UNAIRED != int($epResult['status']) and not $never_aired + #if ($UNAIRED != int($epResult['status']) or $sickbeard.SEARCH_UNAIRED) and not $never_aired #end if diff --git a/gui/slick/interfaces/default/editShow.tmpl b/gui/slick/interfaces/default/editShow.tmpl index b1780469..b754238b 100644 --- a/gui/slick/interfaces/default/editShow.tmpl +++ b/gui/slick/interfaces/default/editShow.tmpl @@ -175,7 +175,7 @@

#echo ('enable to use DVD title and episode ordering', 'disable to use TV network title, number and aired order')[$show.dvdorder]#.  - After changing this setting, a "force full update" is essential, and existing episodes must be manually renamed

+ 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

diff --git a/gui/slick/interfaces/default/manage_backlogOverview.tmpl b/gui/slick/interfaces/default/manage_backlogOverview.tmpl index 7d91ea62..641525dd 100644 --- a/gui/slick/interfaces/default/manage_backlogOverview.tmpl +++ b/gui/slick/interfaces/default/manage_backlogOverview.tmpl @@ -52,69 +52,73 @@ #set $totalQual += $showCounts[$curShow.indexerid][$Overview.QUAL] #end for ## -
- Wanted: $totalWanted - Low Quality: $totalQual -
-
+
+ Wanted: $totalWanted + Low Quality: $totalQual +
+
-
-Jump to Show - -
+#if not $totalWanted +

no shows require a backlog search

+#else +
+ Jump to Show + +
+#end if - -#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 - - - - - - - ## - #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 +
-
-

$curShow.name

-
- Wanted: $showCounts[$curShow.indexerid][$Overview.WANTED] - Low Quality: $showCounts[$curShow.indexerid][$Overview.QUAL] - #if not $curShow.paused: - Force Backlog - #else - Paused - #end if -
-
EpisodeNameAirdate
+ #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 - - - - - - #end for -#end for + + + -
$whichStr$curResult["name"]
#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)))#
+
+

$curShow.name

+
+ Wanted: $showCounts[$curShow.indexerid][$Overview.WANTED] + Low Quality: $showCounts[$curShow.indexerid][$Overview.QUAL] + #if not $curShow.paused: + Force Backlog + #else + Paused + #end if +
+
+ EpisodeNameAirdate + ## + #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']) + # + + $whichStr + $curResult['name'] +
#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)))#
+ + # + #end if + #end for + #end for + + -#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') \ No newline at end of file +#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') diff --git a/lib/tvdb_api/tvdb_api.py b/lib/tvdb_api/tvdb_api.py index f8f2f1c4..5e34ed2d 100644 --- a/lib/tvdb_api/tvdb_api.py +++ b/lib/tvdb_api/tvdb_api.py @@ -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'], '')), 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] diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index 4594ca64..f194fc3e 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -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) 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