def __GetApplicationKey(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 """ applicationKeyEncrypted = AddonSettings.GetSetting(Vault.__APPLICATION_KEY_SETTING) if not applicationKeyEncrypted: return None vaultIncorrectPin = LanguageHelper.GetLocalizedString(LanguageHelper.VaultIncorrectPin) pin = XbmcWrapper.ShowKeyBoard( heading=LanguageHelper.GetLocalizedString(LanguageHelper.VaultInputPin), hidden=True) if not pin: XbmcWrapper.ShowNotification("", vaultIncorrectPin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") pinKey = self.__GetPBK(pin) applicationKey = self.__Decrypt(applicationKeyEncrypted, pinKey) if not applicationKey.startswith(Vault.__APPLICATION_KEY_SETTING): Logger.Critical("Invalid Retrospect PIN") XbmcWrapper.ShowNotification("", vaultIncorrectPin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") applicationKeyValue = applicationKey[len(Vault.__APPLICATION_KEY_SETTING) + 1:] Logger.Info("Successfully decrypted the ApplicationKey.") return applicationKeyValue
def __GetParameters(self, queryString): """ Extracts the actual parameters as a dictionary from the passed in querystring. This method takes the self.quotedPlus into account. Arguments: queryString : String - The querystring Returns: dict() of keywords and values. """ result = dict() queryString = queryString.strip('?') if queryString != '': try: for pair in queryString.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", queryString, exc_info=True) raise return result
def Header(self, uri, proxy=None, params="", referer=None, additionalHeaders=None, noCache=False): """Retrieves header information only Arguments: uri : string - the URI to download Keyword Arguments: proxy : [opt] string - The address and port (proxy.address.ext:port) of a proxy server that should be used. params : [opt] string - data to send with the request (open(uri, params)) Returns: Data and the URL to which a redirect could have occurred. """ Logger.Info("Retreiving Header info for %s", uri) # uri = uri # params = params if not uri: return "", "" try: if params == "": uriHandle = self.__GetOpener( uri, proxy, disableCaching=noCache, headOnly=True, referer=referer, additionalHeaders=additionalHeaders).open(uri) else: uriHandle = self.__GetOpener( uri, proxy, disableCaching=noCache, headOnly=True, referer=referer, additionalHeaders=additionalHeaders).open(uri, params) data = uriHandle.info() realUrl = uriHandle.geturl() data = data.get('Content-Type') uriHandle.close() Logger.Debug("Header info retreived: %s for realUrl %s", data, realUrl) return data, realUrl except: Logger.Critical("Header info not retreived", exc_info=True) return "", ""
def DoRegex(regex, data): """Performs a regular expression Arguments: regex : string - the regex to perform on the data. data : string - the data to perform the regex on. 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. """ try: if not isinstance(regex, (tuple, list)): if "?P<" in regex: return Regexer.__DoDictionaryRegex(regex, data) else: return Regexer.__DoRegex(regex, data) else: Logger.Debug("Performing multi-regex find on '%s'", regex) results = [] count = 0 for r in regex: if "?P<" in r: regexResults = Regexer.__DoDictionaryRegex(r, data) # add to the results with a count in front of the results results += map(lambda x: (count, x), regexResults) else: regexResults = Regexer.__DoRegex(r, data) if len(regexResults) > 0: if isinstance(regexResults[0], (tuple, list)): # is a tupe/list was returned, prepend it with the count results += map(lambda x: (count, ) + x, regexResults) else: # create a tuple with the results results += map(lambda x: (count, x), regexResults) # increase count count += 1 Logger.Debug("Returning %s results", len(results)) return results except: Logger.Critical('error regexing', exc_info=True) return []
def CreateVideoItem(self, resultSet): """Creates a MediaItem of type 'video' using the resultSet from the regex. Arguments: resultSet : tuple (string) - the resultSet of the self.videoItemRegex Returns: A new MediaItem of type 'video' or 'audio' (despite the method's name) This method creates a new MediaItem from the Regular Expression or Json results <resultSet>. 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.UpdateVideoItem method is called if the item is focussed or selected for playback. """ Logger.Trace(resultSet) # Validate the input and raise errors if not isinstance(resultSet, dict): Logger.Critical("No Dictionary as a resultSet. Implement a custom CreateVideoItem") raise NotImplementedError("No Dictionary as a resultSet. Implement a custom CreateVideoItem") # return None elif "title" not in resultSet or "url" not in resultSet: Logger.Warning("No ?P<title> or ?P<url> in resultSet") raise LookupError("No ?P<title> or ?P<url> in resultSet") # return None # The URL url = resultSet["url"] if not url.startswith("http"): url = "%s/%s" % (self.baseUrl.rstrip('/'), url.lstrip('/')) # The title if "subtitle" in resultSet: title = "%(title)s - %(subtitle)s" % resultSet else: title = resultSet["title"] if title.isupper(): title = title.title() item = mediaitem.MediaItem(title, url) item.description = resultSet.get("description", "") item.thumb = resultSet.get("thumburl", "") item.icon = self.icon item.type = 'video' item.fanart = self.fanart item.HttpHeaders = self.httpHeaders item.complete = False return item
def ChangePin(self, applicationKey=None): # type: (str) -> bool """ Stores an existing ApplicationKey using a new PIN @param applicationKey: an existing ApplicationKey that will be stored. If none specified, the existing ApplicationKey of the Vault will be used. @return: indication of success """ 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 applicationKey is None: Logger.Debug("Using the ApplicationKey from the vault.") applicationKey = Vault.__Key else: Logger.Debug("Using the ApplicationKey from the input parameter.") if not applicationKey: raise ValueError("No ApplicationKey specified.") # Now we get a new PIN and (re)encrypt pin = XbmcWrapper.ShowKeyBoard( heading=LanguageHelper.GetLocalizedString(LanguageHelper.VaultNewPin), hidden=True) if not pin: XbmcWrapper.ShowNotification( "", LanguageHelper.GetLocalizedString(LanguageHelper.VaultNoPin), XbmcWrapper.Error) return False pin2 = XbmcWrapper.ShowKeyBoard( heading=LanguageHelper.GetLocalizedString(LanguageHelper.VaultRepeatPin), hidden=True) if pin != pin2: Logger.Critical("Mismatch in PINs") XbmcWrapper.ShowNotification( "", LanguageHelper.GetLocalizedString(LanguageHelper.VaultPinsDontMatch), XbmcWrapper.Error) return False encryptedKey = "%s=%s" % (self.__APPLICATION_KEY_SETTING, applicationKey) # let's generate a pin using the scrypt password-based key derivation pinKey = self.__GetPBK(pin) encryptedKey = self.__Encrypt(encryptedKey, pinKey) AddonSettings.SetSetting(Vault.__APPLICATION_KEY_SETTING, encryptedKey) Logger.Info("Successfully updated the Retrospect PIN") return True
def ShowChannelList(self, category=None): """Displays the channels that are currently available in XOT as a directory listing. Keyword Arguments: category : String - The category to show channels for """ if category: Logger.Info("Plugin::ShowChannelList for %s", category) else: Logger.Info("Plugin::ShowChannelList") try: # only display channels channelRegister = ChannelImporter.GetRegister() channels = channelRegister.GetChannels(infoOnly=True) xbmcItems = [] 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 XBMC item item = channel.GetXBMCItem() # Get the context menu items contextMenuItems = self.__GetContextMenuItems(channel) item.addContextMenuItems(contextMenuItems) # Get the URL for the item url = self.__CreateActionUrl(channel, action=self.actionListFolder) # Append to the list of XBMC Items xbmcItems.append((url, item, True)) # Add the items ok = xbmcplugin.addDirectoryItems(self.handle, xbmcItems, len(xbmcItems)) # 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=self.contentType) xbmcplugin.endOfDirectory(self.handle, ok) except: xbmcplugin.endOfDirectory(self.handle, False) Logger.Critical("Error fetching channels for plugin", exc_info=True)
def CreateFolderItem(self, resultSet): """Creates a MediaItem of type 'folder' using the resultSet from the regex. Arguments: resultSet : tuple(strig) - the resultSet of the self.folderItemRegex Returns: A new MediaItem of type 'folder' This method creates a new MediaItem from the Regular Expression or Json results <resultSet>. The method should be implemented by derived classes and are specific to the channel. """ Logger.Trace(resultSet) # Validate the input and raise errors if not isinstance(resultSet, dict): Logger.Critical("No Dictionary as a resultSet. Implement a custom CreateVideoItem") raise NotImplementedError("No Dictionary as a resultSet. Implement a custom CreateVideoItem") # return None elif "title" not in resultSet or "url" not in resultSet: Logger.Warning("No ?P<title> or ?P<url> in resultSet") raise LookupError("No ?P<title> or ?P<url> in resultSet") # return None # The URL url = resultSet["url"] if not url.startswith("http"): url = "%s/%s" % (self.baseUrl.rstrip('/'), url.lstrip('/')) # The title title = resultSet["title"] if title.isupper(): title = title.title() item = mediaitem.MediaItem(title, url) item.description = resultSet.get("description", "") item.thumb = resultSet.get("thumburl", "") item.icon = self.icon item.type = 'folder' item.fanart = self.fanart item.HttpHeaders = self.httpHeaders item.complete = True return item
def CacheCleanUp(path, cacheTime, 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. """ # let's import htis one here import glob try: Logger.Info("Cleaning up cache in '%s' that is older than %s days", path, cacheTime / 24 / 3600) if not os.path.exists(path): Logger.Info("Did not cleanup cache: folder does not exist") return deleteCount = 0 fileCount = 0 #for item in os.listdir(path): pathMask = os.path.join(path, mask) for item in glob.glob(pathMask): fileName = os.path.join(path, item) if os.path.isfile(fileName): Logger.Trace(fileName) fileCount += 1 createTime = os.path.getctime(fileName) if createTime + cacheTime < time.time(): os.remove(fileName) Logger.Debug("Removed file: %s", fileName) deleteCount += 1 Logger.Info("Removed %s of %s files from cache in: '%s'", deleteCount, fileCount, pathMask) except: Logger.Critical("Error cleaning the cachefolder: %s", path, exc_info=True)
def __ParseChannelVersionInfo(self, path): """ Parses the addon.xml file and checks if all is OK. @param path: path to load the addon from @return: the AddonId-Version """ addonFile = os.path.join(path, "addon.xml") # continue if no addon.xml exists if not os.path.isfile(addonFile): Logger.Info("No addon.xml found at %s", addonFile) return None f = open(addonFile, 'r+') addonXml = f.read() f.close() packVersion = Regexer.DoRegex('id="([^"]+)"\W+version="([^"]{5,10})"', addonXml) if len(packVersion) > 0: # Get the first match packVersion = packVersion[0] packageId = packVersion[0] packageVersion = version.Version(version=packVersion[1]) # channelAddon = os.path.split(path)[-1] # packVersion = packVersion. if Config.version.EqualRevisions(packageVersion): Logger.Info("Loading %s version %s", packageId, packageVersion) # save to the list of present items, for querying in the # future (xbox updates) channelVersionID = "%s-%s" % (packVersion[0], packVersion[1]) return channelVersionID else: Logger.Warning("Skipping %s version %s: Versions do not match.", packageId, packageVersion) return None else: Logger.Critical("Cannot determine channel version. Not loading channel @ '%s'", path) return None
def __GetIndex(self): # type: () -> dict """ 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: """ # 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.__RebuildIndex() if not os.path.isfile(self.__CHANNEL_INDEX): Logger.Info("No index file found at '%s'. Rebuilding.", self.__CHANNEL_INDEX) return self.__RebuildIndex() fd = None try: fd = open(self.__CHANNEL_INDEX) data = fd.read() indexJson = JsonHelper(data, logger=Logger.Instance()) Logger.Debug("Loaded index from '%s'.", self.__CHANNEL_INDEX) if not self.__IsIndexConsistent(indexJson.json): return self.__RebuildIndex() return indexJson.json except: Logger.Critical("Error reading channel index. Rebuilding.", exc_info=True) return self.__RebuildIndex() finally: if fd is not None and not fd.closed: fd.close()
def __ValidateAddOnVersion(self, path): """ Parses the addon.xml file and checks if all is OK. @param path: path to load the addon from @return: the AddonId-Version """ addonFile = os.path.join(path, "addon.xml") # continue if no addon.xml exists if not os.path.isfile(addonFile): Logger.Info("No addon.xml found at %s.", addonFile) return None, None f = open(addonFile, 'r+') addonXml = f.read() f.close() packVersion = Regexer.DoRegex('id="([^"]+)"\W+version="([^"]+)"', addonXml) if len(packVersion) > 0: # Get the first match packVersion = packVersion[0] packageId = packVersion[0] packageVersion = Version(version=packVersion[1]) if Config.version.EqualBuilds(packageVersion): Logger.Info("Adding %s version %s", packageId, packageVersion) return packageId, packageVersion else: Logger.Warning("Skipping %s version %s: Versions do not match.", packageId, packageVersion) return None, None else: Logger.Critical("Cannot determine Channel Add-on version. Not loading Add-on @ '%s'.", path) return None, None
def ProcessFolderList(self): """Wraps the channel.ProcessFolderList""" Logger.Info("Plugin::ProcessFolderList Doing ProcessFolderList") try: ok = True selectedItem = None if self.keywordPickle in self.params: selectedItem = Pickler.DePickleMediaItem(self.params[self.keywordPickle]) watcher = stopwatch.StopWatch("Plugin ProcessFolderList", Logger.Instance()) episodeItems = self.channelObject.ProcessFolderList(selectedItem) watcher.Lap("Class ProcessFolderList finished") if len(episodeItems) == 0: Logger.Warning("ProcessFolderList returned %s items", len(episodeItems)) ok = self.__ShowEmptyInformation(episodeItems) else: Logger.Debug("ProcessFolderList returned %s items", len(episodeItems)) xbmcItems = [] for episodeItem in episodeItems: if episodeItem.thumb == "": episodeItem.thumb = self.channelObject.noImage if episodeItem.fanart == "": episodeItem.fanart = self.channelObject.fanart if episodeItem.type == 'folder' or episodeItem.type == 'append' or episodeItem.type == "page": action = self.actionListFolder folder = True elif episodeItem.IsPlayable(): action = self.actionPlayVideo folder = False else: Logger.Critical("Plugin::ProcessFolderList: Cannot determine what to add") continue # Get the XBMC item item = episodeItem.GetXBMCItem() # Get the context menu items contextMenuItems = self.__GetContextMenuItems(self.channelObject, item=episodeItem) item.addContextMenuItems(contextMenuItems) # Get the action URL url = self.__CreateActionUrl(self.channelObject, action=action, item=episodeItem) # Add them to the list of XBMC items xbmcItems.append((url, 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, xbmcItems, len(xbmcItems)) watcher.Lap("items send to Kodi") if selectedItem is None: # mainlist item register channel. Statistics.RegisterChannelOpen(self.channelObject, Initializer.StartTime) watcher.Lap("Statistics send") watcher.Stop() self.__AddSortMethodToHandle(self.handle, episodeItems) # set the content xbmcplugin.setContent(handle=self.handle, content=self.contentType) xbmcplugin.endOfDirectory(self.handle, ok) except: Statistics.RegisterError(self.channelObject) XbmcWrapper.ShowNotification(LanguageHelper.GetLocalizedString(LanguageHelper.ErrorId), LanguageHelper.GetLocalizedString(LanguageHelper.ErrorList), XbmcWrapper.Error, 4000) Logger.Error("Plugin::Error Processing FolderList", exc_info=True) xbmcplugin.endOfDirectory(self.handle, False)
from environments import Environments from initializer import Initializer from updater import Updater from favourites import Favourites from mediaitem import MediaItem from helpers.channelimporter import ChannelImporter from helpers.languagehelper import LanguageHelper from helpers.htmlentityhelper import HtmlEntityHelper from helpers import stopwatch from helpers.statistics import Statistics from helpers.sessionhelper import SessionHelper from textures import TextureHandler # from streams.youtube import YouTube from pickler import Pickler except: Logger.Critical("Error initializing %s", Config.appName, exc_info=True) #=============================================================================== # Main Plugin Class #=============================================================================== class Plugin: """Main Plugin Class This class makes it possible to access all the XOT channels as a Kodi Add-on instead of a script. s """ def __init__(self, pluginName, params, handle=0): """Initialises the plugin with given arguments."""
def __RetreiveData(self, destHandle, uri, timeOutValue, progressCallback=None, proxy=None, maxBytes=0, params="", referer=None, additionalHeaders=None, noCache=False, blockMultiplier=1): """Open an URL Async using a thread Arguments: uri : string - the URI to download Keyword Arguments: progressCallback : [opt] boolean - should a progress bar be shown proxy : [opt] string - The address and port (proxy.address.ext:port) of a proxy server that should be used. bytes : [opt] integer - the number of bytes to get. params : [opt] string - data to send with the request (open(uri, params)) headers : [opt] dict - a dictionary of additional headers Returns: The data that was retrieved from the URI. """ # init parameters canceled = False timeOut = False error = False srcHandle = None blocksRead = 0 fileSize = 0 charSet = None blockSize = self.blockSize * blockMultiplier try: if uri == "": return error, canceled, "" if uri.startswith("file:"): index = uri.rfind("?") #index = string.rfind(uri, "?") if index > 0: uri = uri[0:index] Logger.Info( "Opening requested uri: %s (callback=%s, timeout=%s)", uri, progressCallback is not None, timeOutValue) self.__DoCallback(progressCallback, 0, blockSize, 0, False) # set the start time in seconds startTime = time.time() # get an opener and handle opener = self.__GetOpener(uri, proxy, disableCaching=noCache, referer=referer, additionalHeaders=additionalHeaders) if params == '': srcHandle = opener.open(uri) else: srcHandle = opener.open(uri, params) # get some metadata Logger.Debug("Determining number of bytes to fetch") data = srcHandle.info() if data.get('Content-length'): fileSize = int(data.get('Content-length')) Logger.Debug('ByteSize is known (fileSize=' + str(fileSize) + ')') else: fileSize = -1 Logger.Debug('ByteSize is unknown') # check for encoding charSet = None try: contentType = data.get('Content-Type') if contentType: Logger.Trace("Found Content-Type header: %s", contentType) charSetNeedle = 'charset=' charSetIndex = contentType.rfind(charSetNeedle) if charSetIndex > 0: charSet = contentType[charSetIndex + len(charSetNeedle):] Logger.Trace("Found Charset HTML Header: %s", charSet) except: charSet = None blocksRead = 0 while True: block = srcHandle.read(blockSize) if block == "": break destHandle.write(block) blocksRead += 1 canceled = self.__DoCallback(progressCallback, blocksRead, blockSize, fileSize, False) if canceled: break if time.time() > startTime + timeOutValue: timeOut = True break if 0 < maxBytes < blocksRead * blockSize: Logger.Info( 'Stopping download because Bytes > maxBytes') break srcHandle.close() except (IncompleteRead, ValueError): # Python 2.6 throws a IncompleteRead on Chuncked data # Python 2.4 throws a ValueError on Chuncked data Logger.Error("IncompleteRead error opening url %s", uri) try: if srcHandle: srcHandle.close() except UnboundLocalError: pass except: Logger.Critical("Error Opening url %s", uri, exc_info=True) error = True try: if srcHandle: srcHandle.close() except UnboundLocalError: pass # we are finished now self.__DoCallback(progressCallback, blocksRead, blockSize, fileSize, True) if timeOut: Logger.Critical( "The URL lookup did not respond within the TimeOut (%s s)", timeOutValue) elif canceled: Logger.Warning("Opening of %s was canceled", uri) elif not error: Logger.Info("Url %s was opened successfully", uri) return error, canceled, charSet
def UpdateAddOnSettingsWithChannels(channels, config): """ updats the settings.xml to include all the channels Arguments: channels : List<channels> - The channels to add to the settings.xml config : Config - The configuration object """ # sort the channels channels.sort() # Then we read the original file filenameTemplate = os.path.join(config.rootDir, "resources", "settings_template.xml") # noinspection PyArgumentEqualDefault settingsXml = open(filenameTemplate, "r") contents = settingsXml.read() settingsXml.close() newContents = AddonSettings.__UpdateAddOnSettingsWithLanguages( contents, channels) newContents = AddonSettings.__UpdateAddOnSettingsWithChannelSelection( newContents, channels) newContents, settingsOffsetForVisibility = \ AddonSettings.__UpdateAddOnSettingsWithChannelSettings(newContents, channels) newContents = AddonSettings.__UpdateAddOnSettingsWithProxies( newContents, channels, settingsOffsetForVisibility) # Now fill the templates, we only import here due to performance penalties of the # large number of imports. from helpers.templatehelper import TemplateHelper th = TemplateHelper(Logger.Instance(), template=newContents) newContents = th.Transform() # No more spoofing or proxies newContents = newContents.replace('<!-- start of proxy selection -->', '<!-- start of proxy selection') newContents = newContents.replace('<!-- end of proxy selection -->', 'end of proxy selection -->') newContents = newContents.replace('<!-- start of proxy settings -->', '<!-- start of proxy settings') newContents = newContents.replace('<!-- end of proxy settings -->', 'end of proxy settings -->') # Finally we insert the new XML into the old one filename = os.path.join(config.rootDir, "resources", "settings.xml") filenameTemp = 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. userSettings = os.path.join(Config.profileDir, "settings.xml") userSettingsBackup = os.path.join(Config.profileDir, "settings.old.xml") Logger.Debug("Backing-up user settings: %s", userSettingsBackup) if os.path.isfile(userSettings): shutil.copy(userSettings, userSettingsBackup) else: Logger.Warning("No user settings found at: %s", userSettings) # Update the addonsettings.xml by first updating a temp xml file. Logger.Debug("Creating new settings.xml file: %s", filenameTemp) Logger.Trace(newContents) settingsXml = open(filenameTemp, "w+") settingsXml.write(newContents) settingsXml.close() Logger.Debug("Replacing existing settings.xml file: %s", filename) shutil.move(filenameTemp, filename) # restore the user profile settings.xml file when needed if os.path.isfile( userSettings) and os.stat(userSettings).st_size != os.stat( userSettingsBackup).st_size: Logger.Critical( "User settings.xml was overwritten during setttings update. Restoring from %s", userSettingsBackup) shutil.copy(userSettingsBackup, userSettings) except: Logger.Error( "Something went wrong trying to update the settings.xml", exc_info=True) try: settingsXml.close() except: pass # clean up time file if os.path.isfile(filenameTemp): os.remove(filenameTemp) # restore original settings settingsXml = open(filenameTemp, "w+") settingsXml.write(contents) settingsXml.close() shutil.move(filenameTemp, filename) return Logger.Info("Settings.xml updated succesfully. Reloading settings.") AddonSettings.__LoadSettings() return
def PlayVideoItem(self): """Starts the videoitem using a playlist. """ Logger.Debug("Playing videoitem using PlayListMethod") item = None try: item = Pickler.DePickleMediaItem(self.params[self.keywordPickle]) if item.isDrmProtected and AddonSettings.ShowDrmWarning(): Logger.Debug("Showing DRM Warning message") title = LanguageHelper.GetLocalizedString(LanguageHelper.DrmTitle) message = LanguageHelper.GetLocalizedString(LanguageHelper.DrmText) XbmcWrapper.ShowDialog(title, message) elif item.isDrmProtected: Logger.Debug("DRM Warning message disabled by settings") if not item.complete: item = self.channelObject.ProcessVideoItem(item) # validated the updated item if not item.complete or not item.HasMediaItemParts(): Logger.Warning("UpdateVideoItem returned an item that had item.complete = False:\n%s", item) Statistics.RegisterError(self.channelObject, item=item) if not item.HasMediaItemParts(): # the update failed or no items where found. Don't play XbmcWrapper.ShowNotification(LanguageHelper.GetLocalizedString(LanguageHelper.ErrorId), LanguageHelper.GetLocalizedString(LanguageHelper.NoStreamsId), XbmcWrapper.Error) Logger.Warning("Could not start playback due to missing streams. Item:\n%s", item) return playData = self.channelObject.PlayVideoItem(item) Logger.Debug("Continuing playback in plugin.py") if not playData: Logger.Warning("PlayVideoItem did not return valid playdata") return else: playList, srt = playData # Get the Kodi Player instance (let Kodi decide what player, see # http://forum.kodi.tv/showthread.php?tid=173887&pid=1516662#pid1516662) xbmcPlayer = xbmc.Player() # now we force the busy dialog to close, else the video will not play and the # setResolved will not work. xbmc.executebuiltin("Dialog.Close(busydialog)") resolvedUrl = None if item.IsResolvable(): # now set the resolve to the first URL startIndex = playList.getposition() # the current location if startIndex < 0: startIndex = 0 Logger.Info("Playing stream @ playlist index %s using setResolvedUrl method", startIndex) resolvedUrl = playList[startIndex].getfilename() xbmcplugin.setResolvedUrl(self.handle, True, playList[startIndex]) else: # playlist do not use the setResolvedUrl Logger.Info("Playing stream using Playlist method") xbmcPlayer.play(playList) # the set the subtitles showSubs = AddonSettings.UseSubtitle() if srt and (srt != ""): Logger.Info("Adding subtitle: %s and setting showSubtitles to %s", srt, showSubs) XbmcWrapper.WaitForPlayerToStart(xbmcPlayer, logger=Logger.Instance(), url=resolvedUrl) xbmcPlayer.setSubtitles(srt) xbmcPlayer.showSubtitles(showSubs) except: if item: Statistics.RegisterError(self.channelObject, item=item) else: Statistics.RegisterError(self.channelObject) XbmcWrapper.ShowNotification(LanguageHelper.GetLocalizedString(LanguageHelper.ErrorId), LanguageHelper.GetLocalizedString(LanguageHelper.NoPlaybackId), XbmcWrapper.Error) Logger.Critical("Could not playback the url", exc_info=True) return
def __init__(self, pluginName, params, handle=0): """Initialises the plugin with given arguments.""" # some constants self.actionDownloadVideo = "downloadVideo".lower() # : Action used to download a video item self.actionFavourites = "favourites".lower() # : Action used to show favorites for a channel self.actionAllFavourites = "allfavourites".lower() # : Action used to show all favorites self.actionRemoveFavourite = "removefromfavourites".lower() # : Action used to remove items from favorites self.actionAddFavourite = "addtofavourites".lower() # : Action used to add items to favorites self.actionPlayVideo = "playvideo".lower() # : Action used to play a video item self.actionUpdateChannels = "updatechannels".lower() # : Action used to update channels self.actionListFolder = "listfolder".lower() # : Action used to list a folder self.actionListCategory = "listcategory" # : Action used to show the channels from a category self.actionConfigureChannel = "configurechannel" # : Action used to configure a channel self.keywordPickle = "pickle".lower() # : Keyword used for the pickle item self.keywordAction = "action".lower() # : Keyword used for the action item self.keywordChannel = "channel".lower() # : Keyword used for the channel self.keywordChannelCode = "channelcode".lower() # : Keyword used for the channelcode self.keywordCategory = "category" # : Keyword used for the category self.keywordRandomLive = "rnd" # : Keyword used for randomizing live items self.pluginName = pluginName self.handle = int(handle) # channel objects self.channelObject = None self.channelFile = "" self.channelCode = None self.contentType = "episodes" self.methodContainer = dict() # : storage for the inspect.getmembers(channel) method. Improves performance # determine the query parameters self.params = self.__GetParameters(params) Logger.Info("*********** Starting %s add-on version %s ***********", Config.appName, Config.version) Logger.Debug("Plugin Params: %s (%s) [handle=%s, name=%s, query=%s]", self.params, len(self.params), self.handle, self.pluginName, params) # are we in session? sessionActive = SessionHelper.IsSessionActive(Logger.Instance()) # fetch some environment settings envCtrl = envcontroller.EnvController(Logger.Instance()) # self.FavouritesEnabled = envCtrl.SQLiteEnabled() self.FavouritesEnabled = not envCtrl.IsPlatform(Environments.Xbox) if not sessionActive: # do add-on start stuff Logger.Info("Add-On start detected. Performing startup actions.") # print the folder structure envCtrl.DirectoryPrinter(Config, AddonSettings) # show notification XbmcWrapper.ShowNotification(None, LanguageHelper.GetLocalizedString(LanguageHelper.StartingAddonId) % ( Config.appName,), fallback=False, logger=Logger) # check for updates if envCtrl.IsPlatform(Environments.Xbox): Updater().AutoUpdate() # check if the repository is available envCtrl.IsInstallMethodValid(Config) # check for cache folder envCtrl.CacheCheck() # do some cache cleanup envCtrl.CacheCleanUp(Config.cacheDir, Config.cacheValidTime) envCtrl.CacheCleanUp(AddonSettings.GetUzgCachePath(), AddonSettings.GetUzgCacheDuration() * 24 * 3600, "xot.*") # create a session SessionHelper.CreateSession(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.ShowCategories(): self.ShowCategories() else: self.ShowChannelList() #=============================================================================== # Start the plugin verion of the episode window #=============================================================================== else: try: # 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 channelRegister = ChannelImporter.GetRegister() channel = channelRegister.GetSingleChannel(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.InitChannel() Logger.Info("Loaded: %s", self.channelObject.channelName) elif self.keywordCategory in self.params: # no channel needed. pass elif 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 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 if self.params[self.keywordAction] == self.actionListCategory: self.ShowChannelList(self.params[self.keywordCategory]) elif self.params[self.keywordAction] == self.actionConfigureChannel: self.__ConfigureChannel(self.channelObject) elif self.params[self.keywordAction] == self.actionFavourites: # we should show the favourites self.ShowFavourites(self.channelObject) elif self.params[self.keywordAction] == self.actionAllFavourites: if self.channelObject is not None: Logger.Warning("We have a self.channelObject with self.actionAllFavourites") self.ShowFavourites(None) elif self.params[self.keywordAction] == self.actionRemoveFavourite: self.RemoveFavourite() elif self.params[self.keywordAction] == self.actionAddFavourite: self.AddFavourite() elif self.params[self.keywordAction] == self.actionListFolder: # channelName and URL is present, Parse the folder self.ProcessFolderList() elif self.params[self.keywordAction] == self.actionPlayVideo: self.PlayVideoItem() elif not self.params[self.keywordAction] == "": self.OnActionFromContextMenu(self.params[self.keywordAction]) else: Logger.Warning("Number of parameters (%s) or parameter (%s) values not implemented", len(self.params), self.params) except: Logger.Critical("Error parsing for add-on", exc_info=True) self.__FetchTextures() return
def __ImportChannels(self): # , className = None): """Import the available channels This method will: - iterate through the Addons folder and find all the folders name <basename>.channel.<channelname>. - then adds all the subfolders into a list (with paths). - then all paths are added to the system path, so they can be imported. - then read all the chn_<name>.xml metadata files and add the ChannelInfo objects to the self.__channelsToImport - then the channels in the self.__channelsToImport list are instantiated into the self.channels list. """ Logger.Debug("Importing available channels") # import each channelPath. On import, the channelPath will call the RegisterChannel Method try: # clear a possible previous import self.__enabledChannels = [] self.__allChannels = [] self.__validChannels = [] self.__channelVersions = [] # first find all folders with channels that we might need to import channelImport = [] importTimer = StopWatch("ChannelImporter :: importing channels", Logger.Instance()) addonPath = self.__GetAddonPath() channelPathStart = "%s.channel" % (Config.addonDir,) for directory in os.listdir(addonPath): if channelPathStart in directory and "BUILD" not in directory: path = os.path.join(addonPath, directory) channelVersion = self.__ParseChannelVersionInfo(path) if channelVersion: self.__channelVersions.append(channelVersion) else: # no info was returned, so we will not include the channel continue # get all nested channels subDirs = os.listdir(path) channelImport.extend( [os.path.abspath(os.path.join(path, weapon)) for weapon in subDirs]) channelImport.sort() importTimer.Lap("Directories scanned for .channel") # we need to make sure we don't load multiple channel classes and track if we found updates channelsUpdated = False loadedChannels = [] channelsToImport = [] # now import the channels for channelPath in channelImport: if not os.path.isdir(channelPath): continue # determine channelname channelName = os.path.split(channelPath)[-1] if channelName == self.__updateChannelPath: Logger.Trace("Update path found and skipping: %s", channelName) continue # if loadedChannels.count(channelName) > 0: if channelName in loadedChannels: Logger.Warning( "Not loading: chn_%s.xml in %s because there is already a path with " "name '%s' that name loaded", channelName, channelPath, channelName) continue if channelName.startswith("."): continue # now we can continue loadedChannels.append(channelName) fileName = os.path.join(channelPath, "chn_" + channelName + ".json") Logger.Trace("Loading info for chn_%s @ %s", channelName, fileName) if os.path.isfile(fileName): try: ci = ChannelInfo.FromJson(fileName) if len(ci) <= 0: Logger.Warning("No channels found in '%s'", fileName) continue # Add them to the list to import channelsToImport += ci if self.__IsChannelSetUpdated(ci[0]): if not channelsUpdated: # this was the first update found (otherwise channelsUpdated was True) show a message: title = LanguageHelper.GetLocalizedString( LanguageHelper.InitChannelTitle) text = LanguageHelper.GetLocalizedString( LanguageHelper.InitChannelText) XbmcWrapper.ShowNotification(title, text, displayTime=15000) # set the updates found bit channelsUpdated |= True # Initialise the channelset. self.__InitialiseChannelSet(ci[0]) # And perform all first actions for the included channels in the set for channelInfo in ci: self.__FirstTimeChannelActions(channelInfo) except: Logger.Error("Error import chn_%s.json", channelName, exc_info=True) importTimer.Lap() # What platform are we platform = envcontroller.EnvController.GetPlatform() # instantiate the registered channels for channelInfo in channelsToImport: # noinspection PyUnusedLocal isValid = self.__ValidateChannelInfo(channelInfo, platform) # sort the channels self.__enabledChannels.sort() if channelsUpdated: Logger.Info("New or updated channels found. Updating add-on configuration for all " "channels and user agent") AddonSettings.UpdateAddOnSettingsWithChannels(self.__validChannels, Config) AddonSettings.UpdateUserAgent() else: Logger.Debug("No channel changes found. Skipping add-on configuration for channels") # Should we update the channel index? if channelsUpdated or not os.path.isfile(self.__CHANNEL_INDEX): self.__CreateChannelIndex() Logger.Info("Imported %s channels from which %s are enabled", len(self.__allChannels), len(self.__enabledChannels)) importTimer.Stop() except: Logger.Critical("Error loading channel modules", exc_info=True)