コード例 #1
0
    def __get_parameters(self, query_string):
        """ Extracts the actual parameters as a dictionary from the passed in querystring.
        This method takes the self.quotedPlus into account.

        :param str query_string:    The querystring

        :return: dict() of keywords and values.
        :rtype: dict[str,str|None]

        """
        result = dict()
        query_string = query_string.strip('?')
        if query_string == '':
            return result

        try:
            for pair in query_string.split("&"):
                (k, v) = pair.split("=")
                result[k] = v

            # if the channelcode was empty, it was stripped, add it again.
            if self.keywordChannelCode not in result:
                Logger.debug(
                    "Adding ChannelCode=None as it was missing from the dict: %s",
                    result)
                result[self.keywordChannelCode] = None
        except:
            Logger.critical("Cannot determine query strings from %s",
                            query_string,
                            exc_info=True)
            raise

        return result
コード例 #2
0
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val:
            Logger.critical("Error in menu handling: %s", str(exc_val), exc_info=True)

        # make sure we leave no references behind
        AddonSettings.clear_cached_addon_settings_object()
        # close the log to prevent locking on next call
        Logger.instance().close_log()
        return False
コード例 #3
0
    def do_regex(regex, data):
        """ Performs a regular expression and returns a list of matches that came from
        the regex.findall method.
        
        Performs a regular expression findall on the <data> and returns the results that came
        from the method.
        
        From the sre.py library:
        If one or more groups are present in the pattern, return a list of groups; this will be
        a list of tuples if the pattern has more than one group.
    
        Empty matches are included in the result.

        :param list[str|unicode]|str|unicode regex:     The regex to perform on the data.
        :param str|unicode data:                        The data to perform the regex on.

        :return:
        :rtype: list[str|dict[str|unicode,str|unicode]]

        """

        try:
            if not isinstance(regex, (tuple, list)):
                if "?P<" in regex:
                    return Regexer.__do_dictionary_regex(regex, data)
                else:
                    return Regexer.__do_regex(regex, data)

            # We got a list of Regexes
            Logger.debug("Performing multi-regex find on '%s'", regex)
            results = []
            count = 0
            for r in regex:
                if "?P<" in r:
                    regex_results = Regexer.__do_dictionary_regex(r, data)
                    # add to the results with a count in front of the results
                    results += [(count, x) for x in regex_results]
                else:
                    regex_results = Regexer.__do_regex(r, data)
                    if len(regex_results) <= 0:
                        continue

                    if isinstance(regex_results[0], (tuple, list)):
                        # is a tupe/list was returned, prepend it with the count
                        # noinspection PyTypeChecker
                        results += [(count, ) + x for x in regex_results]
                    else:
                        # create a tuple with the results
                        results += [(count, x) for x in regex_results]
                # increase count
                count += 1
            Logger.debug("Returning %s results", len(results))
            return list(results)
        except:
            Logger.critical('error regexing', exc_info=True)
            return []
