class Favourites: def __init__(self, path): """ Initializes a Favourites class that can be use to show, add and delete favourites. :param str path: The path to store the favourites file """ self.__filePattern = "%s-%s.xotfav" self.__pickler = Pickler() self.FavouriteFolder = path def add(self, channel, item, action_url): """ Adds a favourite for a specific channel. :param channel: The channel :param item: The mediaitem :param str action_url: The mediaitem's actionUrl """ Logger.debug("Adding item %s\nfor channel %s\n%s", item, channel, action_url) file_name = self.__filePattern % (channel.guid, item.guid) file_path = os.path.join(self.FavouriteFolder, file_name) pickle = self.__pickler.pickle_media_item(item) # Just double check for folder existence if not os.path.isdir(self.FavouriteFolder): os.makedirs(self.FavouriteFolder) # replacing to pickle in the actionUrl to save space action_url = self.__remove_pickle(action_url) try: with io.open(file_path, mode='w', encoding='utf-8') as file_handle: file_handle.write( "%s\n%s\n%s\n%s" % (channel.channelName, item.name, action_url, pickle)) except: Logger.error("Error saving favourite", exc_info=True) raise return # noinspection PyUnusedLocal def remove(self, item): """ Adds a favourite for a specific channel :param item: The mediaitem """ path_mask = os.path.join(self.FavouriteFolder, "*-%s.xotfav" % (item.guid, )) Logger.debug("Removing favourites for mask: %s", path_mask) for fav in glob.glob(path_mask): Logger.trace("Removing item %s\nFileName: %s", item, fav) os.remove(fav) return def list(self, channel=None): """ Lists favourites. If a channel was specified it will limit them to that. :param channel: The channel to limit the favourites to. :return: A list of tupples (action_url, pickle) :rtype: list """ favs = [] if channel: path_mask = os.path.join(self.FavouriteFolder, "%s-*.xotfav" % (channel.guid, )) else: path_mask = os.path.join(self.FavouriteFolder, "*.xotfav") Logger.debug("Fetching favourites for mask: %s", path_mask) for fav in glob.glob(path_mask): Logger.trace("Fetching %s", fav) try: with io.open(fav, mode='r', encoding='utf-8') as file_handle: channel_name = file_handle.readline().rstrip() name = file_handle.readline().rstrip() action_url = file_handle.readline().rstrip() if "pickle=" in action_url and "pickle=%s" not in action_url: # see issue https://bitbucket.org/basrieter/xbmc-online-tv/issues/1037 Logger.debug( "Found favourite with full pickle, removing the pickle as we should use the one from the file." ) action_url = self.__remove_pickle(action_url) pickle = file_handle.readline() except: Logger.error("Error fetching favourite", exc_info=True) raise if channel_name == "" or name == "" or action_url == "" or pickle == "": Logger.error( "Apparently the file had too few lines, corrupt Favourite, removing it:\n" "Pickle: %s\n" "Channel: %s\n" "Item: %s\n" "ActionUrl: %s\n" "Pickle: %s", fav, channel_name, name, action_url, pickle) # Remove the invalid favourite os.remove(fav) continue Logger.debug("Found favourite: %s", name) try: item = self.__pickler.de_pickle_media_item(pickle) except Exception: Logger.error("Cannot depickle item.", exc_info=True) # Let's not remove them for now. Just ignore. # os.remove(fav) continue validation_error = self.__pickler.validate( item, logger=Logger.instance()) if validation_error: Logger.error( "Invalid Pickled Item: %s\nRemoving favourite: %s", validation_error, fav) # Remove the invalid favourite os.remove(fav) continue # add the channel name if channel is None: item.name = "%s [%s]" % (item.name, channel_name) item.clear_date() item.actionUrl = action_url % (pickle, ) favs.append(item) return favs def __remove_pickle(self, action_url): pickle = Regexer.do_regex("pickle=([^&]+)", action_url) if not pickle: return action_url return action_url.replace(pickle[0], "%s")
class ParameterParser(object): def __init__(self, addon_name, params): """ :param str addon_name: The name of the add-on :param str params: The parameteters used to start the ParameterParser """ Logger.debug("Parsing parameters from: %s", params) # Url Keywords self.keywordPickle = "pickle" # : Used for the pickle item self.keywordAction = "action" # : Used for specifying the action self.keywordChannel = "channel" # : Used for the channel self.keywordChannelCode = "channelcode" # : Used for the channelcode self.keywordCategory = "category" # : Used for the category self.keywordRandomLive = "rnd" # : Used for randomizing live items self.keywordSettingId = "settingid" # : Used for setting an encrypted setting self.keywordSettingActionId = "settingactionid" # : Used for passing the actionid for the encryption self.keywordSettingName = "settingname" # : Used for setting an encrypted settings display name self.keywordSettingTabFocus = "tabfocus" # : Used for setting the tabcontrol to focus after changing a setting self.keywordSettingSettingFocus = "settingfocus" # : Used for setting the setting control to focus after changing a setting self.keywordLanguage = "lang" # : Used for the 2 char language information self.keywordProxy = "proxy" # : Used so set the proxy index self.keywordLocalIP = "localip" # : Used to set the local ip index # Url Actions self.actionFavourites = "favourites" # : Used to show favorites for a channel self.actionAllFavourites = "allfavourites" # : Used to show all favorites self.actionRemoveFavourite = "removefromfavourites" # : Used to remove items from favorites self.actionAddFavourite = "addtofavourites" # : Used to add items to favorites self.actionDownloadVideo = "downloadVideo" # : Used to download a video item self.actionPlayVideo = "playvideo" # : Used to play a video item self.actionUpdateChannels = "updatechannels" # : Used to update channels self.actionListFolder = "listfolder" # : Used to list a folder self.actionListCategory = "listcategory" # : Used to show the channels from a category self.actionConfigureChannel = "configurechannel" # : Used to configure a channel self.actionSetEncryptionPin = "changepin" # : Used for setting an application pin self.actionSetEncryptedValue = "encryptsetting" # : Used for setting an application pin self.actionResetVault = "resetvault" # : Used for resetting the vault self.actionPostLog = "postlog" # : Used for sending log files to pastebin.com self.actionProxy = "setproxy" # : Used for setting a proxy 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" # determine the query parameters self.params = self.__get_parameters(params) self.pluginName = addon_name # We need a picker for this instance self._pickler = Pickler() def _create_action_url(self, channel, action, item=None, category=None): """ Creates an URL that includes an action. Arguments: channel : Channel - action : string - Keyword Arguments: item : MediaItem - :param ChannelInfo|Channel channel: The channel object to use for the URL. :param str action: Action to create an url for :param MediaItem item: The media item to add :param str category: The category to use. :return: a complete action url with all keywords and values :rtype: str|unicode """ if action is None: raise Exception("action is required") # catch the plugin:// url's for items and channels. if item is not None and item.url and item.url.startswith("plugin://"): return item.url if item is None and channel is not None and channel.uses_external_addon: return channel.addonUrl params = dict() if channel: params[self.keywordChannel] = channel.moduleName if channel.channelCode: params[self.keywordChannelCode] = channel.channelCode params[self.keywordAction] = action # it might have an item or not if item is not None: params[self.keywordPickle] = self._pickler.pickle_media_item(item) if action == self.actionPlayVideo and item.isLive: params[self.keywordRandomLive] = random.randint(10000, 99999) if category: params[self.keywordCategory] = category url = "%s?" % (self.pluginName, ) for k in params.keys(): url = "%s%s=%s&" % (url, k, params[k]) url = url.strip('&') # Logger.Trace("Created url: '%s'", url) return url 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