Exemplo n.º 1
0
class Menus:
    def __init__(self):
        self.trakt = TraktAPI()
        self.language_code = g.get_language_code()
        self.trakt_database = TraktSyncDatabase()
        self.hidden_database = hidden.TraktSyncDatabase()
        self.bookmark_database = bookmark.TraktSyncDatabase()
        self.shows_database = shows.TraktSyncDatabase()
        self.list_builder = ListBuilder()
        self.page_limit = g.get_int_setting("item.limit")
        self.page_start = (g.PAGE - 1) * self.page_limit
        self.page_end = g.PAGE * self.page_limit

    ######################################################
    # MENUS
    ######################################################

    @trakt_auth_guard
    def on_deck_shows(self):
        hidden_shows = self.hidden_database.get_hidden_items(
            "progress_watched", "tvshow")
        bookmarked_items = [
            i for i in self.bookmark_database.get_all_bookmark_items("episode")
            if i["trakt_show_id"] not in hidden_shows
        ][self.page_start:self.page_end]
        self.list_builder.mixed_episode_builder(bookmarked_items)

    @staticmethod
    def discover_shows():

        g.add_directory_item(
            g.get_language_string(30004),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="popular",
            description=g.get_language_string(30438),
        )
        g.add_directory_item(
            g.get_language_string(30367),
            action="showsPopularRecent",
            description=g.get_language_string(30439),
        )
        if g.get_setting("trakt.auth"):
            g.add_directory_item(
                g.get_language_string(30005),
                action="showsRecommended",
                description=g.get_language_string(30440),
            )
        g.add_directory_item(
            g.get_language_string(30006),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="trending",
            description=g.get_language_string(30441),
        )
        g.add_directory_item(
            g.get_language_string(30368),
            action="showsTrendingRecent",
            description=g.get_language_string(30442),
        )
        g.add_directory_item(
            g.get_language_string(30046),
            action="showsNew",
            description=g.get_language_string(30443),
        )
        g.add_directory_item(
            g.get_language_string(30007),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="played",
            description=g.get_language_string(30444),
        )
        g.add_directory_item(
            g.get_language_string(30008),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="watched",
            description=g.get_language_string(30445),
        )
        g.add_directory_item(
            g.get_language_string(30009),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="collected",
            description=g.get_language_string(30446),
        )
        g.add_directory_item(
            g.get_language_string(30374),
            action="TrendingLists",
            mediatype="shows",
            description=g.get_language_string(30447),
        )
        g.add_directory_item(
            g.get_language_string(30376),
            action="PopularLists",
            mediatype="shows",
            description=g.get_language_string(30448),
        )
        if not g.get_bool_setting("general.hideUnAired"):
            g.add_directory_item(
                g.get_language_string(30010),
                action="genericEndpoint",
                mediatype="shows",
                endpoint="anticipated",
                description=g.get_language_string(30449),
            )

        g.add_directory_item(
            g.get_language_string(30011),
            action="showsUpdated",
            description=g.get_language_string(30450),
        )
        g.add_directory_item(
            g.get_language_string(30182),
            action="showsNetworks",
            description=g.get_language_string(30451),
        )
        g.add_directory_item(
            g.get_language_string(30184),
            action="showYears",
            description=g.get_language_string(30452),
        )
        g.add_directory_item(
            g.get_language_string(30042),
            action="tvGenres",
            description=g.get_language_string(30453),
        )
        g.add_directory_item(
            g.get_language_string(30203),
            action="showsByActor",
            description=g.get_language_string(30454),
        )
        if not g.get_bool_setting("searchHistory"):
            g.add_directory_item(
                g.get_language_string(30013),
                action="showsSearch",
                description=g.get_language_string(30394),
            )
        else:
            g.add_directory_item(
                g.get_language_string(30013),
                action="showsSearchHistory",
                description=g.get_language_string(30396),
            )
        g.close_directory(g.CONTENT_MENU)

    @staticmethod
    @trakt_auth_guard
    def my_shows():
        g.add_directory_item(
            g.get_language_string(30043),
            action="onDeckShows",
            description=g.get_language_string(30455),
        )
        g.add_directory_item(
            g.get_language_string(30014),
            action="showsMyCollection",
            description=g.get_language_string(30456),
        )
        g.add_directory_item(
            g.get_language_string(30015),
            action="showsMyWatchlist",
            description=g.get_language_string(30457),
        )
        g.add_directory_item(
            g.get_language_string(30092),
            action="showsRecentlyWatched",
            description=g.get_language_string(30507),
        )
        g.add_directory_item(
            g.get_language_string(30223),
            action="showsNextUp",
            description=g.get_language_string(30458),
        )
        g.add_directory_item(
            g.get_language_string(30224),
            action="myUpcomingEpisodes",
            description=g.get_language_string(30459),
        )
        g.add_directory_item(
            g.get_language_string(30225),
            action="showsMyProgress",
            description=g.get_language_string(30460),
        )
        g.add_directory_item(
            g.get_language_string(30226),
            action="showsMyRecentEpisodes",
            description=g.get_language_string(30461),
        )
        g.add_directory_item(
            g.get_language_string(30227),
            action="myTraktLists",
            mediatype="shows",
            description=g.get_language_string(30462),
        )
        g.add_directory_item(
            g.get_language_string(30372),
            action="myLikedLists",
            mediatype="shows",
            description=g.get_language_string(30463),
        )
        g.add_directory_item(
            g.get_language_string(30346),
            action="myWatchedEpisodes",
            description=g.get_language_string(30464),
        )
        g.close_directory(g.CONTENT_MENU)

    def generic_endpoint(self, endpoint):
        trakt_list = self.shows_database.extract_trakt_page(
            "shows/{}".format(endpoint), page=g.PAGE, extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_popular_recent(self):
        year_range = "{}-{}".format(datetime.datetime.now().year - 1,
                                    datetime.datetime.now().year)
        trakt_list = self.shows_database.extract_trakt_page("shows/popular",
                                                            years=year_range,
                                                            page=g.PAGE,
                                                            extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_trending_recent(self):
        year_range = "{}-{}".format(datetime.datetime.now().year - 1,
                                    datetime.datetime.now().year)
        trakt_list = self.shows_database.extract_trakt_page("shows/trending",
                                                            years=year_range,
                                                            page=g.PAGE,
                                                            extended="full")
        self.list_builder.show_list_builder(trakt_list)

    @trakt_auth_guard
    def my_shows_collection(self):
        no_paging = not g.get_bool_setting("general.paginatecollection")
        sort = "title" if g.get_int_setting(
            "general.sortcollection") == 1 else False
        trakt_list = self.trakt_database.get_collected_shows(g.PAGE)
        if sort == "title" and not no_paging:
            trakt_list = sorted(
                trakt_list,
                key=lambda k: tools.SORT_TOKEN_REGEX.sub(
                    "", k["trakt_object"]["info"].get('title').lower()))
            offset = (g.PAGE - 1) * self.page_limit
            trakt_list = trakt_list[offset:offset + self.page_limit]
        self.list_builder.show_list_builder(trakt_list,
                                            no_paging=no_paging,
                                            sort=sort)

    @trakt_auth_guard
    def my_shows_watchlist(self):
        paginate = not g.get_bool_setting("general.paginatetraktlists")
        trakt_list = self.shows_database.extract_trakt_page(
            "users/me/watchlist/shows",
            extended="full",
            page=g.PAGE,
            ignore_cache=True,
            no_paging=paginate,
            pull_all=True,
        )
        self.list_builder.show_list_builder(trakt_list, no_paging=paginate)

    @trakt_auth_guard
    def my_show_progress(self):
        no_paging = not g.get_bool_setting("general.paginatecollection")
        sort = "title" if g.get_int_setting(
            "general.sortcollection") == 1 else False
        trakt_list = self.trakt_database.get_unfinished_collected_shows(g.PAGE)
        if sort == "title" and not no_paging:
            trakt_list = sorted(
                trakt_list,
                key=lambda k: tools.SORT_TOKEN_REGEX.sub(
                    "", k["trakt_object"]["info"].get('title').lower()))
            offset = (g.PAGE - 1) * self.page_limit
            trakt_list = trakt_list[offset:offset + self.page_limit]
        self.list_builder.show_list_builder(trakt_list,
                                            no_paging=no_paging,
                                            sort=sort)

    @trakt_auth_guard
    def shows_recommended(self):
        trakt_list = self.shows_database.extract_trakt_page(
            "recommendations/shows", ignore_collected=True, extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_new(self):
        hidden_items = self.hidden_database.get_hidden_items(
            "recommendations", "shows")
        date_string = datetime.datetime.today() - datetime.timedelta(days=29)
        trakt_list = self.shows_database.extract_trakt_page(
            "calendars/all/shows/new/{}/30".format(
                date_string.strftime("%d-%m-%Y")),
            languages=','.join({'en', self.language_code}),
            extended="full",
            no_paging=True,
            ignore_cache=True,
            hide_watched=False,
            hide_unaired=False)
        trakt_list = [
            i for i in trakt_list if i["trakt_id"] not in hidden_items
        ][:self.page_limit]
        self.list_builder.show_list_builder(trakt_list, no_paging=True)

    def shows_recently_watched(self):
        self.list_builder.show_list_builder(
            self.trakt_database.get_recently_watched_shows(), no_paging=True)

    def my_next_up(self):
        episodes = self.trakt_database.get_nextup_episodes(
            g.get_int_setting("nextup.sort") == 1)
        self.list_builder.mixed_episode_builder(episodes, no_paging=True)

    @trakt_auth_guard
    def my_recent_episodes(self):
        hidden_shows = self.hidden_database.get_hidden_items(
            "calendar", "shows")
        date_string = datetime.datetime.today() - datetime.timedelta(days=13)
        trakt_list = self.trakt.get_json("calendars/my/shows/{}/14".format(
            date_string.strftime("%d-%m-%Y")),
                                         extended="full")
        trakt_list = sorted(
            [i for i in trakt_list if i["trakt_show_id"] not in hidden_shows],
            key=lambda t: t["first_aired"],
            reverse=True,
        )

        self.list_builder.mixed_episode_builder(trakt_list)

    @trakt_auth_guard
    def my_upcoming_episodes(self):
        tomorrow = (datetime.date.today() +
                    datetime.timedelta(days=1)).strftime(g.DATE_FORMAT)
        upcoming_episodes = self.trakt.get_json(
            "calendars/my/shows/{}/30".format(tomorrow),
            extended="full")[:self.page_limit]
        self.list_builder.mixed_episode_builder(upcoming_episodes,
                                                prepend_date=True,
                                                no_paging=True,
                                                hide_unaired=False)

    def shows_networks(self):
        trakt_list = self.trakt.get_json_cached("networks")
        list_items = []
        for i in trakt_list:
            list_items.append(
                g.add_directory_item(
                    i["name"],
                    action="showsNetworkShows",
                    action_args=i["name"],
                    bulk_add=True,
                ))
        xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, list_items,
                                     len(list_items))
        g.close_directory(g.CONTENT_MENU)

    def shows_networks_results(self, network):
        trakt_list = self.shows_database.extract_trakt_page("shows/popular",
                                                            networks=network,
                                                            page=g.PAGE,
                                                            extended="full")
        self.list_builder.show_list_builder(trakt_list)
        g.close_directory(g.CONTENT_SHOW)

    def shows_updated(self):
        date = datetime.date.today() - datetime.timedelta(days=29)
        date = date.strftime(g.DATE_FORMAT)
        trakt_list = self.shows_database.extract_trakt_page(
            "shows/updates/{}".format(date),
            extended="full",
            ignore_cache=True,
            hide_watched=False,
            hide_unaired=False)
        self.list_builder.show_list_builder(trakt_list, no_paging=True)

    @staticmethod
    def shows_search_history():
        history = SearchHistory().get_search_history("tvshow")
        g.add_directory_item(
            g.get_language_string(30195),
            action="showsSearch",
            description=g.get_language_string(30394),
        )
        g.add_directory_item(
            g.get_language_string(30193),
            action="clearSearchHistory",
            mediatype="tvshow",
            is_folder=False,
            description=g.get_language_string(30193),
        )
        for i in history:
            g.add_directory_item(
                i,
                action="showsSearchResults",
                action_args=tools.construct_action_args(i),
            )
        g.close_directory(g.CONTENT_MENU)

    def shows_search(self, query=None):
        if not query:
            query = g.get_keyboard_input(g.get_language_string(30013))
            if not query:
                g.cancel_directory()
                return

        if g.get_bool_setting("searchHistory"):
            SearchHistory().add_search_history("tvshow", query)
        self.shows_search_results(query)

    def shows_search_results(self, query):
        trakt_list = self.shows_database.extract_trakt_page("search/show",
                                                            query=query,
                                                            page=g.PAGE,
                                                            extended="full",
                                                            field="title",
                                                            hide_unaired=False,
                                                            hide_watched=False)

        if not trakt_list:
            g.cancel_directory()
            return
        self.list_builder.show_list_builder([
            show for show in trakt_list
            if float(show["trakt_object"]["info"]["score"]) > 0
        ],
                                            hide_unaired=False,
                                            hide_watched=False)

    def shows_by_actor(self, query):
        if not query:
            query = g.get_keyboard_input(g.get_language_string(30013))
            if not query:
                g.cancel_directory()
                return

        if g.get_bool_setting("searchHistory"):
            SearchHistory().add_search_history("showActor", query)

        query = g.transliterate_string(query)
        # Try to deal with transliterated chinese actor names as some character -> word transliterations can be joined
        # I have no idea of the rules and it could well be arbitrary
        # This approach will only work if only one pair of adjoining transliterated chars are joined
        name_parts = query.split()
        for i in range(len(name_parts), 0, -1):
            query = "-".join(name_parts[:i]) + "-".join(name_parts[i:i + 1])
            query = tools.quote_plus(query)

            trakt_list = self.shows_database.extract_trakt_page(
                "people/{}/shows".format(query),
                extended="full",
                page=g.PAGE,
                hide_watched=False,
                hide_unaired=False,
            )
            if not trakt_list:
                continue
            else:
                break

        try:
            if not trakt_list or 'trakt_id' not in trakt_list[0]:
                raise KeyError
        except KeyError:
            g.cancel_directory()
            return
        self.list_builder.show_list_builder(trakt_list,
                                            hide_watched=False,
                                            hide_unaired=False)

    def show_seasons(self, args):
        self.list_builder.season_list_builder(args["trakt_id"], no_paging=True)

    def season_episodes(self, args):
        self.list_builder.episode_list_builder(args["trakt_show_id"],
                                               args["trakt_id"],
                                               no_paging=True)

    def flat_episode_list(self, args):
        self.list_builder.episode_list_builder(args["trakt_id"],
                                               no_paging=True)

    def shows_genres(self):
        g.add_directory_item(
            g.get_language_string(30045),
            action="showsGenresGet",
            menu_item={
                "art":
                dict.fromkeys(['icon', 'poster', 'thumb', 'fanart'],
                              g.GENRES_PATH + "list.png")
            })
        genres = self.trakt.get_json_cached("genres/shows", extended="full")

        if genres is None:
            g.cancel_directory()
            return

        for i in genres:
            g.add_directory_item(
                i["name"],
                action="showGenresGet",
                action_args=i["slug"],
                menu_item={
                    "art":
                    dict.fromkeys(['icon', 'poster', 'thumb', 'fanart'],
                                  "{}{}.png".format(g.GENRES_PATH, i["slug"]))
                })
        g.close_directory(g.CONTENT_GENRES)

    def shows_genre_list(self, args):
        trakt_endpoint = ("trending"
                          if g.get_int_setting("general.genres.endpoint") == 0
                          else "popular")
        if args is None:
            genre_display_list = []
            genre_string = ""
            genres = self.trakt.get_json_cached("genres/shows")

            for genre in genres:
                gi = xbmcgui.ListItem(genre["name"])
                gi.setArt(
                    {"thumb": "{}{}.png".format(g.GENRES_PATH, genre["slug"])})
                genre_display_list.append(gi)
            genre_multiselect = xbmcgui.Dialog().multiselect(
                "{}: {}".format(g.ADDON_NAME, g.get_language_string(30320)),
                genre_display_list,
                useDetails=True)

            if genre_multiselect is None:
                return
            for selection in genre_multiselect:
                genre_string += ", {}".format(genres[selection]["slug"])
            genre_string = genre_string[2:]
        else:
            genre_string = tools.unquote(args)

        trakt_list = self.shows_database.extract_trakt_page(
            "shows/{}".format(trakt_endpoint),
            genres=genre_string,
            page=g.PAGE,
            extended="full")

        if trakt_list is None:
            g.cancel_directory()
            return

        self.list_builder.show_list_builder(trakt_list, next_args=genre_string)

    def shows_related(self, args):
        trakt_list = self.shows_database.extract_trakt_page(
            "shows/{}/related".format(args), extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_years(self, year=None):
        if year is None:
            current_year = int(
                tools.parse_datetime(datetime.datetime.today().strftime(
                    g.DATE_FORMAT)).year)
            all_years = reversed(
                [year for year in range(1900, current_year + 1)])
            menu_items = []
            for year in all_years:
                menu_items.append(
                    g.add_directory_item(g.UNICODE(year),
                                         action="showYears",
                                         action_args=year,
                                         bulk_add=True))
            xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, menu_items,
                                         len(menu_items))
            g.close_directory(g.CONTENT_SHOW)
        else:
            trakt_list = self.shows_database.extract_trakt_page(
                "shows/popular",
                years=year,
                page=g.PAGE,
                extended="full",
                hide_watched=False)
            self.list_builder.show_list_builder(trakt_list)

    @trakt_auth_guard
    def my_watched_episode(self):
        watched_episodes = self.trakt_database.get_watched_episodes(g.PAGE)
        self.list_builder.mixed_episode_builder(watched_episodes)
Exemplo n.º 2
0
class SmartPlay:
    """
    Provides smart operations for playback
    """
    def __init__(self, item_information):
        self.list_builder = ListBuilder()
        if "info" not in item_information:
            item_information = tools.get_item_information(item_information)
        self.item_information = item_information

        if not isinstance(self.item_information, dict):
            raise TypeError("Item Information is not a dictionary")

        self.show_trakt_id = self.item_information.get("trakt_show_id")
        if not self.show_trakt_id and "action_args" in self.item_information:
            self.show_trakt_id = self._extract_show_id_from_args(
                self.item_information["action_args"]
            )

        self.display_style = g.get_int_setting("smartplay.displaystyle")
        self.trakt_api = TraktAPI()

    @staticmethod
    def _extract_show_id_from_args(action_args):
        if action_args["mediatype"] in ["tvshow", "movie"]:
            return action_args["trakt_id"]
        elif action_args["mediatype"] in ["episode", "season"]:
            return action_args["trakt_show_id"]

    def get_season_info(self):
        """
        Fetches all season information for current show from database
        :return:
        :rtype:
        """
        return TraktSyncDatabase().get_season_list(self.show_trakt_id)

    def resume_show(self):
        """
        Identifies resume point for a show and plays from there
        :return:
        :rtype:
        """
        g.cancel_playback()
        g.close_all_dialogs()
        g.PLAYLIST.clear()

        window = self._get_window()

        window.set_text(g.get_language_string(30060))
        window.show()

        window.set_text(g.get_language_string(30061))

        season_id, episode = self.get_resume_episode()

        window.set_text(g.get_language_string(30062))

        window.set_text(g.get_language_string(30063))

        self.build_playlist(season_id, episode)

        window.set_text(g.get_language_string(30311))

        g.log(
            "Begining play from Season ID {} Episode {}".format(season_id, episode),
            "info",
        )

        window.close()
        del window

        xbmc.Player().play(g.PLAYLIST)

    def build_playlist(self, season_id=None, minimum_episode=None):
        """
        Uses available information to add relevant episodes to the current playlist
        :param season_id: Trakt ID of season to build
        :type season_id: int
        :param minimum_episode: Minimum episodes to add from
        :type minimum_episode: int
        :return:
        :rtype:
        """
        if season_id is None:
            season_id = self.item_information["info"]["trakt_season_id"]

        if minimum_episode is None:
            minimum_episode = int(self.item_information["info"]["episode"]) + 1

        try:
            [
                g.PLAYLIST.add(url=i[0], listitem=i[1])
                for i in self.list_builder.episode_list_builder(
                    self.show_trakt_id,
                    season_id,
                    minimum_episode=minimum_episode,
                    smart_play=True,
                    hide_unaired=True,
                )
            ]
        except TypeError:
            g.log(
                "Unable to add more episodes to the playlist, they may not be available for the requested season",
                "warning",
            )
            return

    def get_resume_episode(self):
        """
        Fetches playback information for current show and identifies the next episode to be resumed/watched
        :return: (Season, Episode) tuple
        :rtype: tuple
        """
        get = MetadataHandler().get_trakt_info
        info = MetadataHandler().info
        try:
            playback_history = self.trakt_api.get_json(
                "sync/history/shows/{}".format(self.show_trakt_id), extended="full", limit=1
            )[0]
            action = playback_history["action"]
            episode_info = playback_history["episode"]
            season = get(episode_info, "season")
            episode = get(episode_info, "episode")
        except IndexError:
            # Capture failure to get old playback and resume from first episode
            action = "watch"
            season = 1
            episode = 1

        if action != "watch":
            episode += 1

        all_seasons = self.get_season_info()
        season_info = [i for i in all_seasons if info(i).get("season") == season][0]

        if episode >= info(season_info).get("episode_count"):
            season += 1
            episode = 1

        if self.final_episode_check(season, episode):
            season = 1
            episode = 1

        season_id = info(
            [i for i in all_seasons if info(i).get("season") == season][0]).get("trakt_id")

        return season_id, episode

    def final_episode_check(self, season, episode):
        """
        Checks to see if the current item is the last episode aired for the show
        :param season: Season number of item to check
        :type season: int
        :param episode: Episode number of item to check
        :type episode: int
        :return: True if item is last aired episode else false
        :rtype: bool
        """
        get = MetadataHandler().get_trakt_info
        season = int(season)
        episode = int(episode)

        last_aired = self.trakt_api.get_json(
            "shows/{}/last_episode".format(self.show_trakt_id)
        )

        if season > get(last_aired, "season"):
            return True

        if season == get(last_aired, "season") and episode == get(last_aired, "number"):
            return True

        return False

    def append_next_season(self):
        """
        Checks if current episode is the last episode for the season, if true adds next seasons episodes to playlist
        :return:
        :rtype:
        """
        episode = self.item_information["info"]["episode"]
        season = self.item_information["info"]["season"]
        season_info = self.get_season_info()
        current_season_info = [i for i in season_info if season == i["info"]["season"]][0]
        if episode != current_season_info["episode_count"]:
            return

        next_season = [i for i in season_info if i["info"]["season"] == season + 1]
        if len(next_season) == 0:
            return

        season_id = next_season[0]["trakt_id"]
        self.build_playlist(season_id, 1)

    @staticmethod
    def pre_scrape():
        """
        Checks whether a item exists in the current playlist after current item and then pre-fetches results
        :return:
        :rtype:
        """
        next_position = g.PLAYLIST.getposition() + 1
        if next_position >= g.PLAYLIST.size():
            return

        url = g.PLAYLIST[  # pylint: disable=unsubscriptable-object
            next_position
        ].getPath()

        if not url:
            return

        url = url.replace("getSources", "preScrape")
        g.set_runtime_setting("tempSilent", True)
        g.log("Running Pre-Scrape: {}".format(url))
        xbmc.executebuiltin('RunPlugin("{}")'.format(url))

    def shuffle_play(self):
        """
        Creates a playlist of shuffled episodes for selected show and plays it
        :return:
        :rtype:
        """

        g.PLAYLIST.clear()
        window = self._get_window()
        window.show()
        window.set_text(g.get_language_string(30062))

        season_list = self.trakt_api.get_json(
            "shows/{}/seasons".format(self.show_trakt_id), extended="episodes"
        )
        if season_list[0]["trakt_object"]["info"]["season"] == 0:
            season_list.pop(0)

        window.set_text(g.get_language_string(30063))

        episode_list = [
            episode
            for season in season_list
            for episode in season["trakt_object"]["info"]["episodes"]
        ]
        random.shuffle(episode_list)
        episode_list = episode_list[:40]
        [
            episode.update({"trakt_show_id": self.show_trakt_id})
            for episode in episode_list
        ]

        playlist = self.list_builder.mixed_episode_builder(
            episode_list, smart_play=True
        )

        window.set_text(g.get_language_string(30064))

        for episode in playlist:
            if episode is not None:
                g.PLAYLIST.add(url=episode[0], listitem=episode[1])

        window.close()
        del window

        g.PLAYLIST.shuffle()
        xbmc.Player().play(g.PLAYLIST)

    def play_from_random_point(self):
        """
        Select a random episode for show and plays from that point onwards
        :return:
        :rtype:
        """

        import random

        g.PLAYLIST.clear()

        season_id = random.choice(self.get_season_info())["trakt_id"]
        playlist = self.list_builder.episode_list_builder(
            self.show_trakt_id, trakt_season=season_id, smart_play=True
        )
        random_episode = random.randint(0, len(playlist) - 1)
        playlist = playlist[random_episode]
        g.PLAYLIST.add(url=playlist[0], listitem=playlist[1])
        xbmc.Player().play(g.PLAYLIST)

    def create_single_item_playlist_from_info(self):
        g.cancel_playback()
        name = self.item_information["info"]["title"]
        item = g.add_directory_item(
            name,
            action="getSources",
            menu_item=self.item_information,
            action_args=tools.construct_action_args(self.item_information),
            bulk_add=True,
            is_playable=True,
            )
        g.PLAYLIST.add(url=g.BASE_URL + "/?" + g.PARAM_STRING, listitem=item[1])
        return g.PLAYLIST

    def playlist_present_check(self, ignore_setting=False):
        """
        Confirms if a playlist is currently present. If not or playlist is for a different item, clear current list
        and build a new one
        :param ignore_setting: Force playlist building if setting is disabled
        :type ignore_setting: bool
        :return: Playlist if playlist is present else False
        :rtype: any
        """
        if g.get_bool_setting("smartplay.playlistcreate") or ignore_setting:

            if not self.item_information["info"]["mediatype"] == "episode":
                g.log("Movie playback requested, clearing playlist")
                g.PLAYLIST.clear()
                return False

            playlist_uris = [
                g.PLAYLIST[i].getPath()  # pylint: disable=unsubscriptable-object
                for i in range(g.PLAYLIST.size())
            ]

            # Check to see if we are just starting playback and kodi has created a playlist
            if len(playlist_uris) == 1 and playlist_uris[0].split('/')[-1].lstrip('?') == g.PARAM_STRING:
                return False

            if g.PLAYLIST.getposition() == -1:
                return self.create_single_item_playlist_from_info()

            if [i for i in playlist_uris if g.ADDON_NAME.lower() not in i]:
                g.log("Cleaning up other addon items from playlsit", "debug")
                playlist_uris = []
                
            action_args = [
                g.legacy_action_args_converter(
                    g.legacy_params_converter(
                        dict(tools.parse_qsl(i.split("?")[-1]))
                        )
                    )["action_args"]
                for i in playlist_uris]
  
            show_ids = set(tools.deconstruct_action_args(i).get('trakt_show_id') for i in action_args)

            if len(show_ids) > 1 and self.show_trakt_id not in show_ids:
                g.log("Cleaning up items from other shows", "debug")
                playlist_uris = []

            if (len(playlist_uris) == 0 or
                (len(playlist_uris) > 1 and not any(g.PARAM_STRING in i for i in playlist_uris))) or \
                    g.PLAYLIST.getposition() == -1:
                return self.create_single_item_playlist_from_info()

        return False

    def is_season_final(self):
        """
        Checks if episode in question is the final for the season
        :return: bool
        :rtype: True if last episode of season, else False
        """
        season = [i for i in self.get_season_info()
                  if int(self.item_information["info"]["season"]) == int(i["info"]["season"])][0]

        if self.item_information["info"]["episode"] == season["episode_count"]:
            return True
        else:
            return False

    @staticmethod
    def handle_resume_prompt(resume_switch, force_resume_off=False, force_resume_on=False, force_resume_check=False):
        """
        Handles displaying of resume prompt for item if required
        :param resume_switch: Resume param from arg string
        :type resume_switch: any
        :param force_resume_off: Disable resuming of item
        :type force_resume_off: bool
        :param force_resume_on: Force try resuming item
        :type force_resume_on: bool
        :param force_resume_check: Force a database check for item resume point
        :type force_resume_check: bool
        :return: Resume time in seconds for item
        :rtype: int
        """
        bookmark_style = g.get_int_setting("general.bookmarkstyle")

        if force_resume_check and not resume_switch:
            from resources.lib.database.trakt_sync.bookmark import TraktSyncDatabase

            trakt_id = g.REQUEST_PARAMS.get("action_args").get("trakt_id")

            bookmark = TraktSyncDatabase().get_bookmark(trakt_id)
            if bookmark:
                g.log("bookmark: {}".format(bookmark))
                resume_switch = bookmark["resume_time"]

        if (
            g.PLAYLIST.size() <= 1
            and resume_switch is not None
            and bookmark_style != 2
            and not force_resume_off
        ):

            if bookmark_style == 0 and not force_resume_on:
                import datetime

                selection = xbmcgui.Dialog().contextmenu(
                    [
                        "{} {}".format(
                            g.get_language_string(30059),
                            datetime.timedelta(seconds=int(resume_switch)),
                        ),
                        g.get_language_string(30331),
                    ]
                )
                if selection == -1:
                    g.cancel_playback()
                    sys.exit()
                elif selection != 0:
                    resume_switch = None
        else:
            resume_switch = None

        return resume_switch

    def _get_window(self):
        if self.display_style == 0:
            # not sure about this one either
            return PersistentBackground(
                *SkinManager().confirm_skin_path("persistent_background.xml"),
                item_information=self.item_information
            )
        else:
            return BackgroundWindowAdapter()
class Menus:
    def __init__(self):
        self.trakt = TraktAPI()
        self.movies_database = movies.TraktSyncDatabase()
        self.list_builder = ListBuilder()
        self.page_limit = g.get_int_setting("item.limit")
        self.page_start = (g.PAGE-1)*self.page_limit
        self.page_end = g.PAGE*self.page_limit

    ######################################################
    # MENUS
    ######################################################

    @trakt_auth_guard
    def on_deck_movies(self):
        hidden_movies = HiddenDatabase().get_hidden_items("progress_watched", "movies")
        bookmark_sync = BookmarkDatabase()
        bookmarked_items = [
            i
            for i in bookmark_sync.get_all_bookmark_items("movie")
            if i["trakt_id"] not in hidden_movies
        ][self.page_start:self.page_end]
        self.list_builder.movie_menu_builder(bookmarked_items)

    @staticmethod
    def discover_movies():
        g.add_directory_item(
            g.get_language_string(30004),
            action="genericEndpoint",
            mediatype="movies",
            endpoint="popular",
            description=g.get_language_string(30429),
        )
        g.add_directory_item(
            g.get_language_string(30380),
            action="moviePopularRecent",
            description=g.get_language_string(30430),
        )
        if g.get_setting("trakt.auth"):
            g.add_directory_item(
                g.get_language_string(30005),
                action="moviesRecommended",
                description=g.get_language_string(30431),
            )
        g.add_directory_item(
            g.get_language_string(30006),
            action="genericEndpoint",
            mediatype="movies",
            endpoint="trending",
            description=g.get_language_string(30432),
        )
        g.add_directory_item(
            g.get_language_string(30381),
            action="movieTrendingRecent",
            description=g.get_language_string(30433),
        )
        g.add_directory_item(
            g.get_language_string(30007),
            action="genericEndpoint",
            mediatype="movies",
            endpoint="played",
            description=g.get_language_string(30434),
        )
        g.add_directory_item(
            g.get_language_string(30008),
            action="genericEndpoint",
            mediatype="movies",
            endpoint="watched",
            description=g.get_language_string(30435),
        )
        g.add_directory_item(
            g.get_language_string(30009),
            action="genericEndpoint",
            mediatype="movies",
            endpoint="collected",
            description=g.get_language_string(30436),
        )
        g.add_directory_item(
            g.get_language_string(30386),
            action="TrendingLists",
            mediatype="movies",
            description=g.get_language_string(30437),
        )
        g.add_directory_item(
            g.get_language_string(30388),
            action="PopularLists",
            mediatype="movies",
            description=g.get_language_string(30438),
        )
        if not g.get_bool_setting("general.hideUnAired"):
            g.add_directory_item(
                g.get_language_string(30010),
                action="genericEndpoint",
                mediatype="movies",
                endpoint="anticipated",
                description=g.get_language_string(30439),
            )
        g.add_directory_item(
            g.get_language_string(30012),
            action="genericEndpoint",
            mediatype="movies",
            endpoint="boxoffice",
            description=g.get_language_string(30440),
        )
        g.add_directory_item(
            g.get_language_string(30011),
            action="moviesUpdated",
            description=g.get_language_string(30441),
        )
        g.add_directory_item(
            g.get_language_string(30043),
            action="movieGenres",
            description=g.get_language_string(30442),
        )
        g.add_directory_item(
            g.get_language_string(30188),
            action="movieYears",
            description=g.get_language_string(30443),
        )
        g.add_directory_item(
            g.get_language_string(30212),
            action="movieByActor",
            description=g.get_language_string(30408),
        )
        if not g.get_bool_setting("searchHistory"):
            g.add_directory_item(
                g.get_language_string(30013),
                action="moviesSearch",
                description=g.get_language_string(30404),
            )
        else:
            g.add_directory_item(
                g.get_language_string(30013),
                action="moviesSearchHistory",
                description=g.get_language_string(30406),
            )
        g.close_directory(g.CONTENT_FOLDER)

    @staticmethod
    @trakt_auth_guard
    def my_movies():
        g.add_directory_item(
            g.get_language_string(30044),
            action="onDeckMovies",
            description=g.get_language_string(30444),
        )
        g.add_directory_item(
            g.get_language_string(30014),
            action="moviesMyCollection",
            description=g.get_language_string(30445),
        )
        g.add_directory_item(
            g.get_language_string(30015),
            action="moviesMyWatchlist",
            description=g.get_language_string(30446),
        )
        g.add_directory_item(
            g.get_language_string(30045),
            action="myTraktLists",
            mediatype="movies",
            description=g.get_language_string(30447),
        )
        g.add_directory_item(
            g.get_language_string(30384),
            action="myLikedLists",
            mediatype="movies",
            description=g.get_language_string(30448),
        )
        g.add_directory_item(
            g.get_language_string(30357),
            action="myWatchedMovies",
            description=g.get_language_string(30449),
        )
        g.close_directory(g.CONTENT_FOLDER)

    def generic_endpoint(self, endpoint):
        trakt_list = self.movies_database.extract_trakt_page(
            "movies/{}".format(endpoint), extended="full", page=g.PAGE
        )
        self.list_builder.movie_menu_builder(trakt_list)

    def movie_popular_recent(self):
        year_range = "{}-{}".format(
            datetime.datetime.now().year - 1, datetime.datetime.now().year
        )
        trakt_list = self.movies_database.extract_trakt_page(
            "movies/popular", years=year_range, page=g.PAGE, extended="full"
        )
        self.list_builder.movie_menu_builder(trakt_list)

    def movie_trending_recent(self):
        year_range = "{}-{}".format(
            datetime.datetime.now().year - 1, datetime.datetime.now().year
        )
        trakt_list = self.movies_database.extract_trakt_page(
            "movies/trending", years=year_range, page=g.PAGE, extended="full"
        )
        self.list_builder.movie_menu_builder(trakt_list)

    @trakt_auth_guard
    def my_movie_collection(self):
        paginate = not g.get_bool_setting("general.paginatecollection")
        sort = "title" if paginate else False
        self.list_builder.movie_menu_builder(
            movies.TraktSyncDatabase().get_collected_movies(g.PAGE),
            no_paging=paginate,
            sort=sort,
        )

    @trakt_auth_guard
    def my_movie_watchlist(self):
        paginate = not g.get_bool_setting("general.paginatetraktlists")
        trakt_list = self.movies_database.extract_trakt_page(
            "users/me/watchlist/movies",
            extended="full",
            page=g.PAGE,
            ignore_cache=True,
            no_paging=paginate,
            pull_all=True,
        )
        self.list_builder.movie_menu_builder(trakt_list, no_paging=paginate)

    @trakt_auth_guard
    def movies_recommended(self):
        trakt_list = self.movies_database.extract_trakt_page(
            "recommendations/movies",
            ignore_collected=True,
            extended="full",
            page=g.PAGE,
        )
        self.list_builder.movie_menu_builder(trakt_list)

    def movies_updated(self):
        import datetime

        date = datetime.date.today() - datetime.timedelta(days=31)
        date = date.strftime(g.DATE_FORMAT)
        trakt_list = self.movies_database.extract_trakt_page(
            "movies/updates/{}".format(date), page=g.PAGE, extended="full"
        )
        self.list_builder.movie_menu_builder(trakt_list)

    @staticmethod
    def movies_search_history():
        history = SearchHistory().get_search_history("movie")
        g.add_directory_item(
            g.get_language_string(30203),
            action="moviesSearch",
            description=g.get_language_string(30404),
        )
        g.add_directory_item(
            g.get_language_string(30202),
            action="clearSearchHistory",
            mediatype="movie",
            is_folder=False,
            description=g.get_language_string(30414),
        )

        for i in history:
            g.add_directory_item(i, action="moviesSearchResults", action_args=i)
        g.close_directory(g.CONTENT_FOLDER)

    def movies_search(self, query=None):
        if query is None:
            k = xbmc.Keyboard("", g.get_language_string(30013))
            k.doModal()
            query = k.getText() if k.isConfirmed() else None
            del k
            if not query:
                g.cancel_directory()
                return

        query = g.decode_py2(query)
        if g.get_bool_setting("searchHistory"):
            SearchHistory().add_search_history("movie", query)
        query = g.deaccent_string(g.display_string(query))
        query = tools.quote(query)

        self.movies_search_results(query)

    def movies_search_results(self, query):
        trakt_list = self.trakt.get_json_paged(
            "search/movie", query=tools.unquote(query), extended="full", page=g.PAGE
        )
        if not trakt_list:
            g.cancel_directory()
            return
        self.list_builder.movie_menu_builder(
            [
                movie
                for movie in trakt_list
                if float(movie["trakt_object"]["info"]["score"]) > 0
            ],
            hide_watched=False,
            hide_unaired=False,
        )

    def movies_related(self, args):
        trakt_list = self.movies_database.extract_trakt_page(
            "movies/{}/related".format(args), page=g.PAGE, extended="full"
        )
        self.list_builder.movie_menu_builder(trakt_list)

    @staticmethod
    def movies_years():
        from datetime import datetime

        year = int(datetime.today().year)
        years = []
        for i in range(year - 100, year + 1):
            years.append(i)
        years = sorted(years, reverse=True)
        [
            g.add_directory_item(str(i), action="movieYearsMovies", action_args=i)
            for i in years
        ]
        g.close_directory(g.CONTENT_FOLDER)

    def movie_years_results(self, year):
        trakt_list = self.movies_database.extract_trakt_page(
            "movies/popular", years=year, page=g.PAGE, extended="full"
        )
        self.list_builder.movie_menu_builder(trakt_list)

    def movies_by_actor(self, actor):
        if actor is None:
            k = xbmc.Keyboard("", g.get_language_string(30013))
            k.doModal()
            query = k.getText() if k.isConfirmed() else None
            if not query:
                g.cancel_directory()
                return
        else:
            query = tools.unquote(actor)

        if g.get_bool_setting("searchHistory"):
            SearchHistory().add_search_history("movieActor", query)
        query = g.deaccent_string(query)
        query = query.replace(" ", "-")
        query = tools.quote_plus(query)

        self.list_builder.movie_menu_builder(
            self.trakt.get_json_paged(
                "people/{}/movies".format(query), extended="full", page=g.PAGE
            ),
            hide_watched=False,
            hide_unaired=False,
        )

    def movies_genres(self):
        g.add_directory_item(g.get_language_string(30046), action="movieGenresGet")
        genres = self.trakt.get_json("genres/movies")
        if genres is None:
            g.cancel_directory()
            return
        for i in genres:
            g.add_directory_item(
                i["name"], action="movieGenresGet", action_args=i["slug"]
            )
        g.close_directory(g.CONTENT_GENRES)

    def movies_genre_list(self, args):
        trakt_endpoint = (
            "trending"
            if g.get_int_setting("general.genres.endpoint") == 0
            else "popular"
        )
        if args is None:
            genre_display_list = []
            genres = self.trakt.get_json("genres/movies")
            for genre in genres:
                genre_display_list.append(genre["name"])
            genre_multiselect = xbmcgui.Dialog().multiselect(
                "{}: {}".format(g.ADDON_NAME, g.get_language_string(30330)),
                genre_display_list,
            )
            if genre_multiselect is None:
                return
            genre_string = ",".join([genres[i]["slug"] for i in genre_multiselect])
        else:
            genre_string = tools.unquote(args)

        trakt_list = self.trakt.get_json_cached(
            "movies/{}".format(trakt_endpoint),
            page=g.PAGE,
            extended="full"
        )

        if trakt_list is None:
            g.cancel_directory()
            return

        self.list_builder.movie_menu_builder(trakt_list, next_args=genre_string)

    @trakt_auth_guard
    def my_watched_movies(self):
        watched_movies = movies.TraktSyncDatabase().get_watched_movies(g.PAGE)
        self.list_builder.movie_menu_builder(watched_movies)
Exemplo n.º 4
0
class Menus:
    def __init__(self):
        self.trakt = TraktAPI()
        self.language_code = g.get_language_code()
        self.trakt_database = TraktSyncDatabase()
        self.hidden_database = hidden.TraktSyncDatabase()
        self.bookmark_database = bookmark.TraktSyncDatabase()
        self.shows_database = shows.TraktSyncDatabase()
        self.list_builder = ListBuilder()
        self.page_limit = g.get_int_setting("item.limit")

    ######################################################
    # MENUS
    ######################################################

    @trakt_auth_guard
    def on_deck_shows(self):
        hidden_shows = self.hidden_database.get_hidden_items(
            "progress_watched", "tvshow")
        bookmarked_items = [
            i for i in self.bookmark_database.get_all_bookmark_items("episode")
            if i["trakt_show_id"] not in hidden_shows
        ][:self.page_limit]
        self.list_builder.mixed_episode_builder(bookmarked_items)

    @staticmethod
    def discover_shows():

        g.add_directory_item(
            g.get_language_string(30004),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="popular",
            description=g.get_language_string(30450),
        )
        g.add_directory_item(
            g.get_language_string(30378),
            action="showsPopularRecent",
            description=g.get_language_string(30451),
        )
        if g.get_setting("trakt.auth"):
            g.add_directory_item(
                g.get_language_string(30005),
                action="showsRecommended",
                description=g.get_language_string(30452),
            )
        g.add_directory_item(
            g.get_language_string(30006),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="trending",
            description=g.get_language_string(30453),
        )
        g.add_directory_item(
            g.get_language_string(30379),
            action="showsTrendingRecent",
            description=g.get_language_string(30454),
        )
        g.add_directory_item(
            g.get_language_string(30047),
            action="showsNew",
            description=g.get_language_string(30455),
        )
        g.add_directory_item(
            g.get_language_string(30007),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="played",
            description=g.get_language_string(30456),
        )
        g.add_directory_item(
            g.get_language_string(30008),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="watched",
            description=g.get_language_string(30457),
        )
        g.add_directory_item(
            g.get_language_string(30009),
            action="genericEndpoint",
            mediatype="shows",
            endpoint="collected",
            description=g.get_language_string(30458),
        )
        g.add_directory_item(
            g.get_language_string(30385),
            action="TrendingLists",
            mediatype="shows",
            description=g.get_language_string(30459),
        )
        g.add_directory_item(
            g.get_language_string(30387),
            action="PopularLists",
            mediatype="shows",
            description=g.get_language_string(30460),
        )
        if not g.get_bool_setting("general.hideUnAired"):
            g.add_directory_item(
                g.get_language_string(30010),
                action="genericEndpoint",
                mediatype="shows",
                endpoint="anticipated",
                description=g.get_language_string(30461),
            )

        g.add_directory_item(
            g.get_language_string(30011),
            action="showsUpdated",
            description=g.get_language_string(30462),
        )
        g.add_directory_item(
            g.get_language_string(30186),
            action="showsNetworks",
            description=g.get_language_string(30463),
        )
        g.add_directory_item(
            g.get_language_string(30188),
            action="showYears",
            description=g.get_language_string(30464),
        )
        g.add_directory_item(
            g.get_language_string(30043),
            action="tvGenres",
            description=g.get_language_string(30465),
        )
        g.add_directory_item(
            g.get_language_string(30212),
            action="showsByActor",
            description=g.get_language_string(30466),
        )
        if not g.get_bool_setting("searchHistory"):
            g.add_directory_item(
                g.get_language_string(30013),
                action="showsSearch",
                description=g.get_language_string(30405),
            )
        else:
            g.add_directory_item(
                g.get_language_string(30013),
                action="showsSearchHistory",
                description=g.get_language_string(30407),
            )
        g.close_directory(g.CONTENT_FOLDER)

    @staticmethod
    @trakt_auth_guard
    def my_shows():
        g.add_directory_item(
            g.get_language_string(30044),
            action="onDeckShows",
            description=g.get_language_string(30467),
        )
        g.add_directory_item(
            g.get_language_string(30014),
            action="showsMyCollection",
            description=g.get_language_string(30468),
        )
        g.add_directory_item(
            g.get_language_string(30015),
            action="showsMyWatchlist",
            description=g.get_language_string(30469),
        )
        g.add_directory_item(
            g.get_language_string(30096),
            action="showsRecentlyWatched",
            description=g.get_language_string(30521),
        )
        g.add_directory_item(
            g.get_language_string(30232),
            action="showsNextUp",
            description=g.get_language_string(30470),
        )
        g.add_directory_item(
            g.get_language_string(30233),
            action="myUpcomingEpisodes",
            description=g.get_language_string(30471),
        )
        g.add_directory_item(
            g.get_language_string(30234),
            action="showsMyProgress",
            description=g.get_language_string(30472),
        )
        g.add_directory_item(
            g.get_language_string(30235),
            action="showsMyRecentEpisodes",
            description=g.get_language_string(30473),
        )
        g.add_directory_item(
            g.get_language_string(30236),
            action="myTraktLists",
            mediatype="shows",
            description=g.get_language_string(30474),
        )
        g.add_directory_item(
            g.get_language_string(30383),
            action="myLikedLists",
            mediatype="shows",
            description=g.get_language_string(30475),
        )
        g.add_directory_item(
            g.get_language_string(30356),
            action="myWatchedEpisodes",
            description=g.get_language_string(30476),
        )
        g.close_directory(g.CONTENT_FOLDER)

    def generic_endpoint(self, endpoint):
        trakt_list = self.shows_database.extract_trakt_page(
            "shows/{}".format(endpoint), page=g.PAGE, extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_popular_recent(self):
        year_range = "{}-{}".format(datetime.datetime.now().year - 1,
                                    datetime.datetime.now().year)
        trakt_list = self.shows_database.extract_trakt_page("shows/popular",
                                                            years=year_range,
                                                            page=g.PAGE,
                                                            extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_trending_recent(self):
        year_range = "{}-{}".format(datetime.datetime.now().year - 1,
                                    datetime.datetime.now().year)
        trakt_list = self.shows_database.extract_trakt_page("shows/trending",
                                                            years=year_range,
                                                            page=g.PAGE,
                                                            extended="full")
        self.list_builder.show_list_builder(trakt_list)

    @trakt_auth_guard
    def my_shows_collection(self):
        paginate = not g.get_bool_setting("general.paginatecollection")
        sort = "title" if paginate else False
        trakt_list = self.trakt_database.get_collected_shows(g.PAGE)
        self.list_builder.show_list_builder(trakt_list,
                                            no_paging=paginate,
                                            sort=sort)

    @trakt_auth_guard
    def my_shows_watchlist(self):
        paginate = not g.get_bool_setting("general.paginatetraktlists")
        trakt_list = self.shows_database.extract_trakt_page(
            "users/me/watchlist/shows",
            extended="full",
            page=g.PAGE,
            ignore_cache=True,
            no_paging=paginate,
            pull_all=True,
        )
        self.list_builder.show_list_builder(trakt_list, no_paging=paginate)

    @trakt_auth_guard
    def my_show_progress(self):
        trakt_list = self.trakt_database.get_unfinished_collected_shows(g.PAGE)
        self.list_builder.show_list_builder(trakt_list)

    @trakt_auth_guard
    def shows_recommended(self):
        trakt_list = self.shows_database.extract_trakt_page(
            "recommendations/shows", ignore_collected=True, extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_new(self):
        hidden_items = self.hidden_database.get_hidden_items(
            "recommendations", "shows")
        date_string = datetime.datetime.today() - datetime.timedelta(days=29)
        trakt_list = self.trakt.get_json(
            "calendars/all/shows/new/{}/30".format(
                date_string.strftime("%d-%m-%Y")),
            languages=self.language_code,
            extended="full",
        )
        trakt_list = [
            i for i in trakt_list if i["trakt_show_id"] not in hidden_items
        ]
        self.list_builder.show_list_builder(trakt_list[:40])

    def shows_recently_watched(self):
        self.list_builder.show_list_builder(
            self.trakt_database.get_recently_watched_shows())

    def my_next_up(self):
        episodes = self.trakt_database.get_nextup_episodes(
            g.get_int_setting("nextup.sort") == 1)
        self.list_builder.mixed_episode_builder(episodes)

    @trakt_auth_guard
    def my_recent_episodes(self):
        hidden_shows = self.hidden_database.get_hidden_items(
            "calendar", "shows")
        date_string = datetime.datetime.today() - datetime.timedelta(days=13)
        trakt_list = self.trakt.get_json("calendars/my/shows/{}/14".format(
            date_string.strftime("%d-%m-%Y"), extended="full"))
        trakt_list = sorted(
            [i for i in trakt_list if i["trakt_show_id"] not in hidden_shows],
            key=lambda t: t["first_aired"],
            reverse=True,
        )

        self.list_builder.mixed_episode_builder(trakt_list)

    @trakt_auth_guard
    def my_upcoming_episodes(self):
        tomorrow = (datetime.date.today() +
                    datetime.timedelta(days=1)).strftime("%Y-%m-%d")
        upcoming_episodes = self.trakt.get_json(
            "calendars/my/shows/{}/30".format(tomorrow),
            extended="full")[:self.page_limit]
        self.list_builder.mixed_episode_builder(upcoming_episodes,
                                                prepend_date=True,
                                                no_paging=True,
                                                hide_unaired=False)

    def shows_networks(self):
        trakt_list = self.trakt.get_json_cached("networks")
        list_items = []
        for i in trakt_list:
            list_items.append(
                g.add_directory_item(
                    i["name"],
                    action="showsNetworkShows",
                    action_args=i["name"],
                    bulk_add=True,
                ))
        xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, list_items,
                                     len(list_items))
        g.close_directory(g.CONTENT_FOLDER)

    def shows_networks_results(self, network):
        trakt_list = self.shows_database.extract_trakt_page("shows/popular",
                                                            networks=network,
                                                            page=g.PAGE,
                                                            extended="full")
        self.list_builder.show_list_builder(trakt_list)
        g.close_directory(g.CONTENT_SHOW)

    def shows_updated(self):
        date = datetime.date.today() - datetime.timedelta(days=31)
        date = date.strftime("%Y-%m-%d")
        trakt_list = self.trakt.get_json("shows/updates/{}".format(date),
                                         extended="full")
        self.list_builder.show_list_builder(trakt_list, no_paging=True)

    @staticmethod
    def shows_search_history():
        history = SearchHistory().get_search_history("tvshow")
        g.add_directory_item(
            g.get_language_string(30204),
            action="showsSearch",
            description=g.get_language_string(30405),
        )
        g.add_directory_item(
            g.get_language_string(30202),
            action="clearSearchHistory",
            mediatype="tvshow",
            is_folder=False,
            description=g.get_language_string(30202),
        )
        for i in history:
            g.add_directory_item(
                i,
                action="showsSearchResults",
                action_args=tools.construct_action_args(i),
            )
        g.close_directory(g.CONTENT_FOLDER)

    def shows_search(self, query=None):
        if not query:
            k = xbmc.Keyboard("", g.get_language_string(30013))
            k.doModal()
            query = k.getText() if k.isConfirmed() else None
            del k
            if not query:
                g.cancel_directory()
                return

        query = g.decode_py2(query)
        if g.get_bool_setting("searchHistory"):
            SearchHistory().add_search_history("tvshow", query)
        query = g.deaccent_string(g.display_string(query))
        self.shows_search_results(query)

    def shows_search_results(self, query):
        trakt_list = self.trakt.get_json_paged(
            "search/show",
            query=tools.unquote(query),
            page=g.PAGE,
            extended="full",
            field="title",
        )
        self.list_builder.show_list_builder([
            show for show in trakt_list
            if float(show["trakt_object"]["info"]["score"]) > 0
        ])

    def shows_by_actor(self, actor):
        if not actor:
            k = xbmc.Keyboard("", g.get_language_string(30013))
            k.doModal()
            query = k.getText() if k.isConfirmed() else None
            del k
            if not query:
                g.cancel_directory()
                return
        else:
            query = tools.unquote(actor)

        if g.get_bool_setting("searchHistory"):
            SearchHistory().add_search_history("showActor", query)
        query = g.deaccent_string(query)
        query = query.replace(" ", "-")
        query = tools.quote_plus(query)

        self.list_builder.show_list_builder(
            self.trakt.get_json_paged("people/{}/shows".format(query),
                                      extended="full",
                                      page=g.PAGE),
            hide_watched=False,
            hide_unaired=False,
        )

    def show_seasons(self, args):
        self.list_builder.season_list_builder(args["trakt_id"], no_paging=True)

    def season_episodes(self, args):
        self.list_builder.episode_list_builder(args["trakt_show_id"],
                                               args["trakt_id"],
                                               no_paging=True)

    def flat_episode_list(self, args):
        self.list_builder.episode_list_builder(args["trakt_id"],
                                               no_paging=True)

    def shows_genres(self):
        g.add_directory_item(g.get_language_string(30046),
                             action="showGenresGet")
        genres = self.trakt.get_json_cached("genres/shows", extended="full")

        if genres is None:
            g.cancel_directory()
            return

        for i in genres:
            g.add_directory_item(i["name"],
                                 action="showGenresGet",
                                 action_args=i["slug"])
        g.close_directory(g.CONTENT_GENRES)

    def shows_genre_list(self, args):
        trakt_endpoint = ("trending"
                          if g.get_int_setting("general.genres.endpoint") == 0
                          else "popular")
        if args is None:
            genre_display_list = []
            genre_string = ""
            genres = self.trakt.get_json_cached("genres/shows")

            for genre in genres:
                genre_display_list.append(genre["name"])
            genre_multiselect = xbmcgui.Dialog().multiselect(
                "{}: {}".format(g.ADDON_NAME, g.get_language_string(30330)),
                genre_display_list,
            )

            if genre_multiselect is None:
                return
            for selection in genre_multiselect:
                genre_string += ", {}".format(genres[selection]["slug"])
            genre_string = genre_string[2:]

        else:
            genre_string = args

        trakt_list = self.shows_database.extract_trakt_page(
            "shows/{}".format(trakt_endpoint),
            genres=genre_string,
            page=g.PAGE,
            extended="full",
        )
        if trakt_list is None:
            g.cancel_directory()
            return

        self.list_builder.show_list_builder(trakt_list)

    def shows_related(self, args):
        trakt_list = self.trakt.get_json("shows/{}/related".format(args),
                                         extended="full")
        self.list_builder.show_list_builder(trakt_list)

    def shows_years(self, year=None):
        if year is None:
            current_year = int(
                tools.parse_datetime(
                    datetime.datetime.today().strftime("%Y-%m-%d")).year)
            all_years = reversed(
                [year for year in range(1900, current_year + 1)])
            menu_items = []
            for year in all_years:
                menu_items.append(
                    g.add_directory_item(str(year),
                                         action="showYears",
                                         action_args=year,
                                         bulk_add=True))
            xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, menu_items,
                                         len(menu_items))
            g.close_directory(g.CONTENT_SHOW)
        else:
            trakt_list = self.trakt.get_json("shows/popular",
                                             years=year,
                                             page=g.PAGE,
                                             extended="full")
            self.list_builder.show_list_builder(trakt_list)

    @trakt_auth_guard
    def my_watched_episode(self):
        watched_episodes = self.trakt_database.get_watched_episodes(g.PAGE)
        self.list_builder.mixed_episode_builder(watched_episodes)
Exemplo n.º 5
0
class TraktSyncDatabase(Database):
    def __init__(self, ):
        super(TraktSyncDatabase, self).__init__(g.TRAKT_SYNC_DB_PATH, schema,
                                                migrate_db_lock)
        self.metadataHandler = MetadataHandler()
        self.trakt_api = TraktAPI()

        self.activities = {}
        self.item_list = []
        self.base_date = "1970-01-01T00:00:00"
        self.task_queue = ThreadPool()
        self.mill_task_queue = ThreadPool()
        self.parent_task_queue = ThreadPool()
        self.refresh_activities()

        # If you make changes to the required meta in any indexer that is cached in this database
        # You will need to update the below version number to match the new addon version
        # This will ensure that the metadata required for operations is available

        self.last_meta_update = "2.0.0"
        if self.activities is None:
            self.clear_all_meta(False)
            self.set_base_activities()

        if self.activities is not None:
            self._check_database_version()

        self.notification_prefix = "{}: Trakt".format(g.ADDON_NAME)
        self.hide_unaired = g.get_bool_setting("general.hideUnAired")
        self.hide_specials = g.get_bool_setting("general.hideSpecials")
        self.hide_watched = g.get_bool_setting("general.hideWatched")
        self.date_delay = g.get_bool_setting("general.datedelay")
        self.page_limit = g.get_int_setting("item.limit")

    def clear_specific_item_meta(self, trakt_id, media_type):
        if media_type == "tvshow":
            media_type = "shows"
        elif media_type == "show":
            media_type = "shows"
        elif media_type == "movie":
            media_type = "movies"
        elif media_type == "episode":
            media_type = "episodes"
        elif media_type == "season":
            media_type = "seasons"

        if media_type not in ["shows", "movies", "seasons", "episodes"]:
            raise InvalidMediaTypeException(media_type)

        self.execute_sql("DELETE from {}_meta where id=?".format(media_type),
                         (trakt_id, ))
        self.execute_sql(
            "UPDATE {} SET info=null, art=null, cast=null, meta_hash=null where trakt_id=?"
            "".format(media_type),
            (trakt_id, ),
        )

    @staticmethod
    def _get_datetime_now():
        return datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z")

    def refresh_activities(self):
        self.activities = self.execute_sql(
            "SELECT * FROM activities WHERE sync_id=1").fetchone()

    def set_base_activities(self):
        self.execute_sql(
            "INSERT OR REPLACE INTO activities(sync_id, seren_version, trakt_username) VALUES(1, ?, ?)",
            (self.last_meta_update, g.get_setting("trakt.username")),
        )
        self.activities = self.execute_sql(
            "SELECT * FROM activities WHERE sync_id=1").fetchone()

    def _check_database_version(self):
        # If we are updating from a database prior to database versioning, we must clear the meta data
        # Migrate from older versions before trakt username tracking
        if tools.compare_version_numbers(self.activities["seren_version"],
                                         self.last_meta_update):
            g.log("Rebuilding Trakt Sync Database Version")
            xbmcgui.Dialog().ok(g.ADDON_NAME, g.get_language_string(30363))
            try:
                self.re_build_database(True)
            except:
                self.rebuild_database()

    def flush_activities(self, clear_meta=False):
        if clear_meta:
            self.clear_all_meta()
        self.execute_sql("DELETE FROM activities")
        self.set_base_activities()

    def clear_user_information(self, notify=True):
        username = self.activities["trakt_username"]
        self.execute_sql(
            [
                "UPDATE episodes SET watched=?",
                "UPDATE episodes SET collected=?",
                "UPDATE movies SET watched=?",
                "UPDATE movies SET collected=?",
                "UPDATE shows SET unwatched_episodes=?",
                "UPDATE shows SET watched_episodes=?",
                "UPDATE seasons SET unwatched_episodes=?",
                "UPDATE seasons SET watched_episodes=?",
            ],
            (0, ),
        )
        self.execute_sql(
            [
                "UPDATE episodes SET last_watched_at=?",
                "UPDATE movies SET last_watched_at=?",
            ],
            (None, ),
        )
        self.execute_sql([
            "DELETE from bookmarks WHERE 1=1",
            "DELETE from hidden WHERE 1=1",
        ])
        self.execute_sql("DELETE from lists WHERE username=?", (username, ))
        self.set_trakt_user("")
        self.set_base_activities()
        if notify:
            g.notification(self.notification_prefix,
                           g.get_language_string(30297),
                           time=5000)

    def set_trakt_user(self, trakt_username):
        g.log("Setting Trakt Username: {}".format(trakt_username))
        self.execute_sql("UPDATE activities SET trakt_username=?",
                         (trakt_username, ))

    def clear_all_meta(self, notify=True):
        if notify:
            confirm = xbmcgui.Dialog().yesno(g.ADDON_NAME,
                                             g.get_language_string(30201))
            if confirm == 0:
                return

        self.execute_sql(
            [
                "UPDATE shows SET info=?, cast=?, art=?, meta_hash=?",
                "UPDATE seasons SET info=?, cast=?, art=?, meta_hash=?",
                "UPDATE episodes SET info=?, cast=?, art=?, meta_hash=?",
                "UPDATE movies SET info=?, cast=?, art=?, meta_hash=?",
            ],
            (None, None, None, None),
        )

        self.execute_sql([
            "DELETE FROM movies_meta",
            "DELETE FROM shows_meta",
            "DELETE FROM seasons_meta",
            "DELETE FROM episodes_meta",
        ])
        if notify:
            g.notification(self.notification_prefix,
                           g.get_language_string(30298),
                           time=5000)

    def re_build_database(self, silent=False):
        if not silent:
            confirm = xbmcgui.Dialog().yesno(g.ADDON_NAME,
                                             g.get_language_string(30201))
            if confirm == 0:
                return

        self.clear_all_meta(False)
        self.clear_user_information(False)
        self.rebuild_database()
        self.set_base_activities()
        self.refresh_activities()

        from resources.lib.database.trakt_sync import activities

        sync_errors = activities.TraktSyncDatabase().sync_activities(silent)

        if sync_errors:
            g.notification(self.notification_prefix,
                           g.get_language_string(30364),
                           time=5000)
        elif sync_errors is None:
            self.refresh_activities()
        else:
            g.notification(self.notification_prefix,
                           g.get_language_string(30299),
                           time=5000)

    def filter_items_that_needs_updating(self, requested, media_type):
        if requested is None or len(requested) == 0:
            return []
        query = """WITH requested(trakt_id, meta_hash) AS (VALUES {}) select r.trakt_id as trakt_id from requested as 
        r left join {} as db on r.trakt_id == db.trakt_id where db.trakt_id IS NULL or (db.info is null or db.art is 
        null or db.cast is null or r.meta_hash != db.meta_hash)""".format(
            ",".join([
                "({}, '{}')".format(i.get("trakt_id"),
                                    self.metadataHandler.meta_hash)
                for i in requested
            ]),
            media_type,
        )
        result = set(r["trakt_id"] for r in self.execute_sql(query).fetchall())
        return [r for r in requested if r.get("trakt_id") in result]

    def save_to_meta_table(self, items, meta_type, provider_type, id_column):
        if items is None:
            return
        sql_statement = "replace into {}_meta (id ,type, meta_hash, value) VALUES (?, ?, ?, ?)".format(
            meta_type)
        obj = None
        meta_hash = None
        if provider_type == "trakt":
            obj = MetadataHandler.trakt_object
            meta_hash = self.trakt_api.meta_hash
        elif provider_type == "tmdb":
            obj = MetadataHandler.tmdb_object
            meta_hash = self.metadataHandler.tmdb_api.meta_hash
        elif provider_type == "tvdb":
            obj = MetadataHandler.tvdb_object
            meta_hash = self.metadataHandler.tvdb_api.meta_hash
        elif provider_type == "fanart":
            obj = MetadataHandler.fanart_object
            meta_hash = self.metadataHandler.fanarttv_api.meta_hash
        elif provider_type == "omdb":
            obj = MetadataHandler.omdb_object
            meta_hash = self.metadataHandler.omdb_api.meta_hash

        if obj is None or meta_hash is None:
            raise UnsupportedProviderType(provider_type)

        self.execute_sql(
            sql_statement,
            ((i.get(id_column), provider_type, meta_hash,
              self.clean_meta(obj(i)))
             for i in items if i and obj(i) and i.get(id_column)
             and MetadataHandler.full_meta_up_to_par(meta_type, obj(i))),
        )

        for i in items:
            if i and obj(i):
                if obj(i).get("seasons"):
                    self.save_to_meta_table(i.get("seasons"), "season",
                                            provider_type, id_column)
                if obj(i).get("episodes"):
                    self.save_to_meta_table(i.get("episodes"), "episode",
                                            provider_type, id_column)

    @staticmethod
    def clean_meta(item):
        if not item:
            return None

        result = {
            "info": {
                key: value
                for key, value in item.get("info", {}).items()
                if key != "seasons" and key != "episodes"
            },
            "art": item.get("art"),
            "cast": item.get("cast"),
        }
        if not result.get("info") and not result.get("art") and not result.get(
                "cast"):
            g.log(
                "Bad Item meta discovered when cleaning - item: {}".format(
                    item),
                "error",
            )
            return None
        else:
            return result

    def insert_trakt_movies(self, movies):
        g.log("Inserting Movies into sync database: {}".format(
            [i.get("trakt_id") for i in movies]))
        get = MetadataHandler.get_trakt_info
        self.execute_sql(
            self.upsert_movie_query,
            ((
                i.get("trakt_id"),
                None,
                None,
                None,
                get(i, "collected"),
                get(i, "watched"),
                tools.validate_date(get(i, "aired")),
                tools.validate_date(get(i, "dateadded")),
                get(i, "tmdb_id"),
                get(i, "imdb_id"),
                None,
                self._create_args(i),
                tools.validate_date(get(i, "collected_at")),
                tools.validate_date(get(i, "last_watched_at")),
                i.get("trakt_id"),
            ) for i in movies),
        )
        self.save_to_meta_table(movies, "movies", "trakt", "trakt_id")

    def insert_trakt_shows(self, shows):
        g.log("Inserting Shows into sync database: {}".format(
            [i.get("trakt_id") for i in shows]))
        get = MetadataHandler.get_trakt_info
        self.execute_sql(
            self.upsert_show_query,
            ((
                i.get("trakt_id"),
                None,
                None,
                None,
                tools.validate_date(get(i, "aired")),
                tools.validate_date(get(i, "dateadded")),
                get(i, "tmdb_id"),
                get(i, "tvdb_id"),
                get(i, "imdb_id"),
                None,
                get(i, "season_count"),
                get(i, "episode_count"),
                self._create_args(i),
                get(i, "is_airing"),
                i.get("trakt_id"),
            ) for i in shows),
        )
        self.save_to_meta_table(shows, "shows", "trakt", "trakt_id")

    def insert_trakt_episodes(self, episodes):
        g.log("Inserting episodes into sync database: {}".format(
            [i.get("trakt_id") for i in episodes]))
        get = MetadataHandler.get_trakt_info
        self.execute_sql(
            self.upsert_episode_query,
            ((
                i.get("trakt_id"),
                i.get("trakt_show_id"),
                i.get("trakt_season_id"),
                get(i, "playcount"),
                get(i, "collected"),
                tools.validate_date(get(i, "aired")),
                tools.validate_date(get(i, "dateadded")),
                get(i, "season"),
                get(i, "episode"),
                get(i, "tmdb_id"),
                get(i, "tvdb_id"),
                get(i, "imdb_id"),
                None,
                None,
                None,
                self._create_args(i),
                tools.validate_date(get(i, "last_watched_at")),
                tools.validate_date(get(i, "collected_at")),
                None,
                i.get("trakt_id"),
            ) for i in episodes),
        )
        self.save_to_meta_table(episodes, "episodes", "trakt", "trakt_id")

    def insert_trakt_seasons(self, seasons):
        g.log("Inserting seasons into sync database: {}".format(
            [i.get("trakt_id") for i in seasons]))
        get = MetadataHandler.get_trakt_info
        self.execute_sql(
            self.upsert_season_query,
            ((
                i.get("trakt_show_id"),
                i.get("trakt_id"),
                None,
                None,
                None,
                tools.validate_date(get(i, "aired")),
                tools.validate_date(get(i, "dateadded")),
                get(i, "tmdb_id"),
                get(i, "tvdb_id"),
                None,
                None,
                get(i, "season"),
                self._create_args(i),
                i.get("trakt_id"),
            ) for i in seasons),
        )
        self.save_to_meta_table(seasons, "seasons", "trakt", "trakt_id")

    def _mill_if_needed(self,
                        list_to_update,
                        queue_wrapper=None,
                        mill_episodes=True):
        if queue_wrapper is None:
            queue_wrapper = self._queue_mill_tasks

        query = """select s.trakt_id, CASE WHEN (agg.episode_count is NULL or 
        agg.episode_count != s.episode_count) or (agg.meta_count=0 or agg.meta_count!=s.episode_count) THEN 'True' 
        ELSE 'False' END as needs_update from shows as s left join(select s.trakt_id, count(e.trakt_id) as 
        episode_count, count(em.id) as meta_count from shows as s inner join episodes as e on s.trakt_id = 
        e.trakt_show_id left join episodes_meta as em on em.id = e.trakt_id and em.type = 'trakt' and em.meta_hash = 
        '{}' where e.season != 0 and Datetime(e.air_date) < Datetime('now') GROUP BY s.trakt_id) as agg on s.trakt_id == 
        agg.trakt_id WHERE s.trakt_id in ({})""".format(
            self.trakt_api.meta_hash,
            ",".join(
                str(i.get("trakt_show_id", i.get("trakt_id")))
                for i in list_to_update),
        )
        needs_update = self.execute_sql(query).fetchall()
        if needs_update is None or all(x["needs_update"] == "False"
                                       for x in needs_update):
            return

        g.log(
            "{} items require season milling".format(
                len([i for i in needs_update if i["needs_update"] == "True"])),
            "debug")
        self.mill_seasons([
            i for i in list_to_update
            if any(x["needs_update"] == "True" and x.get("trakt_id") == i.get(
                "trakt_show_id", i.get("trakt_id")) for x in needs_update)
        ], queue_wrapper, mill_episodes)

    def mill_seasons(self,
                     trakt_collection,
                     queue_wrapper,
                     mill_episodes=False):
        with SyncLock(
                "mill_seasons_episodes_{}".format(mill_episodes),
            {
                show.get("trakt_show_id", show.get("trakt_id"))
                for show in trakt_collection
            },
        ) as sync_lock:
            get = MetadataHandler.get_trakt_info
            queue_wrapper(self._pull_show_seasons,
                          [(i, mill_episodes) for i in sync_lock.running_ids])
            results = self.mill_task_queue.wait_completion()

            seasons = []
            episodes = []
            trakt_info = MetadataHandler.trakt_info

            for show in trakt_collection:
                extended_seasons = get(show, "seasons", [])
                for season in results.get(show.get("trakt_id"), []):
                    if self.hide_specials and get(season, "season") == 0:
                        continue

                    trakt_info(season).update(
                        {"trakt_show_id": get(show, "trakt_id")})
                    trakt_info(season).update(
                        {"tmdb_show_id": get(show, "tmdb_id")})
                    trakt_info(season).update(
                        {"tvdb_show_id": get(show, "tvdb_id")})

                    season.update({"trakt_show_id": show.get("trakt_id")})
                    season.update({"tmdb_show_id": show.get("tmdb_id")})
                    season.update({"tvdb_show_id": show.get("tvdb_id")})

                    trakt_info(season).update(
                        {"dateadded": get(show, "dateadded")})
                    trakt_info(season).update(
                        {"tvshowtitle": get(show, "title")})

                    if not get(season, "season") == 0:
                        show.update(
                            {"season_count": show.get("season_count", 0) + 1})
                        show.update({
                            'episode_count':
                            show.get("episode_count", 0) +
                            get(season, "aired_episodes", 0)
                        })
                    for episode in get(season, "episodes", []):
                        trakt_info(episode).update(
                            {"trakt_show_id": get(show, "trakt_id")})
                        trakt_info(episode).update(
                            {"tmdb_show_id": get(show, "tmdb_id")})
                        trakt_info(episode).update(
                            {"tvdb_show_id": get(show, "tvdb_id")})
                        trakt_info(episode).update(
                            {"trakt_season_id": get(season, "trakt_id")})

                        episode.update({"trakt_show_id": show.get("trakt_id")})
                        episode.update({"tmdb_show_id": show.get("tmdb_id")})
                        episode.update({"tvdb_show_id": show.get("tvdb_id")})
                        episode.update(
                            {"trakt_season_id": season.get("trakt_id")})

                        trakt_info(episode).update(
                            {"tvshowtitle": get(show, "title")})
                        for extended_season in (
                                x for x in extended_seasons
                                if get(x, "season") == get(season, "season")):
                            [
                                tools.smart_merge_dictionary(
                                    episode, extended_episode)
                                for extended_episode in
                                (x
                                 for x in get(extended_season, "episodes", [])
                                 if get(x, "episode") == get(
                                     episode, "episode"))
                            ]
                            tools.smart_merge_dictionary(
                                season, extended_season)
                        episodes.append(episode)
                    seasons.append(season)

            self.insert_trakt_seasons(
                self.filter_trakt_items_that_needs_updating(
                    seasons, "seasons"))
            self.insert_trakt_episodes(
                self.filter_trakt_items_that_needs_updating(
                    episodes, "episodes"))

            self.execute_sql(
                "UPDATE shows SET episode_count=?, season_count=? WHERE trakt_id=? ",
                [(i.get("episode_count", 0), i.get("season_count",
                                                   0), i["trakt_id"])
                 for i in trakt_collection])

            self.update_shows_statistics({"trakt_id": i}
                                         for i in sync_lock.running_ids)

            if mill_episodes:
                self.update_season_statistics({"trakt_id": i}
                                              for i in sync_lock.running_ids)

    def filter_trakt_items_that_needs_updating(self, requested, media_type):
        if len(requested) == 0:
            return requested

        get = MetadataHandler.get_trakt_info
        query = """WITH requested(trakt_id, meta_hash, updated_at) AS (VALUES {}) 
        select r.trakt_id as trakt_id from requested as r left join {} as db on r.trakt_id == db.trakt_id 
        left join {}_meta as m on db.trakt_id == id and type=\"trakt\" where db.trakt_id IS NULL or m.value IS NULL or 
        m.meta_hash != r.meta_hash or (Datetime(db.last_updated) < Datetime(r.updated_at))""".format(
            ",".join("({}, '{}', '{}')".format(i.get(
                "trakt_id"), self.trakt_api.meta_hash, get(i, "dateadded"))
                     for i in requested if i.get("trakt_id")),
            media_type,
            media_type,
        )

        result = set(r["trakt_id"] for r in self.execute_sql(query).fetchall())
        return [
            r for r in requested
            if r.get("trakt_id") and r.get("trakt_id") in result
        ]

    def _pull_show_seasons(self, show_id, mill_episodes=False):
        return {
            show_id:
            self.trakt_api.get_json(
                "/shows/{}/seasons".format(show_id),
                extended="full,episodes" if mill_episodes else "full",
                translations=g.get_language_code(),
            )
        }

    @staticmethod
    def _create_args(item):
        get = MetadataHandler.get_trakt_info
        info = MetadataHandler.info
        args = {
            "trakt_id": get(item, "trakt_id",
                            info(item).get("trakt_id")),
            "mediatype": get(item, "mediatype",
                             info(item).get("mediatype")),
        }
        if args["trakt_id"] == None:
            import inspect
            g.log(inspect.stack())
            g.log(item)
        if args["mediatype"] == "season":
            args["trakt_show_id"] = get(item, "trakt_show_id",
                                        info(item).get("trakt_show_id"))
        if args["mediatype"] == "episode":
            args["trakt_show_id"] = get(item, "trakt_show_id",
                                        info(item).get("trakt_show_id"))
            args["trakt_season_id"] = get(item, "trakt_season_id",
                                          info(item).get("trakt_season_id"))
        return tools.quote(json.dumps(args, sort_keys=True))

    def _queue_mill_tasks(self, func, args):
        for arg in args:
            g.log(
                "Requesting season info for show {} - mill_episodes={}".format(
                    arg[0], arg[1]), "debug")
            self.mill_task_queue.put(func, *arg)

    def requires_update(self, new_date, old_date):
        if tools.parse_datetime(new_date, tools.DATE_FORMAT,
                                False) > tools.parse_datetime(
                                    old_date, "%Y-%m-%dT%H:%M:%S", False):
            return True
        else:
            return False

    @staticmethod
    def wrap_in_trakt_object(items):
        for item in items:
            if item.get("show") is not None:
                info = item["show"].pop("info")
                item["show"].update({"trakt_id": info.get("trakt_id")})
                item["show"].update({"trakt_object": {"info": info}})
            if item.get("episode") is not None:
                info = item["episode"].pop("info")
                item["episode"].update({"trakt_id": info.get("trakt_id")})
                item["episode"].update({"tvdb_id": info.get("tvdb_id")})
                item["episode"].update({"tmdb_id": info.get("tmdb_id")})
                item["episode"].update({"trakt_object": {"info": info}})
        return items

    def _get_single_meta(self, trakt_url, trakt_id, media_type):
        return self._update_single_meta(
            trakt_url,
            self.execute_sql(
                """select id as trakt_id, value as trakt_object from 
        {}_meta where id = ? and type = 'trakt' """.format(media_type),
                (int(trakt_id), ),
            ).fetchone(),
            media_type,
        )

    def _update_single_meta(self, trakt_url, item, media_type):
        trakt_object = MetadataHandler.trakt_object
        if item is None:
            item = {}
        if trakt_object(item) is None or trakt_object(item) == {}:
            new_object = self.trakt_api.get_json(trakt_url, extended="full")
            self.save_to_meta_table([new_object], media_type, "trakt",
                                    "trakt_id")
            item.update(new_object)
        return item

    @staticmethod
    def update_missing_trakt_objects(db_list_to_update, list_to_update):
        for item in db_list_to_update:
            if item.get("trakt_object") is None:
                try:
                    item.update(
                        next(i for i in list_to_update
                             if int(i.get("trakt_id") or 0) == int(
                                 item.get("trakt_id") or 0)))
                except StopIteration:
                    g.log(
                        "Failed to find item in list to update, original item: \n {}"
                        .format(item))

    def _extract_trakt_page(self, url, media_type, **params):
        result = []

        def _handle_page(page):
            if not page or len(page) == 0:
                return []
            to_insert = self.filter_trakt_items_that_needs_updating(
                page, media_type)
            if media_type == "movies":
                self.insert_trakt_movies(to_insert)
            elif media_type == "shows":
                self.insert_trakt_shows(to_insert)
            query = (
                "WITH requested(trakt_id) AS (VALUES {}) select r.trakt_id as trakt_id from requested as r inner "
                "join {} as db on r.trakt_id == db.trakt_id left join {}_meta as m on db.trakt_id == "
                "id and type = 'trakt' where 1=1".format(
                    ",".join("({})".format(i.get("trakt_id")) for i in page),
                    media_type,
                    media_type,
                ))
            if self.hide_unaired:
                query += " AND Datetime(air_date) < Datetime('now')"
            if self.hide_watched:
                if media_type == "movies":
                    query += " AND watched = 0"
                if media_type == "shows":
                    query += " AND watched_episodes < episode_count"
            result.extend(self.execute_sql(query).fetchall())

        no_paging = params.get("no_paging", False)
        pull_all = params.pop("pull_all", False)
        page_number = params.pop("page", 1)

        if pull_all:
            _handle_page(self.trakt_api.get_json_cached(url, **params))
            if len(result) >= (self.page_limit *
                               page_number) and not no_paging:
                return result[self.page_limit *
                              (page_number - 1):self.page_limit * page_number]
        else:
            params["limit"] = params.pop("page", self.page_limit)
            for page in self.trakt_api.get_all_pages_json(url, **params):
                _handle_page(page)
                if len(result) >= (self.page_limit *
                                   page_number) and not no_paging:
                    return result[self.page_limit *
                                  (page_number - 1):self.page_limit *
                                  page_number]

        if no_paging:
            return result
        return result[self.page_limit * (page_number - 1):]

    def update_shows_statistics(self, trakt_list):
        to_update = ",".join({str(i.get("trakt_id")) for i in trakt_list})
        self.execute_sql(
            """INSERT or REPLACE into shows (trakt_id, info, art, cast, air_date, last_updated, tmdb_id, tvdb_id,             
            imdb_id, meta_hash, season_count, episode_count, watched_episodes, unwatched_episodes, args, is_airing) 
            SELECT old.trakt_id, old.info, old.art, old.cast, old.air_date, old.last_updated, old.tmdb_id, 
            old.tvdb_id, old.imdb_id, old.meta_hash, old.season_count, old.episode_count, COALESCE( 
            new.watched_episodes, old.watched_episodes), COALESCE(new.unwatched_episodes, old.unwatched_episodes), 
            old.args, old.is_airing FROM (select sh.trakt_id, sh.episode_count - sum(CASE WHEN e.watched > 0 AND 
            e.season != 0 AND Datetime(e.air_date) < Datetime('now') THEN 1 ELSE 0 END) as unwatched_episodes, 
            sum(CASE WHEN e.watched > 0 AND e.season != 0 AND Datetime(e.air_date) < Datetime('now') THEN 1 ELSE 0 
            END) as watched_episodes from shows as sh left join episodes as e on e.trakt_show_id = sh.trakt_id group 
            by sh.trakt_id) AS new LEFT JOIN (SELECT * FROM shows) AS old on old.trakt_id = new.trakt_id where 
            old.trakt_id in ({})""".format(to_update))

    def update_season_statistics(self, trakt_list):
        to_update = ",".join({str(i.get("trakt_id")) for i in trakt_list})

        self.execute_sql(
            """INSERT or REPLACE into seasons ( trakt_show_id, trakt_id, info, art, cast, air_date, last_updated,
             tmdb_id, tvdb_id, meta_hash, episode_count, watched_episodes, unwatched_episodes, is_airing, season, args 
             ) SELECT old.trakt_show_id, old.trakt_id, old.info, old.art, old.cast, old.air_date, old.last_updated, 
             old.tmdb_id, old.tvdb_id, old.meta_hash, COALESCE(new.episode_count, old.episode_count), 
             COALESCE(new.watched_episodes, old.watched_episodes), COALESCE(new.unwatched_episodes, 
             old.unwatched_episodes), COALESCE(new.is_airing, old.is_airing), old.season, old.args FROM ( SELECT 
             se.trakt_id,  sum( CASE WHEN datetime(e.air_date) < datetime('now') THEN 1 ELSE 0 END) AS episode_count, 
             sum( CASE WHEN e.watched == 0 AND datetime(e.air_date) < datetime('now') THEN 1 ELSE 0 END) AS 
             unwatched_episodes, sum( CASE WHEN e.watched > 0 AND datetime(e.air_date) < datetime('now') THEN 1 ELSE 0 
             END) AS watched_episodes, CASE WHEN max(e.air_date) > datetime('now') THEN 1 ELSE 0 END AS is_airing FROM 
             seasons AS se INNER JOIN episodes AS e ON e.trakt_season_id = se.trakt_id WHERE se.season != 0 GROUP BY 
             se.trakt_id) AS new LEFT JOIN ( SELECT * FROM seasons) AS old ON new.trakt_id = old.trakt_id where 
             old.trakt_id in ({})""".format(to_update))

    @property
    def upsert_movie_query(self):
        return """INSERT or REPLACE into movies ( trakt_id, info, art, cast, collected, watched, air_date, 
        last_updated, tmdb_id, imdb_id, meta_hash, args, collected_at, last_watched_at ) SELECT COALESCE(
        new.trakt_id, old.trakt_id), COALESCE(new.info, old.info), COALESCE(new.art, old.art), COALESCE(new.cast, 
        old.cast), COALESCE(new.collected, old.collected), COALESCE(new.watched, old.watched), COALESCE(new.air_date, 
        old.air_date), COALESCE(new.last_updated, old.last_updated), COALESCE(new.tmdb_id, old.tmdb_id), 
        COALESCE(new.imdb_id, old.imdb_id), COALESCE(new.meta_hash, old.meta_hash), COALESCE(new.args, old.args), 
        COALESCE(new.collected_at, old.collected_at), COALESCE(new.last_watched_at, old.last_watched_at) FROM ( 
        SELECT ? AS trakt_id, ? AS info, ? AS art, ? AS cast, ? AS collected, ? as watched, ? AS air_date, 
        ? AS last_updated, ? AS tmdb_id, ? AS imdb_id, ? AS meta_hash, ? AS args, ? AS collected_at, 
        ? AS last_watched_at) AS new LEFT JOIN (SELECT * FROM movies WHERE trakt_id = ? limit 1) AS old """

    @property
    def upsert_show_query(self):
        return """INSERT or REPLACE into shows (trakt_id, info, art, cast, air_date, last_updated, tmdb_id, tvdb_id, 
        imdb_id, meta_hash, season_count, episode_count, watched_episodes, unwatched_episodes, args, is_airing) SELECT 
        COALESCE(new.trakt_id, old.trakt_id), COALESCE(new.info, old.info), COALESCE(new.art, old.art), COALESCE(
        new.cast, old.cast), COALESCE(new.air_date, old.air_date), COALESCE(new.last_updated, old.last_updated), 
        COALESCE(new.tmdb_id, old.tmdb_id), COALESCE(new.tvdb_id, old.tvdb_id), COALESCE(new.imdb_id, old.imdb_id), 
        COALESCE(new.meta_hash, old.meta_hash), COALESCE(new.season_count, old.season_count), 
        COALESCE(new.episode_count, old.episode_count), COALESCE(old.watched_episodes, 0), 
        COALESCE(old.unwatched_episodes, 0), COALESCE(new.args, old.args), COALESCE(new.is_airing, old.is_airing) FROM 
        (SELECT ? AS trakt_id, ? AS info, ? AS art, ? AS cast, ? AS air_date, ? AS last_updated, ? AS tmdb_id, ? AS 
        tvdb_id, ? AS imdb_id, ? AS meta_hash, ? AS season_count,? AS episode_count, ? AS args, ? as is_airing) AS 
        new LEFT JOIN (SELECT * FROM shows WHERE trakt_id = ? limit 1) AS old """

    @property
    def upsert_season_query(self):
        return """INSERT or REPLACE into seasons ( trakt_show_id, trakt_id, info, art, cast, air_date, last_updated, 
        tmdb_id, tvdb_id, meta_hash, episode_count, watched_episodes, unwatched_episodes, season, args, is_airing ) 
        SELECT COALESCE(new.trakt_show_id, old.trakt_show_id), COALESCE(new.trakt_id, old.trakt_id), COALESCE(new.info, 
        old.info), COALESCE(new.art, old.art), COALESCE(new.cast, old.cast), COALESCE(new.air_date, old.air_date), 
        COALESCE(new.last_updated, old.last_updated), COALESCE(new.tmdb_id, old.tmdb_id), COALESCE(new.tvdb_id, 
        old.tvdb_id), COALESCE(new.meta_hash, old.meta_hash), COALESCE(new.episode_count, old.episode_count), 
        old.watched_episodes, old.unwatched_episodes, COALESCE(new.season, old.season), COALESCE(new.args, 
        old.args), old.is_airing FROM ( SELECT ? AS trakt_show_id, ? AS trakt_id, ? AS info, ? AS art, ? AS cast, ? 
        AS air_date, ? AS last_updated, ? AS tmdb_id, ? AS tvdb_id, ? AS meta_hash, ? AS episode_count, ? AS season, ? 
        AS args) AS new LEFT JOIN ( SELECT * FROM seasons WHERE trakt_id = ? limit 1) AS old """

    @property
    def upsert_episode_query(self):
        return """INSERT or REPLACE into episodes (trakt_id, trakt_show_id, trakt_season_id, watched, collected,