diff --git a/addon.xml b/addon.xml index cc850479..8d0c4dc9 100644 --- a/addon.xml +++ b/addon.xml @@ -1,6 +1,6 @@ @@ -8,7 +8,7 @@ provider-name="jurialmunkey"> - + diff --git a/resources/tmdbhelper/lib/api/trakt/api.py b/resources/tmdbhelper/lib/api/trakt/api.py index 02594385..376316e9 100644 --- a/resources/tmdbhelper/lib/api/trakt/api.py +++ b/resources/tmdbhelper/lib/api/trakt/api.py @@ -127,11 +127,11 @@ def _filter_inprogress(self, items): @use_simple_cache(cache_days=CACHE_SHORT) def get_sorted_list( - self, path, sort_by=None, sort_how=None, extended=None, trakt_type=None, permitted_types=None, cache_refresh=False, + self, path, sort_by=None, sort_how=None, extended=None, trakt_type=None, permitted_types=None, cache_refresh=False, cache_only=False, genres=None, years=None, query=None, languages=None, countries=None, runtimes=None, studio_ids=None ): response = self.get_response( - path, extended=extended, limit=4095, + path, extended=extended, limit=4095, cache_only=cache_only, genres=genres, years=years, query=query, languages=languages, countries=countries, runtimes=runtimes, studio_ids=studio_ids ) @@ -161,7 +161,7 @@ def get_simple_list(self, *args, trakt_type=None, **kwargs): @is_authorized def get_mixed_list( - self, path, trakt_types: list, limit: int = None, extended: str = None, authorize=False, + self, path, trakt_types: list, limit: int = None, extended: str = None, authorize=False, cache_only=False, genres=None, years=None, query=None, languages=None, countries=None, runtimes=None, studio_ids=None ): """ Returns a randomised simple list which combines movies and shows @@ -173,7 +173,7 @@ def get_mixed_list( for trakt_type in trakt_types: response = self.get_simple_list( path.format(trakt_type=trakt_type), - extended=extended, page=1, limit=limit * 2, trakt_type=trakt_type, + extended=extended, page=1, limit=limit * 2, trakt_type=trakt_type, cache_only=cache_only, genres=genres, years=years, query=query, languages=languages, countries=countries, runtimes=runtimes, studio_ids=studio_ids ) or {} items += response.get('items') or [] @@ -189,7 +189,7 @@ def get_mixed_list( @is_authorized def get_basic_list( self, path, trakt_type, page: int = 1, limit: int = None, params=None, - sort_by=None, sort_how=None, extended=None, authorize=False, randomise=False, always_refresh=True, + sort_by=None, sort_how=None, extended=None, authorize=False, cache_only=False, randomise=False, always_refresh=True, genres=None, years=None, query=None, languages=None, countries=None, runtimes=None, studio_ids=None ): @@ -198,20 +198,20 @@ def get_basic_list( if randomise: response = self.get_simple_list( - path, extended=extended, page=1, limit=limit * 2, trakt_type=trakt_type, + path, extended=extended, page=1, limit=limit * 2, trakt_type=trakt_type, cache_only=cache_only, genres=genres, years=years, query=query, languages=languages, countries=countries, runtimes=runtimes, studio_ids=studio_ids ) elif sort_by is not None: # Sorted list manually paginated because need to sort first response = self.get_sorted_list( - path, sort_by, sort_how, extended, cache_refresh=cache_refresh, + path, sort_by, sort_how, extended, cache_refresh=cache_refresh, cache_only=cache_only, genres=genres, years=years, query=query, languages=languages, countries=countries, runtimes=runtimes, studio_ids=studio_ids ) response = PaginatedItems(items=response['items'], page=page, limit=limit).get_dict() else: # Unsorted lists can be paginated by the API response = self.get_simple_list( - path, extended=extended, page=page, limit=limit, trakt_type=trakt_type, + path, extended=extended, page=page, limit=limit, trakt_type=trakt_type, cache_only=cache_only, genres=genres, years=years, query=query, languages=languages, countries=countries, runtimes=runtimes, studio_ids=studio_ids ) @@ -226,7 +226,7 @@ def get_basic_list( @is_authorized def get_stacked_list( self, path, trakt_type, page: int = 1, limit: int = None, params=None, sort_by=None, sort_how=None, - extended=None, authorize=False, always_refresh=True, + extended=None, authorize=False, always_refresh=True, cache_only=False, genres=None, years=None, query=None, languages=None, countries=None, runtimes=None, studio_ids=None, **kwargs ): @@ -235,10 +235,13 @@ def get_stacked_list( cache_refresh = True if always_refresh and try_int(page, fallback=1) == 1 else False response = self.get_simple_list( - path, extended=extended, limit=4095, trakt_type=trakt_type, cache_refresh=cache_refresh, + path, extended=extended, limit=4095, trakt_type=trakt_type, cache_refresh=cache_refresh, cache_only=cache_only, genres=genres, years=years, query=query, languages=languages, countries=countries, runtimes=runtimes, studio_ids=studio_ids ) + if not response: + return + response['items'] = self._stack_calendar_tvshows(response['items']) response = PaginatedItems(items=response['items'], page=page, limit=limit).get_dict() @@ -250,7 +253,7 @@ def get_stacked_list( @is_authorized def get_custom_list( self, list_slug, user_slug=None, page: int = 1, limit: int = None, params=None, authorize=False, - sort_by=None, sort_how=None, extended=None, owner=False, always_refresh=True + sort_by=None, sort_how=None, extended=None, owner=False, always_refresh=True, cache_only=False ): limit = limit or self.item_limit @@ -266,7 +269,7 @@ def get_custom_list( sorted_items = self.get_sorted_list( path, sort_by, sort_how, extended, permitted_types=['movie', 'show', 'person', 'episode'], - cache_refresh=cache_refresh + cache_refresh=cache_refresh, cache_only=cache_only ) or {} paginated_items = PaginatedItems( @@ -279,7 +282,6 @@ def get_custom_list( 'persons': sorted_items.get('persons', []), 'next_page': paginated_items.next_page} - @use_activity_cache(cache_days=CACHE_SHORT) def _get_sync_list( self, sync_type, trakt_type, sort_by=None, sort_how=None, decorator_cache_refresh=False, extended=None, filters=None ): @@ -287,14 +289,15 @@ def _get_sync_list( func = TraktItems(items=self.get_sync(sync_type, trakt_type, extended=extended), trakt_type=trakt_type).build_items return func(sort_by, sort_how, filters=filters) - @is_authorized def get_sync_list( self, sync_type, trakt_type, page: int = 1, limit: int = None, params=None, sort_by=None, sort_how=None, next_page=True, always_refresh=True, extended=None, filters=None ): limit = limit or self.sync_item_limit cache_refresh = True if always_refresh and try_int(page, fallback=1) == 1 else False - response = self._get_sync_list(sync_type, trakt_type, sort_by=sort_by, sort_how=sort_how, decorator_cache_refresh=cache_refresh, extended=extended, filters=filters) + response = self._get_sync_list( + sync_type, trakt_type, sort_by=sort_by, sort_how=sort_how, + decorator_cache_refresh=cache_refresh, extended=extended, filters=filters) if not response: return response = PaginatedItems(items=response['items'], page=page, limit=limit) @@ -346,7 +349,7 @@ def _add_icon(i): return items @is_authorized - def get_list_of_lists(self, path, page: int = 1, limit: int = 250, authorize=False, next_page=True, sort_likes=False): + def get_list_of_lists(self, path, page: int = 1, limit: int = 250, authorize=False, next_page=True, sort_likes=False, **kwargs): response = self.get_response(path, page=page, limit=limit) if not response: return @@ -467,7 +470,7 @@ def __init__( self.item_limit = 20 * max(get_setting('pagemulti_trakt', 'int'), page_length) self.login(force) - def authorize(self, login=False): + def authorize(self, login=False, confirmation=False): def _get_token(): token = self.get_stored_token() if not token.get('access_token'): @@ -512,11 +515,15 @@ def _ask_login(): if not token and login: _ask_login() + if not confirmation: + return self.authorization + # First time authorization in this session so let's confirm if ( self.authorization and not boolean(get_property('TraktIsAuth')) - and not get_timestamp(get_property('TraktRefreshTimeStamp', is_type=float) or 0)): + and not get_timestamp(get_property('TraktRefreshTimeStamp', is_type=float) or 0) + ): # Wait if another thread is checking authorization if has_property_lock('TraktCheckingAuth'): diff --git a/resources/tmdbhelper/lib/api/trakt/decorators.py b/resources/tmdbhelper/lib/api/trakt/decorators.py index 0bc9788f..f90a25ac 100644 --- a/resources/tmdbhelper/lib/api/trakt/decorators.py +++ b/resources/tmdbhelper/lib/api/trakt/decorators.py @@ -19,11 +19,21 @@ def wrapper(self, *args, **kwargs): if boolean(get_property('TraktIsAuth')) and self.authorize(): return func(self, *args, **kwargs) - # Ask user to login because they want to use a method requiring authorization: - if not self.attempted_login and self.authorize(login=True): + # User not authorized or not authorized yet so get cached data instead + params = {} + params.update(kwargs) + params['cache_only'] = True + try: + content = None + content = func(self, *args, **params) + except TypeError: + pass + + # Ask user to login because they want to use a method requiring authorization and theres no cached data + if not content and not self.attempted_login and self.authorize(login=True): return func(self, *args, **kwargs) - return + return content return wrapper @@ -57,12 +67,11 @@ def use_lastupdated_cache(cache, func, *args, sync_info=None, cache_name='', **k def use_activity_cache(activity_type=None, activity_key=None, cache_days=None): """ Decorator to cache and refresh if last activity changes - Optionally can pickle instead of cache if necessary (useful for large objects like sync lists) - Optionally send decorator_cache_refresh=True in func kwargs to force refresh + Optionally send decorator_cache_refresh=True in func kwargs to force refresh as long as authorized + If not authorized the decoractor will only return cached object """ def decorator(func): - @is_authorized def wrapper(self, *args, allow_fallback=False, decorator_cache_refresh=None, **kwargs): # Setup getter/setter cache funcs func_get = self._cache.get_cache @@ -73,9 +82,18 @@ def wrapper(self, *args, allow_fallback=False, decorator_cache_refresh=None, **k cache_name = f'{self.__class__.__name__}.{cache_name}' cache_name = format_name(cache_name, *args, **kwargs) - # Cached response last_activity timestamp matches last_activity from trakt so no need to refresh + # Check last activity from Trakt last_activity = self._get_last_activity(activity_type, activity_key) - cache_object = func_get(cache_name) if last_activity and not decorator_cache_refresh else None + + # Trakt not authorized yet so lets use or cached object only + if last_activity == -1: + cache_object = func_get(cache_name) or {} + return cache_object.get('response') + + # Get our cached object + cache_object = None + if last_activity and not decorator_cache_refresh: + cache_object = func_get(cache_name) if cache_object and cache_object.get('last_activity') == last_activity: if cache_object.get('response') and cache_object.get('last_activity'): return cache_object['response'] diff --git a/resources/tmdbhelper/lib/api/trakt/progress.py b/resources/tmdbhelper/lib/api/trakt/progress.py index 4e7ac692..bb6a5f8a 100644 --- a/resources/tmdbhelper/lib/api/trakt/progress.py +++ b/resources/tmdbhelper/lib/api/trakt/progress.py @@ -13,7 +13,6 @@ class _TraktProgress(): - @is_authorized def get_ondeck_list(self, page=1, limit=None, sort_by=None, sort_how=None, trakt_type=None): limit = limit or self.sync_item_limit get_property('TraktSyncLastActivities.Expires', clear_property=True) # Wipe last activities cache to update now @@ -23,7 +22,6 @@ def get_ondeck_list(self, page=1, limit=None, sort_by=None, sort_how=None, trakt response = PaginatedItems(response['items'], page=page, limit=limit) return response.items + response.next_page - @is_authorized def get_towatch_list(self, trakt_type, page=1, limit=None): limit = limit or self.sync_item_limit get_property('TraktSyncLastActivities.Expires', clear_property=True) # Wipe last activities cache to update now @@ -38,7 +36,6 @@ def _get_inprogress_items(self, sync_type, lowest=5, highest=95): response = self.get_sync('playback', sync_type) return [i for i in response if lowest <= try_int(i.get('progress', 0)) <= highest] - @is_authorized def get_inprogress_shows_list(self, page=1, limit=None, params=None, next_page=True, sort_by=None, sort_how=None): limit = limit or self.sync_item_limit get_property('TraktSyncLastActivities.Expires', clear_property=True) # Wipe last activities cache to update now @@ -174,7 +171,6 @@ def _get_comp_item(i): hidden_items = {j for j in (_get_comp_item(i) for i in response) if j} return list(hidden_items) - @is_authorized def get_upnext_list(self, unique_id, id_type=None, page=1, limit=None): """ Gets the next episodes for a show that user should watch next """ limit = limit or self.sync_item_limit @@ -187,7 +183,6 @@ def get_upnext_list(self, unique_id, id_type=None, page=1, limit=None): response = PaginatedItems(response['items'], page=page, limit=limit) return response.items + response.next_page - @is_authorized def get_upnext_episodes_list(self, page=1, sort_by=None, sort_how='desc', limit=None): """ Gets a list of episodes for in-progress shows that user should watch next """ limit = limit or self.sync_item_limit @@ -197,8 +192,10 @@ def get_upnext_episodes_list(self, page=1, sort_by=None, sort_how='desc', limit= response = PaginatedItems(response['items'], page=page, limit=limit) return response.items + response.next_page - @is_authorized def _get_upnext_episodes_list(self, sort_by=None, sort_how='desc'): + + shows = self._get_inprogress_shows() or [] + def _get_upnext_episodes(i, get_single_episode=True): """ Helper func for upnext episodes to pass through threaded """ try: @@ -207,7 +204,6 @@ def _get_upnext_episodes(i, get_single_episode=True): except (AttributeError, KeyError): return return self.get_upnext_episodes(slug, show, get_single_episode=get_single_episode) - shows = self._get_inprogress_shows() or [] # Get upnext episodes threaded with ParallelThread(shows, _get_upnext_episodes) as pt: @@ -284,14 +280,12 @@ def get_upnext_episodes(self, slug, show, get_single_episode=False): except StopIteration: return - @is_authorized def get_movie_playcount(self, unique_id, id_type): try: return self.get_sync('watched', 'movie', id_type)[unique_id]['plays'] except (AttributeError, KeyError): return - @is_authorized def get_movie_playprogress(self, unique_id, id_type, key='progress'): try: return self.get_sync('playback', 'movie', id_type)[unique_id][key] @@ -369,7 +363,6 @@ def get_episode_playcount(self, unique_id, id_type, season, episode): if j.get('number', -1) == episode: return j.get('plays', 1) - @is_authorized def get_episodes_airedcount(self, unique_id, id_type, season=None): """ Gets the number of aired episodes for a tvshow """ try: @@ -394,11 +387,11 @@ def get_season_episodes_airedcount(self, unique_id, id_type, season, trakt_id=No if i.get('number', -1) == season: return i.get('aired_episodes') - def get_calendar(self, trakt_type, user=True, start_date=None, days=None, endpoint=None): + @is_authorized + def get_calendar(self, trakt_type, user=True, start_date=None, days=None, endpoint=None, **kwargs): user = 'my' if user else 'all' return self.get_response_json('calendars', user, trakt_type, endpoint, start_date, days, extended='full') - @is_authorized @use_simple_cache(cache_days=0.25) def get_calendar_episodes(self, startdate=0, days=1, user=True, endpoint=None): # Broaden date range in case utc conversion bumps into different day diff --git a/resources/tmdbhelper/lib/api/trakt/sync.py b/resources/tmdbhelper/lib/api/trakt/sync.py index 33559228..e447365b 100644 --- a/resources/tmdbhelper/lib/api/trakt/sync.py +++ b/resources/tmdbhelper/lib/api/trakt/sync.py @@ -49,7 +49,7 @@ def _get_activity_timestamp(self, activities, activity_type=None, activity_key=N return activities.get(activity_type, {}).get(activity_key) @is_authorized - def _get_last_activity(self, activity_type=None, activity_key=None, cache_refresh=False): + def _get_last_activity(self, activity_type=None, activity_key=None, cache_refresh=False, cache_only=False): def _cache_expired(): """ Check if the cached last_activities has expired """ last_exp = get_property('TraktSyncLastActivities.Expires', is_type=int) @@ -75,6 +75,9 @@ def _cache_router(): return data_loads(get_property('TraktSyncLastActivities')) return _cache_activity() + if cache_only: + return -1 + if not self.last_activities: self.last_activities = _cache_router() diff --git a/resources/tmdbhelper/lib/monitor/cronjob.py b/resources/tmdbhelper/lib/monitor/cronjob.py index 857da8bc..cd47ea35 100644 --- a/resources/tmdbhelper/lib/monitor/cronjob.py +++ b/resources/tmdbhelper/lib/monitor/cronjob.py @@ -1,13 +1,9 @@ -from xbmc import Monitor -from jurialmunkey.parser import try_int from threading import Thread -from tmdbhelper.lib.addon.tmdate import convert_timestamp, get_datetime_now, get_timedelta, get_datetime_today, get_datetime_time, get_datetime_combine -from tmdbhelper.lib.addon.plugin import get_setting, executebuiltin, get_infolabel -from tmdbhelper.lib.script.method.maintenance import recache_kodidb, clean_old_databases class CronJobMonitor(Thread): def __init__(self, update_hour=0): + from xbmc import Monitor Thread.__init__(self) self.exit = False self.poll_time = 1800 # Poll every 30 mins since we don't need to get exact time for update @@ -15,6 +11,15 @@ def __init__(self, update_hour=0): self.xbmc_monitor = Monitor() def run(self): + from jurialmunkey.parser import try_int + from tmdbhelper.lib.api.trakt.api import TraktAPI + from tmdbhelper.lib.addon.tmdate import convert_timestamp, get_datetime_now, get_timedelta, get_datetime_today, get_datetime_time, get_datetime_combine + from tmdbhelper.lib.addon.plugin import get_setting, executebuiltin, get_infolabel + from tmdbhelper.lib.script.method.maintenance import recache_kodidb, clean_old_databases + + # Check Trakt authorization + TraktAPI().authorize(confirmation=True) + clean_old_databases() recache_kodidb(notification=False)