コード例 #4
0
    def create_video_item(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        # Validate the input and raise errors
        if not isinstance(result_set, dict):
            Logger.critical(
                "No Dictionary as a result_set. Implement a custom create_video_item"
            )
            raise NotImplementedError(
                "No Dictionary as a result_set. Implement a custom create_video_item"
            )

        elif "title" not in result_set or "url" not in result_set:
            Logger.warning("No ?P<title> or ?P<url> in result_set")
            raise LookupError("No ?P<title> or ?P<url> in result_set")

        # The URL
        url = self._prefix_urls(result_set["url"])

        # The title
        if "subtitle" in result_set and result_set["subtitle"]:
            # noinspection PyStringFormat
            title = "%(title)s - %(subtitle)s" % result_set
        else:
            title = result_set["title"]
        if title.isupper():
            title = title.title()

        item = MediaItem(title, url)
        item.thumb = self._prefix_urls(result_set.get("thumburl", ""))
        item.description = result_set.get("description", "")
        item.type = 'video'
        item.HttpHeaders = self.httpHeaders
        item.complete = False
        return item
コード例 #5
0
    def __get_application_key(self):
        """ Gets the decrypted application key that is used for all the encryption.

        :return: The decrypted application key that is used for all the encryption.
        :rtype: bytes

        """

        application_key_encrypted = AddonSettings.get_setting(
            Vault.__APPLICATION_KEY_SETTING, store=LOCAL)
        # The key was never in the local store the value was None. It was "" if it was reset.
        if application_key_encrypted is None:
            application_key_encrypted = AddonSettings.get_setting(
                Vault.__APPLICATION_KEY_SETTING, store=KODI)
            if not application_key_encrypted:
                return None

            Logger.info("Moved ApplicationKey to local storage")
            AddonSettings.set_setting(Vault.__APPLICATION_KEY_SETTING,
                                      application_key_encrypted,
                                      store=LOCAL)

        # Still no application key? Then there was no key!
        if application_key_encrypted == "" or application_key_encrypted is None:
            return None

        vault_incorrect_pin = LanguageHelper.get_localized_string(
            LanguageHelper.VaultIncorrectPin)
        pin = XbmcWrapper.show_key_board(
            heading=LanguageHelper.get_localized_string(
                LanguageHelper.VaultInputPin),
            hidden=True)
        if not pin:
            XbmcWrapper.show_notification("", vault_incorrect_pin,
                                          XbmcWrapper.Error)
            raise RuntimeError("Incorrect Retrospect PIN specified")
        pin_key = self.__get_pbk(pin)
        application_key = self.__decrypt(application_key_encrypted, pin_key)
        if not application_key.startswith(Vault.__APPLICATION_KEY_SETTING):
            Logger.critical("Invalid Retrospect PIN")
            XbmcWrapper.show_notification("", vault_incorrect_pin,
                                          XbmcWrapper.Error)
            raise RuntimeError("Incorrect Retrospect PIN specified")

        application_key_value = application_key[
            len(Vault.__APPLICATION_KEY_SETTING) + 1:]
        Logger.info("Successfully decrypted the ApplicationKey.")
        if PY2:
            return application_key_value

        # We return bytes on Python 3
        return application_key_value.encode()
コード例 #6
0
    def cache_clean_up(path, cache_time, mask="*.*"):
        """Cleans up the XOT cache folder.

        Check the cache files create timestamp and compares it with the current datetime extended
        with the amount of seconds as defined in cacheTime.

        Expired items are deleted.
        :param str path:        The cache path to clean.
        :param int cache_time:  The minimum (in seconds) of files that will be deleted.
        :param str mask:        The file mask to consider when cleaning the cache.

        """

        # let's import htis one here
        import fnmatch

        try:
            Logger.info("Cleaning up cache in '%s' that is older than %s days",
                        os.path.join(path, "**", mask), cache_time / 24 / 3600)

            if not os.path.exists(path):
                Logger.info("Did not cleanup cache: folder does not exist")
                return

            delete_count = 0
            file_count = 0

            #for item in os.listdir(path):
            current_dir = None
            for root, dirs, files in os.walk(path):
                if current_dir != root:
                    Logger.debug("Cleaning cache folder: %s", root)
                    current_dir = root

                for basename in files:
                    if fnmatch.fnmatch(basename, mask):
                        filename = os.path.join(root, basename)
                        Logger.trace("Inspecting: %s", filename)
                        file_count += 1
                        create_time = os.path.getctime(filename)
                        if create_time + cache_time < time.time():
                            os.remove(filename)
                            Logger.debug("Removed file: %s", filename)
                            delete_count += 1

            Logger.info("Removed %s of %s files from cache in: '%s'",
                        delete_count, file_count, path)
        except:
            Logger.critical("Error cleaning the cachefolder: %s",
                            path,
                            exc_info=True)
コード例 #7
0
    def create_folder_item(self, result_set):
        """ Creates a MediaItem of type 'folder' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'folder'.
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        # Validate the input and raise errors
        if not isinstance(result_set, dict):
            Logger.critical(
                "No Dictionary as a result_set. Implement a custom create_video_item"
            )
            raise NotImplementedError(
                "No Dictionary as a result_set. Implement a custom create_video_item"
            )

        elif "title" not in result_set or "url" not in result_set:
            Logger.warning("No ?P<title> or ?P<url> in result_set")
            raise LookupError("No ?P<title> or ?P<url> in result_set")

        # The URL
        url = self._prefix_urls(result_set["url"])

        # The title
        title = result_set["title"]
        if title.isupper():
            title = title.title()

        item = MediaItem(title, url)
        item.description = result_set.get("description", "")
        item.thumb = result_set.get("thumburl", "")
        item.type = 'folder'
        item.HttpHeaders = self.httpHeaders
        item.complete = True
        return item
コード例 #8
0
    def __get_index(self):
        """ Loads the channel index and if there is none, makes sure one is created.

        Checks:
        1. Existence of the index
        2. Channel add-ons in the index vs actual add-ons

        :return: The current channel index.
        :rtype: dict

        """

        # if it was not already re-index and the bit was set
        if self.__reindex:
            if self.__reindexed:
                Logger.warning(
                    "Forced re-index set, but a re-index was already done previously. Not Rebuilding."
                )
            else:
                Logger.info("Forced re-index set. Rebuilding.")
                return self.__rebuild_index()

        if not os.path.isfile(self.__CHANNEL_INDEX):
            Logger.info("No index file found at '%s'. Rebuilding.",
                        self.__CHANNEL_INDEX)
            return self.__rebuild_index()

        try:
            with io.open(self.__CHANNEL_INDEX, 'rt', encoding='utf-8') as fd:
                data = fd.read()

            index_json = JsonHelper(data, logger=Logger.instance())
            Logger.debug("Loaded index from '%s'.", self.__CHANNEL_INDEX)

            if not self.__is_index_consistent(index_json.json):
                return self.__rebuild_index()
            return index_json.json
        except:
            Logger.critical("Error reading channel index. Rebuilding.",
                            exc_info=True)
            return self.__rebuild_index()
コード例 #9
0
    def __validate_and_get_add_on_version(self, path):
        """ Parses the channelpack.json file and checks if all is OK.

        :param str|unicode path:    The path to load the addon from.

        :return: the AddonId-Version
        :rtype: tuple[str|unicode|none,str|unicode|none]

        """

        addon_file = os.path.join(path, "channelpack.json")

        # continue if no addon.xml exists
        if not os.path.isfile(addon_file):
            Logger.info("No channelpack.json found at %s.", addon_file)
            return None, None

        with io.open(addon_file, 'rt+', encoding='utf-8') as f:
            channel_json = f.read()

        channels_data = JsonHelper(channel_json)
        pack_version = channels_data.get_value("version")
        package_id = channels_data.get_value("id")
        if not pack_version or not package_id:
            Logger.critical(
                "Cannot determine Channel Pack version. Not loading Add-on @ '%s'.",
                path)
            return None, None

        package_version = Version(version=pack_version)
        if Config.version.are_compatible(package_version):
            Logger.info("Adding %s version %s", package_id, package_version)
            return package_id, package_version
        else:
            Logger.warning("Skipping %s version %s: Versions do not match.",
                           package_id, package_version)
            return None, None
コード例 #10
0
    def __init__(self, addon_name, params, handle=0):  # NOSONAR complexity
        """ Initialises the plugin with given arguments.

        :param str addon_name:      The add-on name.
        :param str params:          The input parameters from the query string.
        :param int handle:          The Kodi directory handle.

        """

        Logger.info("******** Starting %s add-on version %s/repo *********",
                    Config.appName, Config.version)
        # noinspection PyTypeChecker

        super(Plugin, self).__init__(addon_name, handle, params)
        Logger.debug(self)

        # Container Properties
        self.propertyRetrospect = "Retrospect"
        self.propertyRetrospectChannel = "RetrospectChannel"
        self.propertyRetrospectChannelSetting = "RetrospectChannelSettings"
        self.propertyRetrospectFolder = "RetrospectFolder"
        self.propertyRetrospectVideo = "RetrospectVideo"
        self.propertyRetrospectCloaked = "RetrospectCloaked"
        self.propertyRetrospectCategory = "RetrospectCategory"
        self.propertyRetrospectFavorite = "RetrospectFavorite"
        self.propertyRetrospectAdaptive = "RetrospectAdaptive"

        # channel objects
        self.channelObject = None
        self.channelFile = ""
        self.channelCode = None

        self.methodContainer = dict(
        )  # : storage for the inspect.getmembers(channel) method. Improves performance

        # are we in session?
        session_active = SessionHelper.is_session_active(Logger.instance())

        # fetch some environment settings
        env_ctrl = envcontroller.EnvController(Logger.instance())

        if not session_active:
            # do add-on start stuff
            Logger.info("Add-On start detected. Performing startup actions.")

            # print the folder structure
            env_ctrl.print_retrospect_settings_and_folders(
                Config, AddonSettings)

            # show notification
            XbmcWrapper.show_notification(None,
                                          LanguageHelper.get_localized_string(
                                              LanguageHelper.StartingAddonId) %
                                          (Config.appName, ),
                                          fallback=False,
                                          logger=Logger)

            # check for updates. Using local import for performance
            from resources.lib.updater import Updater
            up = Updater(Config.updateUrl, Config.version,
                         UriHandler.instance(), Logger.instance(),
                         AddonSettings.get_release_track())

            if up.is_new_version_available():
                Logger.info("Found new version online: %s vs %s",
                            up.currentVersion, up.onlineVersion)
                notification = LanguageHelper.get_localized_string(
                    LanguageHelper.NewVersion2Id)
                notification = notification % (Config.appName,
                                               up.onlineVersion)
                XbmcWrapper.show_notification(None,
                                              lines=notification,
                                              display_time=20000)

            # check for cache folder
            env_ctrl.cache_check()

            # do some cache cleanup
            env_ctrl.cache_clean_up(Config.cacheDir, Config.cacheValidTime)

            # empty picklestore
            self._pickler.purge_store(Config.addonId)

        # create a session
        SessionHelper.create_session(Logger.instance())

        #===============================================================================
        #        Start the plugin version of progwindow
        #===============================================================================
        if len(self.params) == 0:

            # Show initial start if not in a session
            # now show the list
            if AddonSettings.show_categories():
                self.show_categories()
            else:
                self.show_channel_list()

        #===============================================================================
        #        Start the plugin verion of the episode window
        #===============================================================================
        else:
            # Determine what stage we are in. Check that there are more than 2 Parameters
            if len(self.params) > 1 and self.keywordChannel in self.params:
                # retrieve channel characteristics
                self.channelFile = os.path.splitext(
                    self.params[self.keywordChannel])[0]
                self.channelCode = self.params[self.keywordChannelCode]
                Logger.debug(
                    "Found Channel data in URL: channel='%s', code='%s'",
                    self.channelFile, self.channelCode)

                # import the channel
                channel_register = ChannelIndex.get_register()
                channel = channel_register.get_channel(self.channelFile,
                                                       self.channelCode)

                if channel is not None:
                    self.channelObject = channel
                else:
                    Logger.critical(
                        "None or more than one channels were found, unable to continue."
                    )
                    return

                # init the channel as plugin
                self.channelObject.init_channel()
                Logger.info("Loaded: %s", self.channelObject.channelName)

            elif self.keywordCategory in self.params \
                    or self.keywordAction in self.params and (
                        self.params[self.keywordAction] == self.actionAllFavourites or
                        self.params[self.keywordAction] == self.actionRemoveFavourite):
                # no channel needed for these favourites actions.
                pass

            # ===============================================================================
            # Vault Actions
            # ===============================================================================
            elif self.keywordAction in self.params and \
                    self.params[self.keywordAction] in \
                    (
                        self.actionSetEncryptedValue,
                        self.actionSetEncryptionPin,
                        self.actionResetVault
                    ):
                try:
                    # Import vault here, as it is only used here or in a channel
                    # that supports it
                    from resources.lib.vault import Vault

                    action = self.params[self.keywordAction]
                    if action == self.actionResetVault:
                        Vault.reset()
                        return

                    v = Vault()
                    if action == self.actionSetEncryptionPin:
                        v.change_pin()
                    elif action == self.actionSetEncryptedValue:
                        v.set_setting(
                            self.params[self.keywordSettingId],
                            self.params.get(self.keywordSettingName, ""),
                            self.params.get(self.keywordSettingActionId, None))
                finally:
                    if self.keywordSettingTabFocus in self.params:
                        AddonSettings.show_settings(
                            self.params[self.keywordSettingTabFocus],
                            self.params.get(self.keywordSettingSettingFocus,
                                            None))
                return

            elif self.keywordAction in self.params and \
                    self.actionPostLog in self.params[self.keywordAction]:
                self.__send_log()
                return

            elif self.keywordAction in self.params and \
                    self.actionProxy in self.params[self.keywordAction]:

                # do this here to not close the busy dialog on the SetProxy when
                # a confirm box is shown
                title = LanguageHelper.get_localized_string(
                    LanguageHelper.ProxyChangeConfirmTitle)
                content = LanguageHelper.get_localized_string(
                    LanguageHelper.ProxyChangeConfirm)
                if not XbmcWrapper.show_yes_no(title, content):
                    Logger.warning(
                        "Stopping proxy update due to user intervention")
                    return

                language = self.params.get(self.keywordLanguage, None)
                proxy_id = self.params.get(self.keywordProxy, None)
                local_ip = self.params.get(self.keywordLocalIP, None)
                self.__set_proxy(language, proxy_id, local_ip)
                return

            else:
                Logger.critical("Error determining Plugin action")
                return

            #===============================================================================
            # See what needs to be done.
            #===============================================================================
            if self.keywordAction not in self.params:
                Logger.critical(
                    "Action parameters missing from request. Parameters=%s",
                    self.params)
                return

            elif self.params[self.keywordAction] == self.actionListCategory:
                self.show_channel_list(self.params[self.keywordCategory])

            elif self.params[
                    self.keywordAction] == self.actionConfigureChannel:
                self.__configure_channel(self.channelObject)

            elif self.params[self.keywordAction] == self.actionFavourites:
                # we should show the favourites
                self.show_favourites(self.channelObject)

            elif self.params[self.keywordAction] == self.actionAllFavourites:
                self.show_favourites(None)

            elif self.params[self.keywordAction] == self.actionListFolder:
                # channelName and URL is present, Parse the folder
                self.process_folder_list()

            elif self.params[self.keywordAction] == self.actionPlayVideo:
                self.play_video_item()

            elif not self.params[self.keywordAction] == "":
                self.on_action_from_context_menu(
                    self.params[self.keywordAction])

            else:
                Logger.warning(
                    "Number of parameters (%s) or parameter (%s) values not implemented",
                    len(self.params), self.params)

        self.__fetch_textures()
        return
コード例 #11
0
    def show_channel_list(self, category=None):
        """ Displays the channels that are currently available in XOT as a directory
        listing.

        :param str category:    The category to show channels for

        """

        if category:
            Logger.info("Plugin::show_channel_list for %s", category)
        else:
            Logger.info("Plugin::show_channel_list")
        try:
            # only display channels
            channel_register = ChannelIndex.get_register()
            channels = channel_register.get_channels()

            xbmc_items = []

            # Should we show the "All Favourites"?
            if AddonSettings.show_show_favourites_in_channel_list():
                icon = Config.icon
                fanart = Config.fanart
                name = LanguageHelper.get_localized_string(
                    LanguageHelper.AllFavouritesId)
                kodi_item = xbmcgui.ListItem(name, name)

                # set art
                try:
                    kodi_item.setIconImage(icon)
                except:
                    # it was deprecated
                    pass
                kodi_item.setArt({'thumb': icon, 'icon': icon})
                kodi_item.setProperty(self.propertyRetrospect, "true")
                kodi_item.setProperty(self.propertyRetrospectCategory, "true")

                if not AddonSettings.hide_fanart():
                    kodi_item.setArt({'fanart': fanart})

                url = self._create_action_url(None,
                                              action=self.actionAllFavourites)
                xbmc_items.append((url, kodi_item, True))

            for channel in channels:
                if category and channel.category != category:
                    Logger.debug("Skipping %s (%s) due to category filter",
                                 channel.channelName, channel.category)
                    continue

                # Get the Kodi item
                item = channel.get_kodi_item()
                item.setProperty(self.propertyRetrospect, "true")
                item.setProperty(self.propertyRetrospectChannel, "true")
                if channel.settings:
                    item.setProperty(self.propertyRetrospectChannelSetting,
                                     "true")
                if channel.adaptiveAddonSelectable:
                    item.setProperty(self.propertyRetrospectAdaptive, "true")

                # Get the context menu items
                context_menu_items = self.__get_context_menu_items(channel)
                item.addContextMenuItems(context_menu_items)
                # Get the URL for the item
                url = self._create_action_url(channel,
                                              action=self.actionListFolder)

                # Append to the list of Kodi Items
                xbmc_items.append((url, item, True))

            # Add the items
            ok = xbmcplugin.addDirectoryItems(self.handle, xbmc_items,
                                              len(xbmc_items))

            # Just let Kodi display the order we give.
            xbmcplugin.addSortMethod(
                handle=self.handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED)
            xbmcplugin.addSortMethod(handle=self.handle,
                                     sortMethod=xbmcplugin.SORT_METHOD_TITLE)
            xbmcplugin.addSortMethod(handle=self.handle,
                                     sortMethod=xbmcplugin.SORT_METHOD_GENRE)
            xbmcplugin.setContent(handle=self.handle, content="tvshows")
            xbmcplugin.endOfDirectory(self.handle, ok)
        except:
            xbmcplugin.endOfDirectory(self.handle, False)
            Logger.critical("Error fetching channels for plugin",
                            exc_info=True)
コード例 #12
0
    def run(self):  # NOSONAR
        addon_action = None
        channel_object = None

        if len(self.params) == 0:
            # Show initial start if not in a session now show the list
            if AddonSettings.show_categories():
                from resources.lib.actions.categoryaction import CategoryAction
                addon_action = CategoryAction(self)
            else:
                from resources.lib.actions.channellistaction import ChannelListAction
                addon_action = ChannelListAction(self)

        else:
            # Determine what action to perform based on the parameters
            if keyword.CHANNEL in self.params:
                # retrieve channel characteristics
                channel_file = os.path.splitext(
                    self.params[keyword.CHANNEL])[0]
                channel_code = self.params[keyword.CHANNEL_CODE]
                Logger.debug(
                    "Found Channel data in URL: channel='%s', code='%s'",
                    channel_file, channel_code)

                # import the channel
                channel_register = ChannelIndex.get_register()
                channel = channel_register.get_channel(channel_file,
                                                       channel_code)

                if channel is not None:
                    channel_object = channel
                else:
                    Logger.critical(
                        "None or more than one channels were found, unable to continue."
                    )
                    return

                # init the channel as plugin
                channel_object.init_channel()
                Logger.info("Loaded: %s", channel_object.channelName)

            #===============================================================================
            # See what needs to be done.
            #===============================================================================
            # From here we need the "action" keyword to be present
            if keyword.ACTION not in self.params:
                Logger.critical(
                    "Action parameters missing from request. Parameters=%s",
                    self.params)
                return

            if self.params[keyword.ACTION] in \
                    (action.SET_ENCRYPTED_VALUE, action.SET_ENCRYPTION_PIN, action.RESET_VAULT):
                action_value = self.params[keyword.ACTION]
                from resources.lib.actions.vaultaction import VaultAction
                addon_action = VaultAction(self, action_value)

            elif self.params[keyword.ACTION] == action.POST_LOG:
                from resources.lib.actions.logaction import LogAction
                addon_action = LogAction(self)

            elif self.params[keyword.ACTION] == action.CLEANUP:
                from resources.lib.actions.cleanaction import CleanAction
                addon_action = CleanAction(self)

            elif self.params[keyword.ACTION] == action.LIST_CATEGORY:
                from resources.lib.actions.channellistaction import ChannelListAction
                addon_action = ChannelListAction(self,
                                                 self.params[keyword.CATEGORY])

            elif self.params[keyword.ACTION] == action.CONFIGURE_CHANNEL:
                from resources.lib.actions.configurechannelaction import ConfigureChannelAction
                addon_action = ConfigureChannelAction(self, channel_object)

            elif self.params[keyword.ACTION] == action.CHANNEL_FAVOURITES:
                # we should show the favourites
                from resources.lib.actions.favouritesaction import ShowFavouritesAction
                addon_action = ShowFavouritesAction(self, channel_object)

            elif self.params[keyword.ACTION] == action.ALL_FAVOURITES:
                from resources.lib.actions.favouritesaction import ShowFavouritesAction
                addon_action = ShowFavouritesAction(self, None)

            elif self.params[keyword.ACTION] == action.LIST_FOLDER:
                # channelName and U.lib.aRL is present, Parse the folder
                from resources.lib.actions.folderaction import FolderAction
                addon_action = FolderAction(self, channel_object)

            elif self.params[keyword.ACTION] == action.PLAY_VIDEO:
                from resources.lib.actions.videoaction import VideoAction
                addon_action = VideoAction(self, channel_object)

            elif not self.params[keyword.ACTION] == "":
                from resources.lib.actions.contextaction import ContextMenuAction
                addon_action = ContextMenuAction(self, channel_object,
                                                 self.params[keyword.ACTION])

            else:
                Logger.warning(
                    "Number of parameters (%s) or parameter (%s) values not implemented",
                    len(self.params), self.params)

        # Execute the action
        if addon_action is not None:
            addon_action.execute()

        self.__fetch_textures()
        return
コード例 #13
0
    def change_pin(self, application_key=None):
        """ Stores an existing ApplicationKey using a new PIN.

        :param bytes application_key: an existing ApplicationKey that will be stored. If none
                                      specified, the existing ApplicationKey of the Vault will
                                      be used.

        :return: Indication of success.
        :rtype: bool

        """

        Logger.info("Updating the ApplicationKey with a new PIN")

        if self.__newKeyGeneratedInConstructor:
            Logger.info("A key was just generated, no need to change PINs.")
            return True

        if application_key is None:
            Logger.debug("Using the ApplicationKey from the vault.")
            application_key = Vault.__Key
        else:
            Logger.debug("Using the ApplicationKey from the input parameter.")

        if not application_key:
            raise ValueError("No ApplicationKey specified.")

        # Now we get a new PIN and (re)encrypt

        pin = XbmcWrapper.show_key_board(
            heading=LanguageHelper.get_localized_string(
                LanguageHelper.VaultNewPin),
            hidden=True)
        if not pin:
            XbmcWrapper.show_notification(
                "",
                LanguageHelper.get_localized_string(LanguageHelper.VaultNoPin),
                XbmcWrapper.Error)
            return False

        pin2 = XbmcWrapper.show_key_board(
            heading=LanguageHelper.get_localized_string(
                LanguageHelper.VaultRepeatPin),
            hidden=True)
        if pin != pin2:
            Logger.critical("Mismatch in PINs")
            XbmcWrapper.show_notification(
                "",
                LanguageHelper.get_localized_string(
                    LanguageHelper.VaultPinsDontMatch), XbmcWrapper.Error)
            return False

        if PY2:
            encrypted_key = "%s=%s" % (self.__APPLICATION_KEY_SETTING,
                                       application_key)
        else:
            # make it text to store
            encrypted_key = "%s=%s" % (self.__APPLICATION_KEY_SETTING,
                                       application_key.decode())

        # let's generate a pin using the scrypt password-based key derivation
        pin_key = self.__get_pbk(pin)
        encrypted_key = self.__encrypt(encrypted_key, pin_key)
        AddonSettings.set_setting(Vault.__APPLICATION_KEY_SETTING,
                                  encrypted_key,
                                  store=LOCAL)
        Logger.info("Successfully updated the Retrospect PIN")
        return True
コード例 #14
0
    def update_add_on_settings_with_channels(channels, config):
        """ Updates the settings.xml to include all the channels

        :param list[any] channels:  The channels to add to the settings.xml
        :param type[Config] config: The configuration object

        """

        # sort the channels
        channels.sort(key=lambda c: c.sort_key)

        # Then we read the original file
        filename_template = os.path.join(config.rootDir, "resources", "data", "settings_template.xml")
        if not os.path.isfile(filename_template):
            Logger.debug("No template present in '%s'. Skipping generation.", filename_template)
            return

        # noinspection PyArgumentEqualDefault
        with io.open(filename_template, "r", encoding="utf-8") as fp:
            contents = fp.read()

        new_contents = AddonSettings.__update_add_on_settings_with_country_settings(contents, channels)
        new_contents, settings_offset_for_visibility, channels_with_settings = \
            AddonSettings.__update_add_on_settings_with_channel_settings(new_contents, channels)

        new_contents = AddonSettings.__update_add_on_settings_with_channel_selection(
            new_contents, channels_with_settings)

        # Now fill the templates, we only import here due to performance penalties of the
        # large number of imports.
        from resources.lib.helpers.templatehelper import TemplateHelper
        th = TemplateHelper(Logger.instance(), template=new_contents)
        new_contents = th.transform()

        # Finally we insert the new XML into the old one
        filename = os.path.join(config.rootDir, "resources", "settings.xml")
        filename_temp = os.path.join(config.rootDir, "resources", "settings.tmp.xml")
        try:
            # Backup the user profile settings.xml because sometimes it gets reset. Because in some
            # concurrency situations, Kodi might decide to think we have no settings and just
            # erase all user settings.
            user_settings = os.path.join(Config.profileDir, "settings.xml")
            user_settings_backup = os.path.join(Config.profileDir, "settings.old.xml")
            Logger.debug("Backing-up user settings: %s", user_settings_backup)
            if os.path.isfile(user_settings):
                if os.path.isfile(user_settings_backup):
                    os.remove(user_settings_backup)
                shutil.copyfile(user_settings, user_settings_backup)
            else:
                Logger.warning("No user settings found at: %s", user_settings)

            # Update the addonsettings.xml by first updating a temp xml file.
            Logger.debug("Creating new settings.xml file: %s", filename_temp)
            Logger.trace(new_contents)
            with io.open(filename_temp, "w+", encoding='utf-8') as fp:
                fp.write(new_contents)

            Logger.debug("Replacing existing settings.xml file: %s", filename)
            if os.path.isfile(filename):
                os.remove(filename)
            shutil.move(filename_temp, filename)

            # restore the user profile settings.xml file when needed
            if os.path.isfile(user_settings) and os.stat(user_settings).st_size != os.stat(user_settings_backup).st_size:
                Logger.critical("User settings.xml was overwritten during setttings update. Restoring from %s", user_settings_backup)
                if os.path.isfile(user_settings):
                    os.remove(user_settings)
                shutil.copyfile(user_settings_backup, user_settings)
        except:
            Logger.error("Something went wrong trying to update the settings.xml", exc_info=True)

            #  clean up time file
            if os.path.isfile(filename_temp):
                os.remove(filename_temp)

            # restore original settings
            with io.open(filename_temp, "w+", encoding='utf-8') as fp:
                fp.write(contents)

            if os.path.isfile(filename):
                os.remove(filename)
            shutil.move(filename_temp, filename)
            return

        Logger.info("Settings.xml updated successfully. Reloading settings.")
        AddonSettings.__refresh(KODI)
        return
コード例 #15
0
    def execute(self):
        if self.category:
            Logger.info("Plugin::show_channel_list for %s", self.category)
        else:
            Logger.info("Plugin::show_channel_list")
        try:
            # only display channels
            channel_register = ChannelIndex.get_register()
            channels = channel_register.get_channels()

            xbmc_items = []

            # Should we show the "All Favourites"?
            if AddonSettings.show_show_favourites_in_channel_list():
                icon = Config.icon
                fanart = Config.fanart
                poster = Config.poster
                name = LanguageHelper.get_localized_string(
                    LanguageHelper.AllFavouritesId)
                description = LanguageHelper.get_localized_string(
                    LanguageHelper.AllFavouritesDescriptionId)
                kodi_item = kodifactory.list_item(name, name)
                kodi_item.setInfo("video", {"Plot": description})

                # set art
                try:
                    kodi_item.setIconImage(icon)
                except:
                    # it was deprecated
                    pass
                kodi_item.setArt({
                    'thumb': icon,
                    'icon': icon,
                    'poster': poster
                })
                kodi_item.setProperty(self._propertyRetrospect, "true")
                kodi_item.setProperty(self._propertyRetrospectCategory, "true")

                if not AddonSettings.hide_fanart():
                    kodi_item.setArt({'fanart': fanart})

                url = self.parameter_parser.create_action_url(
                    None, action=action.ALL_FAVOURITES)
                xbmc_items.append((url, kodi_item, True))

            for channel in channels:
                if self.category and channel.category != self.category:
                    Logger.debug("Skipping %s (%s) due to category filter",
                                 channel.channelName, channel.category)
                    continue

                # Get the Kodi item
                item = channel.get_kodi_item()
                item.setProperty(self._propertyRetrospect, "true")
                item.setProperty(self._propertyRetrospectChannel, "true")
                if channel.settings:
                    item.setProperty(self._propertyRetrospectChannelSetting,
                                     "true")
                if channel.adaptiveAddonSelectable:
                    item.setProperty(self._propertyRetrospectAdaptive, "true")

                # Get the context menu items
                context_menu_items = self._get_context_menu_items(channel)
                item.addContextMenuItems(context_menu_items)
                # Get the URL for the item
                url = self.parameter_parser.create_action_url(
                    channel, action=action.LIST_FOLDER)

                # Append to the list of Kodi Items
                xbmc_items.append((url, item, True))

            # Add the items
            ok = xbmcplugin.addDirectoryItems(self.handle, xbmc_items,
                                              len(xbmc_items))

            # Just let Kodi display the order we give.
            xbmcplugin.addSortMethod(
                handle=self.handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED)
            xbmcplugin.addSortMethod(handle=self.handle,
                                     sortMethod=xbmcplugin.SORT_METHOD_TITLE)
            xbmcplugin.addSortMethod(handle=self.handle,
                                     sortMethod=xbmcplugin.SORT_METHOD_GENRE)
            xbmcplugin.setContent(handle=self.handle, content="tvshows")
            xbmcplugin.endOfDirectory(self.handle, ok)
        except:
            xbmcplugin.endOfDirectory(self.handle, False)
            Logger.critical("Error fetching channels for plugin",
                            exc_info=True)
コード例 #16
0
    def process_folder_list(self, favorites=None):
        """Wraps the channel.process_folder_list

        :param list[MediaItem]|None favorites:

        """

        Logger.info("Plugin::process_folder_list Doing process_folder_list")
        try:
            ok = True

            # read the item from the parameters
            selected_item = self.media_item

            # determine the parent guid
            parent_guid = self._get_parent_guid(self.channelObject,
                                                selected_item)

            if favorites is None:
                watcher = StopWatch("Plugin process_folder_list",
                                    Logger.instance())
                media_items = self.channelObject.process_folder_list(
                    selected_item)
                watcher.lap("Class process_folder_list finished")
            else:
                watcher = StopWatch("Plugin process_folder_list With Items",
                                    Logger.instance())
                media_items = favorites

            if len(media_items) == 0:
                Logger.warning("process_folder_list returned %s items",
                               len(media_items))
                ok = self.__show_empty_information(media_items,
                                                   favs=favorites is not None)
            else:
                Logger.debug("process_folder_list returned %s items",
                             len(media_items))

            kodi_items = []

            for media_item in media_items:  # type: MediaItem
                self.__update_artwork(media_item, self.channelObject)

                if media_item.type == 'folder' or media_item.type == 'append' or media_item.type == "page":
                    action = self.actionListFolder
                    folder = True
                elif media_item.is_playable():
                    action = self.actionPlayVideo
                    folder = False
                else:
                    Logger.critical(
                        "Plugin::process_folder_list: Cannot determine what to add"
                    )
                    continue

                # Get the Kodi item
                kodi_item = media_item.get_kodi_item()
                self.__set_kodi_properties(kodi_item,
                                           media_item,
                                           folder,
                                           is_favourite=favorites is not None)

                # Get the context menu items
                context_menu_items = self.__get_context_menu_items(
                    self.channelObject, item=media_item)
                kodi_item.addContextMenuItems(context_menu_items)

                # Get the action URL
                url = media_item.actionUrl
                if url is None:
                    url = self._create_action_url(self.channelObject,
                                                  action=action,
                                                  item=media_item,
                                                  store_id=parent_guid)

                # Add them to the list of Kodi items
                kodi_items.append((url, kodi_item, folder))

            watcher.lap("Kodi Items generated")

            # add items but if OK was False, keep it like that
            ok = ok and xbmcplugin.addDirectoryItems(self.handle, kodi_items,
                                                     len(kodi_items))
            watcher.lap("items send to Kodi")

            if ok and parent_guid is not None:
                self._pickler.store_media_items(parent_guid, selected_item,
                                                media_items)

            watcher.stop()

            self.__add_sort_method_to_handle(self.handle, media_items)
            self.__add_breadcrumb(self.handle, self.channelObject,
                                  selected_item)

            # set the content. It needs to be "episodes" to make the MediaItem.set_season_info() work
            xbmcplugin.setContent(handle=self.handle, content="episodes")

            xbmcplugin.endOfDirectory(self.handle, ok)
        except Exception:
            Logger.error("Plugin::Error Processing FolderList", exc_info=True)
            XbmcWrapper.show_notification(
                LanguageHelper.get_localized_string(LanguageHelper.ErrorId),
                LanguageHelper.get_localized_string(LanguageHelper.ErrorList),
                XbmcWrapper.Error, 4000)
            xbmcplugin.endOfDirectory(self.handle, False)
コード例 #17
0
    def play_video_item(self):
        """ Starts the videoitem using a playlist. """

        from resources.lib import player

        Logger.debug("Playing videoitem using PlayListMethod")

        try:
            media_item = self.media_item

            if not media_item.complete:
                media_item = self.channelObject.process_video_item(media_item)

            # Any warning to show
            self.__show_warnings(media_item)

            # validated the updated media_item
            if not media_item.complete or not media_item.has_media_item_parts(
            ):
                Logger.warning(
                    "process_video_item returned an MediaItem that had MediaItem.complete = False:\n%s",
                    media_item)

            if not media_item.has_media_item_parts():
                # the update failed or no items where found. Don't play
                XbmcWrapper.show_notification(
                    LanguageHelper.get_localized_string(
                        LanguageHelper.ErrorId),
                    LanguageHelper.get_localized_string(
                        LanguageHelper.NoStreamsId), XbmcWrapper.Error)
                Logger.warning(
                    "Could not start playback due to missing streams. Item:\n%s",
                    media_item)
                xbmcplugin.endOfDirectory(self.handle, False)
                return

            kodi_items = media_item.get_kodi_play_list_data(
                AddonSettings.get_max_stream_bitrate(self.channelObject),
                self.channelObject.proxy)

            Logger.debug("Continuing playback in plugin.py")
            if not bool(kodi_items):
                Logger.warning("play_video_item did not return valid playdata")
                xbmcplugin.endOfDirectory(self.handle, False)
                return

            # Now we force the busy dialog to close, else the video will not play and the
            # setResolved will not work.
            LockWithDialog.close_busy_dialog()

            # Append it to the Kodi playlist in a smart way.
            start_url = self.__append_kodi_play_list(kodi_items)

            # Set the mode (if the InputStream Adaptive add-on is used, we also need to set it)
            show_subs = AddonSettings.show_subtitles()

            # TODO: Apparently if we use the InputStream Adaptive, using the setSubtitles() causes sync issues.
            available_subs = [p.Subtitle for p in media_item.MediaItemParts]

            # Get the Kodi Player instance (let Kodi decide what player, see
            # http://forum.kodi.tv/showthread.php?tid=173887&pid=1516662#pid1516662)
            kodi_player = player.Player(show_subs=show_subs,
                                        subs=available_subs)
            kodi_player.waitForPlayBack(url=start_url, time_out=10)

            xbmcplugin.endOfDirectory(self.handle, True)
        except:
            XbmcWrapper.show_notification(
                LanguageHelper.get_localized_string(LanguageHelper.ErrorId),
                LanguageHelper.get_localized_string(
                    LanguageHelper.NoPlaybackId), XbmcWrapper.Error)
            Logger.critical("Could not playback the url", exc_info=True)

            # We need to single Kodi that it failed and it should not wait longer. Either using a
            # `raise` or with `xbmcplugin.endOfDirectory`. Doing the latter for now although we are
            # not really playing.
            xbmcplugin.endOfDirectory(self.handle, False)

        return
コード例 #18
0
    def execute(self):
        Logger.info("Plugin::process_folder_list Doing process_folder_list")
        try:
            ok = True

            # read the item from the parameters
            selected_item = self.__media_item

            # determine the parent guid
            parent_guid = self.parameter_parser.get_parent_guid(
                self.__channel, selected_item)

            if self.__favorites is None:
                watcher = StopWatch("Plugin process_folder_list",
                                    Logger.instance())
                media_items = self.__channel.process_folder_list(selected_item)
                watcher.lap("Class process_folder_list finished")
            else:
                parent_guid = "{}.fav".format(parent_guid)
                watcher = StopWatch("Plugin process_folder_list With Items",
                                    Logger.instance())
                media_items = self.__favorites

            if len(media_items) == 0:
                Logger.warning("process_folder_list returned %s items",
                               len(media_items))
                ok = self.__show_empty_information(media_items,
                                                   favs=self.__favorites
                                                   is not None)
            else:
                Logger.debug("process_folder_list returned %s items",
                             len(media_items))

            kodi_items = []

            use_thumbs_as_fanart = AddonSettings.use_thumbs_as_fanart()
            for media_item in media_items:  # type: MediaItem
                self.__update_artwork(media_item, self.__channel,
                                      use_thumbs_as_fanart)

                if media_item.type == 'folder' or media_item.type == 'append' or media_item.type == "page":
                    action_value = action.LIST_FOLDER
                    folder = True
                elif media_item.is_playable():
                    action_value = action.PLAY_VIDEO
                    folder = False
                else:
                    Logger.critical(
                        "Plugin::process_folder_list: Cannot determine what to add"
                    )
                    continue

                # Get the Kodi item
                kodi_item = media_item.get_kodi_item()
                self.__set_kodi_properties(kodi_item,
                                           media_item,
                                           folder,
                                           is_favourite=self.__favorites
                                           is not None)

                # Get the context menu items
                context_menu_items = self._get_context_menu_items(
                    self.__channel, item=media_item)
                kodi_item.addContextMenuItems(context_menu_items)

                # Get the action URL
                url = media_item.actionUrl
                if url is None:
                    url = self.parameter_parser.create_action_url(
                        self.__channel,
                        action=action_value,
                        item=media_item,
                        store_id=parent_guid)

                # Add them to the list of Kodi items
                kodi_items.append((url, kodi_item, folder))

            watcher.lap("Kodi Items generated")

            # add items but if OK was False, keep it like that
            ok = ok and xbmcplugin.addDirectoryItems(self.handle, kodi_items,
                                                     len(kodi_items))
            watcher.lap("items send to Kodi")

            if ok and parent_guid is not None:
                self.parameter_parser.pickler.store_media_items(
                    parent_guid, selected_item, media_items)

            watcher.stop()

            self.__add_sort_method_to_handle(self.handle, media_items)
            self.__add_breadcrumb(self.handle, self.__channel, selected_item)
            self.__add_content_type(self.handle, self.__channel, selected_item)

            xbmcplugin.endOfDirectory(self.handle, ok)
        except Exception:
            Logger.error("Plugin::Error Processing FolderList", exc_info=True)
            XbmcWrapper.show_notification(
                LanguageHelper.get_localized_string(LanguageHelper.ErrorId),
                LanguageHelper.get_localized_string(LanguageHelper.ErrorList),
                XbmcWrapper.Error, 4000)
            xbmcplugin.endOfDirectory(self.handle, False)