From 7a0f69e0141550eef061040aa2bf8fc3fa55fbec Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 00:43:36 -0500 Subject: [PATCH 01/28] Centralized logging --- resources/lib/api.py | 13 +- resources/lib/artwork.py | 236 ++++++++++--------- resources/lib/playbackutils.py | 61 +++-- resources/lib/read_embyserver.py | 141 ++++++----- resources/lib/userclient.py | 110 ++++----- resources/lib/utils.py | 377 +++++++++++++++++------------- resources/lib/videonodes.py | 23 +- resources/lib/websocket_client.py | 63 +++-- service.py | 34 +-- 9 files changed, 542 insertions(+), 516 deletions(-) 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): From b9d40d91a630348935462af18483ae7863829b86 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 00:49:54 -0500 Subject: [PATCH 02/28] Fix SeriesName missing Prevent crash from happening. --- resources/lib/itemtypes.py | 3 +-- resources/lib/librarysync.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 18c90517..8151ee93 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -192,7 +192,7 @@ class Items(object): title = item['Name'] if itemtype == "Episode": - title = "%s - %s" % (item['SeriesName'], title) + title = "%s - %s" % (item.get('SeriesName', "Unknown"), title) if pdialog: percentage = int((float(count) / float(total))*100) @@ -1284,7 +1284,6 @@ class TVShows(Items): self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1) return False - seriesName = item['SeriesName'] season = item.get('ParentIndexNumber') episode = item.get('IndexNumber', -1) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index d3a441dd..1335f585 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -1363,7 +1363,8 @@ class ManualSync(LibrarySync): if pdialog: percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message="%s - %s" % (episode['SeriesName'], episode['Name'])) + title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) + pdialog.update(percentage, message=title) count += 1 tvshows.add_updateEpisode(episode) From e7bdfacd4750b4a7a837d6ecc50325f3ba244897 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 14:13:38 -0500 Subject: [PATCH 03/28] Empty show Prevent crash in the event RecursiveItemCount is missing. Sounds like a server bug if it's missing from the api --- resources/lib/itemtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 8151ee93..25f0db93 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1004,7 +1004,7 @@ class TVShows(Items): artwork = self.artwork API = api.API(item) - if utils.settings('syncEmptyShows') == "false" and not item['RecursiveItemCount']: + if utils.settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): self.logMsg("Skipping empty show: %s" % item['Name'], 1) return # If the item already exist in the local Kodi DB we'll perform a full item update From 9314c4a363ddac26fcdb84e9bb75b79215a89def Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 16:24:07 -0500 Subject: [PATCH 04/28] Centralized Logging --- resources/lib/artwork.py | 6 ++-- resources/lib/clientinfo.py | 30 ++++++++-------- resources/lib/playbackutils.py | 2 +- resources/lib/playlist.py | 46 +++++++++++------------- resources/lib/playutils.py | 64 ++++++++++++++-------------------- 5 files changed, 67 insertions(+), 81 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 2c8afea9..663cfb06 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -279,7 +279,7 @@ class Artwork(): # add a new thread or wait and retry if we hit our limit if len(self.imageCacheThreads) < self.imageCacheLimitThreads: newThread = image_cache_thread.image_cache_thread() - newThread.setUrl(self.double_urlencode(urlToAdd)) + newThread.setUrl(self.double_urlencode(url)) newThread.setHost(self.xbmc_host, self.xbmc_port) newThread.setAuth(self.xbmc_username, self.xbmc_password) newThread.start() @@ -519,7 +519,7 @@ class Artwork(): % (self.server, item_id, item_type)) return image - def getAllArtwork(self, item, parent_artwork=False): + def getAllArtwork(self, item, parentInfo=False): itemid = item['Id'] artworks = item['ImageTags'] @@ -566,7 +566,7 @@ class Artwork(): allartworks[art] = artwork # Process parent items if the main item is missing artwork - if parent_artwork: + if parentInfo: # Process parent backdrops if not allartworks['Backdrop']: diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index 8b3fe655..d5550388 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -9,7 +9,7 @@ import xbmc import xbmcaddon import xbmcvfs -import utils +from utils import Logging, window, settings ################################################################################################# @@ -19,14 +19,12 @@ class ClientInfo(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.addon = xbmcaddon.Addon() self.addonName = self.getAddonName() - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def getAddonName(self): # Used for logging @@ -42,11 +40,11 @@ class ClientInfo(): def getDeviceName(self): - if utils.settings('deviceNameOpt') == "false": + if settings('deviceNameOpt') == "false": # Use Kodi's deviceName deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') else: - deviceName = utils.settings('deviceName') + deviceName = settings('deviceName') deviceName = deviceName.replace("\"", "_") deviceName = deviceName.replace("/", "_") @@ -71,16 +69,18 @@ class ClientInfo(): def getDeviceId(self, reset=False): - clientId = utils.window('emby_deviceId') + clientId = window('emby_deviceId') if clientId: return clientId addon_path = self.addon.getAddonInfo('path').decode('utf-8') if os.path.supports_unicode_filenames: - GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8') + path = os.path.join(addon_path, "machine_guid") else: - GUID_file = xbmc.translatePath(os.path.join(addon_path.encode("utf-8"), "machine_guid")).decode('utf-8') - + path = os.path.join(addon_path.encode('utf-8'), "machine_guid") + + GUID_file = xbmc.translatePath(path).decode('utf-8') + if reset and xbmcvfs.exists(GUID_file): # Reset the file xbmcvfs.delete(GUID_file) @@ -88,14 +88,14 @@ class ClientInfo(): GUID = xbmcvfs.File(GUID_file) clientId = GUID.read() if not clientId: - self.logMsg("Generating a new deviceid...", 1) + log("Generating a new deviceid...", 1) clientId = str("%012X" % uuid4()) GUID = xbmcvfs.File(GUID_file, 'w') GUID.write(clientId) GUID.close() - self.logMsg("DeviceId loaded: %s" % clientId, 1) - utils.window('emby_deviceId', value=clientId) + log("DeviceId loaded: %s" % clientId, 1) + window('emby_deviceId', value=clientId) return clientId \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 0afb0c8b..4da87f27 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -271,7 +271,7 @@ class PlaybackUtils(): def setArtwork(self, listItem): # Set up item and item info - allartwork = self.artwork.getAllArtwork(self.item, parent_artwork=True) + allartwork = self.artwork.getAllArtwork(self.item, parentInfo=True) # Set artwork for listitem arttypes = { diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index bcd34a46..1f0819b6 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -13,7 +13,7 @@ import playutils import playbackutils import embydb_functions as embydb import read_embyserver as embyserver -import utils +from utils import Logging, window, settings, language as lang, kodiSQL ################################################################################################# @@ -23,25 +23,21 @@ class Playlist(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() - 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.emby = embyserver.Read_EmbyServer() - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) - def playAll(self, itemids, startat): - window = utils.window - - embyconn = utils.kodiSQL('emby') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) @@ -49,8 +45,8 @@ class Playlist(): playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() - self.logMsg("---*** PLAY ALL ***---", 1) - self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1) + log("---*** PLAY ALL ***---", 1) + log("Items: %s and start at: %s" % (itemids, startat), 1) started = False window('emby_customplaylist', value="true") @@ -66,14 +62,14 @@ class Playlist(): mediatype = embydb_item[4] except TypeError: # Item is not found in our database, add item manually - self.logMsg("Item was not found in the database, manually adding item.", 1) + log("Item was not found in the database, manually adding item.", 1) item = self.emby.getItem(itemid) self.addtoPlaylist_xbmc(playlist, item) else: # Add to playlist self.addtoPlaylist(dbid, mediatype) - self.logMsg("Adding %s to playlist." % itemid, 1) + log("Adding %s to playlist." % itemid, 1) if not started: started = True @@ -84,12 +80,12 @@ class Playlist(): def modifyPlaylist(self, itemids): - embyconn = utils.kodiSQL('emby') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - self.logMsg("---*** ADD TO PLAYLIST ***---", 1) - self.logMsg("Items: %s" % itemids, 1) + log("---*** ADD TO PLAYLIST ***---", 1) + log("Items: %s" % itemids, 1) player = xbmc.Player() playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) @@ -107,7 +103,7 @@ class Playlist(): # Add to playlist self.addtoPlaylist(dbid, mediatype) - self.logMsg("Adding %s to playlist." % itemid, 1) + log("Adding %s to playlist." % itemid, 1) self.verifyPlaylist() embycursor.close() @@ -130,17 +126,17 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) def addtoPlaylist_xbmc(self, playlist, item): playurl = playutils.PlayUtils(item).getPlayUrl() if not playurl: # Playurl failed - self.logMsg("Failed to retrieve playurl.", 1) + log("Failed to retrieve playurl.", 1) return - self.logMsg("Playurl: %s" % playurl) + log("Playurl: %s" % playurl) listitem = xbmcgui.ListItem() playbackutils.PlaybackUtils(item).setProperties(playurl, listitem) @@ -164,7 +160,7 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) def verifyPlaylist(self): @@ -178,7 +174,7 @@ class Playlist(): 'playlistid': 1 } } - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) def removefromPlaylist(self, position): @@ -193,4 +189,4 @@ class Playlist(): 'position': position } } - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) \ No newline at end of file diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index a1de2948..ed9b57b0 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -7,7 +7,7 @@ import xbmcgui import xbmcvfs import clientinfo -import utils +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -17,41 +17,37 @@ class PlayUtils(): def __init__(self, item): + global log + log = Logging(self.__class__.__name__).log + self.item = item self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() - self.userid = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userid) - - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) def getPlayUrl(self): - window = utils.window - playurl = None - if (self.item.get('Type') in ("Recording", "TvChannel") and - self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"): + if (self.item.get('Type') in ("Recording", "TvChannel") and self.item.get('MediaSources') + and self.item['MediaSources'][0]['Protocol'] == "Http"): # Play LiveTV or recordings - self.logMsg("File protocol is http (livetv).", 1) + log("File protocol is http (livetv).", 1) playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, self.item['Id']) window('emby_%s.playmethod' % playurl, value="Transcode") elif self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http": # Only play as http, used for channels, or online hosting of content - self.logMsg("File protocol is http.", 1) + log("File protocol is http.", 1) playurl = self.httpPlay() window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isDirectPlay(): - self.logMsg("File is direct playing.", 1) + log("File is direct playing.", 1) playurl = self.directPlay() playurl = playurl.encode('utf-8') # Set playmethod property @@ -59,14 +55,14 @@ class PlayUtils(): elif self.isDirectStream(): - self.logMsg("File is direct streaming.", 1) + log("File is direct streaming.", 1) playurl = self.directStream() # Set playmethod property window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isTranscoding(): - self.logMsg("File is transcoding.", 1) + log("File is transcoding.", 1) playurl = self.transcoding() # Set playmethod property window('emby_%s.playmethod' % playurl, value="Transcode") @@ -88,21 +84,18 @@ class PlayUtils(): def isDirectPlay(self): - lang = utils.language - settings = utils.settings dialog = xbmcgui.Dialog() - # Requirement: Filesystem, Accessible path if settings('playFromStream') == "true": # User forcing to play via HTTP - self.logMsg("Can't direct play, play from HTTP enabled.", 1) + log("Can't direct play, play from HTTP enabled.", 1) return False videotrack = self.item['MediaSources'][0]['Name'] transcodeH265 = settings('transcodeH265') videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x] - transcodeHi10P = utils.settings('transcodeHi10P') + transcodeHi10P = settings('transcodeHi10P') if transcodeHi10P == "true" and "H264" in videotrack and "High 10" in videoprofiles: return False @@ -116,7 +109,7 @@ class PlayUtils(): '2': 720, '3': 1080 } - self.logMsg("Resolution is: %sP, transcode for resolution: %sP+" + log("Resolution is: %sP, transcode for resolution: %sP+" % (resolution, res[transcodeH265]), 1) if res[transcodeH265] <= resolution: return False @@ -124,19 +117,19 @@ class PlayUtils(): canDirectPlay = self.item['MediaSources'][0]['SupportsDirectPlay'] # Make sure direct play is supported by the server if not canDirectPlay: - self.logMsg("Can't direct play, server doesn't allow/support it.", 1) + log("Can't direct play, server doesn't allow/support it.", 1) return False location = self.item['LocationType'] if location == "FileSystem": # Verify the path if not self.fileExists(): - self.logMsg("Unable to direct play.") + log("Unable to direct play.", 1) try: count = int(settings('failCount')) except ValueError: count = 0 - self.logMsg("Direct play failed: %s times." % count, 1) + log("Direct play failed: %s times." % count, 1) if count < 2: # Let the user know that direct play failed @@ -192,23 +185,22 @@ class PlayUtils(): # Convert path to direct play path = self.directPlay() - self.logMsg("Verifying path: %s" % path, 1) + log("Verifying path: %s" % path, 1) if xbmcvfs.exists(path): - self.logMsg("Path exists.", 1) + log("Path exists.", 1) return True elif ":" not in path: - self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1) + log("Can't verify path, assumed linux. Still try to direct play.", 1) return True else: - self.logMsg("Failed to find file.", 1) + log("Failed to find file.", 1) return False def isDirectStream(self): - videotrack = self.item['MediaSources'][0]['Name'] transcodeH265 = utils.settings('transcodeH265') videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x] @@ -226,7 +218,7 @@ class PlayUtils(): '2': 720, '3': 1080 } - self.logMsg("Resolution is: %sP, transcode for resolution: %sP+" + log("Resolution is: %sP, transcode for resolution: %sP+" % (resolution, res[transcodeH265]), 1) if res[transcodeH265] <= resolution: return False @@ -239,7 +231,7 @@ class PlayUtils(): # Verify the bitrate if not self.isNetworkSufficient(): - self.logMsg("The network speed is insufficient to direct stream file.", 1) + log("The network speed is insufficient to direct stream file.", 1) return False return True @@ -258,15 +250,14 @@ class PlayUtils(): def isNetworkSufficient(self): - settings = self.getBitrate()*1000 try: sourceBitrate = int(self.item['MediaSources'][0]['Bitrate']) except (KeyError, TypeError): - self.logMsg("Bitrate value is missing.", 1) + log("Bitrate value is missing.", 1) else: - self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" + log("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1) if settings < sourceBitrate: return False @@ -329,7 +320,6 @@ class PlayUtils(): def audioSubsPref(self, url, listitem): - lang = utils.language dialog = xbmcgui.Dialog() # For transcoding only # Present the list of audio to select from From 1dac1c4f4b8bfc43c199a73d042eb3dfb8fb4df2 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 14:52:53 -0500 Subject: [PATCH 05/28] Fix typo --- resources/lib/artwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 663cfb06..4ed0b8d9 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -427,7 +427,7 @@ class Artwork(): # Only for the main backdrop, poster if (window('emby_initialScan') != "true" and - imageType in ("fanart", "poster")): + image_type in ("fanart", "poster")): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) From 02e7c2946bf70c3ede08e95583e1ef2240dd2298 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 16:42:48 -0500 Subject: [PATCH 06/28] Centralize path validation and logging --- resources/lib/itemtypes.py | 275 ++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 158 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 25f0db93..0bf87c69 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -14,11 +14,11 @@ import api import artwork import clientinfo import downloadutils -import utils import embydb_functions as embydb import kodidb_functions as kodidb import read_embyserver as embyserver import musicutils +from utils import Logging, window, settings, language as lang ################################################################################################## @@ -28,6 +28,9 @@ class Items(object): def __init__(self, embycursor, kodicursor): + global log + log = Logging(self.__class__.__name__).log + self.embycursor = embycursor self.kodicursor = kodicursor @@ -35,23 +38,18 @@ class Items(object): self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils() - self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) - self.directpath = utils.settings('useDirectPaths') == "1" - self.music_enabled = utils.settings('enableMusic') == "true" - self.contentmsg = utils.settings('newContent') == "true" - self.newvideo_time = int(utils.settings('newvideotime'))*1000 - self.newmusic_time = int(utils.settings('newmusictime'))*1000 + self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) + self.directpath = settings('useDirectPaths') == "1" + self.music_enabled = settings('enableMusic') == "true" + self.contentmsg = settings('newContent') == "true" + self.newvideo_time = int(settings('newvideotime'))*1000 + self.newmusic_time = int(settings('newmusictime'))*1000 self.artwork = artwork.Artwork() self.emby = embyserver.Read_EmbyServer() self.emby_db = embydb.Embydb_Functions(embycursor) self.kodi_db = kodidb.Kodidb_Functions(kodicursor) - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def itemsbyId(self, items, process, pdialog=None): # Process items by itemid. Process can be added, update, userdata, remove @@ -81,7 +79,7 @@ class Items(object): if total == 0: return False - self.logMsg("Processing %s: %s" % (process, items), 1) + log("Processing %s: %s" % (process, items), 1) if pdialog: pdialog.update(heading="Processing %s: %s items" % (process, total)) @@ -173,7 +171,7 @@ class Items(object): 'remove': items_process.remove } else: - self.logMsg("Unsupported itemtype: %s." % itemtype, 1) + log("Unsupported itemtype: %s." % itemtype, 1) actions = {} if actions.get(process): @@ -204,12 +202,29 @@ class Items(object): if musicconn is not None: # close connection for special types - self.logMsg("Updating music database.", 1) + log("Updating music database.", 1) musicconn.commit() musiccursor.close() return (True, update_videolibrary) + def pathValidation(self, path): + # Verify if direct path is accessible or not + if window('emby_pathverified') != "true" and not xbmcvfs.exists(path): + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. " + "You may need to verify your network credentials in the " + "add-on settings or use the Emby path substitution " + "to format your path correctly (Emby dashboard > library). " + "Stop syncing?" % playurl)) + if resp: + window('emby_shouldStop', value="true") + return False + + return True + def contentPop(self, name, time=5000): if time: @@ -272,11 +287,11 @@ class Movies(Items): movieid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) + log("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) except TypeError: update_item = False - self.logMsg("movieid: %s not found." % itemid, 2) + log("movieid: %s not found." % itemid, 2) # movieid kodicursor.execute("select coalesce(max(idMovie),0) from movie") movieid = kodicursor.fetchone()[0] + 1 @@ -290,12 +305,12 @@ class Movies(Items): except TypeError: # item is not found, let's recreate it. update_item = False - self.logMsg("movieid: %s missing from Kodi, repairing the entry." % movieid, 1) + log("movieid: %s missing from Kodi, repairing the entry." % movieid, 1) if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - self.logMsg("View tag found: %s" % viewtag, 2) + log("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -338,7 +353,7 @@ class Movies(Items): try: trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] except IndexError: - self.logMsg("Failed to process local trailer.", 1) + log("Failed to process local trailer.", 1) trailer = None else: # Try to get the youtube trailer @@ -350,7 +365,7 @@ class Movies(Items): try: trailerId = trailer.rsplit('=', 1)[1] except IndexError: - self.logMsg("Failed to process trailer: %s" % trailer, 1) + log("Failed to process trailer: %s" % trailer, 1) trailer = None else: trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId @@ -367,22 +382,11 @@ class Movies(Items): if self.directpath: # Direct paths is set the Kodi way - if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): - # Validate the path is correct with user intervention - resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. Verify the path. " - "You may to verify your network credentials in the " - "add-on settings or use the emby path substitution " - "to format your path correctly. Stop syncing?" - % playurl)) - if resp: - utils.window('emby_shouldStop', value="true") - return False + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path and media flags using real filename path = "plugin://plugin.video.emby.movies/" @@ -398,7 +402,7 @@ class Movies(Items): ##### UPDATE THE MOVIE ##### if update_item: - self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) # Update the movie entry query = ' '.join(( @@ -418,7 +422,7 @@ class Movies(Items): ##### OR ADD THE MOVIE ##### else: - self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) # Add path pathid = self.kodi_db.addPath(path) @@ -528,10 +532,10 @@ class Movies(Items): try: movieid = emby_dbitem[0] except TypeError: - self.logMsg("Failed to add: %s to boxset." % movie['Name'], 1) + log("Failed to add: %s to boxset." % movie['Name'], 1) continue - self.logMsg("New addition to boxset %s: %s" % (title, movie['Name']), 1) + log("New addition to boxset %s: %s" % (title, movie['Name']), 1) self.kodi_db.assignBoxset(setid, movieid) # Update emby reference emby_db.updateParentId(itemid, setid) @@ -542,7 +546,7 @@ class Movies(Items): # Process removals from boxset for movie in process: movieid = current[movie] - self.logMsg("Remove from boxset %s: %s" % (title, movieid)) + log("Remove from boxset %s: %s" % (title, movieid)) self.kodi_db.removefromBoxset(movieid) # Update emby reference emby_db.updateParentId(movie, None) @@ -567,7 +571,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - self.logMsg( + log( "Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1) except TypeError: @@ -585,7 +589,7 @@ class Movies(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - self.logMsg("%s New resume point: %s" % (itemid, resume)) + log("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) emby_db.updateReference(itemid, checksum) @@ -601,7 +605,7 @@ class Movies(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - self.logMsg("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) except TypeError: return @@ -627,7 +631,7 @@ class Movies(Items): kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) - self.logMsg("Deleted %s %s from kodi database" % (mediatype, itemid), 1) + log("Deleted %s %s from kodi database" % (mediatype, itemid), 1) class MusicVideos(Items): @@ -667,11 +671,11 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) + log("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) except TypeError: update_item = False - self.logMsg("mvideoid: %s not found." % itemid, 2) + log("mvideoid: %s not found." % itemid, 2) # mvideoid kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") mvideoid = kodicursor.fetchone()[0] + 1 @@ -685,12 +689,12 @@ class MusicVideos(Items): except TypeError: # item is not found, let's recreate it. update_item = False - self.logMsg("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1) + log("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1) if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - self.logMsg("View tag found: %s" % viewtag, 2) + log("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -726,22 +730,11 @@ class MusicVideos(Items): if self.directpath: # Direct paths is set the Kodi way - if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): - # Validate the path is correct with user intervention - resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. Verify the path. " - "You may to verify your network credentials in the " - "add-on settings or use the emby path substitution " - "to format your path correctly. Stop syncing?" - % playurl)) - if resp: - utils.window('emby_shouldStop', value="true") - return False + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path and media flags using real filename path = "plugin://plugin.video.emby.musicvideos/" @@ -757,7 +750,7 @@ class MusicVideos(Items): ##### UPDATE THE MUSIC VIDEO ##### if update_item: - self.logMsg("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -783,7 +776,7 @@ class MusicVideos(Items): ##### OR ADD THE MUSIC VIDEO ##### else: - self.logMsg("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) # Add path query = ' '.join(( @@ -883,7 +876,7 @@ class MusicVideos(Items): try: mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] - self.logMsg( + log( "Update playstate for musicvideo: %s fileid: %s" % (item['Name'], fileid), 1) except TypeError: @@ -915,7 +908,7 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) + log("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) except TypeError: return @@ -941,7 +934,7 @@ class MusicVideos(Items): kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,)) self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,)) - self.logMsg("Deleted musicvideo %s from kodi database" % itemid, 1) + log("Deleted musicvideo %s from kodi database" % itemid, 1) class TVShows(Items): @@ -1004,8 +997,8 @@ class TVShows(Items): artwork = self.artwork API = api.API(item) - if utils.settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): - self.logMsg("Skipping empty show: %s" % item['Name'], 1) + if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): + log("Skipping empty show: %s" % item['Name'], 1) return # If the item already exist in the local Kodi DB we'll perform a full item update # If the item doesn't exist, we'll add it to the database @@ -1016,11 +1009,11 @@ class TVShows(Items): try: showid = emby_dbitem[0] pathid = emby_dbitem[2] - self.logMsg("showid: %s pathid: %s" % (showid, pathid), 1) + log("showid: %s pathid: %s" % (showid, pathid), 1) except TypeError: update_item = False - self.logMsg("showid: %s not found." % itemid, 2) + log("showid: %s not found." % itemid, 2) kodicursor.execute("select coalesce(max(idShow),0) from tvshow") showid = kodicursor.fetchone()[0] + 1 @@ -1033,7 +1026,7 @@ class TVShows(Items): except TypeError: # item is not found, let's recreate it. update_item = False - self.logMsg("showid: %s missing from Kodi, repairing the entry." % showid, 1) + log("showid: %s missing from Kodi, repairing the entry." % showid, 1) # Force re-add episodes after the show is re-created. force_episodes = True @@ -1041,7 +1034,7 @@ class TVShows(Items): if viewtag is None or viewid is None: # Get view tag from emby viewtag, viewid, mediatype = emby.getView_embyId(itemid) - self.logMsg("View tag found: %s" % viewtag, 2) + log("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -1078,21 +1071,10 @@ class TVShows(Items): path = "%s/" % playurl toplevelpath = "%s/" % dirname(dirname(path)) - if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(path): - # Validate the path is correct with user intervention - resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. Verify the path. " - "You may to verify your network credentials in the " - "add-on settings or use the emby path substitution " - "to format your path correctly. Stop syncing?" - % playurl)) - if resp: - utils.window('emby_shouldStop', value="true") - return False + if not self.pathValidation(playurl): + return False - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path toplevelpath = "plugin://plugin.video.emby.tvshows/" @@ -1101,7 +1083,7 @@ class TVShows(Items): ##### UPDATE THE TVSHOW ##### if update_item: - self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) # Update the tvshow entry query = ' '.join(( @@ -1119,7 +1101,7 @@ class TVShows(Items): ##### OR ADD THE TVSHOW ##### else: - self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) # Add top path toppathid = self.kodi_db.addPath(toplevelpath) @@ -1190,7 +1172,7 @@ class TVShows(Items): if force_episodes: # We needed to recreate the show entry. Re-add episodes now. - self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1) + log("Repairing episodes for showid: %s %s" % (showid, title), 1) all_episodes = emby.getEpisodesbyShow(itemid) self.added_episode(all_episodes['Items'], None) @@ -1239,11 +1221,11 @@ class TVShows(Items): episodeid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) + log("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) except TypeError: update_item = False - self.logMsg("episodeid: %s not found." % itemid, 2) + log("episodeid: %s not found." % itemid, 2) # episodeid kodicursor.execute("select coalesce(max(idEpisode),0) from episode") episodeid = kodicursor.fetchone()[0] + 1 @@ -1257,7 +1239,7 @@ class TVShows(Items): except TypeError: # item is not found, let's recreate it. update_item = False - self.logMsg("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1) + log("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1) # fileId information checksum = API.getChecksum() @@ -1281,7 +1263,7 @@ class TVShows(Items): seriesId = item['SeriesId'] except KeyError: # Missing seriesId, skip - self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1) + log("Skipping: %s. SeriesId is missing." % itemid, 1) return False season = item.get('ParentIndexNumber') @@ -1319,7 +1301,7 @@ class TVShows(Items): try: showid = show[0] except TypeError: - self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) + log("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) return False seasonid = self.kodi_db.addSeason(showid, season) @@ -1336,22 +1318,11 @@ class TVShows(Items): if self.directpath: # Direct paths is set the Kodi way - if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): - # Validate the path is correct with user intervention - resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. Verify the path. " - "You may to verify your network credentials in the " - "add-on settings or use the emby path substitution " - "to format your path correctly. Stop syncing?" - % playurl)) - if resp: - utils.window('emby_shouldStop', value="true") - return False + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path and media flags using real filename path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId @@ -1367,7 +1338,7 @@ class TVShows(Items): ##### UPDATE THE EPISODE ##### if update_item: - self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) # Update the movie entry if self.kodiversion in (16, 17): @@ -1401,7 +1372,7 @@ class TVShows(Items): ##### OR ADD THE EPISODE ##### else: - self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) # Add path pathid = self.kodi_db.addPath(path) @@ -1504,7 +1475,7 @@ class TVShows(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - self.logMsg( + log( "Update playstate for %s: %s fileid: %s" % (mediatype, item['Name'], fileid), 1) except TypeError: @@ -1523,7 +1494,7 @@ class TVShows(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - self.logMsg("%s New resume point: %s" % (itemid, resume)) + log("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) if not self.directpath and not resume: @@ -1561,7 +1532,7 @@ class TVShows(Items): pathid = emby_dbitem[2] parentid = emby_dbitem[3] mediatype = emby_dbitem[4] - self.logMsg("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) except TypeError: return @@ -1651,14 +1622,14 @@ class TVShows(Items): self.removeShow(parentid) emby_db.removeItem_byKodiId(parentid, "tvshow") - self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) def removeShow(self, kodiid): kodicursor = self.kodicursor self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor) kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,)) - self.logMsg("Removed tvshow: %s." % kodiid, 2) + log("Removed tvshow: %s." % kodiid, 2) def removeSeason(self, kodiid): @@ -1666,7 +1637,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "season", kodicursor) kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) - self.logMsg("Removed season: %s." % kodiid, 2) + log("Removed season: %s." % kodiid, 2) def removeEpisode(self, kodiid, fileid): @@ -1675,7 +1646,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "episode", kodicursor) kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,)) kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) - self.logMsg("Removed episode: %s." % kodiid, 2) + log("Removed episode: %s." % kodiid, 2) class Music(Items): @@ -1684,12 +1655,12 @@ class Music(Items): Items.__init__(self, embycursor, musiccursor) - self.directstream = utils.settings('streamMusic') == "true" - self.enableimportsongrating = utils.settings('enableImportSongRating') == "true" - self.enableexportsongrating = utils.settings('enableExportSongRating') == "true" - self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true" - self.userid = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userid) + self.directstream = settings('streamMusic') == "true" + self.enableimportsongrating = settings('enableImportSongRating') == "true" + self.enableexportsongrating = settings('enableExportSongRating') == "true" + self.enableupdatesongrating = settings('enableUpdateSongRating') == "true" + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) def added(self, items, pdialog): @@ -1749,7 +1720,7 @@ class Music(Items): artistid = emby_dbitem[0] except TypeError: update_item = False - self.logMsg("artistid: %s not found." % itemid, 2) + log("artistid: %s not found." % itemid, 2) ##### The artist details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1776,13 +1747,13 @@ class Music(Items): ##### UPDATE THE ARTIST ##### if update_item: - self.logMsg("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1) + log("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1) # Update the checksum in emby table emby_db.updateReference(itemid, checksum) ##### OR ADD THE ARTIST ##### else: - self.logMsg("ADD artist itemid: %s - Name: %s" % (itemid, name), 1) + log("ADD artist itemid: %s - Name: %s" % (itemid, name), 1) # safety checks: It looks like Emby supports the same artist multiple times. # Kodi doesn't allow that. In case that happens we just merge the artist entries. artistid = self.kodi_db.addArtist(name, musicBrainzId) @@ -1830,7 +1801,7 @@ class Music(Items): albumid = emby_dbitem[0] except TypeError: update_item = False - self.logMsg("albumid: %s not found." % itemid, 2) + log("albumid: %s not found." % itemid, 2) ##### The album details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1861,13 +1832,13 @@ class Music(Items): ##### UPDATE THE ALBUM ##### if update_item: - self.logMsg("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1) + log("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1) # Update the checksum in emby table emby_db.updateReference(itemid, checksum) ##### OR ADD THE ALBUM ##### else: - self.logMsg("ADD album itemid: %s - Name: %s" % (itemid, name), 1) + log("ADD album itemid: %s - Name: %s" % (itemid, name), 1) # safety checks: It looks like Emby supports the same artist multiple times. # Kodi doesn't allow that. In case that happens we just merge the artist entries. albumid = self.kodi_db.addAlbum(name, musicBrainzId) @@ -2000,7 +1971,7 @@ class Music(Items): albumid = emby_dbitem[3] except TypeError: update_item = False - self.logMsg("songid: %s not found." % itemid, 2) + log("songid: %s not found." % itemid, 2) ##### The song details ##### checksum = API.getChecksum() @@ -2047,27 +2018,15 @@ class Music(Items): filename = playurl.rsplit("/", 1)[1] # Direct paths is set the Kodi way - if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): - # Validate the path is correct with user intervention - utils.window('emby_directPath', clear=True) - resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. Verify the path. " - "You may to verify your network credentials in the " - "add-on settings or use the emby path substitution " - "to format your path correctly. Stop syncing?" - % playurl)) - if resp: - utils.window('emby_shouldStop', value="true") - return False + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") ##### UPDATE THE SONG ##### if update_item: - self.logMsg("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -2090,7 +2049,7 @@ class Music(Items): ##### OR ADD THE SONG ##### else: - self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD song itemid: %s - Title: %s" % (itemid, title), 1) # Add path pathid = self.kodi_db.addPath(path) @@ -2103,27 +2062,27 @@ class Music(Items): # Verify if there's an album associated. album_name = item.get('Album') if album_name: - self.logMsg("Creating virtual music album for song: %s." % itemid, 1) + log("Creating virtual music album for song: %s." % itemid, 1) albumid = self.kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum')) emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album") else: # No album Id associated to the song. - self.logMsg("Song itemid: %s has no albumId associated." % itemid, 1) + log("Song itemid: %s has no albumId associated." % itemid, 1) return False except TypeError: # No album found. Let's create it - self.logMsg("Album database entry missing.", 1) + log("Album database entry missing.", 1) emby_albumId = item['AlbumId'] album = emby.getItem(emby_albumId) self.add_updateAlbum(album) emby_dbalbum = emby_db.getItem_byId(emby_albumId) try: albumid = emby_dbalbum[0] - self.logMsg("Found albumid: %s" % albumid, 1) + log("Found albumid: %s" % albumid, 1) except TypeError: # No album found, create a single's album - self.logMsg("Failed to add album. Creating singles.", 1) + log("Failed to add album. Creating singles.", 1) kodicursor.execute("select coalesce(max(idAlbum),0) from album") albumid = kodicursor.fetchone()[0] + 1 if self.kodiversion == 16: @@ -2305,7 +2264,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - self.logMsg("Update playstate for %s: %s" % (mediatype, item['Name']), 1) + log("Update playstate for %s: %s" % (mediatype, item['Name']), 1) except TypeError: return @@ -2313,8 +2272,8 @@ class Music(Items): #should we ignore this item ? #happens when userdata updated by ratings method - if utils.window("ignore-update-%s" %itemid): - utils.window("ignore-update-%s" %itemid,clear=True) + if window("ignore-update-%s" %itemid): + window("ignore-update-%s" %itemid,clear=True) return # Process playstates @@ -2344,7 +2303,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - self.logMsg("Removing %s kodiid: %s" % (mediatype, kodiid), 1) + log("Removing %s kodiid: %s" % (mediatype, kodiid), 1) except TypeError: return @@ -2412,7 +2371,7 @@ class Music(Items): # Remove artist self.removeArtist(kodiid) - self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) def removeSong(self, kodiid): From 5658801f7280e51ccea021653e1d9155bd4ed190 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 22:03:28 -0500 Subject: [PATCH 07/28] Centralized logging --- resources/lib/connect.py | 58 ++++---- resources/lib/downloadutils.py | 113 ++++++++-------- resources/lib/image_cache_thread.py | 32 +++-- resources/lib/initialsetup.py | 77 +++++------ resources/lib/kodidb_functions.py | 44 +++--- resources/lib/kodimonitor.py | 80 ++++++----- resources/lib/librarysync.py | 202 +++++++++++++--------------- resources/lib/musicutils.py | 26 ++-- resources/lib/player.py | 75 +++++------ resources/lib/utils.py | 2 +- 10 files changed, 336 insertions(+), 373 deletions(-) diff --git a/resources/lib/connect.py b/resources/lib/connect.py index 2bd5c05d..87dbc2e1 100644 --- a/resources/lib/connect.py +++ b/resources/lib/connect.py @@ -6,8 +6,8 @@ import json import requests import logging -import utils import clientinfo +from utils import Logging, window ################################################################################################## @@ -34,28 +34,26 @@ class ConnectUtils(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def setUserId(self, userId): # Reserved for userclient only self.userId = userId - self.logMsg("Set connect userId: %s" % userId, 2) + log("Set connect userId: %s" % userId, 2) def setServer(self, server): # Reserved for userclient only self.server = server - self.logMsg("Set connect server: %s" % server, 2) + log("Set connect server: %s" % server, 2) def setToken(self, token): # Reserved for userclient only self.token = token - self.logMsg("Set connect token: %s" % token, 2) + log("Set connect token: %s" % token, 2) def startSession(self): @@ -73,7 +71,7 @@ class ConnectUtils(): if self.sslclient is not None: verify = self.sslclient except: - self.logMsg("Could not load SSL settings.", 1) + log("Could not load SSL settings.", 1) # Start session self.c = requests.Session() @@ -83,13 +81,13 @@ class ConnectUtils(): self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1)) - self.logMsg("Requests session started on: %s" % self.server, 1) + log("Requests session started on: %s" % self.server, 1) def stopSession(self): try: self.c.close() except Exception as e: - self.logMsg("Requests session could not be terminated: %s" % e, 1) + log("Requests session could not be terminated: %s" % e, 1) def getHeader(self, authenticate=True): @@ -103,7 +101,7 @@ class ConnectUtils(): 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': "application/json" } - self.logMsg("Header: %s" % header, 1) + log("Header: %s" % header, 1) else: token = self.token @@ -115,17 +113,17 @@ class ConnectUtils(): 'X-Application': "Kodi/%s" % version, 'X-Connect-UserToken': token } - self.logMsg("Header: %s" % header, 1) + log("Header: %s" % header, 1) return header def doUrl(self, url, data=None, postBody=None, rtype="GET", parameters=None, authenticate=True, timeout=None): - window = utils.window - - self.logMsg("=== ENTER connectUrl ===", 2) + log("=== ENTER connectUrl ===", 2) + default_link = "" + if timeout is None: timeout = self.timeout @@ -209,25 +207,25 @@ class ConnectUtils(): verify=verifyssl) ##### THE RESPONSE ##### - self.logMsg(r.url, 1) - self.logMsg(r, 1) + log(r.url, 1) + log(r, 1) if r.status_code == 204: # No body in the response - self.logMsg("====== 204 Success ======", 1) + log("====== 204 Success ======", 1) elif r.status_code == requests.codes.ok: try: # UNICODE - JSON object r = r.json() - self.logMsg("====== 200 Success ======", 1) - self.logMsg("Response: %s" % r, 1) + log("====== 200 Success ======", 1) + log("Response: %s" % r, 1) return r except: if r.headers.get('content-type') != "text/html": - self.logMsg("Unable to convert the response for: %s" % url, 1) + log("Unable to convert the response for: %s" % url, 1) else: r.raise_for_status() @@ -238,8 +236,8 @@ class ConnectUtils(): pass except requests.exceptions.ConnectTimeout as e: - self.logMsg("Server timeout at: %s" % url, 0) - self.logMsg(e, 1) + log("Server timeout at: %s" % url, 0) + log(e, 1) except requests.exceptions.HTTPError as e: @@ -255,11 +253,11 @@ class ConnectUtils(): pass except requests.exceptions.SSLError as e: - self.logMsg("Invalid SSL certificate for: %s" % url, 0) - self.logMsg(e, 1) + log("Invalid SSL certificate for: %s" % url, 0) + log(e, 1) except requests.exceptions.RequestException as e: - self.logMsg("Unknown error connecting to: %s" % url, 0) - self.logMsg(e, 1) + log("Unknown error connecting to: %s" % url, 0) + log(e, 1) - return default_link + return default_link \ No newline at end of file diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index a74ee6f2..ea55e7d1 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -9,14 +9,15 @@ import logging import xbmc import xbmcgui -import utils import clientinfo +from utils import Logging, window, settings ################################################################################################## # Disable requests logging -from requests.packages.urllib3.exceptions import InsecureRequestWarning +from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) +requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) #logging.getLogger('requests').setLevel(logging.WARNING) ################################################################################################## @@ -36,40 +37,38 @@ class DownloadUtils(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def setUsername(self, username): # Reserved for userclient only self.username = username - self.logMsg("Set username: %s" % username, 2) + log("Set username: %s" % username, 2) def setUserId(self, userId): # Reserved for userclient only self.userId = userId - self.logMsg("Set userId: %s" % userId, 2) + log("Set userId: %s" % userId, 2) def setServer(self, server): # Reserved for userclient only self.server = server - self.logMsg("Set server: %s" % server, 2) + log("Set server: %s" % server, 2) def setToken(self, token): # Reserved for userclient only self.token = token - self.logMsg("Set token: %s" % token, 2) + log("Set token: %s" % token, 2) def setSSL(self, ssl, sslclient): # Reserved for userclient only self.sslverify = ssl self.sslclient = sslclient - self.logMsg("Verify SSL host certificate: %s" % ssl, 2) - self.logMsg("SSL client side certificate: %s" % sslclient, 2) + log("Verify SSL host certificate: %s" % ssl, 2) + log("SSL client side certificate: %s" % sslclient, 2) def postCapabilities(self, deviceId): @@ -94,11 +93,11 @@ class DownloadUtils(): ) } - self.logMsg("Capabilities URL: %s" % url, 2) - self.logMsg("Postdata: %s" % data, 2) + log("Capabilities URL: %s" % url, 2) + log("Postdata: %s" % data, 2) self.downloadUrl(url, postBody=data, action_type="POST") - self.logMsg("Posted capabilities to %s" % self.server, 2) + log("Posted capabilities to %s" % self.server, 2) # Attempt at getting sessionId url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId @@ -107,20 +106,19 @@ class DownloadUtils(): sessionId = result[0]['Id'] except (KeyError, TypeError): - self.logMsg("Failed to retrieve sessionId.", 1) + log("Failed to retrieve sessionId.", 1) else: - self.logMsg("Session: %s" % result, 2) - self.logMsg("SessionId: %s" % sessionId, 1) - utils.window('emby_sessionId', value=sessionId) + log("Session: %s" % result, 2) + log("SessionId: %s" % sessionId, 1) + window('emby_sessionId', value=sessionId) # Post any permanent additional users - additionalUsers = utils.settings('additionalUsers') + additionalUsers = settings('additionalUsers') if additionalUsers: additionalUsers = additionalUsers.split(',') - self.logMsg( - "List of permanent users added to the session: %s" + log("List of permanent users added to the session: %s" % additionalUsers, 1) # Get the user list from server to get the userId @@ -158,7 +156,7 @@ class DownloadUtils(): if self.sslclient is not None: verify = self.sslclient except: - self.logMsg("Could not load SSL settings.", 1) + log("Could not load SSL settings.", 1) # Start session self.s = requests.Session() @@ -168,18 +166,18 @@ class DownloadUtils(): self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1)) - self.logMsg("Requests session started on: %s" % self.server, 1) + log("Requests session started on: %s" % self.server, 1) def stopSession(self): try: self.s.close() except: - self.logMsg("Requests session could not be terminated.", 1) + log("Requests session could not be terminated.", 1) def getHeader(self, authenticate=True): deviceName = self.clientInfo.getDeviceName() - deviceName = utils.normalize_string(deviceName.encode('utf-8')) + deviceName = deviceName.encode('utf-8') deviceId = self.clientInfo.getDeviceId() version = self.clientInfo.getVersion() @@ -195,7 +193,7 @@ class DownloadUtils(): 'Accept-Charset': 'UTF-8,*', 'Authorization': auth } - self.logMsg("Header: %s" % header, 2) + log("Header: %s" % header, 2) else: userId = self.userId @@ -212,19 +210,20 @@ class DownloadUtils(): 'Authorization': auth, 'X-MediaBrowser-Token': token } - self.logMsg("Header: %s" % header, 2) + log("Header: %s" % header, 2) return header - def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True): + def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, + authenticate=True): - self.logMsg("=== ENTER downloadUrl ===", 2) + log("=== ENTER downloadUrl ===", 2) default_link = "" try: # If user is authenticated - if (authenticate): + if authenticate: # Get requests session try: s = self.s @@ -243,18 +242,18 @@ class DownloadUtils(): except AttributeError: # request session does not exists # Get user information - self.userId = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userId) - self.token = utils.window('emby_accessToken%s' % self.userId) + self.userId = window('emby_currUser') + self.server = window('emby_server%s' % self.userId) + self.token = window('emby_accessToken%s' % self.userId) header = self.getHeader() verifyssl = False cert = None # IF user enables ssl verification - if utils.settings('sslverify') == "true": + if settings('sslverify') == "true": verifyssl = True - if utils.settings('sslcert') != "None": - verifyssl = utils.settings('sslcert') + if settings('sslcert') != "None": + verifyssl = settings('sslcert') # Replace for the real values url = url.replace("{server}", self.server) @@ -314,23 +313,23 @@ class DownloadUtils(): verify=verifyssl) ##### THE RESPONSE ##### - self.logMsg(r.url, 2) + log(r.url, 2) if r.status_code == 204: # No body in the response - self.logMsg("====== 204 Success ======", 2) + log("====== 204 Success ======", 2) elif r.status_code == requests.codes.ok: try: # UNICODE - JSON object r = r.json() - self.logMsg("====== 200 Success ======", 2) - self.logMsg("Response: %s" % r, 2) + log("====== 200 Success ======", 2) + log("Response: %s" % r, 2) return r except: if r.headers.get('content-type') != "text/html": - self.logMsg("Unable to convert the response for: %s" % url, 1) + log("Unable to convert the response for: %s" % url, 1) else: r.raise_for_status() @@ -338,26 +337,26 @@ class DownloadUtils(): except requests.exceptions.ConnectionError as e: # Make the addon aware of status - if utils.window('emby_online') != "false": - self.logMsg("Server unreachable at: %s" % url, 0) - self.logMsg(e, 2) - utils.window('emby_online', value="false") + if window('emby_online') != "false": + log("Server unreachable at: %s" % url, 0) + log(e, 2) + window('emby_online', value="false") except requests.exceptions.ConnectTimeout as e: - self.logMsg("Server timeout at: %s" % url, 0) - self.logMsg(e, 1) + log("Server timeout at: %s" % url, 0) + log(e, 1) except requests.exceptions.HTTPError as e: if r.status_code == 401: # Unauthorized - status = utils.window('emby_serverStatus') + status = window('emby_serverStatus') if 'X-Application-Error-Code' in r.headers: # Emby server errors if r.headers['X-Application-Error-Code'] == "ParentalControl": # Parental control - access restricted - utils.window('emby_serverStatus', value="restricted") + window('emby_serverStatus', value="restricted") xbmcgui.Dialog().notification( heading="Emby server", message="Access restricted.", @@ -371,8 +370,8 @@ class DownloadUtils(): elif status not in ("401", "Auth"): # Tell userclient token has been revoked. - utils.window('emby_serverStatus', value="401") - self.logMsg("HTTP Error: %s" % e, 0) + window('emby_serverStatus', value="401") + log("HTTP Error: %s" % e, 0) xbmcgui.Dialog().notification( heading="Error connecting", message="Unauthorized.", @@ -387,11 +386,11 @@ class DownloadUtils(): pass except requests.exceptions.SSLError as e: - self.logMsg("Invalid SSL certificate for: %s" % url, 0) - self.logMsg(e, 1) + log("Invalid SSL certificate for: %s" % url, 0) + log(e, 1) except requests.exceptions.RequestException as e: - self.logMsg("Unknown error connecting to: %s" % url, 0) - self.logMsg(e, 1) + log("Unknown error connecting to: %s" % url, 0) + log(e, 1) return default_link \ No newline at end of file diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py index 626be481..fdf63d63 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -1,8 +1,14 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + import threading -import utils -import xbmc import requests +from utils import Logging + +################################################################################################# + class image_cache_thread(threading.Thread): urlToProcess = None @@ -13,28 +19,32 @@ class image_cache_thread(threading.Thread): xbmc_username = "" xbmc_password = "" + def __init__(self): - self.monitor = xbmc.Monitor() + + global log + log = Logging(self.__class__.__name__).log + threading.Thread.__init__(self) - - def logMsg(self, msg, lvl=1): - className = self.__class__.__name__ - utils.logMsg("%s" % className, msg, lvl) + def setUrl(self, url): + self.urlToProcess = url def setHost(self, host, port): + self.xbmc_host = host self.xbmc_port = port def setAuth(self, user, pwd): + self.xbmc_username = user self.xbmc_password = pwd def run(self): - self.logMsg("Image Caching Thread Processing : " + self.urlToProcess, 2) + log("Image Caching Thread Processing: %s" % self.urlToProcess, 2) try: response = requests.head( @@ -46,7 +56,5 @@ class image_cache_thread(threading.Thread): # We don't need the result except: pass - self.logMsg("Image Caching Thread Exited", 2) - - self.isFinished = True - \ No newline at end of file + log("Image Caching Thread Exited", 2) + self.isFinished = True \ No newline at end of file diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 7bf0dbb9..4b1f63f3 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -9,10 +9,10 @@ import xbmc import xbmcgui import xbmcaddon -import utils import clientinfo import downloadutils import userclient +from utils import Logging, settings, language as lang, passwordsXML ################################################################################################# @@ -22,74 +22,67 @@ class InitialSetup(): def __init__(self): - self.addon = xbmcaddon.Addon() - self.__language__ = self.addon.getLocalizedString + global log + log = Logging(self.__class__.__name__).log self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() self.addonId = self.clientInfo.getAddonId() self.doUtils = downloadutils.DownloadUtils() self.userClient = userclient.UserClient() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) def setup(self): # Check server, user, direct paths, music, direct stream if not direct path. - string = self.__language__ addonId = self.addonId ##### SERVER INFO ##### - self.logMsg("Initial setup called.", 2) + log("Initial setup called.", 2) server = self.userClient.getServer() if server: - self.logMsg("Server is already set.", 2) + log("Server is already set.", 2) return - self.logMsg("Looking for server...", 2) + log("Looking for server...", 2) server = self.getServerDetails() - self.logMsg("Found: %s" % server, 2) + log("Found: %s" % server, 2) try: prefix, ip, port = server.replace("/", "").split(":") except: # Failed to retrieve server information - self.logMsg("getServerDetails failed.", 1) + log("getServerDetails failed.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return else: server_confirm = xbmcgui.Dialog().yesno( heading="Emby for Kodi", line1="Proceed with the following server?", - line2="%s %s" % (string(30169), server)) + line2="%s %s" % (lang(30169), server)) if server_confirm: # Correct server found - self.logMsg("Server is selected. Saving the information.", 1) - utils.settings('ipaddress', value=ip) - utils.settings('port', value=port) + log("Server is selected. Saving the information.", 1) + settings('ipaddress', value=ip) + settings('port', value=port) if prefix == "https": - utils.settings('https', value="true") + settings('https', value="true") else: # User selected no or cancelled the dialog - self.logMsg("No server selected.", 1) + log("No server selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return ##### USER INFO ##### - self.logMsg("Getting user list.", 1) + log("Getting user list.", 1) url = "%s/emby/Users/Public?format=json" % server result = self.doUtils.downloadUrl(url, authenticate=False) if result == "": - self.logMsg("Unable to connect to %s" % server, 1) + log("Unable to connect to %s" % server, 1) return - self.logMsg("Response: %s" % result, 2) + log("Response: %s" % result, 2) # Process the list of users usernames = [] users_hasPassword = [] @@ -103,14 +96,14 @@ class InitialSetup(): name = "%s (secure)" % name users_hasPassword.append(name) - self.logMsg("Presenting user list: %s" % users_hasPassword, 1) - user_select = xbmcgui.Dialog().select(string(30200), users_hasPassword) + log("Presenting user list: %s" % users_hasPassword, 1) + user_select = xbmcgui.Dialog().select(lang(30200), users_hasPassword) if user_select > -1: selected_user = usernames[user_select] - self.logMsg("Selected user: %s" % selected_user, 1) - utils.settings('username', value=selected_user) + log("Selected user: %s" % selected_user, 1) + settings('username', value=selected_user) else: - self.logMsg("No user selected.", 1) + log("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) ##### ADDITIONAL PROMPTS ##### @@ -126,8 +119,8 @@ class InitialSetup(): nolabel="Addon (Default)", yeslabel="Native (Direct Paths)") if directPaths: - self.logMsg("User opted to use direct paths.", 1) - utils.settings('useDirectPaths', value="1") + log("User opted to use direct paths.", 1) + settings('useDirectPaths', value="1") # ask for credentials credentials = dialog.yesno( @@ -138,15 +131,15 @@ class InitialSetup(): "during the initial scan of your content if Kodi can't " "locate your content.")) if credentials: - self.logMsg("Presenting network credentials dialog.", 1) - utils.passwordsXML() + log("Presenting network credentials dialog.", 1) + passwordsXML() musicDisabled = dialog.yesno( heading="Music Library", line1="Disable Emby music library?") if musicDisabled: - self.logMsg("User opted to disable Emby music library.", 1) - utils.settings('enableMusic', value="false") + log("User opted to disable Emby music library.", 1) + settings('enableMusic', value="false") else: # Only prompt if the user didn't select direct paths for videos if not directPaths: @@ -157,12 +150,12 @@ class InitialSetup(): "this option only if you plan on listening " "to music outside of your network.")) if musicAccess: - self.logMsg("User opted to direct stream music.", 1) - utils.settings('streamMusic', value="true") + log("User opted to direct stream music.", 1) + settings('streamMusic', value="true") def getServerDetails(self): - self.logMsg("Getting Server Details from Network", 1) + log("Getting Server Details from Network", 1) MULTI_GROUP = ("", 7359) MESSAGE = "who is EmbyServer?" @@ -176,15 +169,15 @@ class InitialSetup(): sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) - self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2) - self.logMsg("Sending UDP Data: %s" % MESSAGE, 2) + log("MultiGroup : %s" % str(MULTI_GROUP), 2) + log("Sending UDP Data: %s" % MESSAGE, 2) sock.sendto(MESSAGE, MULTI_GROUP) try: data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes - self.logMsg("Received Response: %s" % data) + log("Received Response: %s" % data) except: - self.logMsg("No UDP Response") + log("No UDP Response") return None else: # Get the address diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 6c3dd8b1..86981e7b 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -7,7 +7,7 @@ import xbmc import api import artwork import clientinfo -import utils +from utils import Logging ################################################################################################## @@ -19,16 +19,14 @@ class Kodidb_Functions(): def __init__(self, cursor): + global log + log = Logging(self.__class__.__name__).log + self.cursor = cursor self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() self.artwork = artwork.Artwork() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) def addPath(self, path): @@ -153,7 +151,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(country_id, name) values(?, ?)" self.cursor.execute(query, (country_id, country)) - self.logMsg("Add country to media, processing: %s" % country, 2) + log("Add country to media, processing: %s" % country, 2) finally: # Assign country to content query = ( @@ -187,7 +185,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(idCountry, strCountry) values(?, ?)" self.cursor.execute(query, (idCountry, country)) - self.logMsg("Add country to media, processing: %s" % country, 2) + log("Add country to media, processing: %s" % country, 2) finally: # Only movies have a country field @@ -232,7 +230,7 @@ class Kodidb_Functions(): query = "INSERT INTO actor(actor_id, name) values(?, ?)" self.cursor.execute(query, (actorid, name)) - self.logMsg("Add people to media, processing: %s" % name, 2) + log("Add people to media, processing: %s" % name, 2) finally: # Link person to content @@ -302,7 +300,7 @@ class Kodidb_Functions(): query = "INSERT INTO actors(idActor, strActor) values(?, ?)" self.cursor.execute(query, (actorid, name)) - self.logMsg("Add people to media, processing: %s" % name, 2) + log("Add people to media, processing: %s" % name, 2) finally: # Link person to content @@ -462,7 +460,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(genre_id, name) values(?, ?)" self.cursor.execute(query, (genre_id, genre)) - self.logMsg("Add Genres to media, processing: %s" % genre, 2) + log("Add Genres to media, processing: %s" % genre, 2) finally: # Assign genre to item @@ -507,7 +505,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" self.cursor.execute(query, (idGenre, genre)) - self.logMsg("Add Genres to media, processing: %s" % genre, 2) + log("Add Genres to media, processing: %s" % genre, 2) finally: # Assign genre to item @@ -566,7 +564,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(studio_id, name) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - self.logMsg("Add Studios to media, processing: %s" % studio, 2) + log("Add Studios to media, processing: %s" % studio, 2) finally: # Assign studio to item query = ( @@ -597,7 +595,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - self.logMsg("Add Studios to media, processing: %s" % studio, 2) + log("Add Studios to media, processing: %s" % studio, 2) finally: # Assign studio to item if "movie" in mediatype: @@ -728,7 +726,7 @@ class Kodidb_Functions(): self.cursor.execute(query, (kodiid, mediatype)) # Add tags - self.logMsg("Adding Tags: %s" % tags, 2) + log("Adding Tags: %s" % tags, 2) for tag in tags: self.addTag(kodiid, tag, mediatype) @@ -750,7 +748,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag, because it does not exist tag_id = self.createTag(tag) - self.logMsg("Adding tag: %s" % tag, 2) + log("Adding tag: %s" % tag, 2) finally: # Assign tag to item @@ -779,7 +777,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag tag_id = self.createTag(tag) - self.logMsg("Adding tag: %s" % tag, 2) + log("Adding tag: %s" % tag, 2) finally: # Assign tag to item @@ -815,7 +813,7 @@ class Kodidb_Functions(): query = "INSERT INTO tag(tag_id, name) values(?, ?)" self.cursor.execute(query, (tag_id, name)) - self.logMsg("Create tag_id: %s name: %s" % (tag_id, name), 2) + log("Create tag_id: %s name: %s" % (tag_id, name), 2) else: # Kodi Helix query = ' '.join(( @@ -835,13 +833,13 @@ class Kodidb_Functions(): query = "INSERT INTO tag(idTag, strTag) values(?, ?)" self.cursor.execute(query, (tag_id, name)) - self.logMsg("Create idTag: %s name: %s" % (tag_id, name), 2) + log("Create idTag: %s name: %s" % (tag_id, name), 2) return tag_id def updateTag(self, oldtag, newtag, kodiid, mediatype): - self.logMsg("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2) + log("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2) if self.kodiversion in (15, 16, 17): # Kodi Isengard, Jarvis, Krypton @@ -858,7 +856,7 @@ class Kodidb_Functions(): except Exception as e: # The new tag we are going to apply already exists for this item # delete current tag instead - self.logMsg("Exception: %s" % e, 1) + log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM tag_link", @@ -882,7 +880,7 @@ class Kodidb_Functions(): except Exception as e: # The new tag we are going to apply already exists for this item # delete current tag instead - self.logMsg("Exception: %s" % e, 1) + log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM taglinks", @@ -943,7 +941,7 @@ class Kodidb_Functions(): def createBoxset(self, boxsetname): - self.logMsg("Adding boxset: %s" % boxsetname, 2) + log("Adding boxset: %s" % boxsetname, 2) query = ' '.join(( "SELECT idSet", diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index ea1a4f17..cc217de9 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -11,7 +11,7 @@ import clientinfo import downloadutils import embydb_functions as embydb import playbackutils as pbutils -import utils +from utils import Logging, window, settings, kodiSQL ################################################################################################# @@ -21,27 +21,25 @@ class KodiMonitor(xbmc.Monitor): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils() - self.logMsg("Kodi monitor started.", 1) - - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) + log("Kodi monitor started.", 1) def onScanStarted(self, library): - self.logMsg("Kodi library scan %s running." % library, 2) + log("Kodi library scan %s running." % library, 2) if library == "video": - utils.window('emby_kodiScan', value="true") + window('emby_kodiScan', value="true") def onScanFinished(self, library): - self.logMsg("Kodi library scan %s finished." % library, 2) + log("Kodi library scan %s finished." % library, 2) if library == "video": - utils.window('emby_kodiScan', clear=True) + window('emby_kodiScan', clear=True) def onSettingsChanged(self): # Monitor emby settings @@ -50,7 +48,7 @@ class KodiMonitor(xbmc.Monitor): '''currentPath = utils.settings('useDirectPaths') if utils.window('emby_pluginpath') != currentPath: # Plugin path value changed. Offer to reset - self.logMsg("Changed to playback mode detected", 1) + log("Changed to playback mode detected", 1) utils.window('emby_pluginpath', value=currentPath) resp = xbmcgui.Dialog().yesno( heading="Playback mode change detected", @@ -61,17 +59,17 @@ class KodiMonitor(xbmc.Monitor): if resp: utils.reset()''' - currentLog = utils.settings('logLevel') - if utils.window('emby_logLevel') != currentLog: + currentLog = settings('logLevel') + if window('emby_logLevel') != currentLog: # The log level changed, set new prop - self.logMsg("New log level: %s" % currentLog, 1) - utils.window('emby_logLevel', value=currentLog) + log("New log level: %s" % currentLog, 1) + window('emby_logLevel', value=currentLog) def onNotification(self, sender, method, data): doUtils = self.doUtils if method not in ("Playlist.OnAdd"): - self.logMsg("Method: %s Data: %s" % (method, data), 1) + log("Method: %s Data: %s" % (method, data), 1) if data: data = json.loads(data,'utf-8') @@ -84,23 +82,23 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - self.logMsg("Item is invalid for playstate update.", 1) + log("Item is invalid for playstate update.", 1) else: - if ((utils.settings('useDirectPaths') == "1" and not item_type == "song") or - (item_type == "song" and utils.settings('enableMusic') == "true")): + if ((settings('useDirectPaths') == "1" and not item_type == "song") or + (item_type == "song" and settings('enableMusic') == "true")): # Set up properties for player - embyconn = utils.kodiSQL('emby') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type) try: itemid = emby_dbitem[0] except TypeError: - self.logMsg("No kodiid returned.", 1) + log("No kodiid returned.", 1) else: url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid result = doUtils.downloadUrl(url) - self.logMsg("Item: %s" % result, 2) + log("Item: %s" % result, 2) playurl = None count = 0 @@ -114,12 +112,10 @@ class KodiMonitor(xbmc.Monitor): listItem = xbmcgui.ListItem() playback = pbutils.PlaybackUtils(result) - if item_type == "song" and utils.settings('streamMusic') == "true": - utils.window('emby_%s.playmethod' % playurl, - value="DirectStream") + if item_type == "song" and settings('streamMusic') == "true": + window('emby_%s.playmethod' % playurl, value="DirectStream") else: - utils.window('emby_%s.playmethod' % playurl, - value="DirectPlay") + window('emby_%s.playmethod' % playurl, value="DirectPlay") # Set properties for player.py playback.setProperties(playurl, listItem) finally: @@ -134,31 +130,31 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - self.logMsg("Item is invalid for playstate update.", 1) + log("Item is invalid for playstate update.", 1) else: # Send notification to the server. - embyconn = utils.kodiSQL('emby') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type) try: itemid = emby_dbitem[0] except TypeError: - self.logMsg("Could not find itemid in emby database.", 1) + log("Could not find itemid in emby database.", 1) else: # Stop from manually marking as watched unwatched, with actual playback. - if utils.window('emby_skipWatched%s' % itemid) == "true": + if window('emby_skipWatched%s' % itemid) == "true": # property is set in player.py - utils.window('emby_skipWatched%s' % itemid, clear=True) + window('emby_skipWatched%s' % itemid, clear=True) else: # notify the server url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid if playcount != 0: doUtils.downloadUrl(url, action_type="POST") - self.logMsg("Mark as watched for itemid: %s" % itemid, 1) + log("Mark as watched for itemid: %s" % itemid, 1) else: doUtils.downloadUrl(url, action_type="DELETE") - self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1) + log("Mark as unwatched for itemid: %s" % itemid, 1) finally: embycursor.close() @@ -172,7 +168,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = data['id'] type = data['type'] except (KeyError, TypeError): - self.logMsg("Item is invalid for emby deletion.", 1) + log("Item is invalid for emby deletion.", 1) else: # Send the delete action to the server. embyconn = utils.kodiSQL('emby') @@ -182,19 +178,19 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - self.logMsg("Could not find itemid in emby database.", 1) + log("Could not find itemid in emby database.", 1) else: if utils.settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1="Delete file on Emby Server?") if not resp: - self.logMsg("User skipped deletion.", 1) + log("User skipped deletion.", 1) embycursor.close() return url = "{server}/emby/Items/%s?format=json" % itemid - self.logMsg("Deleting request: %s" % itemid) + log("Deleting request: %s" % itemid) doUtils.downloadUrl(url, action_type="DELETE") finally: embycursor.close()''' @@ -203,13 +199,13 @@ class KodiMonitor(xbmc.Monitor): elif method == "System.OnWake": # Allow network to wake up xbmc.sleep(10000) - utils.window('emby_onWake', value="true") + window('emby_onWake', value="true") elif method == "GUI.OnScreensaverDeactivated": - if utils.settings('dbSyncScreensaver') == "true": + if settings('dbSyncScreensaver') == "true": xbmc.sleep(5000); - utils.window('emby_onWake', value="true") + window('emby_onWake', value="true") elif method == "Playlist.OnClear": diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 1335f585..6d047148 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -20,6 +20,7 @@ import kodidb_functions as kodidb import read_embyserver as embyserver import userclient import videonodes +from utils import Logging, window, settings, language as lang ################################################################################################## @@ -42,6 +43,9 @@ class LibrarySync(threading.Thread): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() @@ -54,26 +58,20 @@ class LibrarySync(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 progressDialog(self, title, forced=False): dialog = None - if utils.settings('dbSyncIndicator') == "true" or forced: + if settings('dbSyncIndicator') == "true" or forced: dialog = xbmcgui.DialogProgressBG() dialog.create("Emby for Kodi", title) - self.logMsg("Show progress dialog: %s" % title, 2) + log("Show progress dialog: %s" % title, 2) return dialog def startSync(self): - settings = utils.settings # Run at start up - optional to use the server plugin if settings('SyncInstallRunDone') == "true": @@ -88,7 +86,7 @@ class LibrarySync(threading.Thread): for plugin in result: if plugin['Name'] == "Emby.Kodi Sync Queue": - self.logMsg("Found server plugin.", 2) + log("Found server plugin.", 2) completed = self.fastSync() break @@ -103,37 +101,31 @@ class LibrarySync(threading.Thread): def fastSync(self): - lastSync = utils.settings('LastIncrementalSync') + lastSync = settings('LastIncrementalSync') if not lastSync: lastSync = "2010-01-01T00:00:00Z" - lastSyncTime = utils.convertdate(lastSync) - self.logMsg("Last sync run: %s" % lastSyncTime, 1) + lastSyncTime = utils.convertDate(lastSync) + log("Last sync run: %s" % lastSyncTime, 1) # get server RetentionDateTime result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") - retention_time = "2010-01-01T00:00:00Z" - if result and result.get('RetentionDateTime'): - retention_time = result['RetentionDateTime'] - - #Try/except equivalent - ''' try: retention_time = result['RetentionDateTime'] except (TypeError, KeyError): retention_time = "2010-01-01T00:00:00Z" - ''' - retention_time = utils.convertdate(retention_time) - self.logMsg("RetentionDateTime: %s" % retention_time, 1) + retention_time = utils.convertDate(retention_time) + log("RetentionDateTime: %s" % retention_time, 1) # if last sync before retention time do a full sync if retention_time > lastSyncTime: - self.logMsg("Fast sync server retention insufficient, fall back to full sync", 1) + log("Fast sync server retention insufficient, fall back to full sync", 1) return False params = {'LastUpdateDT': lastSync} - result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json", parameters=params) + url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json" + result = self.doUtils(url, parameters=params) try: processlist = { @@ -145,11 +137,11 @@ class LibrarySync(threading.Thread): } except (KeyError, TypeError): - self.logMsg("Failed to retrieve latest updates using fast sync.", 1) + log("Failed to retrieve latest updates using fast sync.", 1) return False else: - self.logMsg("Fast sync changes: %s" % result, 1) + log("Fast sync changes: %s" % result, 1) for action in processlist: self.triage_items(action, processlist[action]) @@ -163,60 +155,55 @@ class LibrarySync(threading.Thread): result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") try: # datetime fails when used more than once, TypeError server_time = result['ServerDateTime'] - server_time = utils.convertdate(server_time) + server_time = utils.convertDate(server_time) except Exception as e: # If the server plugin is not installed or an error happened. - self.logMsg("An exception occurred: %s" % e, 1) + log("An exception occurred: %s" % e, 1) time_now = datetime.utcnow()-timedelta(minutes=overlap) lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("New sync time: client time -%s min: %s" % (overlap, lastSync), 1) + log("New sync time: client time -%s min: %s" % (overlap, lastSync), 1) else: lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1) + log("New sync time: server time -%s min: %s" % (overlap, lastSync), 1) finally: - utils.settings('LastIncrementalSync', value=lastSync) + settings('LastIncrementalSync', value=lastSync) def shouldStop(self): # Checkpoint during the syncing process if self.monitor.abortRequested(): return True - elif utils.window('emby_shouldStop') == "true": + elif window('emby_shouldStop') == "true": return True else: # Keep going return False def dbCommit(self, connection): - - window = utils.window # Central commit, verifies if Kodi database update is running kodidb_scan = window('emby_kodiScan') == "true" while kodidb_scan: - self.logMsg("Kodi scan is running. Waiting...", 1) + log("Kodi scan is running. Waiting...", 1) kodidb_scan = window('emby_kodiScan') == "true" if self.shouldStop(): - self.logMsg("Commit unsuccessful. Sync terminated.", 1) + log("Commit unsuccessful. Sync terminated.", 1) break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit - self.logMsg("Commit unsuccessful.", 1) + log("Commit unsuccessful.", 1) break else: connection.commit() - self.logMsg("Commit successful.", 1) + log("Commit successful.", 1) def fullSync(self, manualrun=False, repair=False, forceddialog=False): - - window = utils.window - settings = utils.settings # Only run once when first setting up. Can be run manually. - music_enabled = utils.settings('enableMusic') == "true" + music_enabled = settings('enableMusic') == "true" xbmc.executebuiltin('InhibitIdleShutdown(true)') screensaver = utils.getScreensaver() @@ -284,7 +271,7 @@ class LibrarySync(threading.Thread): self.dbCommit(kodiconn) embyconn.commit() elapsedTime = datetime.now() - startTime - self.logMsg("SyncDatabase (finished %s in: %s)" + log("SyncDatabase (finished %s in: %s)" % (itemtype, str(elapsedTime).split('.')[0]), 1) else: # Close the Kodi cursor @@ -312,7 +299,7 @@ class LibrarySync(threading.Thread): musicconn.commit() embyconn.commit() elapsedTime = datetime.now() - startTime - self.logMsg("SyncDatabase (finished music in: %s)" + log("SyncDatabase (finished music in: %s)" % (str(elapsedTime).split('.')[0]), 1) musiccursor.close() @@ -335,7 +322,7 @@ class LibrarySync(threading.Thread): xbmcgui.Dialog().notification( heading="Emby for Kodi", message="%s %s %s" % - (message, utils.language(33025), str(elapsedtotal).split('.')[0]), + (message, lang(33025), str(elapsedtotal).split('.')[0]), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) return True @@ -378,7 +365,7 @@ class LibrarySync(threading.Thread): if view['type'] == "mixed": sorted_views.append(view['name']) sorted_views.append(view['name']) - self.logMsg("Sorted views: %s" % sorted_views, 1) + log("Sorted views: %s" % sorted_views, 1) # total nodes for window properties self.vnodes.clearProperties() @@ -415,7 +402,8 @@ class LibrarySync(threading.Thread): 'Limit': 1, 'IncludeItemTypes': emby_mediatypes[mediatype] } # Get one item from server using the folderid - 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) try: verifyitem = result['Items'][0]['Id'] except (TypeError, IndexError): @@ -430,14 +418,14 @@ class LibrarySync(threading.Thread): # Take the userview, and validate the item belong to the view if self.emby.verifyView(grouped_view['Id'], verifyitem): # Take the name of the userview - self.logMsg("Found corresponding view: %s %s" + log("Found corresponding view: %s %s" % (grouped_view['Name'], grouped_view['Id']), 1) foldername = grouped_view['Name'] break else: # Unable to find a match, add the name to our sorted_view list sorted_views.append(foldername) - self.logMsg("Couldn't find corresponding grouped view: %s" % sorted_views, 1) + log("Couldn't find corresponding grouped view: %s" % sorted_views, 1) # Failsafe try: @@ -453,7 +441,7 @@ class LibrarySync(threading.Thread): current_tagid = view[2] except TypeError: - self.logMsg("Creating viewid: %s in Emby database." % folderid, 1) + log("Creating viewid: %s in Emby database." % folderid, 1) tagid = kodi_db.createTag(foldername) # Create playlist for the video library if (foldername not in playlists and @@ -472,7 +460,7 @@ class LibrarySync(threading.Thread): emby_db.addView(folderid, foldername, viewtype, tagid) else: - self.logMsg(' '.join(( + log(' '.join(( "Found viewid: %s" % folderid, "viewname: %s" % current_viewname, @@ -488,7 +476,7 @@ class LibrarySync(threading.Thread): # View was modified, update with latest info if current_viewname != foldername: - self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1) + log("viewid: %s new viewname: %s" % (folderid, foldername), 1) tagid = kodi_db.createTag(foldername) # Update view with new info @@ -556,20 +544,19 @@ class LibrarySync(threading.Thread): utils.window('Emby.nodes.total', str(totalnodes)) # Remove any old referenced views - self.logMsg("Removing views: %s" % current_views, 1) + log("Removing views: %s" % current_views, 1) for view in current_views: emby_db.removeView(view) def movies(self, embycursor, kodicursor, pdialog): - lang = utils.language # Get movies from emby emby_db = embydb.Embydb_Functions(embycursor) movies = itemtypes.Movies(embycursor, kodicursor) views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) ##### PROCESS MOVIES ##### for view in views: @@ -604,7 +591,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_update(embymovie, view['name'], view['id']) else: - self.logMsg("Movies finished.", 2) + log("Movies finished.", 2) ##### PROCESS BOXSETS ##### @@ -631,7 +618,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_updateBoxset(boxset) else: - self.logMsg("Boxsets finished.", 2) + log("Boxsets finished.", 2) return True @@ -642,7 +629,7 @@ class LibrarySync(threading.Thread): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) for view in views: @@ -656,7 +643,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33019), viewName)) + message="%s %s..." % (lang(33019), viewName)) # Initial or repair sync all_embymvideos = self.emby.getMusicVideos(viewId, dialog=pdialog) @@ -679,7 +666,7 @@ class LibrarySync(threading.Thread): count += 1 mvideos.add_update(embymvideo, viewName, viewId) else: - self.logMsg("MusicVideos finished.", 2) + log("MusicVideos finished.", 2) return True @@ -691,7 +678,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) for view in views: @@ -702,7 +689,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33020), view['name'])) + message="%s %s..." % (lang(33020), view['name'])) all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog) total = all_embytvshows['TotalRecordCount'] @@ -737,7 +724,7 @@ class LibrarySync(threading.Thread): pdialog.update(percentage, message="%s - %s" % (title, episodetitle)) tvshows.add_updateEpisode(episode) else: - self.logMsg("TVShows finished.", 2) + log("TVShows finished.", 2) return True @@ -757,7 +744,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33021), itemtype)) + message="%s %s..." % (lang(33021), itemtype)) all_embyitems = process[itemtype][0](dialog=pdialog) total = all_embyitems['TotalRecordCount'] @@ -778,7 +765,7 @@ class LibrarySync(threading.Thread): process[itemtype][1](embyitem) else: - self.logMsg("%s finished." % itemtype, 2) + log("%s finished." % itemtype, 2) return True @@ -799,7 +786,7 @@ class LibrarySync(threading.Thread): itemids.append(item['ItemId']) items = itemids - self.logMsg("Queue %s: %s" % (process, items), 1) + log("Queue %s: %s" % (process, items), 1) processlist[process].extend(items) def incrementalSync(self): @@ -833,7 +820,7 @@ class LibrarySync(threading.Thread): } for process_type in ['added', 'update', 'userdata', 'remove']: - if process[process_type] and utils.window('emby_kodiScan') != "true": + if process[process_type] and window('emby_kodiScan') != "true": listItems = list(process[process_type]) del process[process_type][:] # Reset class list @@ -871,7 +858,7 @@ class LibrarySync(threading.Thread): if update_embydb: update_embydb = False - self.logMsg("Updating emby database.", 1) + log("Updating emby database.", 1) embyconn.commit() self.saveLastSync() @@ -880,8 +867,8 @@ class LibrarySync(threading.Thread): self.forceLibraryUpdate = False self.dbCommit(kodiconn) - self.logMsg("Updating video library.", 1) - utils.window('emby_kodiScan', value="true") + log("Updating video library.", 1) + window('emby_kodiScan', value="true") xbmc.executebuiltin('UpdateLibrary(video)') if pDialog: @@ -893,7 +880,7 @@ class LibrarySync(threading.Thread): def compareDBVersion(self, current, minimum): # It returns True is database is up to date. False otherwise. - self.logMsg("current: %s minimum: %s" % (current, minimum), 1) + log("current: %s minimum: %s" % (current, minimum), 1) currMajor, currMinor, currPatch = current.split(".") minMajor, minMinor, minPatch = minimum.split(".") @@ -911,7 +898,7 @@ class LibrarySync(threading.Thread): try: self.run_internal() except Exception as e: - utils.window('emby_dbScan', clear=True) + window('emby_dbScan', clear=True) xbmcgui.Dialog().ok( heading="Emby for Kodi", line1=( @@ -922,14 +909,11 @@ class LibrarySync(threading.Thread): def run_internal(self): - lang = utils.language - window = utils.window - settings = utils.settings dialog = xbmcgui.Dialog() startupComplete = False - self.logMsg("---===### Starting LibrarySync ###===---", 0) + log("---===### Starting LibrarySync ###===---", 0) while not self.monitor.abortRequested(): @@ -947,12 +931,12 @@ class LibrarySync(threading.Thread): uptoDate = self.compareDBVersion(currentVersion, minVersion) if not uptoDate: - self.logMsg("Database version out of date: %s minimum version required: %s" + log("Database version out of date: %s minimum version required: %s" % (currentVersion, minVersion), 0) resp = dialog.yesno("Emby for Kodi", lang(33022)) if not resp: - self.logMsg("Database version is out of date! USER IGNORED!", 0) + log("Database version is out of date! USER IGNORED!", 0) dialog.ok("Emby for Kodi", lang(33023)) else: utils.reset() @@ -967,7 +951,7 @@ class LibrarySync(threading.Thread): videoDb = utils.getKodiVideoDBPath() if not xbmcvfs.exists(videoDb): # Database does not exists - self.logMsg( + log( "The current Kodi version is incompatible " "with the Emby for Kodi add-on. Please visit " "https://github.com/MediaBrowser/Emby.Kodi/wiki " @@ -979,12 +963,12 @@ class LibrarySync(threading.Thread): break # Run start up sync - self.logMsg("Database version: %s" % settings('dbCreatedWithVersion'), 0) - self.logMsg("SyncDatabase (started)", 1) + log("Database version: %s" % settings('dbCreatedWithVersion'), 0) + log("SyncDatabase (started)", 1) startTime = datetime.now() librarySync = self.startSync() elapsedTime = datetime.now() - startTime - self.logMsg("SyncDatabase (finished in: %s) %s" + log("SyncDatabase (finished in: %s) %s" % (str(elapsedTime).split('.')[0], librarySync), 1) # Only try the initial sync once per kodi session regardless # This will prevent an infinite loop in case something goes wrong. @@ -999,32 +983,32 @@ class LibrarySync(threading.Thread): # Set in kodimonitor.py window('emby_onWake', clear=True) if window('emby_syncRunning') != "true": - self.logMsg("SyncDatabase onWake (started)", 0) + log("SyncDatabase onWake (started)", 0) librarySync = self.startSync() - self.logMsg("SyncDatabase onWake (finished) %s" % librarySync, 0) + log("SyncDatabase onWake (finished) %s" % librarySync, 0) if self.stop_thread: # Set in service.py - self.logMsg("Service terminated thread.", 2) + log("Service terminated thread.", 2) break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit break - self.logMsg("###===--- LibrarySync Stopped ---===###", 0) + log("###===--- LibrarySync Stopped ---===###", 0) def stopThread(self): self.stop_thread = True - self.logMsg("Ending thread...", 2) + log("Ending thread...", 2) def suspendThread(self): self.suspend_thread = True - self.logMsg("Pausing thread...", 0) + log("Pausing thread...", 0) def resumeThread(self): self.suspend_thread = False - self.logMsg("Resuming thread...", 0) + log("Resuming thread...", 0) class ManualSync(LibrarySync): @@ -1041,14 +1025,13 @@ class ManualSync(LibrarySync): def movies(self, embycursor, kodicursor, pdialog): - lang = utils.language # Get movies from emby emby_db = embydb.Embydb_Functions(embycursor) movies = itemtypes.Movies(embycursor, kodicursor) views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of movies and boxsets in Kodi try: @@ -1095,7 +1078,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1) + log("Movies to update for %s: %s" % (viewName, updatelist), 1) embymovies = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1137,7 +1120,7 @@ class ManualSync(LibrarySync): updatelist.append(itemid) embyboxsets.append(boxset) - self.logMsg("Boxsets to update: %s" % updatelist, 1) + log("Boxsets to update: %s" % updatelist, 1) total = len(updatelist) if pdialog: @@ -1161,13 +1144,13 @@ class ManualSync(LibrarySync): if kodimovie not in all_embymoviesIds: movies.remove(kodimovie) else: - self.logMsg("Movies compare finished.", 1) + log("Movies compare finished.", 1) for boxset in all_kodisets: if boxset not in all_embyboxsetsIds: movies.remove(boxset) else: - self.logMsg("Boxsets compare finished.", 1) + log("Boxsets compare finished.", 1) return True @@ -1178,7 +1161,7 @@ class ManualSync(LibrarySync): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of musicvideos in Kodi try: @@ -1201,7 +1184,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33028), viewName)) + message="%s %s..." % (lang(33028), viewName)) all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog) for embymvideo in all_embymvideos['Items']: @@ -1218,7 +1201,7 @@ class ManualSync(LibrarySync): # Only update if musicvideo is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1) + log("MusicVideos to update for %s: %s" % (viewName, updatelist), 1) embymvideos = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1245,20 +1228,19 @@ class ManualSync(LibrarySync): if kodimvideo not in all_embymvideosIds: mvideos.remove(kodimvideo) else: - self.logMsg("MusicVideos compare finished.", 1) + log("MusicVideos compare finished.", 1) return True def tvshows(self, embycursor, kodicursor, pdialog): - lang = utils.language # Get shows from emby emby_db = embydb.Embydb_Functions(embycursor) tvshows = itemtypes.TVShows(embycursor, kodicursor) views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of tvshows and episodes in Kodi try: @@ -1305,7 +1287,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1) + log("TVShows to update for %s: %s" % (viewName, updatelist), 1) embytvshows = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1349,7 +1331,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1) + log("Episodes to update for %s: %s" % (viewName, updatelist), 1) embyepisodes = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1374,13 +1356,13 @@ class ManualSync(LibrarySync): if koditvshow not in all_embytvshowsIds: tvshows.remove(koditvshow) else: - self.logMsg("TVShows compare finished.", 1) + log("TVShows compare finished.", 1) for kodiepisode in all_kodiepisodes: if kodiepisode not in all_embyepisodesIds: tvshows.remove(kodiepisode) else: - self.logMsg("Episodes compare finished.", 1) + log("Episodes compare finished.", 1) return True @@ -1421,7 +1403,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33031), data_type)) + message="%s %s..." % (lang(33031), data_type)) if data_type != "artists": all_embyitems = process[data_type][0](basic=True, dialog=pdialog) else: @@ -1446,7 +1428,7 @@ class ManualSync(LibrarySync): if all_kodisongs.get(itemid) != API.getChecksum(): # Only update if songs is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("%s to update: %s" % (data_type, updatelist), 1) + log("%s to update: %s" % (data_type, updatelist), 1) embyitems = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1467,15 +1449,15 @@ class ManualSync(LibrarySync): if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: music.remove(kodiartist) else: - self.logMsg("Artist compare finished.", 1) + log("Artist compare finished.", 1) for kodialbum in all_kodialbums: if kodialbum not in all_embyalbumsIds: music.remove(kodialbum) else: - self.logMsg("Albums compare finished.", 1) + log("Albums compare finished.", 1) for kodisong in all_kodisongs: if kodisong not in all_embysongsIds: music.remove(kodisong) else: - self.logMsg("Songs compare finished.", 1) - return True + log("Songs compare finished.", 1) + return True \ No newline at end of file diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index b058c5c5..dd44e830 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -14,20 +14,18 @@ from mutagen import id3 import base64 import read_embyserver as embyserver -import utils +from utils import Logging, window ################################################################################################# # Helper for the music library, intended to fix missing song ID3 tags on Emby - -def logMsg(msg, lvl=1): - utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl) +log = Logging('MusicTools').log def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... if not xbmcvfs.exists(filename): - logMsg( "File does not exist! %s" %(filename), 0) + log("File does not exist! %s" % filename, 0) return (False, "") #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string @@ -104,7 +102,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei elif file_rating is None and not currentvalue: return (emby_rating, comment, False) - logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) + log("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) updateFileRating = False updateEmbyRating = False @@ -171,7 +169,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei if updateEmbyRating and enableexportsongrating: # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: like, favourite, deletelike = getEmbyRatingFromKodiRating(rating) - utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update + window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update emby.updateUserRating(embyid, like, favourite, deletelike) return (rating, comment, hasEmbeddedCover) @@ -183,7 +181,7 @@ def getSongTags(file): hasEmbeddedCover = False isTemp,filename = getRealFileName(file) - logMsg( "getting song ID3 tags for " + filename) + log( "getting song ID3 tags for " + filename) try: ###### FLAC FILES ############# @@ -217,14 +215,14 @@ def getSongTags(file): #POPM rating is 0-255 and needs to be converted to 0-5 range if rating > 5: rating = (rating / 255) * 5 else: - logMsg( "Not supported fileformat or unable to access file: %s" %(filename)) + log( "Not supported fileformat or unable to access file: %s" %(filename)) #the rating must be a round value rating = int(round(rating,0)) except Exception as e: #file in use ? - utils.logMsg("Exception in getSongTags", str(e),0) + log("Exception in getSongTags", str(e),0) rating = None #remove tempfile if needed.... @@ -246,7 +244,7 @@ def updateRatingToFile(rating, file): xbmcvfs.copy(file, tempfile) tempfile = xbmc.translatePath(tempfile).decode("utf-8") - logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) + log( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) if not tempfile: return @@ -263,7 +261,7 @@ def updateRatingToFile(rating, file): audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: - logMsg( "Not supported fileformat: %s" %(tempfile)) + log( "Not supported fileformat: %s" %(tempfile)) #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp #safety check: we check the file size of the temp file before proceeding with overwite of original file @@ -274,14 +272,14 @@ def updateRatingToFile(rating, file): xbmcvfs.delete(file) xbmcvfs.copy(tempfile,file) else: - logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) + log( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) #always delete the tempfile xbmcvfs.delete(tempfile) except Exception as e: #file in use ? - logMsg("Exception in updateRatingToFile %s" %e,0) + log("Exception in updateRatingToFile %s" %e,0) \ No newline at end of file diff --git a/resources/lib/player.py b/resources/lib/player.py index 7f323460..1cc187bb 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -7,11 +7,11 @@ import json import xbmc import xbmcgui -import utils import clientinfo import downloadutils import kodidb_functions as kodidb import websocket_client as wsc +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -28,6 +28,9 @@ class Player(xbmc.Player): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.clientInfo = clientinfo.ClientInfo() @@ -36,20 +39,13 @@ class Player(xbmc.Player): self.ws = wsc.WebSocket_Client() self.xbmcplayer = xbmc.Player() - self.logMsg("Starting playback monitor.", 2) - - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) + log("Starting playback monitor.", 2) def GetPlayStats(self): return self.playStats def onPlayBackStarted(self): - - window = utils.window # Will be called when xbmc starts playing a file self.stopAll() @@ -67,7 +63,7 @@ class Player(xbmc.Player): except: pass if count == 5: # try 5 times - self.logMsg("Cancelling playback report...", 1) + log("Cancelling playback report...", 1) break else: count += 1 @@ -84,12 +80,12 @@ class Player(xbmc.Player): xbmc.sleep(200) itemId = window("emby_%s.itemid" % currentFile) if tryCount == 20: # try 20 times or about 10 seconds - self.logMsg("Could not find itemId, cancelling playback report...", 1) + log("Could not find itemId, cancelling playback report...", 1) break else: tryCount += 1 else: - self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) + log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) # Only proceed if an itemId was found. embyitem = "emby_%s" % currentFile @@ -102,7 +98,7 @@ class Player(xbmc.Player): customseek = window('emby_customPlaylist.seektime') if window('emby_customPlaylist') == "true" and customseek: # Start at, when using custom playlist (play to Kodi from webclient) - self.logMsg("Seeking to: %s" % customseek, 1) + log("Seeking to: %s" % customseek, 1) self.xbmcplayer.seekTime(int(customseek)/10000000.0) window('emby_customPlaylist.seektime', clear=True) @@ -189,7 +185,7 @@ class Player(xbmc.Player): if mapping: # Set in playbackutils.py - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + log("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -207,7 +203,7 @@ class Player(xbmc.Player): # Post playback to server - self.logMsg("Sending POST play started: %s." % postdata, 2) + log("Sending POST play started: %s." % postdata, 2) self.doUtils(url, postBody=postdata, action_type="POST") # Ensure we do have a runtime @@ -215,7 +211,7 @@ class Player(xbmc.Player): runtime = int(runtime) except ValueError: runtime = self.xbmcplayer.getTotalTime() - self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1) + log("Runtime is missing, Kodi runtime: %s" % runtime, 1) # Save data map for updates and position calls data = { @@ -232,7 +228,7 @@ class Player(xbmc.Player): } self.played_info[currentFile] = data - self.logMsg("ADDING_FILE: %s" % self.played_info, 1) + log("ADDING_FILE: %s" % self.played_info, 1) # log some playback stats '''if(itemType != None): @@ -251,7 +247,7 @@ class Player(xbmc.Player): def reportPlayback(self): - self.logMsg("reportPlayback Called", 2) + log("reportPlayback Called", 2) # Get current file currentFile = self.currentFile @@ -345,11 +341,11 @@ class Player(xbmc.Player): # Number of audiotracks to help get Emby Index audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - mapping = utils.window("emby_%s.indexMapping" % currentFile) + mapping = window("emby_%s.indexMapping" % currentFile) if mapping: # Set in PlaybackUtils.py - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + log("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -369,13 +365,13 @@ class Player(xbmc.Player): # Report progress via websocketclient postdata = json.dumps(postdata) - self.logMsg("Report: %s" % postdata, 2) + log("Report: %s" % postdata, 2) self.ws.sendProgressUpdate(postdata) def onPlayBackPaused(self): currentFile = self.currentFile - self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2) + log("PLAYBACK_PAUSED: %s" % currentFile, 2) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True @@ -385,7 +381,7 @@ class Player(xbmc.Player): def onPlayBackResumed(self): currentFile = self.currentFile - self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2) + log("PLAYBACK_RESUMED: %s" % currentFile, 2) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False @@ -395,7 +391,7 @@ class Player(xbmc.Player): def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile - self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2) + log("PLAYBACK_SEEK: %s" % currentFile, 2) if self.played_info.get(currentFile): position = self.xbmcplayer.getTime() @@ -404,39 +400,34 @@ class Player(xbmc.Player): self.reportPlayback() def onPlayBackStopped(self): - - window = utils.window # Will be called when user stops xbmc playing a file - self.logMsg("ONPLAYBACK_STOPPED", 2) + log("ONPLAYBACK_STOPPED", 2) window('emby_customPlaylist', clear=True) window('emby_customPlaylist.seektime', clear=True) window('emby_playbackProps', clear=True) - self.logMsg("Clear playlist properties.", 1) + log("Clear playlist properties.", 1) self.stopAll() def onPlayBackEnded(self): # Will be called when xbmc stops playing a file - self.logMsg("ONPLAYBACK_ENDED", 2) - utils.window('emby_customPlaylist.seektime', clear=True) + log("ONPLAYBACK_ENDED", 2) + window('emby_customPlaylist.seektime', clear=True) self.stopAll() def stopAll(self): - lang = utils.language - settings = utils.settings - if not self.played_info: return - self.logMsg("Played_information: %s" % self.played_info, 1) + log("Played_information: %s" % self.played_info, 1) # Process each items for item in self.played_info: data = self.played_info.get(item) if data: - self.logMsg("Item path: %s" % item, 2) - self.logMsg("Item data: %s" % data, 2) + log("Item path: %s" % item, 2) + log("Item data: %s" % data, 2) runtime = data['runtime'] currentPosition = data['currentPosition'] @@ -447,7 +438,7 @@ class Player(xbmc.Player): playMethod = data['playmethod'] # Prevent manually mark as watched in Kodi monitor - utils.window('emby_skipWatched%s' % itemid, value="true") + window('emby_skipWatched%s' % itemid, value="true") if currentPosition and runtime: try: @@ -457,7 +448,7 @@ class Player(xbmc.Player): percentComplete = 0 markPlayedAt = float(settings('markPlayed')) / 100 - self.logMsg("Percent complete: %s Mark played at: %s" + log("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt), 1) # Send the delete action to the server. @@ -475,18 +466,18 @@ class Player(xbmc.Player): if percentComplete >= markPlayedAt and offerDelete: resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) if not resp: - self.logMsg("User skipped deletion.", 1) + log("User skipped deletion.", 1) continue url = "{server}/emby/Items/%s?format=json" % itemid - self.logMsg("Deleting request: %s" % itemid, 1) + log("Deleting request: %s" % itemid, 1) self.doUtils(url, action_type="DELETE") self.stopPlayback(data) # Stop transcoding if playMethod == "Transcode": - self.logMsg("Transcoding for %s terminated." % itemid, 1) + log("Transcoding for %s terminated." % itemid, 1) deviceId = self.clientInfo.getDeviceId() url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId self.doUtils(url, action_type="DELETE") @@ -495,7 +486,7 @@ class Player(xbmc.Player): def stopPlayback(self, data): - self.logMsg("stopPlayback called", 2) + log("stopPlayback called", 2) itemId = data['item_id'] currentPosition = data['currentPosition'] diff --git a/resources/lib/utils.py b/resources/lib/utils.py index dd2d04d4..203a7dee 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -188,7 +188,7 @@ def setScreensaver(value): result = xbmc.executeJSONRPC(json.dumps(query)) log("Toggling screensaver: %s %s" % (value, result), 1) -def convertdate(date): +def convertDate(date): try: date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") except TypeError: From 55998c796f03ffe14b7e79c75568d9896df796fc Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 22:05:18 -0500 Subject: [PATCH 08/28] Remove old log method --- resources/lib/utils.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 203a7dee..b203ba24 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -9,10 +9,10 @@ import pstats import sqlite3 import StringIO import os -from datetime import datetime, time import time import unicodedata import xml.etree.ElementTree as etree +from datetime import datetime, time import xbmc import xbmcaddon @@ -22,27 +22,6 @@ import xbmcvfs ################################################################################################# # Main methods -def logMsg(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'))) - class Logging(): LOGGINGCLASS = None From 14b133917967c0e68389a3988889ac76072b7db7 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 13:56:56 -0500 Subject: [PATCH 09/28] Fix typos --- resources/lib/playutils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index ed9b57b0..8e3c9a08 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -202,9 +202,9 @@ class PlayUtils(): def isDirectStream(self): videotrack = self.item['MediaSources'][0]['Name'] - transcodeH265 = utils.settings('transcodeH265') + transcodeH265 = settings('transcodeH265') videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x] - transcodeHi10P = utils.settings('transcodeHi10P') + transcodeHi10P = settings('transcodeHi10P') if transcodeHi10P == "true" and "H264" in videotrack and "High 10" in videoprofiles: return False @@ -316,7 +316,7 @@ class PlayUtils(): } # max bit rate supported by server (max signed 32bit integer) - return bitrate.get(utils.settings('videoBitrate'), 2147483) + return bitrate.get(settings('videoBitrate'), 2147483) def audioSubsPref(self, url, listitem): From 9c585b682c7fac451d17471efe387233109f980c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 18:39:13 -0500 Subject: [PATCH 10/28] Fix logging --- default.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/default.py b/default.py index 2a9ff290..9a686753 100644 --- a/default.py +++ b/default.py @@ -21,9 +21,11 @@ sys.path.append(base_resource) import entrypoint import utils +from utils import Logging, window ################################################################################################# +log = Logging('Default').log enableProfiling = False class Main: @@ -103,15 +105,15 @@ class Main: if mode == "settings": xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') elif mode in ("manualsync", "fastsync", "repair"): - if utils.window('emby_online') != "true": + if window('emby_online') != "true": # Server is not online, do not run the sync xbmcgui.Dialog().ok(heading="Emby for Kodi", line1=("Unable to run the sync, the add-on is not " "connected to the Emby server.")) - utils.logMsg("EMBY", "Not connected to the emby server.", 1) + log("EMBY", "Not connected to the emby server.", 1) return - if utils.window('emby_dbScan') != "true": + if window('emby_dbScan') != "true": import librarysync lib = librarysync.LibrarySync() if mode == "manualsync": @@ -121,7 +123,7 @@ class Main: else: lib.fullSync(repair=True) else: - utils.logMsg("EMBY", "Database scan is already running.", 1) + log("EMBY", "Database scan is already running.", 1) elif mode == "texturecache": import artwork @@ -131,7 +133,7 @@ class Main: if ( __name__ == "__main__" ): - xbmc.log('plugin.video.emby started') + log('plugin.video.emby started', 1) if enableProfiling: import cProfile @@ -152,4 +154,4 @@ if ( __name__ == "__main__" ): else: Main() - xbmc.log('plugin.video.emby stopped') \ No newline at end of file + log('plugin.video.emby stopped', 1) \ No newline at end of file From 6ebabf780563a17523fce2b5fd2668646b336137 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 18:52:35 -0500 Subject: [PATCH 11/28] Fix log typo --- default.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/default.py b/default.py index 9a686753..72e3ebfa 100644 --- a/default.py +++ b/default.py @@ -110,7 +110,7 @@ class Main: xbmcgui.Dialog().ok(heading="Emby for Kodi", line1=("Unable to run the sync, the add-on is not " "connected to the Emby server.")) - log("EMBY", "Not connected to the emby server.", 1) + log("Not connected to the emby server.", 1) return if window('emby_dbScan') != "true": @@ -123,7 +123,7 @@ class Main: else: lib.fullSync(repair=True) else: - log("EMBY", "Database scan is already running.", 1) + log("Database scan is already running.", 1) elif mode == "texturecache": import artwork From 354877d31c1726426a38dc2810f28cf27a1010ee Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 19:48:19 -0500 Subject: [PATCH 12/28] Centralized Logging --- resources/lib/embydb_functions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py index cfebd5ff..73e808d8 100644 --- a/resources/lib/embydb_functions.py +++ b/resources/lib/embydb_functions.py @@ -2,8 +2,10 @@ ################################################################################################# -import utils +from sqlite3 import OperationalError + import clientinfo +from utils import Logging ################################################################################################# @@ -13,15 +15,14 @@ class Embydb_Functions(): def __init__(self, embycursor): + global log + log = Logging(self.__class__.__name__).log + self.embycursor = embycursor 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 getViews(self): From aa3a6fa17f7510196b606099568fc7f36ba783c1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 13:30:54 -0500 Subject: [PATCH 13/28] Centralize logging --- contextmenu.py | 71 +++++++++++++++++++----------------- resources/lib/entrypoint.py | 72 +++++++++++++++++++------------------ 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index c91da6a1..fa21c38a 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -10,13 +10,16 @@ import xbmc import xbmcaddon import xbmcgui -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 = 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) + +################################################################################################# import artwork -import utils import clientinfo import downloadutils import librarysync @@ -25,9 +28,11 @@ import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils import api +from utils import Logging, settings language as lang, kodiSQL -def logMsg(msg, lvl=1): - utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl) +################################################################################################# + +log = Logging('ContextMenu').log #Kodi contextmenu item to configure the emby settings @@ -47,14 +52,14 @@ if __name__ == '__main__': if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): embyid = xbmc.getInfoLabel("ListItem.Property(embyid)") else: - embyconn = utils.kodiSQL('emby') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) item = emby_db.getItem_byKodiId(itemid, itemtype) embycursor.close() if item: embyid = item[0] - logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype)) + log("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype)) if embyid: item = emby.getItem(embyid) @@ -66,45 +71,45 @@ if __name__ == '__main__': options=[] if likes == True: #clear like for the item - options.append(utils.language(30402)) + options.append(lang(30402)) if likes == False or likes == None: #Like the item - options.append(utils.language(30403)) + options.append(lang(30403)) if likes == True or likes == None: #Dislike the item - options.append(utils.language(30404)) + options.append(lang(30404)) if favourite == False: #Add to emby favourites - options.append(utils.language(30405)) + options.append(lang(30405)) if favourite == True: #Remove from emby favourites - options.append(utils.language(30406)) + options.append(lang(30406)) if itemtype == "song": #Set custom song rating - options.append(utils.language(30407)) + options.append(lang(30407)) #delete item - options.append(utils.language(30409)) + options.append(lang(30409)) #addon settings - options.append(utils.language(30408)) + options.append(lang(30408)) #display select dialog and process results - header = utils.language(30401) + header = lang(30401) ret = xbmcgui.Dialog().select(header, options) if ret != -1: - if options[ret] == utils.language(30402): + if options[ret] == lang(30402): emby.updateUserRating(embyid, deletelike=True) - if options[ret] == utils.language(30403): + if options[ret] == lang(30403): emby.updateUserRating(embyid, like=True) - if options[ret] == utils.language(30404): + if options[ret] == lang(30404): emby.updateUserRating(embyid, like=False) - if options[ret] == utils.language(30405): + if options[ret] == lang(30405): emby.updateUserRating(embyid, favourite=True) - if options[ret] == utils.language(30406): + if options[ret] == lang(30406): emby.updateUserRating(embyid, favourite=False) - if options[ret] == utils.language(30407): - kodiconn = utils.kodiSQL('music') + if options[ret] == lang(30407): + kodiconn = kodiSQL('music') kodicursor = kodiconn.cursor() query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" )) kodicursor.execute(query, (itemid,)) @@ -113,39 +118,39 @@ if __name__ == '__main__': if newvalue: newvalue = int(newvalue) if newvalue > 5: newvalue = "5" - if utils.settings('enableUpdateSongRating') == "true": + if settings('enableUpdateSongRating') == "true": musicutils.updateRatingToFile(newvalue, API.getFilePath()) - if utils.settings('enableExportSongRating') == "true": + if settings('enableExportSongRating') == "true": like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) emby.updateUserRating(embyid, like, favourite, deletelike) query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" )) kodicursor.execute(query, (newvalue,itemid,)) kodiconn.commit() - if options[ret] == utils.language(30408): + if options[ret] == lang(30408): #Open addon settings xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)") - if options[ret] == utils.language(30409): + if options[ret] == lang(30409): #delete item from the server delete = True - if utils.settings('skipContextMenu') != "true": + if settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - logMsg("User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % embyid, 1) delete = False if delete: import downloadutils doUtils = downloadutils.DownloadUtils() url = "{server}/emby/Items/%s?format=json" % embyid - logMsg("Deleting request: %s" % embyid, 0) + log("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") - '''if utils.settings('skipContextMenu') != "true": + '''if settings('skipContextMenu') != "true": if xbmcgui.Dialog().yesno( heading="Confirm delete", line1=("Delete file on Emby Server? This will " diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index bc81ad0a..581af349 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -24,10 +24,12 @@ import playlist import playbackutils as pbutils import playutils import api - +from utils import Logging, window, settings ################################################################################################# +log = Logging('Entrypoint').log + def doPlayback(itemid, dbid): @@ -44,8 +46,8 @@ def resetAuth(): "Emby might lock your account if you fail to log in too many times. " "Proceed anyway?")) if resp == 1: - utils.logMsg("EMBY", "Reset login attempts.", 1) - utils.window('emby_serverStatus', value="Auth") + log("EMBY", "Reset login attempts.", 1) + window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -59,15 +61,15 @@ def addDirectoryItem(label, path, folder=True): def doMainListing(): xbmcplugin.setContent(int(sys.argv[1]), 'files') # Get emby nodes from the window props - embyprops = utils.window('Emby.nodes.total') + embyprops = window('Emby.nodes.total') if embyprops: totalnodes = int(embyprops) for i in range(totalnodes): - path = utils.window('Emby.nodes.%s.index' % i) + path = window('Emby.nodes.%s.index' % i) if not path: - path = utils.window('Emby.nodes.%s.content' % i) - label = utils.window('Emby.nodes.%s.title' % i) - node_type = utils.window('Emby.nodes.%s.type' % i) + path = window('Emby.nodes.%s.content' % i) + label = window('Emby.nodes.%s.title' % i) + node_type = window('Emby.nodes.%s.type' % i) #because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing. #for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node_type == "photos": @@ -101,17 +103,17 @@ def resetDeviceId(): dialog = xbmcgui.Dialog() language = utils.language - deviceId_old = utils.window('emby_deviceId') + deviceId_old = window('emby_deviceId') try: - utils.window('emby_deviceId', clear=True) + window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) except Exception as e: - utils.logMsg("EMBY", "Failed to generate a new device Id: %s" % e, 1) + log("EMBY", "Failed to generate a new device Id: %s" % e, 1) dialog.ok( heading="Emby for Kodi", line1=language(33032)) else: - utils.logMsg("EMBY", "Successfully removed old deviceId: %s New deviceId: %s" + log("EMBY", "Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( heading="Emby for Kodi", @@ -139,7 +141,7 @@ def deleteItem(): elif xbmc.getCondVisibility('Container.Content(pictures)'): itemtype = "picture" else: - utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1) + log("EMBY delete", "Unknown type, unable to proceed.", 1) return embyconn = utils.kodiSQL('emby') @@ -151,21 +153,21 @@ def deleteItem(): try: embyid = item[0] except TypeError: - utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1) + log("EMBY delete", "Unknown embyId, unable to proceed.", 1) return - if utils.settings('skipContextMenu') != "true": + if settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - utils.logMsg("EMBY delete", "User skipped deletion for: %s." % embyid, 1) + log("EMBY delete", "User skipped deletion for: %s." % embyid, 1) return doUtils = downloadutils.DownloadUtils() url = "{server}/emby/Items/%s?format=json" % embyid - utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0) + log("EMBY delete", "Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") ##### ADD ADDITIONAL USERS ##### @@ -176,7 +178,7 @@ def addUser(): clientInfo = clientinfo.ClientInfo() deviceId = clientInfo.getDeviceId() deviceName = clientInfo.getDeviceName() - userid = utils.window('emby_currUser') + userid = window('emby_currUser') dialog = xbmcgui.Dialog() # Get session @@ -229,8 +231,8 @@ def addUser(): time=1000) # clear picture - position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId) - utils.window('EmbyAdditionalUserImage.%s' % position, clear=True) + position = window('EmbyAdditionalUserPosition.%s' % selected_userId) + window('EmbyAdditionalUserImage.%s' % position, clear=True) return else: return @@ -247,7 +249,7 @@ def addUser(): return # Subtract any additional users - utils.logMsg("EMBY", "Displaying list of users: %s" % users) + log("EMBY", "Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: @@ -262,7 +264,7 @@ def addUser(): time=1000) except: - utils.logMsg("EMBY", "Failed to add user to session.") + log("EMBY", "Failed to add user to session.") dialog.notification( heading="Error", message="Unable to add/remove user from the session.", @@ -272,9 +274,9 @@ def addUser(): # always clear the individual items first totalNodes = 10 for i in range(totalNodes): - if not utils.window('EmbyAdditionalUserImage.%s' % i): + if not window('EmbyAdditionalUserImage.%s' % i): break - utils.window('EmbyAdditionalUserImage.%s' % i, clear=True) + window('EmbyAdditionalUserImage.%s' % i, clear=True) url = "{server}/emby/Sessions?DeviceId=%s" % deviceId result = doUtils.downloadUrl(url) @@ -284,9 +286,9 @@ def addUser(): userid = additionaluser['UserId'] url = "{server}/emby/Users/%s?format=json" % userid result = doUtils.downloadUrl(url) - utils.window('EmbyAdditionalUserImage.%s' % count, + window('EmbyAdditionalUserImage.%s' % count, value=art.getUserArtwork(result['Id'], 'Primary')) - utils.window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) + window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) count +=1 ##### THEME MUSIC/VIDEOS ##### @@ -318,7 +320,7 @@ def getThemeMedia(): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) - utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1) + log("EMBY", "TV Tunes custom path is enabled and set.", 1) else: # if it does not exist this will not work so warn user # often they need to edit the settings first for it to be created. @@ -468,7 +470,7 @@ def refreshPlaylist(): sound=False) except Exception as e: - utils.logMsg("EMBY", "Refresh playlists/nodes failed: %s" % e, 1) + log("EMBY", "Refresh playlists/nodes failed: %s" % e, 1) dialog.notification( heading="Emby for Kodi", message="Emby playlists/nodes refresh failed", @@ -480,9 +482,9 @@ def refreshPlaylist(): def GetSubFolders(nodeindex): nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"] for node in nodetypes: - title = utils.window('Emby.nodes.%s%s.title' %(nodeindex,node)) + title = window('Emby.nodes.%s%s.title' %(nodeindex,node)) if title: - path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node)) + path = window('Emby.nodes.%s%s.content' %(nodeindex,node)) addDirectoryItem(title, path) xbmcplugin.endOfDirectory(int(sys.argv[1])) @@ -510,7 +512,7 @@ def BrowseContent(viewname, browse_type="", folderid=""): break if viewname is not None: - utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8'))) + log("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8'))) #set the correct params for the content type #only proceed if we have a folderid if folderid: @@ -795,7 +797,7 @@ def getNextUpEpisodes(tagname, limit): pass else: for item in items: - if utils.settings('ignoreSpecialsNextEpisodes') == "true": + if settings('ignoreSpecialsNextEpisodes') == "true": query = { 'jsonrpc': "2.0", @@ -1043,7 +1045,7 @@ def getExtraFanArt(embyId,embyPath): if embyId: #only proceed if we actually have a emby id - utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0) + log("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0) # We need to store the images locally for this to work # because of the caching system in xbmc @@ -1072,7 +1074,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - utils.logMsg("EMBY", "Found cached backdrop.", 2) + log("EMBY", "Found cached backdrop.", 2) # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1083,7 +1085,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0) + log("EMBY", "Error getting extrafanart: %s" % e, 0) # Always do endofdirectory to prevent errors in the logs xbmcplugin.endOfDirectory(int(sys.argv[1])) \ No newline at end of file From a8fd73740cf9ad210371baaffd7528b453be69c5 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 16:24:34 -0500 Subject: [PATCH 14/28] Strings --- default.py | 59 ++++++++++---------------- resources/language/English/strings.xml | 39 +++++++++++++---- resources/lib/initialsetup.py | 54 +++++++++-------------- resources/settings.xml | 2 +- service.py | 4 +- 5 files changed, 76 insertions(+), 82 deletions(-) diff --git a/default.py b/default.py index 72e3ebfa..110ae76d 100644 --- a/default.py +++ b/default.py @@ -12,32 +12,32 @@ import xbmcgui ################################################################################################# -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 = 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) ################################################################################################# import entrypoint import utils -from utils import Logging, window +from utils import Logging, window, language as lang +log = Logging('Default').log ################################################################################################# -log = Logging('Default').log -enableProfiling = False -class Main: +class Main(): # MAIN ENTRY POINT + #@utils.profiling() def __init__(self): # Parse parameters base_url = sys.argv[0] params = urlparse.parse_qs(sys.argv[2][1:]) - xbmc.log("Parameter string: %s" % sys.argv[2]) + log("Parameter string: %s" % sys.argv[2], 0) try: mode = params['mode'][0] itemid = params.get('id') @@ -72,11 +72,13 @@ class Main: embypath = sys.argv[2][1:] embyid = params.get('id',[""])[0] entrypoint.getExtraFanArt(embyid,embypath) + return if "/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0]: embypath = sys.argv[2][1:] embyid = params.get('id',[""])[0] - entrypoint.getVideoFiles(embyid,embypath) + entrypoint.getVideoFiles(embyid, embypath) + return if modes.get(mode): # Simple functions @@ -88,11 +90,11 @@ class Main: limit = int(params['limit'][0]) modes[mode](itemid, limit) - elif mode in ["channels","getsubfolders"]: + elif mode in ("channels","getsubfolders"): modes[mode](itemid) elif mode == "browsecontent": - modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0] ) + modes[mode](itemid, params.get('type',[""])[0], params.get('folderid',[""])[0]) elif mode == "channelsfolder": folderid = params['folderid'][0] @@ -104,12 +106,13 @@ class Main: # Other functions if mode == "settings": xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') + elif mode in ("manualsync", "fastsync", "repair"): + if window('emby_online') != "true": # Server is not online, do not run the sync - xbmcgui.Dialog().ok(heading="Emby for Kodi", - line1=("Unable to run the sync, the add-on is not " - "connected to the Emby server.")) + xbmcgui.Dialog().ok(heading=lang(29999), + line1=lang(33034)) log("Not connected to the emby server.", 1) return @@ -128,30 +131,12 @@ class Main: elif mode == "texturecache": import artwork artwork.Artwork().FullTextureCacheSync() + else: entrypoint.doMainListing() - -if ( __name__ == "__main__" ): + +if __name__ == "__main__": log('plugin.video.emby started', 1) - - if enableProfiling: - import cProfile - import pstats - import random - from time import gmtime, strftime - addonid = addon_.getAddonInfo('id').decode( 'utf-8' ) - datapath = os.path.join( xbmc.translatePath( "special://profile/" ).decode( 'utf-8' ), "addon_data", addonid ) - - filename = os.path.join( datapath, strftime( "%Y%m%d%H%M%S",gmtime() ) + "-" + str( random.randrange(0,100000) ) + ".log" ) - cProfile.run( 'Main()', filename ) - - stream = open( filename + ".txt", 'w') - p = pstats.Stats( filename, stream = stream ) - p.sort_stats( "cumulative" ) - p.print_stats() - - else: - Main() - + Main() log('plugin.video.emby stopped', 1) \ No newline at end of file diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 1a145adc..b15b7fe8 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -1,25 +1,30 @@  + + Emby for Kodi Primary Server Address Play from HTTP instead of SMB Log level - Username: - Password: + Username + Port Number + + + + + + + Network Username: Network Password: Transcode: - Enable Performance Profiling - Local caching system Emby Network Device Name Advanced - Username - Port Number Number of recent Movies to show: Number of recent TV episodes to show: Number of recent Music Albums to show: @@ -141,7 +146,7 @@ Transcoding Server Detection Succeeded Found server - Address : + Address: Recently Added TV Shows @@ -175,7 +180,8 @@ Search Set Views - Select User + Select User + Profiling enabled. Please remember to turn off when finished testing. Error in ArtworkRotationThread @@ -262,6 +268,12 @@ Delete item from the server + Primary Server Address + Play from HTTP instead of SMB + Log level + Username + Port Number + Verify Host SSL Certificate Client SSL certificate Use alternate address @@ -299,7 +311,8 @@ Server messages Generate a new device Id Sync when screensaver is deactivated - Force Transcode Hi10P + Force Transcode Hi10P + Disabled Welcome @@ -337,4 +350,12 @@ Failed to generate a new device Id. See your logs for more information. A new device Id has been generated. Kodi will now restart. + Proceed with the following server? + Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule. + Addon (Default) + Native (Direct Paths) + "Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. + Disable Emby music library? + Direct stream the music library? Select this option if the music library will be remotely accessed. + diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 4b1f63f3..da0a5108 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -25,15 +25,15 @@ class InitialSetup(): global log log = Logging(self.__class__.__name__).log - self.clientInfo = clientinfo.ClientInfo() - self.addonId = self.clientInfo.getAddonId() - self.doUtils = downloadutils.DownloadUtils() + self.addonId = clientinfo.ClientInfo().getAddonId() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userClient = userclient.UserClient() def setup(self): # Check server, user, direct paths, music, direct stream if not direct path. addonId = self.addonId + dialog = xbmcgui.Dialog() ##### SERVER INFO ##### @@ -54,10 +54,10 @@ class InitialSetup(): xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return else: - server_confirm = xbmcgui.Dialog().yesno( - heading="Emby for Kodi", - line1="Proceed with the following server?", - line2="%s %s" % (lang(30169), server)) + server_confirm = dialog.yesno( + heading=lang(29999), + line1=lang(33034), + line2="%s %s" % (lang(30169), server)) if server_confirm: # Correct server found log("Server is selected. Saving the information.", 1) @@ -75,9 +75,8 @@ class InitialSetup(): ##### USER INFO ##### log("Getting user list.", 1) - - url = "%s/emby/Users/Public?format=json" % server - result = self.doUtils.downloadUrl(url, authenticate=False) + + result = self.doUtils("%s/emby/Users/Public?format=json" % server, authenticate=False) if result == "": log("Unable to connect to %s" % server, 1) return @@ -97,7 +96,7 @@ class InitialSetup(): users_hasPassword.append(name) log("Presenting user list: %s" % users_hasPassword, 1) - user_select = xbmcgui.Dialog().select(lang(30200), users_hasPassword) + user_select = dialog.select(lang(30200), users_hasPassword) if user_select > -1: selected_user = usernames[user_select] log("Selected user: %s" % selected_user, 1) @@ -105,38 +104,30 @@ class InitialSetup(): else: log("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) + return ##### ADDITIONAL PROMPTS ##### - dialog = xbmcgui.Dialog() directPaths = dialog.yesno( - heading="Playback Mode", - line1=( - "Caution! If you choose Native mode, you " - "will lose access to certain Emby features such as: " - "Emby cinema mode, direct stream/transcode options, " - "parental access schedule."), - nolabel="Addon (Default)", - yeslabel="Native (Direct Paths)") + heading=lang(30511), + line1=lang(33035), + nolabel=lang(33036), + yeslabel=lang(33037)) if directPaths: log("User opted to use direct paths.", 1) settings('useDirectPaths', value="1") # ask for credentials credentials = dialog.yesno( - heading="Network credentials", - line1= ( - "Add network credentials to allow Kodi access to your " - "content? Note: Skipping this step may generate a message " - "during the initial scan of your content if Kodi can't " - "locate your content.")) + heading=lang(30517), + line1= lang(33038)) if credentials: log("Presenting network credentials dialog.", 1) passwordsXML() musicDisabled = dialog.yesno( - heading="Music Library", - line1="Disable Emby music library?") + heading=lang(29999), + line1=lang(33039)) if musicDisabled: log("User opted to disable Emby music library.", 1) settings('enableMusic', value="false") @@ -144,11 +135,8 @@ class InitialSetup(): # Only prompt if the user didn't select direct paths for videos if not directPaths: musicAccess = dialog.yesno( - heading="Music Library", - line1=( - "Direct stream the music library? Select " - "this option only if you plan on listening " - "to music outside of your network.")) + heading=lang(29999), + line1=lang(33040)) if musicAccess: log("User opted to direct stream music.", 1) settings('streamMusic', value="true") diff --git a/resources/settings.xml b/resources/settings.xml index 77d57a33..7ca31f61 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -33,7 +33,7 @@ - + diff --git a/service.py b/service.py index f47be05c..311e00a9 100644 --- a/service.py +++ b/service.py @@ -167,7 +167,7 @@ class Service(): else: add = "" xbmcgui.Dialog().notification( - heading="Emby for Kodi", + heading=lang(29999), message=("%s %s%s!" % (lang(33000), user.currUser.decode('utf-8'), add.decode('utf-8'))), @@ -242,7 +242,7 @@ class Service(): break # Alert the user that server is online. xbmcgui.Dialog().notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33003), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, From ad6e0bb7daaa50ac1aed5c612b8d6d3137b65839 Mon Sep 17 00:00:00 2001 From: delphiactual Date: Sun, 19 Jun 2016 15:47:59 -0600 Subject: [PATCH 15/28] Fixed: Typo in strings.xml --- resources/language/English/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index b15b7fe8..1d3d1b92 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -354,7 +354,7 @@ Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule. Addon (Default) Native (Direct Paths) - "Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. + Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. Disable Emby music library? Direct stream the music library? Select this option if the music library will be remotely accessed. From f90349d6a849b0d1e8f923dee37bd108158edc6e Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 17:23:46 -0500 Subject: [PATCH 16/28] Fix typo --- resources/lib/itemtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 0bf87c69..d433d65b 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -218,7 +218,7 @@ class Items(object): "You may need to verify your network credentials in the " "add-on settings or use the Emby path substitution " "to format your path correctly (Emby dashboard > library). " - "Stop syncing?" % playurl)) + "Stop syncing?" % path)) if resp: window('emby_shouldStop', value="true") return False From 3c60eddf55aab78ac46546701421e0817d594d7c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 19:24:42 -0500 Subject: [PATCH 17/28] Add refresh item to context menu --- contextmenu.py | 121 +++++++++++++------------ resources/language/English/strings.xml | 1 + resources/lib/read_embyserver.py | 16 +++- 3 files changed, 79 insertions(+), 59 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index fa21c38a..a983ab59 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -19,95 +19,100 @@ sys.path.append(_base_resource) ################################################################################################# +import api import artwork -import clientinfo import downloadutils import librarysync import read_embyserver as embyserver import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils -import api -from utils import Logging, settings language as lang, kodiSQL +from utils import Logging, settings, language as lang, kodiSQL +log = Logging('ContextMenu').log ################################################################################################# -log = Logging('ContextMenu').log - - -#Kodi contextmenu item to configure the emby settings -#for now used to set ratings but can later be used to sync individual items etc. +# Kodi contextmenu item to configure the emby settings if __name__ == '__main__': - itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8") - itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8") + + kodiId = xbmc.getInfoLabel('ListItem.DBID').decode('utf-8') + itemType = xbmc.getInfoLabel('ListItem.DBTYPE').decode('utf-8') + itemId = "" - emby = embyserver.Read_EmbyServer() + if not itemType: + + if xbmc.getCondVisibility("Container.Content(albums)"): + itemType = "album" + elif xbmc.getCondVisibility("Container.Content(artists)"): + itemType = "artist" + elif xbmc.getCondVisibility("Container.Content(songs)"): + itemType = "song" + elif xbmc.getCondVisibility("Container.Content(pictures)"): + itemType = "picture" + else: + log("Itemtype is unknown.") + + if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): + itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") - embyid = "" - if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album" - if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist" - if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song" - if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture" - - if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): - embyid = xbmc.getInfoLabel("ListItem.Property(embyid)") - else: + elif kodiId and itemType: embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - item = emby_db.getItem_byKodiId(itemid, itemtype) + item = emby_db.getItem_byKodiId(kodiId, itemType) embycursor.close() - if item: embyid = item[0] - - log("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype)) + try: + itemId = item[0] + except TypeError: + pass - if embyid: - item = emby.getItem(embyid) + + log("Found ItemId: %s/Itemtype: %s" % (itemId, itemType), 1) + if itemId: + + emby = embyserver.Read_EmbyServer() + item = emby.getItem(itemId) API = api.API(item) userdata = API.getUserData() likes = userdata['Likes'] favourite = userdata['Favorite'] - options=[] - if likes == True: - #clear like for the item - options.append(lang(30402)) - if likes == False or likes == None: - #Like the item - options.append(lang(30403)) - if likes == True or likes == None: - #Dislike the item - options.append(lang(30404)) - if favourite == False: - #Add to emby favourites - options.append(lang(30405)) - if favourite == True: - #Remove from emby favourites + options = [] + + if favourite: + # Remove from emby favourites options.append(lang(30406)) - if itemtype == "song": - #Set custom song rating + else: + # Add to emby favourites + options.append(lang(30405)) + + if itemType == "song": + # Set custom song rating options.append(lang(30407)) - #delete item + # Refresh item + options.append(lang(30410)) + # Delete item options.append(lang(30409)) - - #addon settings + # Addon settings options.append(lang(30408)) - #display select dialog and process results - header = lang(30401) - ret = xbmcgui.Dialog().select(header, options) + # Display select dialog and process results + + ret = xbmcgui.Dialog().select(lang(30401), options) if ret != -1: + if options[ret] == lang(30410): + emby.refreshItem(itemId) if options[ret] == lang(30402): - emby.updateUserRating(embyid, deletelike=True) + emby.updateUserRating(itemId, deletelike=True) if options[ret] == lang(30403): - emby.updateUserRating(embyid, like=True) + emby.updateUserRating(itemId, like=True) if options[ret] == lang(30404): - emby.updateUserRating(embyid, like=False) + emby.updateUserRating(itemId, like=False) if options[ret] == lang(30405): - emby.updateUserRating(embyid, favourite=True) + emby.updateUserRating(itemId, favourite=True) if options[ret] == lang(30406): - emby.updateUserRating(embyid, favourite=False) + emby.updateUserRating(itemId, favourite=False) if options[ret] == lang(30407): kodiconn = kodiSQL('music') kodicursor = kodiconn.cursor() @@ -122,7 +127,7 @@ if __name__ == '__main__': musicutils.updateRatingToFile(newvalue, API.getFilePath()) if settings('enableExportSongRating') == "true": like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) - emby.updateUserRating(embyid, like, favourite, deletelike) + emby.updateUserRating(itemId, like, favourite, deletelike) query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" )) kodicursor.execute(query, (newvalue,itemid,)) kodiconn.commit() @@ -140,13 +145,13 @@ if __name__ == '__main__': line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - log("User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % itemId, 1) delete = False if delete: import downloadutils doUtils = downloadutils.DownloadUtils() - url = "{server}/emby/Items/%s?format=json" % embyid + url = "{server}/emby/Items/%s?format=json" % itemId log("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") @@ -160,4 +165,4 @@ if __name__ == '__main__': doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")''' xbmc.sleep(500) - xbmc.executebuiltin("Container.Update") \ No newline at end of file + xbmc.executebuiltin('Container.Update') \ No newline at end of file diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 1d3d1b92..1655957a 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -266,6 +266,7 @@ Set custom song rating Emby addon settings Delete item from the server + Refresh this item Primary Server Address diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index cc0c4b4a..bc51b5ca 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -564,4 +564,18 @@ class Read_EmbyServer(): 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 + % (itemid, like, favourite, deletelike), 1) + + def refreshItem(self, itemid): + + url = "{server}/emby/Items/%s/Refresh?format=json" % itemid + params = { + + 'Recursive': True, + 'ImageRefreshMode': "FullRefresh", + 'MetadataRefreshMode': "FullRefresh", + 'ReplaceAllImages': False, + 'ReplaceAllMetadata': True + + } + self.doUtils(url, postBody=params, action_type="POST") \ No newline at end of file From 313899c8e7c8063770e8fc70d5bc91574ca32133 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 20:17:10 -0500 Subject: [PATCH 18/28] Fix typo --- resources/lib/itemtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index d433d65b..197b71c5 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -18,7 +18,7 @@ import embydb_functions as embydb import kodidb_functions as kodidb import read_embyserver as embyserver import musicutils -from utils import Logging, window, settings, language as lang +from utils import Logging, window, settings, language as lang, kodiSQL ################################################################################################## @@ -100,7 +100,7 @@ class Items(object): if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'): if music_enabled: - musicconn = utils.kodiSQL('music') + musicconn = kodiSQL('music') musiccursor = musicconn.cursor() items_process = itemtypes[itemtype](embycursor, musiccursor) else: From 0efc37f646cedf79cad46e02e78a09043df6ef01 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 20:32:09 -0500 Subject: [PATCH 19/28] String & remove like/dislike To review: music rating since server does not have like/dislike anymore --- contextmenu.py | 102 ++++++++++++------------- resources/language/English/strings.xml | 5 +- resources/lib/musicutils.py | 2 +- resources/lib/read_embyserver.py | 25 +++--- 4 files changed, 63 insertions(+), 71 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index a983ab59..5c16e441 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -50,7 +50,7 @@ if __name__ == '__main__': elif xbmc.getCondVisibility("Container.Content(pictures)"): itemType = "picture" else: - log("Itemtype is unknown.") + log("ItemType is unknown.") if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") @@ -67,9 +67,11 @@ if __name__ == '__main__': pass - log("Found ItemId: %s/Itemtype: %s" % (itemId, itemType), 1) + log("Found ItemId: %s ItemType: %s" % (itemId, itemType), 1) if itemId: + dialog = xbmcgui.Dialog() + emby = embyserver.Read_EmbyServer() item = emby.getItem(itemId) API = api.API(item) @@ -98,71 +100,69 @@ if __name__ == '__main__': options.append(lang(30408)) # Display select dialog and process results + resp = xbmcgui.Dialog().select(lang(30401), options) + if resp > -1: + selected = options[resp] - ret = xbmcgui.Dialog().select(lang(30401), options) - if ret != -1: - if options[ret] == lang(30410): + if selected == lang(30410): + # Refresh item emby.refreshItem(itemId) - if options[ret] == lang(30402): - emby.updateUserRating(itemId, deletelike=True) - if options[ret] == lang(30403): - emby.updateUserRating(itemId, like=True) - if options[ret] == lang(30404): - emby.updateUserRating(itemId, like=False) - if options[ret] == lang(30405): + elif selected == lang(30405): + # Add favourite emby.updateUserRating(itemId, favourite=True) - if options[ret] == lang(30406): + elif selected == lang(30406): + # Delete favourite emby.updateUserRating(itemId, favourite=False) - if options[ret] == lang(30407): + elif selected == lang(30407): + # Update song rating kodiconn = kodiSQL('music') kodicursor = kodiconn.cursor() - query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" )) - kodicursor.execute(query, (itemid,)) - currentvalue = int(round(float(kodicursor.fetchone()[0]),0)) - newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue)) - if newvalue: - newvalue = int(newvalue) - if newvalue > 5: newvalue = "5" - if settings('enableUpdateSongRating') == "true": - musicutils.updateRatingToFile(newvalue, API.getFilePath()) - if settings('enableExportSongRating') == "true": - like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) - emby.updateUserRating(itemId, like, favourite, deletelike) - query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" )) - kodicursor.execute(query, (newvalue,itemid,)) - kodiconn.commit() + query = "SELECT rating FROM song WHERE idSong = ?" + kodicursor.execute(query, (kodiId,)) + try: + value = kodicursor.fetchone()[0] + current_value = int(round(float(value),0)) + except TypeError: + pass + else: + new_value = dialog.numeric(0, lang(30411), str(current_value)) + if new_value > -1: + + new_value = int(new_value) + if new_value > 5: + new_value = 5 - if options[ret] == lang(30408): - #Open addon settings + if settings('enableUpdateSongRating') == "true": + musicutils.updateRatingToFile(new_value, API.getFilePath()) + + query = "UPDATE song SET rating = ? WHERE idSong = ?" + kodicursor.execute(query, (new_value, kodiId,)) + kodiconn.commit() + + '''if settings('enableExportSongRating') == "true": + like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(new_value) + emby.updateUserRating(itemId, like, favourite, deletelike)''' + finally: + kodicursor.close() + + elif selected == lang(30408): + # Open addon settings xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)") - if options[ret] == lang(30409): - #delete item from the server + elif selected == lang(30409): + # delete item from the server delete = True if settings('skipContextMenu') != "true": - resp = xbmcgui.Dialog().yesno( - heading="Confirm delete", - line1=("Delete file from Emby Server? This will " - "also delete the file(s) from disk!")) + resp = dialog.yesno( + heading=lang(29999), + line1=lang(33041)) if not resp: log("User skipped deletion for: %s." % itemId, 1) delete = False if delete: - import downloadutils - doUtils = downloadutils.DownloadUtils() - url = "{server}/emby/Items/%s?format=json" % itemId - log("Deleting request: %s" % embyid, 0) - doUtils.downloadUrl(url, action_type="DELETE") - - '''if settings('skipContextMenu') != "true": - if xbmcgui.Dialog().yesno( - heading="Confirm delete", - line1=("Delete file on Emby Server? This will " - "also delete the file(s) from disk!")): - import downloadutils - doUtils = downloadutils.DownloadUtils() - doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")''' + log("Deleting request: %s" % itemId, 0) + emby.deleteItem(itemId) xbmc.sleep(500) xbmc.executebuiltin('Container.Update') \ No newline at end of file diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 1655957a..9c6d4ad2 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -258,15 +258,13 @@ Emby options - Clear like for this item - Like this item - Dislike this item Add to Emby favorites Remove from Emby favorites Set custom song rating Emby addon settings Delete item from the server Refresh this item + Set custom song rating (0-5) Primary Server Address @@ -358,5 +356,6 @@ Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. Disable Emby music library? Direct stream the music library? Select this option if the music library will be remotely accessed. + Delete file(s) from Emby Server? This will also delete the file(s) from disk! diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index dd44e830..66da24a1 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -15,11 +15,11 @@ import base64 import read_embyserver as embyserver from utils import Logging, window +log = Logging('MusicTools').log ################################################################################################# # Helper for the music library, intended to fix missing song ID3 tags on Emby -log = Logging('MusicTools').log def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index bc51b5ca..5e62425e 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -539,32 +539,20 @@ class Read_EmbyServer(): return sorted_items - def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False): + def updateUserRating(self, itemid, favourite=None): # Updates the user rating to Emby doUtils = self.doUtils if favourite: url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid doUtils(url, action_type="POST") - elif favourite == False: + elif not favourite: url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid doUtils(url, action_type="DELETE") - - if not deletelike and like: - 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: - url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid - doUtils(url, action_type="POST") - elif deletelike: - url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid - doUtils(url, action_type="DELETE") else: log("Error processing user rating.", 1) - log("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 | favourite: %s" % (itemid, favourite), 1) def refreshItem(self, itemid): @@ -578,4 +566,9 @@ class Read_EmbyServer(): 'ReplaceAllMetadata': True } - self.doUtils(url, postBody=params, action_type="POST") \ No newline at end of file + self.doUtils(url, postBody=params, action_type="POST") + + def deleteItem(self, itemid): + + url = "{server}/emby/Items/%s?format=json" % itemId + self.doUtils(url, action_type="DELETE") \ No newline at end of file From 423a4c9c40f435aec1c029928818ede64eac582b Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 20:39:22 -0500 Subject: [PATCH 20/28] Update refresh So it works for music modifications and such --- contextmenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contextmenu.py b/contextmenu.py index 5c16e441..b690fd8f 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -165,4 +165,4 @@ if __name__ == '__main__': emby.deleteItem(itemId) xbmc.sleep(500) - xbmc.executebuiltin('Container.Update') \ No newline at end of file + xbmc.executebuiltin('Container.Refresh') \ No newline at end of file From f47ead1ddcc1ba746753ea9ebe594f9547283d05 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 23:09:07 -0500 Subject: [PATCH 21/28] Fix logging --- resources/lib/entrypoint.py | 40 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 581af349..942a0d78 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -24,12 +24,11 @@ import playlist import playbackutils as pbutils import playutils import api -from utils import Logging, window, settings +from utils import Logging, window, settings, language as lang +log = Logging('Entrypoint').log ################################################################################################# -log = Logging('Entrypoint').log - def doPlayback(itemid, dbid): @@ -46,7 +45,7 @@ def resetAuth(): "Emby might lock your account if you fail to log in too many times. " "Proceed anyway?")) if resp == 1: - log("EMBY", "Reset login attempts.", 1) + log("Reset login attempts.", 1) window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -108,16 +107,15 @@ def resetDeviceId(): window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) except Exception as e: - log("EMBY", "Failed to generate a new device Id: %s" % e, 1) + log("Failed to generate a new device Id: %s" % e, 1) dialog.ok( heading="Emby for Kodi", line1=language(33032)) else: - log("EMBY", "Successfully removed old deviceId: %s New deviceId: %s" - % (deviceId_old, deviceId), 1) + log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( - heading="Emby for Kodi", - line1=language(33033)) + heading=lang(29999), + line1=lang(33033)) xbmc.executebuiltin('RestartApp') ##### Delete Item @@ -141,7 +139,7 @@ def deleteItem(): elif xbmc.getCondVisibility('Container.Content(pictures)'): itemtype = "picture" else: - log("EMBY delete", "Unknown type, unable to proceed.", 1) + log("Unknown type, unable to proceed.", 1) return embyconn = utils.kodiSQL('emby') @@ -153,7 +151,7 @@ def deleteItem(): try: embyid = item[0] except TypeError: - log("EMBY delete", "Unknown embyId, unable to proceed.", 1) + log("Unknown embyId, unable to proceed.", 1) return if settings('skipContextMenu') != "true": @@ -162,12 +160,12 @@ def deleteItem(): line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - log("EMBY delete", "User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % embyid, 1) return doUtils = downloadutils.DownloadUtils() url = "{server}/emby/Items/%s?format=json" % embyid - log("EMBY delete", "Deleting request: %s" % embyid, 0) + log("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") ##### ADD ADDITIONAL USERS ##### @@ -249,7 +247,7 @@ def addUser(): return # Subtract any additional users - log("EMBY", "Displaying list of users: %s" % users) + log("Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: @@ -264,7 +262,7 @@ def addUser(): time=1000) except: - log("EMBY", "Failed to add user to session.") + log("Failed to add user to session.") dialog.notification( heading="Error", message="Unable to add/remove user from the session.", @@ -320,7 +318,7 @@ def getThemeMedia(): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) - log("EMBY", "TV Tunes custom path is enabled and set.", 1) + log("TV Tunes custom path is enabled and set.", 1) else: # if it does not exist this will not work so warn user # often they need to edit the settings first for it to be created. @@ -470,7 +468,7 @@ def refreshPlaylist(): sound=False) except Exception as e: - log("EMBY", "Refresh playlists/nodes failed: %s" % e, 1) + log("Refresh playlists/nodes failed: %s" % e, 1) dialog.notification( heading="Emby for Kodi", message="Emby playlists/nodes refresh failed", @@ -512,7 +510,7 @@ def BrowseContent(viewname, browse_type="", folderid=""): break if viewname is not None: - log("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8'))) + log("viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8'))) #set the correct params for the content type #only proceed if we have a folderid if folderid: @@ -1045,7 +1043,7 @@ def getExtraFanArt(embyId,embyPath): if embyId: #only proceed if we actually have a emby id - log("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0) + log("Requesting extrafanart for Id: %s" % embyId, 0) # We need to store the images locally for this to work # because of the caching system in xbmc @@ -1074,7 +1072,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - log("EMBY", "Found cached backdrop.", 2) + log("Found cached backdrop.", 2) # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1085,7 +1083,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - log("EMBY", "Error getting extrafanart: %s" % e, 0) + log("Error getting extrafanart: %s" % e, 0) # Always do endofdirectory to prevent errors in the logs xbmcplugin.endOfDirectory(int(sys.argv[1])) \ No newline at end of file From 49157bbbeac7c3b77de4395dde0580398cca42f0 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 13:58:17 -0500 Subject: [PATCH 22/28] Fix logging typo --- resources/lib/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index b203ba24..77220304 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -267,7 +267,7 @@ def reset(): window('emby_shouldStop', value="true") count = 10 while window('emby_dbScan') == "true": - logMsg("EMBY", "Sync is running, will retry: %s..." % count) + log("Sync is running, will retry: %s..." % count) count -= 1 if count == 0: dialog.ok("Warning", "Could not stop the database from running. Try again.") From 48fbce22114755e54a5d6f85573287e999f638c1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 13:59:55 -0500 Subject: [PATCH 23/28] Fix typo --- resources/lib/artwork.py | 76 +++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 4ed0b8d9..fab75b6b 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -14,7 +14,7 @@ import xbmcvfs import clientinfo import image_cache_thread -from utils import Logging, window, settings, kodiSQL +from utils import Logging, window, settings, language as lang, kodiSQL ################################################################################################# @@ -165,25 +165,23 @@ class Artwork(): except TypeError: pass - def FullTextureCacheSync(self): + 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 dialog.yesno( - heading="Image Texture Cache", - line1=( - "Running the image cache process can take some time. " - "Are you sure you want continue?")): + heading=lang(29999), + line1=lang(33042)): return log("Doing Image Cache Sync", 1) pdialog = xbmcgui.DialogProgress() - pdialog.create("Emby for Kodi", "Image Cache Sync") + pdialog.create(lang(29999), lang(33043)) # ask to rest all existing or not - if dialog.yesno("Image Texture Cache", "Reset all existing cache data first?"): + if dialog.yesno(lang(29999), lang(33044)): log("Resetting all cache data first.", 1) # Remove all existing textures first @@ -228,11 +226,10 @@ class Artwork(): percentage = int((float(count) / float(total))*100) message = "%s of %s (%s)" % (count, total, self.imageCacheThreads) - pdialog.update(percentage, "Updating Image Cache: %s" % message) + pdialog.update(percentage, "%s %s" % (lang(33045), message)) self.cacheTexture(url[0]) count += 1 - # Cache all entries in music DB connection = kodiSQL('music') cursor = connection.cursor() @@ -250,19 +247,18 @@ class Artwork(): percentage = int((float(count) / float(total))*100) message = "%s of %s" % (count, total) - pdialog.update(percentage, "Updating Image Cache: %s" % message) + pdialog.update(percentage, "%s %s" % (lang(33045), message)) self.cacheTexture(url[0]) count += 1 - - pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + pdialog.update(100, "%s %s" % (lang(33046), 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) - pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1) xbmc.sleep(500) @@ -311,7 +307,7 @@ class Artwork(): self.addWorkerImageCacheThread(url) - def addArtwork(self, artwork, kodi_id, media_type, cursor): + def addArtwork(self, artwork, kodiId, mediaType, cursor): # Kodi conversion table kodiart = { @@ -342,7 +338,7 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodi_id, media_type, "fanart%",)) + cursor.execute(query, (kodiId, mediaType, "fanart%",)) rows = cursor.fetchall() if len(rows) > backdropsNumber: @@ -354,15 +350,15 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodi_id, media_type, "fanart_",)) + cursor.execute(query, (kodiId, mediaType, "fanart_",)) # Process backdrops and extra fanart index = "" for backdrop in backdrops: self.addOrUpdateArt( image_url=backdrop, - kodi_id=kodi_id, - media_type=media_type, + kodi_id=kodiId, + media_type=mediaType, image_type="%s%s" % ("fanart", index), cursor=cursor) @@ -377,8 +373,8 @@ class Artwork(): for artType in kodiart[art]: self.addOrUpdateArt( image_url=artwork[art], - kodi_id=kodi_id, - media_type=media_type, + kodi_id=kodiId, + media_type=mediaType, image_type=artType, cursor=cursor) @@ -386,14 +382,14 @@ class Artwork(): # Process the rest artwork type that Kodi can use self.addOrUpdateArt( image_url=artwork[art], - kodi_id=kodi_id, - media_type=media_type, + kodi_id=kodiId, + media_type=mediaType, image_type=kodiart[art], cursor=cursor) - def addOrUpdateArt(self, image_url, kodi_id, media_type, image_type, cursor): + def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): # Possible that the imageurl is an empty string - if image_url: + if imageUrl: cacheimage = False query = ' '.join(( @@ -404,13 +400,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (kodi_id, media_type, image_type,)) + cursor.execute(query, (kodiId, mediaType, imageType,)) try: # Update the artwork url = cursor.fetchone()[0] except TypeError: # Add the artwork cacheimage = True - log("Adding Art Link for kodiId: %s (%s)" % (kodi_id, image_url), 2) + log("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) query = ( ''' @@ -419,20 +415,20 @@ class Artwork(): VALUES (?, ?, ?, ?) ''' ) - cursor.execute(query, (kodi_id, media_type, image_type, image_url)) + cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) else: # Only cache artwork if it changed - if url != image_url: + if url != imageUrl: cacheimage = True # Only for the main backdrop, poster if (window('emby_initialScan') != "true" and - image_type in ("fanart", "poster")): + imageType in ("fanart", "poster")): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) log("Updating Art url for %s kodiId: %s (%s) -> (%s)" - % (image_type, kodi_id, url, image_url), 1) + % (imageType, kodiId, url, imageUrl), 1) query = ' '.join(( @@ -442,13 +438,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (image_url, kodi_id, media_type, image_type)) + cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) # Cache fanart and poster in Kodi texture cache - if cacheimage and image_type in ("fanart", "poster"): - self.cacheTexture(image_url) + if cacheimage and imageType in ("fanart", "poster"): + self.cacheTexture(imageUrl) - def deleteArtwork(self, kodi_id, media_type, cursor): + def deleteArtwork(self, kodiId, mediaType, cursor): query = ' '.join(( @@ -457,13 +453,13 @@ class Artwork(): "WHERE media_id = ?", "AND media_type = ?" )) - cursor.execute(query, (kodi_id, media_type,)) + cursor.execute(query, (kodiId, mediaType,)) rows = cursor.fetchall() for row in rows: url = row[0] - imagetype = row[1] - if imagetype in ("poster", "fanart"): + imageType = row[1] + if imageType in ("poster", "fanart"): self.deleteCachedArtwork(url) def deleteCachedArtwork(self, url): @@ -513,10 +509,10 @@ class Artwork(): return people - def getUserArtwork(self, item_id, item_type): + def getUserArtwork(self, itemId, itemType): # Load user information set by UserClient image = ("%s/emby/Users/%s/Images/%s?Format=original" - % (self.server, item_id, item_type)) + % (self.server, itemId, itemType)) return image def getAllArtwork(self, item, parentInfo=False): From 924c3a4a0549b0ed0069365b69f0935d0a6f4dec Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 14:04:10 -0500 Subject: [PATCH 24/28] Fix arguments --- resources/lib/artwork.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index fab75b6b..f2cd0a32 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -356,10 +356,10 @@ class Artwork(): index = "" for backdrop in backdrops: self.addOrUpdateArt( - image_url=backdrop, - kodi_id=kodiId, - media_type=mediaType, - image_type="%s%s" % ("fanart", index), + imageUrl=backdrop, + kodiId=kodiId, + mediaType=mediaType, + imageType="%s%s" % ("fanart", index), cursor=cursor) if backdropsNumber > 1: @@ -372,19 +372,19 @@ class Artwork(): # Primary art is processed as thumb and poster for Kodi. for artType in kodiart[art]: self.addOrUpdateArt( - image_url=artwork[art], - kodi_id=kodiId, - media_type=mediaType, - image_type=artType, + imageUrl=artwork[art], + kodiId=kodiId, + mediaType=mediaType, + imageType=artType, cursor=cursor) elif kodiart.get(art): # Process the rest artwork type that Kodi can use self.addOrUpdateArt( - image_url=artwork[art], - kodi_id=kodiId, - media_type=mediaType, - image_type=kodiart[art], + imageUrl=artwork[art], + kodiId=kodiId, + mediaType=mediaType, + imageType=kodiart[art], cursor=cursor) def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): From 3e1aa94c786f6b21bb50469ce6a19f929d45c94a Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 20:57:29 -0500 Subject: [PATCH 25/28] Strings - translation --- default.py | 2 +- resources/language/English/strings.xml | 180 ++++++++++--------------- resources/lib/entrypoint.py | 146 ++++++++++---------- resources/lib/itemtypes.py | 35 ++--- resources/lib/kodimonitor.py | 2 +- resources/lib/librarysync.py | 36 ++--- resources/lib/playutils.py | 4 +- resources/lib/userclient.py | 4 +- resources/lib/utils.py | 38 +++--- resources/lib/websocket_client.py | 6 +- 10 files changed, 201 insertions(+), 252 deletions(-) diff --git a/default.py b/default.py index 110ae76d..3912b4f5 100644 --- a/default.py +++ b/default.py @@ -130,7 +130,7 @@ class Main(): elif mode == "texturecache": import artwork - artwork.Artwork().FullTextureCacheSync() + artwork.Artwork().fullTextureCacheSync() else: entrypoint.doMainListing() diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 9c6d4ad2..ee2dc385 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -6,56 +6,23 @@ Primary Server Address Play from HTTP instead of SMB Log level + Device Name + Advanced Username Port Number + Number of recent Music Albums to show: + Number of recent Movies to show: + Number of recent TV episodes to show: - - - - - - Network Username: - Network Password: - Transcode: - - Emby - Network - Device Name - - Advanced - - Number of recent Movies to show: - Number of recent TV episodes to show: - Number of recent Music Albums to show: - Mark watched at start of playback: - Set Season poster for episodes - - Genre Filter ... - Play All from Here Refresh Delete - Add Movie to CouchPotato - Incorrect Username/Password Username not found - Deleting Waiting for server to delete - Server Default - Title - Year - Premiere Date - Date Created - Critic Rating - Community Rating - Play Count - Budget - - Sort By - None Action Adventure @@ -80,69 +47,40 @@ Genre Filter Confirm file deletion - Delete this item? This action will delete media and associated data files. - Mark Watched - Mark Unwatched - Add to Favorites - Remove from Favorites - Sort By ... + Mark watched + Mark unwatched + + Sort by Sort Order Descending Sort Order Ascending - Show People Resume Resume from Start from beginning - Interface - Include Stream Info - Include People - Include Overview Offer delete after playback For Episodes For Movies - Background Art Refresh Rate (seconds) + Add Resume Percent Add Episode Number Show Load Progress Loading Content Retrieving Data + Done - Processing Item : - Play Error - This item is not playable - Local path detected - Your MB3 Server contains local paths. Please change server paths to UNC or change XBMB3C setting 'Play from Stream' to true. Path: - Warning - Debug logging enabled. - This will affect performance. + Warning Error - Monitoring service is not running - If you have just installed please restart Kodi Search - Enable Theme Music (Requires Restart) - - Loop Theme Music - Enable Background Image (Requires Restart) - Services - - Skin does not support setting views - Select item action (Requires Restart) - - Sort NextUp by Show Title Enable Enhanced Images (eg CoverArt) Metadata Artwork Video Quality - Enable Suggested Loader (Requires Restart) - Add Season Number - Flatten Seasons - - Direct Play - HTTP - Direct Play + Direct Play Transcoding Server Detection Succeeded Found server @@ -176,39 +114,24 @@ TV Genres TV Networks TV Actors - Playlists - Search - Set Views - - Select User + Playlists - Profiling enabled. - Please remember to turn off when finished testing. - Error in ArtworkRotationThread + Set Views + Select User Unable to connect to server - Error in LoadMenuOptionsThread - - Enable Playlists Loader (Requires Restart) Songs Albums Album Artists Artists Music Genres - - Enable Theme Videos (Requires Restart) - - Loop Theme Videos - - AutoPlay remaining episodes in a season - Compress Artwork - Latest - In Progress - NextUp + + Latest + In Progress + NextUp User Views Report Metrics - Use Kodi Sorting - Runtime - + Random Movies Random Episodes Random Items @@ -220,15 +143,9 @@ Sync Movie BoxSets Reset local Kodi database - Enable watched/resume status sync - DB Sync Indication: - Play Count Sync Indication: Enable HTTPS Force Transcoding Codecs - Enable Netflix style next up notification - - The number of seconds before the end to show the notification - Show Emby Info dialog on play/select action Enable server connection message on startup Recently added Home Videos @@ -267,12 +184,6 @@ Set custom song rating (0-5) - Primary Server Address - Play from HTTP instead of SMB - Log level - Username - Port Number - Verify Host SSL Certificate Client SSL certificate Use alternate address @@ -357,5 +268,52 @@ Disable Emby music library? Direct stream the music library? Select this option if the music library will be remotely accessed. Delete file(s) from Emby Server? This will also delete the file(s) from disk! + Running the caching process may take some time. Continue anyway? + Artwork cache sync + Reset existing artwork cache? + Updating artwork cache: + Waiting for all threads to exit: + Kodi can't locate file: + You may need to verify your network credentials in the add-on settings or use the Emby path substitution to format your path correctly (Emby dashboard > library). Stop syncing? + Added: + If you fail to log in too many times, the Emby server might lock your account. Proceed anyway? + Live TV Channels (experimental) + Live TV Recordings (experimental) + Settings + Add user to session + Refresh Emby playlists/Video nodes + Perform manual sync + Repair local database (force update all content) + Perform local database reset + Cache all artwork + Sync Emby Theme Media to Kodi + Add/Remove user from the session + Add user + Remove user + Remove user from the session + Success! + Removed from viewing session: + Added to viewing session: + Unable to add/remove user from the session. + The task succeeded + The task failed + Direct Stream + Playback method for your themes + The settings file does not exist in TV Tunes. Change a setting and run the task again. + Are you sure you want to reset your local Kodi database? + Modify/Remove network credentials + Modify + Remove + Removed: + Enter the network username + Enter the network password + Added network credentials for: + Input the server name or IP address as indicated in your emby library paths. For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC" + Modify the server name or IP address + Enter the server name or IP address + Could not reset the database. Try again. + Remove all cached artwork? + Reset all Emby add-on settings? + Database reset has completed, Kodi will now restart to apply the changes. - + \ No newline at end of file diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 942a0d78..ea63add0 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -30,21 +30,19 @@ log = Logging('Entrypoint').log ################################################################################################# -def doPlayback(itemid, dbid): +def doPlayback(itemId, dbId): emby = embyserver.Read_EmbyServer() - item = emby.getItem(itemid) - pbutils.PlaybackUtils(item).play(itemid, dbid) + item = emby.getItem(itemId) + pbutils.PlaybackUtils(item).play(itemId, dbId) ##### DO RESET AUTH ##### def resetAuth(): # User tried login and failed too many times resp = xbmcgui.Dialog().yesno( - heading="Warning", - line1=( - "Emby might lock your account if you fail to log in too many times. " - "Proceed anyway?")) - if resp == 1: + heading=lang(30132), + line1=lang(33050)) + if resp: log("Reset login attempts.", 1) window('emby_serverStatus', value="Auth") else: @@ -58,6 +56,7 @@ def addDirectoryItem(label, path, folder=True): xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder) def doMainListing(): + xbmcplugin.setContent(int(sys.argv[1]), 'files') # Get emby nodes from the window props embyprops = window('Emby.nodes.total') @@ -68,39 +67,54 @@ def doMainListing(): if not path: path = window('Emby.nodes.%s.content' % i) label = window('Emby.nodes.%s.title' % i) - node_type = window('Emby.nodes.%s.type' % i) - #because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing. - #for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window - if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node_type == "photos": - addDirectoryItem(label, path) - elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node_type != "photos": - addDirectoryItem(label, path) - elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): - addDirectoryItem(label, path) + node = window('Emby.nodes.%s.type' % i) + + ''' because we do not use seperate entrypoints for each content type, + we need to figure out which items to show in each listing. + for now we just only show picture nodes in the picture library + video nodes in the video library and all nodes in any other window ''' - #experimental live tv nodes - addDirectoryItem("Live Tv Channels (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root") - addDirectoryItem("Live Tv Recordings (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root") + '''if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node == "photos": + addDirectoryItem(label, path) + elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") + and node != "photos": + addDirectoryItem(label, path) + elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | + Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): + addDirectoryItem(label, path)''' + + if path: + if xbmc.getCondVisibility("Window.IsActive(Pictures)") and node == "photos": + addDirectoryItem(label, path) + elif xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node != "photos": + addDirectoryItem(label, path) + else: + addDirectoryItem(label, path) + + # experimental live tv nodes + if not xbmc.getCondVisibility("Window.IsActive(Pictures)"): + addDirectoryItem(lang(33051), + "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root") + addDirectoryItem(lang(33052), + "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root") # some extra entries for settings and stuff. TODO --> localize the labels - addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords") - addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings") - addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser") - addDirectoryItem("Refresh Emby playlists/nodes", "plugin://plugin.video.emby/?mode=refreshplaylist") - addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync") - addDirectoryItem("Repair local database (force update all content)", "plugin://plugin.video.emby/?mode=repair") - addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset") - addDirectoryItem("Cache all images to Kodi texture cache", "plugin://plugin.video.emby/?mode=texturecache") - addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia") + addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords") + addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings") + addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser") + addDirectoryItem(lang(33055), "plugin://plugin.video.emby/?mode=refreshplaylist") + addDirectoryItem(lang(33056), "plugin://plugin.video.emby/?mode=manualsync") + addDirectoryItem(lang(33057), "plugin://plugin.video.emby/?mode=repair") + addDirectoryItem(lang(33058), "plugin://plugin.video.emby/?mode=reset") + addDirectoryItem(lang(33059), "plugin://plugin.video.emby/?mode=texturecache") + addDirectoryItem(lang(33060), "plugin://plugin.video.emby/?mode=thememedia") xbmcplugin.endOfDirectory(int(sys.argv[1])) - ##### Generate a new deviceId def resetDeviceId(): dialog = xbmcgui.Dialog() - language = utils.language deviceId_old = window('emby_deviceId') try: @@ -109,8 +123,8 @@ def resetDeviceId(): except Exception as e: log("Failed to generate a new device Id: %s" % e, 1) dialog.ok( - heading="Emby for Kodi", - line1=language(33032)) + heading=lang(29999), + line1=lang(33032)) else: log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( @@ -123,21 +137,21 @@ def deleteItem(): # Serves as a keymap action if xbmc.getInfoLabel('ListItem.Property(embyid)'): # If we already have the embyid - embyid = xbmc.getInfoLabel('ListItem.Property(embyid)') + itemId = xbmc.getInfoLabel('ListItem.Property(embyid)') else: - dbid = xbmc.getInfoLabel('ListItem.DBID') - itemtype = xbmc.getInfoLabel('ListItem.DBTYPE') + dbId = xbmc.getInfoLabel('ListItem.DBID') + itemType = xbmc.getInfoLabel('ListItem.DBTYPE') - if not itemtype: + if not itemType: if xbmc.getCondVisibility('Container.Content(albums)'): - itemtype = "album" + itemType = "album" elif xbmc.getCondVisibility('Container.Content(artists)'): - itemtype = "artist" + itemType = "artist" elif xbmc.getCondVisibility('Container.Content(songs)'): - itemtype = "song" + itemType = "song" elif xbmc.getCondVisibility('Container.Content(pictures)'): - itemtype = "picture" + itemType = "picture" else: log("Unknown type, unable to proceed.", 1) return @@ -145,7 +159,7 @@ def deleteItem(): embyconn = utils.kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - item = emby_db.getItem_byKodiId(dbid, itemtype) + item = emby_db.getItem_byKodiId(dbId, itemType) embycursor.close() try: @@ -156,17 +170,13 @@ def deleteItem(): if settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( - heading="Confirm delete", - line1=("Delete file from Emby Server? This will " - "also delete the file(s) from disk!")) + heading=lang(29999), + line1=lang(33041)) if not resp: - log("User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % itemId, 1) return - doUtils = downloadutils.DownloadUtils() - url = "{server}/emby/Items/%s?format=json" % embyid - log("Deleting request: %s" % embyid, 0) - doUtils.downloadUrl(url, action_type="DELETE") + embyserver.Read_EmbyServer().deleteItem(itemId) ##### ADD ADDITIONAL USERS ##### def addUser(): @@ -203,7 +213,7 @@ def addUser(): # Display dialog if there's additional users if additionalUsers: - option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"]) + option = dialog.select(lang(33061), [lang(33062), lang(33063)]) # Users currently in the session additionalUserlist = {} additionalUsername = [] @@ -216,15 +226,15 @@ def addUser(): if option == 1: # User selected Remove user - resp = dialog.select("Remove user from the session", additionalUsername) + resp = dialog.select(lang(33064), additionalUsername) if resp > -1: selected = additionalUsername[resp] selected_userId = additionalUserlist[selected] url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId) doUtils.downloadUrl(url, postBody={}, action_type="DELETE") dialog.notification( - heading="Success!", - message="%s removed from viewing session" % selected, + heading=lang(29999), + message="%s %s" % (lang(33066), selected), icon="special://home/addons/plugin.video.emby/icon.png", time=1000) @@ -256,16 +266,16 @@ def addUser(): url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId) doUtils.downloadUrl(url, postBody={}, action_type="POST") dialog.notification( - heading="Success!", - message="%s added to viewing session" % selected, + heading=lang(29999), + message="%s %s" % (lang(33067), selected), icon="special://home/addons/plugin.video.emby/icon.png", time=1000) except: log("Failed to add user to session.") dialog.notification( - heading="Error", - message="Unable to add/remove user from the session.", + heading=lang(29999), + message=lang(33068), icon=xbmcgui.NOTIFICATION_ERROR) # Add additional user images @@ -297,7 +307,7 @@ def getThemeMedia(): playback = None # Choose playback method - resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"]) + resp = dialog.select(lang(33072), [lang(30165), lang(33071)]) if resp == 0: playback = "DirectPlay" elif resp == 1: @@ -322,11 +332,7 @@ def getThemeMedia(): else: # if it does not exist this will not work so warn user # often they need to edit the settings first for it to be created. - dialog.ok( - heading="Warning", - line1=( - "The settings file does not exist in tvtunes. ", - "Go to the tvtunes addon and change a setting, then come back and re-run.")) + dialog.ok(heading=lang(29999), line1=lang(33073))0 xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') return @@ -442,8 +448,8 @@ def getThemeMedia(): nfo_file.close() dialog.notification( - heading="Emby for Kodi", - message="Themes added!", + heading=lang(29999), + message=lang(33069), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) @@ -461,8 +467,8 @@ def refreshPlaylist(): # Refresh views lib.refreshViews() dialog.notification( - heading="Emby for Kodi", - message="Emby playlists/nodes refreshed", + heading=lang(29999), + message=lang(33069), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) @@ -470,8 +476,8 @@ def refreshPlaylist(): except Exception as e: log("Refresh playlists/nodes failed: %s" % e, 1) dialog.notification( - heading="Emby for Kodi", - message="Emby playlists/nodes refresh failed", + heading=lang(29999), + message=lang(33070), icon=xbmcgui.NOTIFICATION_ERROR, time=1000, sound=False) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 197b71c5..934efcee 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -212,13 +212,8 @@ class Items(object): # Verify if direct path is accessible or not if window('emby_pathverified') != "true" and not xbmcvfs.exists(path): resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. " - "You may need to verify your network credentials in the " - "add-on settings or use the Emby path substitution " - "to format your path correctly (Emby dashboard > library). " - "Stop syncing?" % path)) + heading=lang(29999), + line1="%s %s. %s" % (lang(33047), path, lang(33048))) if resp: window('emby_shouldStop', value="true") return False @@ -230,8 +225,8 @@ class Items(object): if time: # It's possible for the time to be 0. It should be considered disabled in this case. xbmcgui.Dialog().notification( - heading="Emby for Kodi", - message="Added: %s" % name, + heading=lang(29999), + message="%s %s" % (lang(33049), name), icon="special://home/addons/plugin.video.emby/icon.png", time=time, sound=False) @@ -571,9 +566,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - log( - "Update playstate for movie: %s fileid: %s" - % (item['Name'], fileid), 1) + log("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1) except TypeError: return @@ -2373,19 +2366,19 @@ class Music(Items): log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) - def removeSong(self, kodiid): + def removeSong(self, kodiId): kodicursor = self.kodicursor - self.artwork.deleteArtwork(kodiid, "song", self.kodicursor) - self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,)) + self.artwork.deleteArtwork(kodiId, "song", self.kodicursor) + self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiId,)) - def removeAlbum(self, kodiid): + def removeAlbum(self, kodiId): - self.artwork.deleteArtwork(kodiid, "album", self.kodicursor) - self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,)) + self.artwork.deleteArtwork(kodiId, "album", self.kodicursor) + self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiId,)) - def removeArtist(self, kodiid): + def removeArtist(self, kodiId): - self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor) - self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,)) + self.artwork.deleteArtwork(kodiId, "artist", self.kodicursor) + self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiId,)) \ No newline at end of file diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index cc217de9..16baa487 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -94,7 +94,7 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - log("No kodiid returned.", 1) + log("No kodiId returned.", 1) else: url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid result = doUtils.downloadUrl(url) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 6d047148..e31a83cb 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -320,7 +320,7 @@ class LibrarySync(threading.Thread): window('emby_initialScan', clear=True) if forceddialog: xbmcgui.Dialog().notification( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s %s" % (message, lang(33025), str(elapsedtotal).split('.')[0]), icon="special://home/addons/plugin.video.emby/icon.png", @@ -541,7 +541,7 @@ class LibrarySync(threading.Thread): self.vnodes.singleNode(totalnodes, "channels", "movies", "channels") totalnodes += 1 # Save total - utils.window('Emby.nodes.total', str(totalnodes)) + window('Emby.nodes.total', str(totalnodes)) # Remove any old referenced views log("Removing views: %s" % current_views, 1) @@ -567,7 +567,7 @@ class LibrarySync(threading.Thread): # Get items per view if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33017), view['name'])) # Initial or repair sync @@ -596,7 +596,7 @@ class LibrarySync(threading.Thread): ##### PROCESS BOXSETS ##### if pdialog: - pdialog.update(heading="Emby for Kodi", message=lang(33018)) + pdialog.update(heading=lang(29999), message=lang(33018)) boxsets = self.emby.getBoxset(dialog=pdialog) total = boxsets['TotalRecordCount'] @@ -642,7 +642,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33019), viewName)) # Initial or repair sync @@ -688,7 +688,7 @@ class LibrarySync(threading.Thread): # Get items per view if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33020), view['name'])) all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog) @@ -743,7 +743,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33021), itemtype)) all_embyitems = process[itemtype][0](dialog=pdialog) @@ -900,13 +900,13 @@ class LibrarySync(threading.Thread): except Exception as e: window('emby_dbScan', clear=True) xbmcgui.Dialog().ok( - heading="Emby for Kodi", + heading=lang(29999), line1=( "Library sync thread has exited! " "You should restart Kodi now. " "Please report this on the forum.")) raise - + @utils.profiling() def run_internal(self): dialog = xbmcgui.Dialog() @@ -934,10 +934,10 @@ class LibrarySync(threading.Thread): log("Database version out of date: %s minimum version required: %s" % (currentVersion, minVersion), 0) - resp = dialog.yesno("Emby for Kodi", lang(33022)) + resp = dialog.yesno(lang(29999), lang(33022)) if not resp: log("Database version is out of date! USER IGNORED!", 0) - dialog.ok("Emby for Kodi", lang(33023)) + dialog.ok(lang(29999), lang(33023)) else: utils.reset() @@ -958,7 +958,7 @@ class LibrarySync(threading.Thread): "to know which Kodi versions are supported.", 0) dialog.ok( - heading="Emby for Kodi", + heading=lang(29999), line1=lang(33024)) break @@ -1060,7 +1060,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33026), viewName)) all_embymovies = self.emby.getMovies(viewId, basic=True, dialog=pdialog) @@ -1104,7 +1104,7 @@ class ManualSync(LibrarySync): embyboxsets = [] if pdialog: - pdialog.update(heading="Emby for Kodi", message=lang(33027)) + pdialog.update(heading=lang(29999), message=lang(33027)) for boxset in boxsets['Items']: @@ -1183,7 +1183,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33028), viewName)) all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog) @@ -1269,7 +1269,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33029), viewName)) all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog) @@ -1314,7 +1314,7 @@ class ManualSync(LibrarySync): # Get all episodes in view if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33030), viewName)) all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog) @@ -1402,7 +1402,7 @@ class ManualSync(LibrarySync): for data_type in ['artists', 'albums', 'songs']: if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33031), data_type)) if data_type != "artists": all_embyitems = process[data_type][0](basic=True, dialog=pdialog) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 8e3c9a08..d364bbb3 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -135,7 +135,7 @@ class PlayUtils(): # Let the user know that direct play failed settings('failCount', value=str(count+1)) dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33011), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) @@ -144,7 +144,7 @@ class PlayUtils(): settings('playFromStream', value="true") settings('failCount', value="0") dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33012), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index ce985647..c5d9caeb 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -221,7 +221,7 @@ class UserClient(threading.Thread): log("Access is granted.", 1) self.HasAccess = True window('emby_serverStatus', clear=True) - xbmcgui.Dialog().notification("Emby for Kodi", lang(33007)) + xbmcgui.Dialog().notification(lang(29999), lang(33007)) def loadCurrUser(self, authenticated=False): @@ -350,7 +350,7 @@ class UserClient(threading.Thread): if accessToken is not None: self.currUser = username - dialog.notification("Emby for Kodi", + dialog.notification(lang(29999), "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) settings('accessToken', value=accessToken) settings('userId%s' % username, value=result['User']['Id']) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 77220304..74f216a7 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -260,7 +260,7 @@ def reset(): dialog = xbmcgui.Dialog() - if not dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?"): + if not dialog.yesno(language(29999), language(33074)): return # first stop any db sync @@ -270,7 +270,7 @@ def reset(): log("Sync is running, will retry: %s..." % count) count -= 1 if count == 0: - dialog.ok("Warning", "Could not stop the database from running. Try again.") + dialog.ok(language(29999), language(33085)) return xbmc.sleep(1000) @@ -322,7 +322,7 @@ def reset(): cursor.close() # Offer to wipe cached thumbnails - resp = dialog.yesno("Warning", "Remove all cached artwork?") + resp = dialog.yesno(language(29999), language(33086)) if resp: log("Resetting all cached artwork.", 0) # Remove all existing textures first @@ -353,7 +353,7 @@ def reset(): settings('SyncInstallRunDone', value="false") # Remove emby info - resp = dialog.yesno("Warning", "Reset all Emby Addon settings?") + resp = dialog.yesno(language(29999), language(33087)) if resp: # Delete the settings addon = xbmcaddon.Addon() @@ -362,9 +362,7 @@ def reset(): 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.") + dialog.ok(heading=language(29999), line1=language(33088)) xbmc.executebuiltin('RestartApp') def sourcesXML(): @@ -424,7 +422,7 @@ def passwordsXML(): credentials = settings('networkCreds') if credentials: # Present user with options - option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"]) + option = dialog.select(language(33075), [language(33076), language(33077)]) if option < 0: # User cancelled dialog @@ -444,8 +442,8 @@ def passwordsXML(): settings('networkCreds', value="") xbmcgui.Dialog().notification( - heading="Emby for Kodi", - message="%s removed from passwords.xml" % credentials, + heading=language(29999), + message="%s %s" % (language(33078), credentials), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) @@ -453,28 +451,22 @@ def passwordsXML(): elif option == 0: # User selected to modify - server = dialog.input("Modify the computer name or ip address", credentials) + server = dialog.input(language(33083), credentials) if not server: return else: # No credentials added - dialog.ok( - heading="Network credentials", - line1= ( - "Input the server name or IP address as indicated in your emby library paths. " - 'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".')) - server = dialog.input("Enter the server name or IP address") + dialog.ok(heading=language(29999), line1=language(33082)) + server = dialog.input(language(33084)) if not server: return # Network username - user = dialog.input("Enter the network username") + user = dialog.input(language(33079)) if not user: return # Network password - password = dialog.input( - heading="Enter the network password", - option=xbmcgui.ALPHANUM_HIDE_INPUT) + password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT) if not password: return @@ -503,8 +495,8 @@ def passwordsXML(): etree.ElementTree(root).write(xmlpath) dialog.notification( - heading="Emby for Kodi", - message="%s added to passwords.xml" % server, + heading=language(29999), + message="%s %s" % (language(33081), server), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 90e9499e..87d1e012 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -80,7 +80,7 @@ class WebSocket_Client(threading.Thread): if command == "PlayNow": dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s" % (len(itemIds), lang(33004)), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) @@ -89,7 +89,7 @@ class WebSocket_Client(threading.Thread): elif command == "PlayNext": dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s" % (len(itemIds), lang(33005)), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) @@ -244,7 +244,7 @@ class WebSocket_Client(threading.Thread): elif messageType == "ServerRestarting": if settings('supressRestartMsg') == "true": dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33006), icon="special://home/addons/plugin.video.emby/icon.png") From cb18f17dbeae4648f0e94086b7d52093d6b0ec42 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 21:21:24 -0500 Subject: [PATCH 26/28] Fix typo --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index ea63add0..9ecb8bd3 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -332,7 +332,7 @@ def getThemeMedia(): else: # if it does not exist this will not work so warn user # often they need to edit the settings first for it to be created. - dialog.ok(heading=lang(29999), line1=lang(33073))0 + dialog.ok(heading=lang(29999), line1=lang(33073)) xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') return From 8423342371d92a5ce7c7e0f2be3445fcb88e22cd Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 21:23:36 -0500 Subject: [PATCH 27/28] Fix active listing --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 9ecb8bd3..14e08196 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -88,7 +88,7 @@ def doMainListing(): addDirectoryItem(label, path) elif xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node != "photos": addDirectoryItem(label, path) - else: + elif not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): addDirectoryItem(label, path) # experimental live tv nodes From 8575a1404357cc2ba8c51490123ea957c83d437f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 22:18:04 -0500 Subject: [PATCH 28/28] Version bump 2.2.11 --- addon.xml | 2 +- changelog.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 67c54a29..1a78b02f 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 3a2da525..93b264fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +version 2.2.11 +- Preparation for feature requests +- Add option to refresh Emby items via context menu +- Minor fixes + version 2.2.10 - Add keymap action for delete content: RunPlugin(plugin://plugin.video.emby?mode=delete) - Fix various bugs