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
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
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 []
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
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()
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)
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
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()
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
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
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)
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
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
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
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)
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)
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
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)