diff --git a/CHANGES.md b/CHANGES.md index b36fd34..55e55b6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -83,6 +83,7 @@ * Add show name/networks card user input filter * Change only auto refresh card view if a recoverable error occurs * Update Requests library 2.25.1 (bdc00eb) to 2.26.0 (b0e025a) +* Fix handling of card filters and sort states [develop changelog] @@ -93,6 +94,7 @@ * Fix genre field in tvmaze_api * Change api interface folders to api_* to fix legacy tmdb_api folder cleanup on new installs * Fix TVmaze genres property to be lowercase string +* Change use diskcache instead of memcache for TVmaze cards ### 0.24.15 (2021-08-05 11:45:00 UTC) diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl index 8ae6865..ebfcc7e 100644 --- a/gui/slick/interfaces/default/home_browseShows.tmpl +++ b/gui/slick/interfaces/default/home_browseShows.tmpl @@ -102,7 +102,7 @@ $(document).ready(function(){ }); #raw $('#showsort').on('change', function(){ - var sortCriteria = this.value.replace('by_', ''), el$ = $('#container'), shuffle = !1; + var elValue = this.value, sortCriteria = elValue.replace('by_', ''), el$ = $('#container'), shuffle = !1; switch (sortCriteria) { case 'asc': case 'desc': @@ -119,15 +119,13 @@ $(document).ready(function(){ #end if break; #if $use_ratings - case 'rating': - shuffle = !0; - break; #if $use_votes case 'rating_votes': sortCriteria = ['rating', 'votes']; + #end if + case 'rating': shuffle = !0; break; - #end if #end if default: sortCriteria = 'name' @@ -140,21 +138,21 @@ $(document).ready(function(){ option$ = showSort.filter('option[value="asc"].selected, option[value="desc"].selected'); option$.removeClass('selected').removeAttr('selected').html(option$.text().replace(/^>\s/i, '')); - option$ = showSort.filter($('option[value="' + this.value + '"]')); + option$ = showSort.filter($('option[value="' + elValue + '"]')); option$.addClass('selected').attr('selected', !0).html('> ' + option$.text()); savePrefs(); el$.one('layoutComplete', llUpdate); el$.isotope({ - sortAscending: 'asc' === this.value, + sortAscending: 'asc' === elValue, sortBy: showSort.filter($('option[value^="by_"][class*="selected"]')).val().replace('by_', '') }); - } else if(0 == this.value.indexOf('by_')){ + } else if(0 == elValue.indexOf('by_')){ option$ = showSort.filter($('option[value^="by_"][class*="selected"]')); option$.removeClass('selected').removeAttr('selected').html(option$.text().replace(/^>\s/i, '')); - option$ = showSort.filter($('option[value="' + this.value + '"]')); + option$ = showSort.filter($('option[value="' + elValue + '"]')); option$.addClass('selected').attr('selected', !0).html('> ' + option$.text()); savePrefs(); @@ -184,12 +182,12 @@ $(document).ready(function(){ option$ = showSort.filter($('option[value^="*"].selected, #showsort option[value^="."].selected')); option$.removeClass('selected').removeAttr('selected').html(option$.text().replace(/^>\s/i, '')); - option$ = showSort.filter($('option[value="' + this.value + '"]')); + option$ = showSort.filter($('option[value="' + elValue + '"]')); option$.addClass('selected').attr('selected', !0).html('> ' + option$.text()); savePrefs(); - var showCards = $('.show-card'), filter = this.value; + var showCards = $('.show-card'), filter = elValue; if('.hide' === filter){ showCards.filter($('.hide')).removeClass('hide').addClass('to-hide'); filter = '.to-hide'; @@ -278,7 +276,7 @@ $(document).ready(function(){ } }); }); - $('.resetshows').click(function() { + $('.resetshows').click(function() { var input = $('#search_show_name'); if ('' !== input.val()){ input.val('').trigger('input').change(); @@ -324,7 +322,7 @@ $(document).ready(function(){ -# omit for TVMaze as Original == First Aired +## omit for TVMaze as original order == First Aired #if 'TVmaze' not in $browse_type #end if diff --git a/lib/api_tvmaze/tvmaze_api.py b/lib/api_tvmaze/tvmaze_api.py index 55e0063..78d04f5 100644 --- a/lib/api_tvmaze/tvmaze_api.py +++ b/lib/api_tvmaze/tvmaze_api.py @@ -14,6 +14,7 @@ import requests from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter +from _23 import filter_iter from six import integer_types, iteritems, string_types from sg_helpers import get_url, try_int from lib.dateutil.parser import parser @@ -549,18 +550,16 @@ class TvMaze(TVInfoBase): if p: return self._convert_person(p) - def get_premieres(self, result_count=100, get_extra_images=False, **kwargs): + def get_premieres(self, **kwargs): # type: (...) -> List[TVInfoEpisode] - return self._filtered_schedule(lambda e: all([1 == e.season_number, 1 == e.episode_number]), - get_images=get_extra_images) + return self._filtered_schedule(**kwargs).get('premieres') - def get_returning(self, result_count=100, get_extra_images=False, **kwargs): + def get_returning(self, **kwargs): # type: (...) -> List[TVInfoEpisode] - return self._filtered_schedule(lambda e: all([1 != e.season_number, 1 == e.episode_number]), - get_images=get_extra_images) + return self._filtered_schedule(**kwargs).get('returning') - def _make_episode(self, episode_data, show_data=None, get_images=False): - # type: (TVMazeEpisode, TVMazeShow, bool) -> TVInfoEpisode + def _make_episode(self, episode_data, show_data=None, get_images=False, get_akas=False): + # type: (TVMazeEpisode, TVMazeShow, bool, bool) -> TVInfoEpisode """ make out of TVMazeEpisode object and optionally TVMazeShow a TVInfoEpisode """ @@ -581,7 +580,8 @@ class TvMaze(TVInfoBase): ti_show.show_type = show_data.type ti_show.lastupdated = show_data.updated ti_show.poster = show_data.image and show_data.image.get('original') - ti_show.aliases = [a.name for a in show_data.akas] + if get_akas: + ti_show.aliases = [a.name for a in show_data.akas] if 'days' in show_data.schedule: ti_show.airs_dayofweek = ', '.join(show_data.schedule['days']) network = show_data.network or show_data.web_channel @@ -656,12 +656,32 @@ class TvMaze(TVInfoBase): ti_episode.show = ti_show return ti_episode - def _filtered_schedule(self, condition, get_images=False): - try: - result = sorted([ - e for e in tvmaze.get_full_schedule() - if condition(e) and (None is e.show.language or re.search('(?i)eng|jap', e.show.language))], - key=lambda x: x.show.premiered or x.airstamp) - return [self._make_episode(r, r.show, get_images) for r in result] - except(BaseException, Exception): - return [] + def _filtered_schedule(self, **kwargs): + cache_name_key = 'tvmaze_schedule' + is_none, schedule = self._get_cache_entry(cache_name_key) + if None is schedule and not is_none: + schedule = [] + try: + schedule = tvmaze.get_full_schedule() + except(BaseException, Exception): + pass + + premieres = [] + returning = [] + rc_lang = re.compile('(?i)eng|jap') + for cur_show in filter_iter(lambda s: 1 == s.episode_number and ( + None is s.show.language or rc_lang.search(s.show.language)), schedule): + if 1 == cur_show.season_number: + premieres += [cur_show] + else: + returning += [cur_show] + + premieres = [self._make_episode(r, r.show, **kwargs) + for r in sorted(premieres, key=lambda e: e.show.premiered)] + returning = [self._make_episode(r, r.show, **kwargs) + for r in sorted(returning, key=lambda e: e.airstamp)] + + schedule = dict(premieres=premieres, returning=returning) + self._set_cache_entry(cache_name_key, schedule, expire=self.schedule_cache_expire) + + return schedule diff --git a/lib/tvinfo_base/base.py b/lib/tvinfo_base/base.py index c4806f9..82bd80b 100644 --- a/lib/tvinfo_base/base.py +++ b/lib/tvinfo_base/base.py @@ -384,6 +384,18 @@ class TVInfoShow(dict): return results + def __getstate__(self): + d = dict(self.__dict__) + try: + del d['lock'] + except (BaseException, Exception): + pass + return d + + def __setstate__(self, d): + self.__dict__ = d + self.lock = threading.RLock() + __repr__ = __str__ __nonzero__ = __bool__ @@ -795,6 +807,7 @@ class TVInfoBase(object): self.diskcache = diskcache.Cache(directory=self._cachedir, disk_pickle_protocol=2) # type: diskcache.Cache self.cache_expire = 60 * 60 * 18 # type: integer_types self.search_cache_expire = 60 * 15 # type: integer_types + self.schedule_cache_expire = 60 * 30 # type: integer_types self.config = { 'apikey': '', 'debug_enabled': False, @@ -1156,14 +1169,14 @@ class TVInfoBase(object): # type: (...) -> List[TVInfoEpisode] return [] - def get_premieres(self, result_count=100, **kwargs): + def get_premieres(self, **kwargs): # type: (...) -> List[TVInfoEpisode] """ get all premiering shows """ return [] - def get_returning(self, result_count=100, get_extra_images=False, **kwargs): + def get_returning(self, **kwargs): # type: (...) -> List[TVInfoShow] """ get all returning shows diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index b459355..6ba39ad 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -5187,28 +5187,19 @@ class AddShows(Home): footnote = None filtered = [] - def card_cache(mem_key): - # noinspection PyProtectedMember - from lib.dateutil.tz.tz import _datetime_to_timestamp - - now = int(_datetime_to_timestamp(datetime.datetime.now())) - if (sickbeard.MEMCACHE.get(mem_key, {}).get('data') - and (now < sickbeard.MEMCACHE.get(mem_key, {}).get('expire', 0))): - return sickbeard.MEMCACHE.get(mem_key).get('data') - - tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVMAZE).api_params.copy() - t = sickbeard.TVInfoAPI(TVINFO_TVMAZE).setup(**tvinfo_config) - if 'prem' in mem_key: - data = t.get_premieres() - else: - data = t.get_returning() - sickbeard.MEMCACHE[mem_key] = dict(expire=(30*60) + now, data=data) - return data - + tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVMAZE).api_params.copy() + t = sickbeard.TVInfoAPI(TVINFO_TVMAZE).setup(**tvinfo_config) if 'New' in browse_title: - episodes = card_cache('tvmaze_premiere') + episodes = t.get_premieres() else: - episodes = card_cache('tvmaze_returning') + episodes = t.get_returning() + + # handle switching between returning and premieres + sickbeard.BROWSELIST_MRU.setdefault(browse_type, dict()) + showfilter = ('by_returning', 'by_premiered')['New' in browse_title] + saved_showsort = sickbeard.BROWSELIST_MRU[browse_type].get('tvm_%s' % kwargs['mode']) or '*,asc' + showsort = saved_showsort + (',%s' % showfilter, '')[3 == len(saved_showsort.split(','))] + sickbeard.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort)) oldest, newest, oldest_dt, newest_dt, use_networks = None, None, 9999999, 0, False dedupe = [] @@ -5680,8 +5671,12 @@ class AddShows(Home): save_config = False if browse_type in ('AniDB', 'IMDb', 'Metacritic', 'Trakt', 'TVCalendar', 'TVmaze', 'Nextepisode'): save_config = True - sickbeard.BROWSELIST_MRU[browse_type] = dict( - showfilter=kwargs.get('showfilter', ''), showsort=kwargs.get('showsort', '')) + if browse_type in ('TVmaze',) and kwargs.get('showfilter') and kwargs.get('showsort'): + sickbeard.BROWSELIST_MRU.setdefault(browse_type, dict()) \ + .update({kwargs.get('showfilter'): kwargs.get('showsort')}) + else: + sickbeard.BROWSELIST_MRU[browse_type] = dict( + showfilter=kwargs.get('showfilter', ''), showsort=kwargs.get('showsort', '')) if save_config: sickbeard.save_config() return json.dumps({'success': save_config}) @@ -5700,8 +5695,7 @@ class AddShows(Home): showsort = t.saved_showsort.split(',') t.saved_showsort_sortby = 3 == len(showsort) and showsort[2] or 'by_order' t.reset_showsort_sortby = ('votes' in t.saved_showsort_sortby and not kwargs.get('use_votes', True) - or 'rating' in t.saved_showsort_sortby and not kwargs.get('use_ratings', True) - or 'returning' in t.saved_showsort_sortby and 'Returning' not in browse_title) + or 'rating' in t.saved_showsort_sortby and not kwargs.get('use_ratings', True)) t.is_showsort_desc = ('desc' == (2 <= len(showsort) and showsort[1] or 'asc')) and not t.reset_showsort_sortby t.saved_showsort_view = 1 <= len(showsort) and showsort[0] or '*' t.all_shows = []