diff --git a/resources/lib/api.py b/resources/lib/api.py index 97ad4178..e5641e52 100644 --- a/resources/lib/api.py +++ b/resources/lib/api.py @@ -5,7 +5,7 @@ ################################################################################################## import clientinfo -import utils +from utils import Logging, settings ################################################################################################## @@ -13,17 +13,16 @@ import utils class API(): def __init__(self, item): + + global log + log = Logging(self.__class__.__name__).log + # item is the api response self.item = item self.clientinfo = clientinfo.ClientInfo() self.addonName = self.clientinfo.getAddonName() - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def getUserData(self): # Default @@ -223,7 +222,7 @@ class API(): resume = 0 if resume_seconds: resume = round(float(resume_seconds), 6) - jumpback = int(utils.settings('resumeJumpBack')) + jumpback = int(settings('resumeJumpBack')) if resume > jumpback: # To avoid negative bookmark resume = resume - jumpback diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 9885829c..2c8afea9 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -12,9 +12,9 @@ import xbmc import xbmcgui import xbmcvfs -import utils import clientinfo import image_cache_thread +from utils import Logging, window, settings, kodiSQL ################################################################################################# @@ -29,24 +29,25 @@ class Artwork(): imageCacheThreads = [] imageCacheLimitThreads = 0 + def __init__(self): + + global log + log = Logging(self.__class__.__name__).log + self.clientinfo = clientinfo.ClientInfo() self.addonName = self.clientinfo.getAddonName() - self.enableTextureCache = utils.settings('enableTextureCache') == "true" - self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit")) + self.enableTextureCache = settings('enableTextureCache') == "true" + self.imageCacheLimitThreads = int(settings('imageCacheLimit')) self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5) - utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1) + log("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads, 1) if not self.xbmc_port and self.enableTextureCache: self.setKodiWebServerDetails() - self.userId = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userId) - - def logMsg(self, msg, lvl=1): - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + self.userId = window('emby_currUser') + self.server = window('emby_server%s' % self.userId) def double_urlencode(self, text): @@ -56,8 +57,8 @@ class Artwork(): return text def single_urlencode(self, text): - - text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string + # urlencode needs a utf- string + text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) text = text[13:] return text.decode("utf-8") #return the result again as unicode @@ -167,102 +168,116 @@ class Artwork(): def FullTextureCacheSync(self): # This method will sync all Kodi artwork to textures13.db # and cache them locally. This takes diskspace! + dialog = xbmcgui.Dialog() - if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"): + if not dialog.yesno( + heading="Image Texture Cache", + line1=( + "Running the image cache process can take some time. " + "Are you sure you want continue?")): return - self.logMsg("Doing Image Cache Sync", 1) + log("Doing Image Cache Sync", 1) - dialog = xbmcgui.DialogProgress() - dialog.create("Emby for Kodi", "Image Cache Sync") + pdialog = xbmcgui.DialogProgress() + pdialog.create("Emby for Kodi", "Image Cache Sync") # ask to rest all existing or not - if xbmcgui.Dialog().yesno("Image Texture Cache", "Reset all existing cache data first?", ""): - self.logMsg("Resetting all cache data first", 1) + if dialog.yesno("Image Texture Cache", "Reset all existing cache data first?"): + log("Resetting all cache data first.", 1) + # Remove all existing textures first - path = xbmc.translatePath("special://thumbnails/").decode('utf-8') + path = xbmc.translatePath('special://thumbnails/').decode('utf-8') if xbmcvfs.exists(path): allDirs, allFiles = xbmcvfs.listdir(path) for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path+dir) for file in allFiles: if os.path.supports_unicode_filenames: - xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))) + path = os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')) + xbmcvfs.delete(path) else: xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file)) # remove all existing data from texture DB - textureconnection = utils.kodiSQL('texture') - texturecursor = textureconnection.cursor() - texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = texturecursor.fetchall() + connection = kodiSQL('texture') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() for row in rows: tableName = row[0] - if(tableName != "version"): - texturecursor.execute("DELETE FROM " + tableName) - textureconnection.commit() - texturecursor.close() + if tableName != "version": + cursor.execute("DELETE FROM " + tableName) + connection.commit() + cursor.close() # Cache all entries in video DB - connection = utils.kodiSQL('video') + connection = kodiSQL('video') cursor = connection.cursor() cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors result = cursor.fetchall() total = len(result) - count = 1 - percentage = 0 - self.logMsg("Image cache sync about to process " + str(total) + " images", 1) - for url in result: - if dialog.iscanceled(): - break - percentage = int((float(count) / float(total))*100) - textMessage = str(count) + " of " + str(total) + " (" + str(len(self.imageCacheThreads)) + ")" - dialog.update(percentage, "Updating Image Cache: " + textMessage) - self.CacheTexture(url[0]) - count += 1 + log("Image cache sync about to process %s images" % total, 1) cursor.close() + count = 0 + for url in result: + + if pdialog.iscanceled(): + break + + percentage = int((float(count) / float(total))*100) + message = "%s of %s (%s)" % (count, total, self.imageCacheThreads) + pdialog.update(percentage, "Updating Image Cache: %s" % message) + self.cacheTexture(url[0]) + count += 1 + + # Cache all entries in music DB - connection = utils.kodiSQL('music') + connection = kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) - count = 1 - percentage = 0 - self.logMsg("Image cache sync about to process " + str(total) + " images", 1) - for url in result: - if dialog.iscanceled(): - break - percentage = int((float(count) / float(total))*100) - textMessage = str(count) + " of " + str(total) - dialog.update(percentage, "Updating Image Cache: " + textMessage) - self.CacheTexture(url[0]) - count += 1 + log("Image cache sync about to process %s images" % total, 1) cursor.close() - dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) - self.logMsg("Waiting for all threads to exit", 1) - while len(self.imageCacheThreads) > 0: + count = 0 + for url in result: + + if pdialog.iscanceled(): + break + + percentage = int((float(count) / float(total))*100) + message = "%s of %s" % (count, total) + pdialog.update(percentage, "Updating Image Cache: %s" % message) + self.cacheTexture(url[0]) + count += 1 + + + pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + log("Waiting for all threads to exit", 1) + + while len(self.imageCacheThreads): for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) - dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) - self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1) + pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1) xbmc.sleep(500) - dialog.close() + pdialog.close() - def addWorkerImageCacheThread(self, urlToAdd): + def addWorkerImageCacheThread(self, url): - while(True): + while True: # removed finished for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) # add a new thread or wait and retry if we hit our limit - if(len(self.imageCacheThreads) < self.imageCacheLimitThreads): + if len(self.imageCacheThreads) < self.imageCacheLimitThreads: newThread = image_cache_thread.image_cache_thread() newThread.setUrl(self.double_urlencode(urlToAdd)) newThread.setHost(self.xbmc_host, self.xbmc_port) @@ -271,23 +286,21 @@ class Artwork(): self.imageCacheThreads.append(newThread) return else: - self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2) + log("Waiting for empty queue spot: %s" % len(self.imageCacheThreads), 2) xbmc.sleep(50) - - def CacheTexture(self, url): + def cacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: - self.logMsg("Processing: %s" % url, 2) + log("Processing: %s" % url, 2) - if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None): - #Add image to texture cache by simply calling it at the http endpoint + if not self.imageCacheLimitThreads: + # Add image to texture cache by simply calling it at the http endpoint url = self.double_urlencode(url) try: # Extreme short timeouts so we will have a exception. response = requests.head( - url=( - "http://%s:%s/image/image://%s" + url=("http://%s:%s/image/image://%s" % (self.xbmc_host, self.xbmc_port, url)), auth=(self.xbmc_username, self.xbmc_password), timeout=(0.01, 0.01)) @@ -298,7 +311,7 @@ class Artwork(): self.addWorkerImageCacheThread(url) - def addArtwork(self, artwork, kodiId, mediaType, cursor): + def addArtwork(self, artwork, kodi_id, media_type, cursor): # Kodi conversion table kodiart = { @@ -329,7 +342,7 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodiId, mediaType, "fanart%",)) + cursor.execute(query, (kodi_id, media_type, "fanart%",)) rows = cursor.fetchall() if len(rows) > backdropsNumber: @@ -341,16 +354,16 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodiId, mediaType, "fanart_",)) + cursor.execute(query, (kodi_id, media_type, "fanart_",)) # Process backdrops and extra fanart index = "" for backdrop in backdrops: self.addOrUpdateArt( - imageUrl=backdrop, - kodiId=kodiId, - mediaType=mediaType, - imageType="%s%s" % ("fanart", index), + image_url=backdrop, + kodi_id=kodi_id, + media_type=media_type, + image_type="%s%s" % ("fanart", index), cursor=cursor) if backdropsNumber > 1: @@ -363,24 +376,24 @@ class Artwork(): # Primary art is processed as thumb and poster for Kodi. for artType in kodiart[art]: self.addOrUpdateArt( - imageUrl=artwork[art], - kodiId=kodiId, - mediaType=mediaType, - imageType=artType, + image_url=artwork[art], + kodi_id=kodi_id, + media_type=media_type, + image_type=artType, cursor=cursor) elif kodiart.get(art): # Process the rest artwork type that Kodi can use self.addOrUpdateArt( - imageUrl=artwork[art], - kodiId=kodiId, - mediaType=mediaType, - imageType=kodiart[art], + image_url=artwork[art], + kodi_id=kodi_id, + media_type=media_type, + image_type=kodiart[art], cursor=cursor) - def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): + def addOrUpdateArt(self, image_url, kodi_id, media_type, image_type, cursor): # Possible that the imageurl is an empty string - if imageUrl: + if image_url: cacheimage = False query = ' '.join(( @@ -391,13 +404,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (kodiId, mediaType, imageType,)) + cursor.execute(query, (kodi_id, media_type, image_type,)) try: # Update the artwork url = cursor.fetchone()[0] except TypeError: # Add the artwork cacheimage = True - self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) + log("Adding Art Link for kodiId: %s (%s)" % (kodi_id, image_url), 2) query = ( ''' @@ -406,21 +419,20 @@ class Artwork(): VALUES (?, ?, ?, ?) ''' ) - cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) + cursor.execute(query, (kodi_id, media_type, image_type, image_url)) else: # Only cache artwork if it changed - if url != imageUrl: + if url != image_url: cacheimage = True # Only for the main backdrop, poster - if (utils.window('emby_initialScan') != "true" and + if (window('emby_initialScan') != "true" and imageType in ("fanart", "poster")): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) - self.logMsg( - "Updating Art url for %s kodiId: %s (%s) -> (%s)" - % (imageType, kodiId, url, imageUrl), 1) + log("Updating Art url for %s kodiId: %s (%s) -> (%s)" + % (image_type, kodi_id, url, image_url), 1) query = ' '.join(( @@ -430,13 +442,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) + cursor.execute(query, (image_url, kodi_id, media_type, image_type)) # Cache fanart and poster in Kodi texture cache - if cacheimage and imageType in ("fanart", "poster"): - self.CacheTexture(imageUrl) + if cacheimage and image_type in ("fanart", "poster"): + self.cacheTexture(image_url) - def deleteArtwork(self, kodiid, mediatype, cursor): + def deleteArtwork(self, kodi_id, media_type, cursor): query = ' '.join(( @@ -445,7 +457,7 @@ class Artwork(): "WHERE media_id = ?", "AND media_type = ?" )) - cursor.execute(query, (kodiid, mediatype,)) + cursor.execute(query, (kodi_id, media_type,)) rows = cursor.fetchall() for row in rows: @@ -456,7 +468,7 @@ class Artwork(): def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster - connection = utils.kodiSQL('texture') + connection = kodiSQL('texture') cursor = connection.cursor() try: @@ -464,21 +476,21 @@ class Artwork(): cachedurl = cursor.fetchone()[0] except TypeError: - self.logMsg("Could not find cached url.", 1) + log("Could not find cached url.", 1) except OperationalError: - self.logMsg("Database is locked. Skip deletion process.", 1) + log("Database is locked. Skip deletion process.", 1) else: # Delete thumbnail as well as the entry thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8') - self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1) + log("Deleting cached thumbnail: %s" % thumbnails, 1) xbmcvfs.delete(thumbnails) try: cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() except OperationalError: - self.logMsg("Issue deleting url from cache. Skipping.", 2) + log("Issue deleting url from cache. Skipping.", 2) finally: cursor.close() @@ -501,26 +513,26 @@ class Artwork(): return people - def getUserArtwork(self, itemid, itemtype): + def getUserArtwork(self, item_id, item_type): # Load user information set by UserClient image = ("%s/emby/Users/%s/Images/%s?Format=original" - % (self.server, itemid, itemtype)) + % (self.server, item_id, item_type)) return image - def getAllArtwork(self, item, parentInfo=False): + def getAllArtwork(self, item, parent_artwork=False): itemid = item['Id'] artworks = item['ImageTags'] - backdrops = item.get('BackdropImageTags',[]) + backdrops = item.get('BackdropImageTags', []) maxHeight = 10000 maxWidth = 10000 customquery = "" - if utils.settings('compressArt') == "true": + if settings('compressArt') == "true": customquery = "&Quality=90" - if utils.settings('enableCoverArt') == "false": + if settings('enableCoverArt') == "false": customquery += "&EnableImageEnhancers=false" allartworks = { @@ -554,7 +566,7 @@ class Artwork(): allartworks[art] = artwork # Process parent items if the main item is missing artwork - if parentInfo: + if parent_artwork: # Process parent backdrops if not allartworks['Backdrop']: @@ -601,4 +613,4 @@ class Artwork(): % (self.server, parentId, maxWidth, maxHeight, parentTag, customquery)) allartworks['Primary'] = artwork - return allartworks + return allartworks \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 4fbbc636..0afb0c8b 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -16,7 +16,7 @@ import downloadutils import playutils as putils import playlist import read_embyserver as embyserver -import utils +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -26,6 +26,9 @@ class PlaybackUtils(): def __init__(self, item): + global log + log = Logging(self.__class__.__name__).log + self.item = item self.API = api.API(self.item) @@ -33,28 +36,20 @@ class PlaybackUtils(): self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils().downloadUrl - self.userid = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userid) + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) self.artwork = artwork.Artwork() self.emby = embyserver.Read_EmbyServer() self.pl = playlist.Playlist() - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) - def play(self, itemid, dbid=None): - window = utils.window - settings = utils.settings - listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(self.item) - self.logMsg("Play called.", 1) + log("Play called.", 1) playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -77,9 +72,9 @@ class PlaybackUtils(): introsPlaylist = False dummyPlaylist = False - self.logMsg("Playlist start position: %s" % startPos, 2) - self.logMsg("Playlist plugin position: %s" % currentPosition, 2) - self.logMsg("Playlist size: %s" % sizePlaylist, 2) + log("Playlist start position: %s" % startPos, 2) + log("Playlist plugin position: %s" % currentPosition, 2) + log("Playlist size: %s" % sizePlaylist, 2) ############### RESUME POINT ################ @@ -91,12 +86,11 @@ class PlaybackUtils(): if not propertiesPlayback: window('emby_playbackProps', value="true") - self.logMsg("Setting up properties in playlist.", 1) + log("Setting up properties in playlist.", 1) - if (not homeScreen and not seektime and - window('emby_customPlaylist') != "true"): + if not homeScreen and not seektime and window('emby_customPlaylist') != "true": - self.logMsg("Adding dummy file to playlist.", 2) + log("Adding dummy file to playlist.", 2) dummyPlaylist = True playlist.add(playurl, listitem, index=startPos) # Remove the original item from playlist @@ -116,18 +110,18 @@ class PlaybackUtils(): getTrailers = True if settings('askCinema') == "true": - resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016)) + resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016)) if not resp: # User selected to not play trailers getTrailers = False - self.logMsg("Skip trailers.", 1) + log("Skip trailers.", 1) if getTrailers: for intro in intros['Items']: # The server randomly returns intros, process them. introListItem = xbmcgui.ListItem() introPlayurl = putils.PlayUtils(intro).getPlayUrl() - self.logMsg("Adding Intro: %s" % introPlayurl, 1) + log("Adding Intro: %s" % introPlayurl, 1) # Set listitem and properties for intros pbutils = PlaybackUtils(intro) @@ -143,7 +137,7 @@ class PlaybackUtils(): if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first - self.logMsg("Adding main item to playlist.", 1) + log("Adding main item to playlist.", 1) self.pl.addtoPlaylist(dbid, self.item['Type'].lower()) # Ensure that additional parts are played after the main item @@ -160,7 +154,7 @@ class PlaybackUtils(): additionalListItem = xbmcgui.ListItem() additionalPlayurl = putils.PlayUtils(part).getPlayUrl() - self.logMsg("Adding additional part: %s" % partcount, 1) + log("Adding additional part: %s" % partcount, 1) # Set listitem and properties for each additional parts pbutils = PlaybackUtils(part) @@ -174,13 +168,13 @@ class PlaybackUtils(): if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. - self.logMsg("Processed as a playlist. First item is skipped.", 1) + log("Processed as a playlist. First item is skipped.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: - self.logMsg("Resetting properties playback flag.", 2) + log("Resetting properties playback flag.", 2) window('emby_playbackProps', clear=True) #self.pl.verifyPlaylist() @@ -190,7 +184,7 @@ class PlaybackUtils(): if window('emby_%s.playmethod' % playurl) == "Transcode": # Filter ISO since Emby does not probe anymore if self.item.get('VideoType') == "Iso": - self.logMsg("Skipping audio/subs prompt, ISO detected.", 1) + log("Skipping audio/subs prompt, ISO detected.", 1) else: playurl = playutils.audioSubsPref(playurl, listitem) window('emby_%s.playmethod' % playurl, value="Transcode") @@ -201,23 +195,22 @@ class PlaybackUtils(): ############### PLAYBACK ################ if homeScreen and seektime and window('emby_customPlaylist') != "true": - self.logMsg("Play as a widget item.", 1) + log("Play as a widget item.", 1) self.setListItem(listitem) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) elif ((introsPlaylist and window('emby_customPlaylist') == "true") or (homeScreen and not sizePlaylist)): # Playlist was created just now, play it. - self.logMsg("Play playlist.", 1) + log("Play playlist.", 1) xbmc.Player().play(playlist, startpos=startPos) else: - self.logMsg("Play as a regular item.", 1) + log("Play as a regular item.", 1) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) def setProperties(self, playurl, listitem): - window = utils.window # Set all properties necessary for plugin path playback itemid = self.item['Id'] itemtype = self.item['Type'] @@ -233,7 +226,7 @@ class PlaybackUtils(): window('%s.refreshid' % embyitem, value=itemid) # Append external subtitles to stream - playmethod = utils.window('%s.playmethod' % embyitem) + playmethod = window('%s.playmethod' % embyitem) # Only for direct stream if playmethod in ("DirectStream"): # Direct play automatically appends external @@ -272,13 +265,13 @@ class PlaybackUtils(): kodiindex += 1 mapping = json.dumps(mapping) - utils.window('emby_%s.indexMapping' % playurl, value=mapping) + window('emby_%s.indexMapping' % playurl, value=mapping) return externalsubs def setArtwork(self, listItem): # Set up item and item info - allartwork = self.artwork.getAllArtwork(self.item, parentInfo=True) + allartwork = self.artwork.getAllArtwork(self.item, parent_artwork=True) # Set artwork for listitem arttypes = { diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 3121a07a..cc0c4b4a 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -4,21 +4,22 @@ import xbmc -import utils import clientinfo import downloadutils +from utils import Logging, window, settings, kodiSQL ################################################################################################# class Read_EmbyServer(): - limitIndex = int(utils.settings('limitindex')) + limitIndex = int(settings('limitindex')) def __init__(self): - window = utils.window + global log + log = Logging(self.__class__.__name__).log self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() @@ -27,17 +28,11 @@ class Read_EmbyServer(): self.userId = window('emby_currUser') self.server = window('emby_server%s' % self.userId) - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def split_list(self, itemlist, size): # Split up list in pieces of size. Will generate a list of lists return [itemlist[i:i+size] for i in range(0, len(itemlist), size)] - def getItem(self, itemid): # This will return the full item item = {} @@ -60,7 +55,8 @@ class Read_EmbyServer(): 'Ids': ",".join(itemlist), 'Fields': "Etag" } - result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params) + url = "{server}/emby/Users/{UserId}/Items?&format=json" + result = self.doUtils(url, parameters=params) if result: items.extend(result['Items']) @@ -86,7 +82,8 @@ class Read_EmbyServer(): "MediaSources,VoteCount" ) } - result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params) + url = "{server}/emby/Users/{UserId}/Items?format=json" + result = self.doUtils(url, parameters=params) if result: items.extend(result['Items']) @@ -96,14 +93,15 @@ class Read_EmbyServer(): # Returns ancestors using embyId viewId = None - for view in self.doUtils("{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid): + url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid + for view in self.doUtils(url): if view['Type'] == "CollectionFolder": # Found view viewId = view['Id'] # Compare to view table in emby database - emby = utils.kodiSQL('emby') + emby = kodiSQL('emby') cursor_emby = emby.cursor() query = ' '.join(( @@ -124,7 +122,8 @@ class Read_EmbyServer(): return [viewName, viewId, mediatype] - def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""): + def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, + limit=None, sortorder="Ascending", filter=""): params = { 'ParentId': parentid, @@ -137,39 +136,54 @@ class Read_EmbyServer(): 'SortBy': sortby, 'SortOrder': sortorder, 'Filters': filter, - 'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers") + 'Fields': ( + + "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers" + ) } return self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params) def getTvChannels(self): + params = { 'EnableImages': True, - 'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers") + 'Fields': ( + + "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers" + ) } - return self.doUtils("{server}/emby/LiveTv/Channels/?userid={UserId}&format=json", parameters=params) + url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json" + return self.doUtils(url, parameters=params) def getTvRecordings(self, groupid): - if groupid == "root": groupid = "" + + if groupid == "root": + groupid = "" + params = { 'GroupId': groupid, 'EnableImages': True, - 'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers") + 'Fields': ( + + "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers" + ) } - return self.doUtils("{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json", parameters=params) + url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json" + return self.doUtils(url, parameters=params) def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): @@ -197,7 +211,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + log("%s:%s Failed to retrieve the server response." % (url, params), 2) else: index = 0 @@ -239,27 +253,27 @@ class Read_EmbyServer(): # Something happened to the connection if not throttled: throttled = True - self.logMsg("Throttle activated.", 1) + log("Throttle activated.", 1) if jump == highestjump: # We already tried with the highestjump, but it failed. Reset value. - self.logMsg("Reset highest value.", 1) + log("Reset highest value.", 1) highestjump = 0 # Lower the number by half if highestjump: throttled = False jump = highestjump - self.logMsg("Throttle deactivated.", 1) + log("Throttle deactivated.", 1) else: jump = int(jump/4) - self.logMsg("Set jump limit to recover: %s" % jump, 2) + log("Set jump limit to recover: %s" % jump, 2) retry = 0 - while utils.window('emby_online') != "true": + while window('emby_online') != "true": # Wait server to come back online if retry == 5: - self.logMsg("Unable to reconnect to server. Abort process.", 1) + log("Unable to reconnect to server. Abort process.", 1) return items retry += 1 @@ -287,7 +301,7 @@ class Read_EmbyServer(): increment = 10 jump += increment - self.logMsg("Increase jump limit to: %s" % jump, 1) + log("Increase jump limit to: %s" % jump, 1) return items def getViews(self, mediatype="", root=False, sortedlist=False): @@ -304,7 +318,7 @@ class Read_EmbyServer(): try: items = result['Items'] except TypeError: - self.logMsg("Error retrieving views for type: %s" % mediatype, 2) + log("Error retrieving views for type: %s" % mediatype, 2) else: for item in items: @@ -373,15 +387,18 @@ class Read_EmbyServer(): return belongs def getMovies(self, parentId, basic=False, dialog=None): + return self.getSection(parentId, "Movie", basic=basic, dialog=dialog) def getBoxset(self, dialog=None): + return self.getSection(None, "BoxSet", dialog=dialog) def getMovies_byBoxset(self, boxsetid): return self.getSection(boxsetid, "Movie") def getMusicVideos(self, parentId, basic=False, dialog=None): + return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog) def getHomeVideos(self, parentId): @@ -389,6 +406,7 @@ class Read_EmbyServer(): return self.getSection(parentId, "Video") def getShows(self, parentId, basic=False, dialog=None): + return self.getSection(parentId, "Series", basic=basic, dialog=dialog) def getSeasons(self, showId): @@ -404,7 +422,8 @@ class Read_EmbyServer(): 'IsVirtualUnaired': False, 'Fields': "Etag" } - result = self.doUtils("{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId, parameters=params) + url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId + result = self.doUtils(url, parameters=params) if result: items = result @@ -422,7 +441,6 @@ class Read_EmbyServer(): return self.getSection(seasonId, "Episode") - def getArtists(self, dialog=None): items = { @@ -444,7 +462,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + log("%s:%s Failed to retrieve the server response." % (url, params), 2) else: index = 1 @@ -478,17 +496,20 @@ class Read_EmbyServer(): return items def getAlbums(self, basic=False, dialog=None): + return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog) def getAlbumsbyArtist(self, artistId): + return self.getSection(artistId, "MusicAlbum", sortby="DateCreated") def getSongs(self, basic=False, dialog=None): + return self.getSection(None, "Audio", basic=basic, dialog=dialog) def getSongsbyAlbum(self, albumId): - return self.getSection(albumId, "Audio") + return self.getSection(albumId, "Audio") def getAdditionalParts(self, itemId): @@ -497,8 +518,8 @@ class Read_EmbyServer(): 'Items': [], 'TotalRecordCount': 0 } - - result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId) + url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId + result = self.doUtils(url) if result: items = result @@ -520,21 +541,27 @@ class Read_EmbyServer(): def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False): # Updates the user rating to Emby - + doUtils = self.doUtils + if favourite: - self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST") + url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid + doUtils(url, action_type="POST") elif favourite == False: - self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE") + url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid + doUtils(url, action_type="DELETE") if not deletelike and like: - self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid, action_type="POST") + url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid + doUtils(url, action_type="POST") elif not deletelike and like is False: - self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, action_type="POST") + url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid + doUtils(url, action_type="POST") elif deletelike: - self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE") + url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid + doUtils(url, action_type="DELETE") else: - self.logMsg("Error processing user rating.", 1) + log("Error processing user rating.", 1) - self.logMsg("Update user rating to emby for itemid: %s " - "| like: %s | favourite: %s | deletelike: %s" - % (itemid, like, favourite, deletelike), 1) + log("Update user rating to emby for itemid: %s " + "| like: %s | favourite: %s | deletelike: %s" + % (itemid, like, favourite, deletelike), 1) \ No newline at end of file diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index f068b772..ce985647 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -11,9 +11,9 @@ import xbmcaddon import xbmcvfs import artwork -import utils import clientinfo import downloadutils +from utils import Logging, window, settings, language as lang ################################################################################################## @@ -39,6 +39,9 @@ class UserClient(threading.Thread): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.addon = xbmcaddon.Addon() @@ -47,25 +50,20 @@ class UserClient(threading.Thread): threading.Thread.__init__(self) - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def getAdditionalUsers(self): - additionalUsers = utils.settings('additionalUsers') + additionalUsers = settings('additionalUsers') if additionalUsers: self.AdditionalUser = additionalUsers.split(',') def getUsername(self): - username = utils.settings('username') + username = settings('username') if not username: - self.logMsg("No username saved.", 2) + log("No username saved.", 2) return "" return username @@ -73,7 +71,7 @@ class UserClient(threading.Thread): def getLogLevel(self): try: - logLevel = int(utils.settings('logLevel')) + logLevel = int(settings('logLevel')) except ValueError: logLevel = 0 @@ -81,9 +79,6 @@ class UserClient(threading.Thread): def getUserId(self): - window = utils.window - settings = utils.settings - username = self.getUsername() w_userId = window('emby_currUser') s_userId = settings('userId%s' % username) @@ -93,22 +88,20 @@ class UserClient(threading.Thread): if not s_userId: # Save access token if it's missing from settings settings('userId%s' % username, value=w_userId) - self.logMsg("Returning userId from WINDOW for username: %s UserId: %s" + log("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2) return w_userId # Verify the settings elif s_userId: - self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" + log("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2) return s_userId # No userId found else: - self.logMsg("No userId saved for username: %s." % username, 1) + log("No userId saved for username: %s." % username, 1) def getServer(self, prefix=True): - settings = utils.settings - alternate = settings('altip') == "true" if alternate: # Alternate host @@ -124,7 +117,7 @@ class UserClient(threading.Thread): server = host + ":" + port if not host: - self.logMsg("No server information saved.", 2) + log("No server information saved.", 2) return False # If https is true @@ -141,9 +134,6 @@ class UserClient(threading.Thread): def getToken(self): - window = utils.window - settings = utils.settings - username = self.getUsername() userId = self.getUserId() w_token = window('emby_accessToken%s' % userId) @@ -154,23 +144,21 @@ class UserClient(threading.Thread): if not s_token: # Save access token if it's missing from settings settings('accessToken', value=w_token) - self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s" + log("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2) return w_token # Verify the settings elif s_token: - self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" + log("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2) window('emby_accessToken%s' % username, value=s_token) return s_token else: - self.logMsg("No token found.", 1) + log("No token found.", 1) return "" def getSSLverify(self): # Verify host certificate - settings = utils.settings - s_sslverify = settings('sslverify') if settings('altip') == "true": s_sslverify = settings('secondsslverify') @@ -182,8 +170,6 @@ class UserClient(threading.Thread): def getSSL(self): # Client side certificate - settings = utils.settings - s_cert = settings('sslcert') if settings('altip') == "true": s_cert = settings('secondsslcert') @@ -201,16 +187,16 @@ class UserClient(threading.Thread): self.userSettings = result # Set user image for skin display if result.get('PrimaryImageTag'): - utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary')) + window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary')) # Set resume point max result = doUtils("{server}/emby/System/Configuration?format=json") - - utils.settings('markPlayed', value=str(result['MaxResumePct'])) + settings('markPlayed', value=str(result['MaxResumePct'])) def getPublicUsers(self): # Get public Users - result = self.doUtils.downloadUrl("%s/emby/Users/Public?format=json" % self.getServer(), authenticate=False) + url = "%s/emby/Users/Public?format=json" % self.getServer() + result = self.doUtils.downloadUrl(url, authenticate=False) if result != "": return result else: @@ -220,13 +206,11 @@ class UserClient(threading.Thread): def hasAccess(self): # hasAccess is verified in service.py - window = utils.window - result = self.doUtils.downloadUrl("{server}/emby/Users?format=json") if result == False: # Access is restricted, set in downloadutils.py via exception - self.logMsg("Access is restricted.", 1) + log("Access is restricted.", 1) self.HasAccess = False elif window('emby_online') != "true": @@ -234,15 +218,13 @@ class UserClient(threading.Thread): pass elif window('emby_serverStatus') == "restricted": - self.logMsg("Access is granted.", 1) + log("Access is granted.", 1) self.HasAccess = True window('emby_serverStatus', clear=True) - xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007)) + xbmcgui.Dialog().notification("Emby for Kodi", lang(33007)) def loadCurrUser(self, authenticated=False): - window = utils.window - doUtils = self.doUtils username = self.getUsername() userId = self.getUserId() @@ -290,9 +272,6 @@ class UserClient(threading.Thread): def authenticate(self): - lang = utils.language - window = utils.window - settings = utils.settings dialog = xbmcgui.Dialog() # Get /profile/addon_data @@ -304,12 +283,12 @@ class UserClient(threading.Thread): # If there's no settings.xml if not hasSettings: - self.logMsg("No settings.xml found.", 1) + log("No settings.xml found.", 1) self.auth = False return # If no user information elif not server or not username: - self.logMsg("Missing server information.", 1) + log("Missing server information.", 1) self.auth = False return # If there's a token, load the user @@ -319,9 +298,9 @@ class UserClient(threading.Thread): if result is False: pass else: - self.logMsg("Current user: %s" % self.currUser, 1) - self.logMsg("Current userId: %s" % self.currUserId, 1) - self.logMsg("Current accessToken: %s" % self.currToken, 2) + log("Current user: %s" % self.currUser, 1) + log("Current userId: %s" % self.currUserId, 1) + log("Current accessToken: %s" % self.currToken, 2) return ##### AUTHENTICATE USER ##### @@ -341,7 +320,7 @@ class UserClient(threading.Thread): option=xbmcgui.ALPHANUM_HIDE_INPUT) # If password dialog is cancelled if not password: - self.logMsg("No password entered.", 0) + log("No password entered.", 0) window('emby_serverStatus', value="Stop") self.auth = False return @@ -356,16 +335,17 @@ class UserClient(threading.Thread): # Authenticate username and password data = {'username': username, 'password': sha1} - self.logMsg(data, 2) + log(data, 2) - result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, action_type="POST", authenticate=False) + url = "%s/emby/Users/AuthenticateByName?format=json" % server + result = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False) try: - self.logMsg("Auth response: %s" % result, 1) + log("Auth response: %s" % result, 1) accessToken = result['AccessToken'] except (KeyError, TypeError): - self.logMsg("Failed to retrieve the api key.", 1) + log("Failed to retrieve the api key.", 1) accessToken = None if accessToken is not None: @@ -374,19 +354,19 @@ class UserClient(threading.Thread): "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) settings('accessToken', value=accessToken) settings('userId%s' % username, value=result['User']['Id']) - self.logMsg("User Authenticated: %s" % accessToken, 1) + log("User Authenticated: %s" % accessToken, 1) self.loadCurrUser(authenticated=True) window('emby_serverStatus', clear=True) self.retry = 0 else: - self.logMsg("User authentication failed.", 1) + log("User authentication failed.", 1) settings('accessToken', value="") settings('userId%s' % username, value="") dialog.ok(lang(33001), lang(33009)) # Give two attempts at entering password if self.retry == 2: - self.logMsg("Too many retries. " + log("Too many retries. " "You can retry by resetting attempts in the addon settings.", 1) window('emby_serverStatus', value="Stop") dialog.ok(lang(33001), lang(33010)) @@ -396,23 +376,21 @@ class UserClient(threading.Thread): def resetClient(self): - self.logMsg("Reset UserClient authentication.", 1) + log("Reset UserClient authentication.", 1) if self.currToken is not None: # In case of 401, removed saved token - utils.settings('accessToken', value="") - utils.window('emby_accessToken%s' % self.getUserId(), clear=True) + settings('accessToken', value="") + window('emby_accessToken%s' % self.getUserId(), clear=True) self.currToken = None - self.logMsg("User token has been removed.", 1) + log("User token has been removed.", 1) self.auth = True self.currUser = None def run(self): - window = utils.window - monitor = xbmc.Monitor() - self.logMsg("----===## Starting UserClient ##===----", 0) + log("----===## Starting UserClient ##===----", 0) while not monitor.abortRequested(): @@ -447,8 +425,8 @@ class UserClient(threading.Thread): # The status Stop is for when user cancelled password dialog. if server and username and status != "Stop": # Only if there's information found to login - self.logMsg("Server found: %s" % server, 2) - self.logMsg("Username found: %s" % username, 2) + log("Server found: %s" % server, 2) + log("Username found: %s" % username, 2) self.auth = True @@ -461,7 +439,7 @@ class UserClient(threading.Thread): break self.doUtils.stopSession() - self.logMsg("##===---- UserClient Stopped ----===##", 0) + log("##===---- UserClient Stopped ----===##", 0) def stopClient(self): # When emby for kodi terminates diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 40b711c3..dd2d04d4 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -20,7 +20,7 @@ import xbmcgui import xbmcvfs ################################################################################################# - +# Main methods def logMsg(title, msg, level=1): @@ -43,43 +43,80 @@ def logMsg(title, msg, level=1): except UnicodeEncodeError: xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) -def window(property, value=None, clear=False, windowid=10000): - # Get or set window property - WINDOW = xbmcgui.Window(windowid) +class Logging(): - #setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues - '''if isinstance(property, unicode): - property = property.encode("utf-8") - if isinstance(value, unicode): - value = value.encode("utf-8")''' + LOGGINGCLASS = None + + + def __init__(self, classname=""): + + self.LOGGINGCLASS = classname + + def log(self, msg, level=1): + + self.logMsg("EMBY %s" % self.LOGGINGCLASS, msg, level) + + def logMsg(self, title, msg, level=1): + + # Get the logLevel set in UserClient + try: + logLevel = int(window('emby_logLevel')) + except ValueError: + logLevel = 0 + + if logLevel >= level: + + if logLevel == 2: # inspect.stack() is expensive + try: + xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg)) + except UnicodeEncodeError: + xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8'))) + else: + try: + xbmc.log("%s -> %s" % (title, msg)) + except UnicodeEncodeError: + xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) + +# Initiate class for utils.py document logging +log = Logging('Utils').log + + +def window(property, value=None, clear=False, window_id=10000): + # Get or set window property + WINDOW = xbmcgui.Window(window_id) if clear: WINDOW.clearProperty(property) elif value is not None: WINDOW.setProperty(property, value) - else: #getproperty returns string so convert to unicode - return WINDOW.getProperty(property)#.decode("utf-8") + else: + return WINDOW.getProperty(property) def settings(setting, value=None): # Get or add addon setting + addon = xbmcaddon.Addon(id='plugin.video.emby') + if value is not None: - xbmcaddon.Addon(id='plugin.video.emby').setSetting(setting, value) - else: - return xbmcaddon.Addon(id='plugin.video.emby').getSetting(setting) #returns unicode object + addon.setSetting(setting, value) + else: # returns unicode object + return addon.getSetting(setting) -def language(stringid): - # Central string retrieval - string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(stringid) #returns unicode object +def language(string_id): + # Central string retrieval - unicode + string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id) return string +################################################################################################# +# Database related methods + def kodiSQL(media_type="video"): if media_type == "emby": dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8') - elif media_type == "music": - dbPath = getKodiMusicDBPath() elif media_type == "texture": dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8') + elif media_type == "music": + dbPath = getKodiMusicDBPath() else: dbPath = getKodiVideoDBPath() @@ -98,8 +135,8 @@ def getKodiVideoDBPath(): } dbPath = xbmc.translatePath( - "special://database/MyVideos%s.db" - % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') + "special://database/MyVideos%s.db" + % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') return dbPath def getKodiMusicDBPath(): @@ -114,10 +151,13 @@ def getKodiMusicDBPath(): } dbPath = xbmc.translatePath( - "special://database/MyMusic%s.db" - % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') + "special://database/MyMusic%s.db" + % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') return dbPath +################################################################################################# +# Utility methods + def getScreensaver(): # Get the current screensaver value query = { @@ -145,139 +185,8 @@ def setScreensaver(value): 'value': value } } - logMsg("EMBY", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1) - -def reset(): - - dialog = xbmcgui.Dialog() - - if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0: - return - - # first stop any db sync - window('emby_shouldStop', value="true") - count = 10 - while window('emby_dbScan') == "true": - logMsg("EMBY", "Sync is running, will retry: %s..." % count) - count -= 1 - if count == 0: - dialog.ok("Warning", "Could not stop the database from running. Try again.") - return - xbmc.sleep(1000) - - # Clean up the playlists - deletePlaylists() - - # Clean up the video nodes - deleteNodes() - - # Wipe the kodi databases - logMsg("EMBY", "Resetting the Kodi video database.", 0) - connection = kodiSQL('video') - cursor = connection.cursor() - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tablename = row[0] - if tablename != "version": - cursor.execute("DELETE FROM " + tablename) - connection.commit() - cursor.close() - - if settings('enableMusic') == "true": - logMsg("EMBY", "Resetting the Kodi music database.") - connection = kodiSQL('music') - cursor = connection.cursor() - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tablename = row[0] - if tablename != "version": - cursor.execute("DELETE FROM " + tablename) - connection.commit() - cursor.close() - - # Wipe the emby database - logMsg("EMBY", "Resetting the Emby database.", 0) - connection = kodiSQL('emby') - cursor = connection.cursor() - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tablename = row[0] - if tablename != "version": - cursor.execute("DELETE FROM " + tablename) - cursor.execute('DROP table IF EXISTS emby') - cursor.execute('DROP table IF EXISTS view') - connection.commit() - cursor.close() - - # Offer to wipe cached thumbnails - resp = dialog.yesno("Warning", "Remove all cached artwork?") - if resp: - logMsg("EMBY", "Resetting all cached artwork.", 0) - # Remove all existing textures first - path = xbmc.translatePath("special://thumbnails/").decode('utf-8') - if xbmcvfs.exists(path): - allDirs, allFiles = xbmcvfs.listdir(path) - for dir in allDirs: - allDirs, allFiles = xbmcvfs.listdir(path+dir) - for file in allFiles: - if os.path.supports_unicode_filenames: - xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))) - else: - xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file)) - - # remove all existing data from texture DB - connection = kodiSQL('texture') - cursor = connection.cursor() - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tableName = row[0] - if(tableName != "version"): - cursor.execute("DELETE FROM " + tableName) - connection.commit() - cursor.close() - - # reset the install run flag - settings('SyncInstallRunDone', value="false") - - # Remove emby info - resp = dialog.yesno("Warning", "Reset all Emby Addon settings?") - if resp: - # Delete the settings - addon = xbmcaddon.Addon() - addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8') - dataPath = "%ssettings.xml" % addondir - xbmcvfs.delete(dataPath) - logMsg("EMBY", "Deleting: settings.xml", 1) - - dialog.ok( - heading="Emby for Kodi", - line1="Database reset has completed, Kodi will now restart to apply the changes.") - xbmc.executebuiltin('RestartApp') - -def profiling(sortby="cumulative"): - # Will print results to Kodi log - def decorator(func): - def wrapper(*args, **kwargs): - - pr = cProfile.Profile() - - pr.enable() - result = func(*args, **kwargs) - pr.disable() - - s = StringIO.StringIO() - ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - ps.print_stats() - logMsg("EMBY Profiling", s.getvalue(), 1) - - return result - - return wrapper - return decorator + result = xbmc.executeJSONRPC(json.dumps(query)) + log("Toggling screensaver: %s %s" % (value, result), 1) def convertdate(date): try: @@ -344,6 +253,141 @@ def indent(elem, level=0): if level and (not elem.tail or not elem.tail.strip()): elem.tail = i +def profiling(sortby="cumulative"): + # Will print results to Kodi log + def decorator(func): + def wrapper(*args, **kwargs): + + pr = cProfile.Profile() + + pr.enable() + result = func(*args, **kwargs) + pr.disable() + + s = StringIO.StringIO() + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + log(s.getvalue(), 1) + + return result + + return wrapper + return decorator + +################################################################################################# +# Addon utilities + +def reset(): + + dialog = xbmcgui.Dialog() + + if not dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?"): + return + + # first stop any db sync + window('emby_shouldStop', value="true") + count = 10 + while window('emby_dbScan') == "true": + logMsg("EMBY", "Sync is running, will retry: %s..." % count) + count -= 1 + if count == 0: + dialog.ok("Warning", "Could not stop the database from running. Try again.") + return + xbmc.sleep(1000) + + # Clean up the playlists + deletePlaylists() + + # Clean up the video nodes + deleteNodes() + + # Wipe the kodi databases + log("Resetting the Kodi video database.", 0) + connection = kodiSQL('video') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tablename = row[0] + if tablename != "version": + cursor.execute("DELETE FROM " + tablename) + connection.commit() + cursor.close() + + if settings('enableMusic') == "true": + log("Resetting the Kodi music database.", 0) + connection = kodiSQL('music') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tablename = row[0] + if tablename != "version": + cursor.execute("DELETE FROM " + tablename) + connection.commit() + cursor.close() + + # Wipe the emby database + log("Resetting the Emby database.", 0) + connection = kodiSQL('emby') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tablename = row[0] + if tablename != "version": + cursor.execute("DELETE FROM " + tablename) + cursor.execute('DROP table IF EXISTS emby') + cursor.execute('DROP table IF EXISTS view') + connection.commit() + cursor.close() + + # Offer to wipe cached thumbnails + resp = dialog.yesno("Warning", "Remove all cached artwork?") + if resp: + log("Resetting all cached artwork.", 0) + # Remove all existing textures first + path = xbmc.translatePath("special://thumbnails/").decode('utf-8') + if xbmcvfs.exists(path): + allDirs, allFiles = xbmcvfs.listdir(path) + for dir in allDirs: + allDirs, allFiles = xbmcvfs.listdir(path+dir) + for file in allFiles: + if os.path.supports_unicode_filenames: + xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))) + else: + xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file)) + + # remove all existing data from texture DB + connection = kodiSQL('texture') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tableName = row[0] + if(tableName != "version"): + cursor.execute("DELETE FROM " + tableName) + connection.commit() + cursor.close() + + # reset the install run flag + settings('SyncInstallRunDone', value="false") + + # Remove emby info + resp = dialog.yesno("Warning", "Reset all Emby Addon settings?") + if resp: + # Delete the settings + addon = xbmcaddon.Addon() + addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8') + dataPath = "%ssettings.xml" % addondir + xbmcvfs.delete(dataPath) + log("Deleting: settings.xml", 1) + + dialog.ok( + heading="Emby for Kodi", + line1="Database reset has completed, Kodi will now restart to apply the changes.") + xbmc.executebuiltin('RestartApp') + def sourcesXML(): # To make Master lock compatible path = xbmc.translatePath("special://profile/").decode('utf-8') @@ -413,12 +457,11 @@ def passwordsXML(): for path in paths: if path.find('.//from').text == "smb://%s/" % credentials: paths.remove(path) - logMsg("EMBY", "Successfully removed credentials for: %s" - % credentials, 1) + log("Successfully removed credentials for: %s" % credentials, 1) etree.ElementTree(root).write(xmlpath) break else: - logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1) + log("Failed to find saved server: %s in passwords.xml" % credentials, 1) settings('networkCreds', value="") xbmcgui.Dialog().notification( @@ -473,7 +516,7 @@ def passwordsXML(): # Add credentials settings('networkCreds', value="%s" % server) - logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1) + log("Added server: %s to passwords.xml" % server, 1) # Prettify and write to file try: indent(root) @@ -501,7 +544,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): # Create the playlist directory if not xbmcvfs.exists(path): - logMsg("EMBY", "Creating directory: %s" % path, 1) + log("Creating directory: %s" % path, 1) xbmcvfs.mkdirs(path) # Only add the playlist if it doesn't already exists @@ -509,7 +552,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): if delete: xbmcvfs.delete(xsppath) - logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1) + log("Successfully removed playlist: %s." % tagname, 1) return @@ -517,11 +560,11 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): itemtypes = { 'homevideos': "movies" } - logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1) + log("Writing playlist file to: %s" % xsppath, 1) try: f = xbmcvfs.File(xsppath, 'w') except: - logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1) + log("Failed to create playlist: %s" % xsppath, 1) return else: f.write( @@ -535,7 +578,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): '' % (itemtypes.get(mediatype, mediatype), plname, tagname)) f.close() - logMsg("EMBY", "Successfully added playlist: %s" % tagname) + log("Successfully added playlist: %s" % tagname, 1) def deletePlaylists(): @@ -557,10 +600,10 @@ def deleteNodes(): try: shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) except: - logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8')) + log("Failed to delete directory: %s" % dir.decode('utf-8'), 0) for file in files: if file.decode('utf-8').startswith('emby'): try: xbmcvfs.delete("%s%s" % (path, file.decode('utf-8'))) except: - logMsg("EMBY", "Failed to file: %s" % file.decode('utf-8')) + log("Failed to file: %s" % file.decode('utf-8'), 0) \ No newline at end of file diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index f7f63c3c..bf1d20f4 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -11,6 +11,7 @@ import xbmcvfs import clientinfo import utils +from utils import Logging, window, language as lang ################################################################################################# @@ -20,16 +21,14 @@ class VideoNodes(object): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + clientInfo = clientinfo.ClientInfo() self.addonName = clientInfo.getAddonName() self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def commonRoot(self, order, label, tagname, roottype=1): @@ -54,8 +53,6 @@ class VideoNodes(object): def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): - window = utils.window - if viewtype == "mixed": dirname = "%s - %s" % (viewid, mediatype) else: @@ -82,7 +79,7 @@ class VideoNodes(object): for file in files: xbmcvfs.delete(nodepath + file) - self.logMsg("Sucessfully removed videonode: %s." % tagname, 1) + log("Sucessfully removed videonode: %s." % tagname, 1) return # Create index entry @@ -184,7 +181,7 @@ class VideoNodes(object): # Get label stringid = nodes[node] if node != "1": - label = utils.language(stringid) + label = lang(stringid) if not label: label = xbmc.getLocalizedString(stringid) else: @@ -319,8 +316,6 @@ class VideoNodes(object): def singleNode(self, indexnumber, tagname, mediatype, itemtype): - window = utils.window - tagname = tagname.encode('utf-8') cleantagname = utils.normalize_nodes(tagname) nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8') @@ -342,7 +337,7 @@ class VideoNodes(object): 'Favorite tvshows': 30181, 'channels': 30173 } - label = utils.language(labels[tagname]) + label = lang(labels[tagname]) embynode = "Emby.nodes.%s" % indexnumber window('%s.title' % embynode, value=label) window('%s.path' % embynode, value=windowpath) @@ -369,9 +364,7 @@ class VideoNodes(object): def clearProperties(self): - window = utils.window - - self.logMsg("Clearing nodes properties.", 1) + log("Clearing nodes properties.", 1) embyprops = window('Emby.nodes.total') propnames = [ diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 559cb152..90e9499e 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -14,10 +14,7 @@ import downloadutils import librarysync import playlist import userclient -import utils - -import logging -logging.basicConfig() +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -32,6 +29,9 @@ class WebSocket_Client(threading.Thread): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() @@ -43,15 +43,10 @@ class WebSocket_Client(threading.Thread): threading.Thread.__init__(self) - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) - def sendProgressUpdate(self, data): - self.logMsg("sendProgressUpdate", 2) + log("sendProgressUpdate", 2) try: messageData = { @@ -60,23 +55,21 @@ class WebSocket_Client(threading.Thread): } messageString = json.dumps(messageData) self.client.send(messageString) - self.logMsg("Message data: %s" % messageString, 2) + log("Message data: %s" % messageString, 2) except Exception as e: - self.logMsg("Exception: %s" % e, 1) + log("Exception: %s" % e, 1) def on_message(self, ws, message): - - window = utils.window - lang = utils.language result = json.loads(message) messageType = result['MessageType'] data = result['Data'] + dialog = xbmcgui.Dialog() if messageType not in ('SessionEnded'): # Mute certain events - self.logMsg("Message: %s" % message, 1) + log("Message: %s" % message, 1) if messageType == "Play": # A remote control play command has been sent from the server. @@ -84,7 +77,6 @@ class WebSocket_Client(threading.Thread): command = data['PlayCommand'] pl = playlist.Playlist() - dialog = xbmcgui.Dialog() if command == "PlayNow": dialog.notification( @@ -126,10 +118,10 @@ class WebSocket_Client(threading.Thread): seekto = data['SeekPositionTicks'] seektime = seekto / 10000000.0 action(seektime) - self.logMsg("Seek to %s." % seektime, 1) + log("Seek to %s." % seektime, 1) else: action() - self.logMsg("Command: %s completed." % command, 1) + log("Command: %s completed." % command, 1) window('emby_command', value="true") @@ -199,11 +191,11 @@ class WebSocket_Client(threading.Thread): header = arguments['Header'] text = arguments['Text'] - xbmcgui.Dialog().notification( - heading=header, - message=text, - icon="special://home/addons/plugin.video.emby/icon.png", - time=4000) + dialog.notification( + heading=header, + message=text, + icon="special://home/addons/plugin.video.emby/icon.png", + time=4000) elif command == "SendString": @@ -250,11 +242,11 @@ class WebSocket_Client(threading.Thread): xbmc.executebuiltin(action) elif messageType == "ServerRestarting": - if utils.settings('supressRestartMsg') == "true": - xbmcgui.Dialog().notification( - heading="Emby for Kodi", - message=lang(33006), - icon="special://home/addons/plugin.video.emby/icon.png") + if settings('supressRestartMsg') == "true": + dialog.notification( + heading="Emby for Kodi", + message=lang(33006), + icon="special://home/addons/plugin.video.emby/icon.png") elif messageType == "UserConfigurationUpdated": # Update user data set in userclient @@ -262,7 +254,7 @@ class WebSocket_Client(threading.Thread): self.librarySync.refresh_views = True def on_close(self, ws): - self.logMsg("Closed.", 2) + log("Closed.", 2) def on_open(self, ws): self.doUtils.postCapabilities(self.deviceId) @@ -272,11 +264,10 @@ class WebSocket_Client(threading.Thread): # Server is offline pass else: - self.logMsg("Error: %s" % error, 2) + log("Error: %s" % error, 2) def run(self): - window = utils.window loglevel = int(window('emby_logLevel')) # websocket.enableTrace(True) @@ -290,7 +281,7 @@ class WebSocket_Client(threading.Thread): server = server.replace('http', "ws") websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId) - self.logMsg("websocket url: %s" % websocket_url, 1) + log("websocket url: %s" % websocket_url, 1) self.client = websocket.WebSocketApp(websocket_url, on_message=self.on_message, @@ -298,7 +289,7 @@ class WebSocket_Client(threading.Thread): on_close=self.on_close) self.client.on_open = self.on_open - self.logMsg("----===## Starting WebSocketClient ##===----", 0) + log("----===## Starting WebSocketClient ##===----", 0) while not self.monitor.abortRequested(): @@ -310,10 +301,10 @@ class WebSocket_Client(threading.Thread): # Abort was requested, exit break - self.logMsg("##===---- WebSocketClient Stopped ----===##", 0) + log("##===---- WebSocketClient Stopped ----===##", 0) def stopClient(self): self.stopWebsocket = True self.client.close() - self.logMsg("Stopping thread.", 1) \ No newline at end of file + log("Stopping thread.", 1) \ No newline at end of file diff --git a/service.py b/service.py index b54a4a7c..f47be05c 100644 --- a/service.py +++ b/service.py @@ -16,9 +16,9 @@ import xbmcvfs ################################################################################################# _addon = xbmcaddon.Addon(id='plugin.video.emby') -addon_path = _addon.getAddonInfo('path').decode('utf-8') -base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8') -sys.path.append(base_resource) +_addon_path = _addon.getAddonInfo('path').decode('utf-8') +_base_resource = xbmc.translatePath(os.path.join(_addon_path, 'resources', 'lib')).decode('utf-8') +sys.path.append(_base_resource) ################################################################################################# @@ -28,9 +28,9 @@ import initialsetup import kodimonitor import librarysync import player -import utils import videonodes import websocket_client as wsc +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -49,8 +49,8 @@ class Service(): def __init__(self): - log = self.logMsg - window = utils.window + global log + log = Logging(self.__class__.__name__).log self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() @@ -58,15 +58,14 @@ class Service(): self.monitor = xbmc.Monitor() window('emby_logLevel', value=str(logLevel)) - window('emby_kodiProfile', value=xbmc.translatePath("special://profile")) - window('emby_pluginpath', value=utils.settings('useDirectPaths')) + window('emby_kodiProfile', value=xbmc.translatePath('special://profile')) # Initial logging log("======== START %s ========" % self.addonName, 0) log("Platform: %s" % (self.clientInfo.getPlatform()), 0) log("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0) log("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0) - log("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0) + log("Using plugin paths: %s" % (settings('useDirectPaths') == "0"), 0) log("Log Level: %s" % logLevel, 0) # Reset window props for profile switch @@ -86,22 +85,13 @@ class Service(): # Set the minimum database version window('emby_minDBVersion', value="1.1.63") - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def ServiceEntryPoint(self): - log = self.logMsg - window = utils.window - lang = utils.language - # Important: Threads depending on abortRequest will not trigger # if profile switch happens more than once. monitor = self.monitor - kodiProfile = xbmc.translatePath("special://profile") + kodiProfile = xbmc.translatePath('special://profile') # Server auto-detect initialsetup.InitialSetup().setup() @@ -119,7 +109,7 @@ class Service(): if window('emby_kodiProfile') != kodiProfile: # Profile change happened, terminate this thread and others log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." - % (kodiProfile, utils.window('emby_kodiProfile')), 1) + % (kodiProfile, window('emby_kodiProfile')), 1) break @@ -167,7 +157,7 @@ class Service(): else: # Start up events self.warn_auth = True - if utils.settings('connectMsg') == "true" and self.welcome_msg: + if settings('connectMsg') == "true" and self.welcome_msg: # Reset authentication warnings self.welcome_msg = False # Get additional users @@ -291,7 +281,7 @@ class Service(): log("======== STOP %s ========" % self.addonName, 0) # Delay option -delay = int(utils.settings('startupDelay')) +delay = int(settings('startupDelay')) xbmc.log("Delaying emby startup by: %s sec..." % delay) if delay and xbmc.Monitor().waitForAbort(delay):