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 diff --git a/contextmenu.py b/contextmenu.py index c91da6a1..b690fd8f 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -10,149 +10,159 @@ 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 api import artwork -import utils -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 +log = Logging('ContextMenu').log -def logMsg(msg, lvl=1): - utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl) +################################################################################################# - -#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: - embyconn = utils.kodiSQL('emby') + 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] - - logMsg("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: + + dialog = xbmcgui.Dialog() + + 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(utils.language(30402)) - if likes == False or likes == None: - #Like the item - options.append(utils.language(30403)) - if likes == True or likes == None: - #Dislike the item - options.append(utils.language(30404)) - if favourite == False: - #Add to emby favourites - options.append(utils.language(30405)) - if favourite == True: - #Remove from emby favourites - options.append(utils.language(30406)) - if itemtype == "song": - #Set custom song rating - options.append(utils.language(30407)) - - #delete item - options.append(utils.language(30409)) - - #addon settings - options.append(utils.language(30408)) - - #display select dialog and process results - header = utils.language(30401) - ret = xbmcgui.Dialog().select(header, options) - if ret != -1: - if options[ret] == utils.language(30402): - emby.updateUserRating(embyid, deletelike=True) - if options[ret] == utils.language(30403): - emby.updateUserRating(embyid, like=True) - if options[ret] == utils.language(30404): - emby.updateUserRating(embyid, like=False) - if options[ret] == utils.language(30405): - emby.updateUserRating(embyid, favourite=True) - if options[ret] == utils.language(30406): - emby.updateUserRating(embyid, favourite=False) - if options[ret] == utils.language(30407): - kodiconn = utils.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 utils.settings('enableUpdateSongRating') == "true": - musicutils.updateRatingToFile(newvalue, API.getFilePath()) - if utils.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() + options = [] - if options[ret] == utils.language(30408): - #Open addon settings + if favourite: + # Remove from emby favourites + options.append(lang(30406)) + else: + # Add to emby favourites + options.append(lang(30405)) + + if itemType == "song": + # Set custom song rating + options.append(lang(30407)) + + # Refresh item + options.append(lang(30410)) + # Delete item + options.append(lang(30409)) + # Addon settings + options.append(lang(30408)) + + # Display select dialog and process results + resp = xbmcgui.Dialog().select(lang(30401), options) + if resp > -1: + selected = options[resp] + + if selected == lang(30410): + # Refresh item + emby.refreshItem(itemId) + elif selected == lang(30405): + # Add favourite + emby.updateUserRating(itemId, favourite=True) + elif selected == lang(30406): + # Delete favourite + emby.updateUserRating(itemId, favourite=False) + elif selected == lang(30407): + # Update song rating + kodiconn = kodiSQL('music') + kodicursor = kodiconn.cursor() + 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 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] == utils.language(30409): - #delete item from the server + elif selected == lang(30409): + # delete item from the server delete = True - if utils.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 settings('skipContextMenu') != "true": + resp = dialog.yesno( + heading=lang(29999), + line1=lang(33041)) if not resp: - logMsg("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 - logMsg("Deleting request: %s" % embyid, 0) - doUtils.downloadUrl(url, action_type="DELETE") - - '''if utils.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 + xbmc.executebuiltin('Container.Refresh') \ No newline at end of file diff --git a/default.py b/default.py index 2a9ff290..3912b4f5 100644 --- a/default.py +++ b/default.py @@ -12,30 +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, language as lang +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') @@ -70,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 @@ -86,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] @@ -102,16 +106,17 @@ class Main: # Other functions 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) + xbmcgui.Dialog().ok(heading=lang(29999), + line1=lang(33034)) + log("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,35 +126,17 @@ class Main: else: lib.fullSync(repair=True) else: - utils.logMsg("EMBY", "Database scan is already running.", 1) + log("Database scan is already running.", 1) elif mode == "texturecache": import artwork - artwork.Artwork().FullTextureCacheSync() + artwork.Artwork().fullTextureCacheSync() + else: entrypoint.doMainListing() - -if ( __name__ == "__main__" ): - xbmc.log('plugin.video.emby started') - - 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() - - xbmc.log('plugin.video.emby stopped') \ No newline at end of file + +if __name__ == "__main__": + log('plugin.video.emby started', 1) + 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..ee2dc385 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -1,56 +1,28 @@  + + Emby for Kodi Primary Server Address Play from HTTP instead of SMB Log level - Username: - Password: - Network Username: - Network Password: - Transcode: - Enable Performance Profiling - Local caching system - - Emby - Network - Device Name - + 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: - Mark watched at start of playback: - Set Season poster for episodes - Genre Filter ... - Play All from Here + Number of recent Music Albums to show: + Number of recent Movies to show: + Number of recent TV episodes to show: + 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 @@ -75,73 +47,44 @@ 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 - Address : + Address: Recently Added TV Shows @@ -171,38 +114,24 @@ TV Genres TV Networks TV Actors - Playlists - Search - Set Views - - Select User - Profiling enabled. - Please remember to turn off when finished testing. - Error in ArtworkRotationThread + Playlists + + 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 @@ -214,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 @@ -252,14 +175,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) Verify Host SSL Certificate @@ -299,7 +221,8 @@ Server messages Generate a new device Id Sync when screensaver is deactivated - Force Transcode Hi10P + Force Transcode Hi10P + Disabled Welcome @@ -337,4 +260,60 @@ 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. + 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/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..f2cd0a32 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, language as lang, 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 @@ -164,130 +165,138 @@ 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 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=lang(29999), + line1=lang(33042)): 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(lang(29999), lang(33043)) # 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(lang(29999), lang(33044)): + 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, "%s %s" % (lang(33045), 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, "%s %s" % (lang(33045), message)) + self.cacheTexture(url[0]) + count += 1 + + 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) - 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, "%s %s" % (lang(33046), 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.setUrl(self.double_urlencode(url)) newThread.setHost(self.xbmc_host, self.xbmc_port) newThread.setAuth(self.xbmc_username, self.xbmc_password) newThread.start() 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)) @@ -397,7 +406,7 @@ class Artwork(): 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)" % (kodiId, imageUrl), 2) query = ( ''' @@ -413,13 +422,12 @@ class Artwork(): 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)" + log("Updating Art url for %s kodiId: %s (%s) -> (%s)" % (imageType, kodiId, url, imageUrl), 1) query = ' '.join(( @@ -434,9 +442,9 @@ class Artwork(): # Cache fanart and poster in Kodi texture cache if cacheimage and imageType in ("fanart", "poster"): - self.CacheTexture(imageUrl) + self.cacheTexture(imageUrl) - def deleteArtwork(self, kodiid, mediatype, cursor): + def deleteArtwork(self, kodiId, mediaType, cursor): query = ' '.join(( @@ -445,18 +453,18 @@ class Artwork(): "WHERE media_id = ?", "AND media_type = ?" )) - cursor.execute(query, (kodiid, mediatype,)) + 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): # Only necessary to remove and apply a new backdrop or poster - connection = utils.kodiSQL('texture') + connection = kodiSQL('texture') cursor = connection.cursor() try: @@ -464,21 +472,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 +509,26 @@ class Artwork(): return people - def getUserArtwork(self, itemid, itemtype): + def getUserArtwork(self, itemId, itemType): # Load user information set by UserClient image = ("%s/emby/Users/%s/Images/%s?Format=original" - % (self.server, itemid, itemtype)) + % (self.server, itemId, itemType)) return image def getAllArtwork(self, item, parentInfo=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 = { @@ -601,4 +609,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/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/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/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): diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index bc81ad0a..14e08196 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -24,28 +24,27 @@ import playlist import playbackutils as pbutils import playutils import api - +from utils import Logging, window, settings, language as lang +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: - utils.logMsg("EMBY", "Reset login attempts.", 1) - utils.window('emby_serverStatus', value="Auth") + heading=lang(30132), + line1=lang(33050)) + if resp: + log("Reset login attempts.", 1) + window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -57,65 +56,80 @@ 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 = 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) - #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) + path = window('Emby.nodes.%s.content' % i) + label = window('Emby.nodes.%s.title' % i) + 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) + elif not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): + 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 = 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("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: - utils.logMsg("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 @@ -123,50 +137,46 @@ 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: - utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1) + log("Unknown type, unable to proceed.", 1) return 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: embyid = item[0] except TypeError: - utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1) + log("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!")) + heading=lang(29999), + line1=lang(33041)) if not resp: - utils.logMsg("EMBY delete", "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 - utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0) - doUtils.downloadUrl(url, action_type="DELETE") + embyserver.Read_EmbyServer().deleteItem(itemId) ##### ADD ADDITIONAL USERS ##### def addUser(): @@ -176,7 +186,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 @@ -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,21 +226,21 @@ 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) # 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 +257,7 @@ def addUser(): return # Subtract any additional users - utils.logMsg("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: @@ -256,25 +266,25 @@ 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: - utils.logMsg("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.", + heading=lang(29999), + message=lang(33068), icon=xbmcgui.NOTIFICATION_ERROR) # Add additional user images # 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 +294,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 ##### @@ -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: @@ -318,15 +328,11 @@ 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("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. - 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)) 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,17 +467,17 @@ 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) except Exception as e: - utils.logMsg("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", + heading=lang(29999), + message=lang(33070), icon=xbmcgui.NOTIFICATION_ERROR, time=1000, sound=False) @@ -480,9 +486,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 +516,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("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 +801,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 +1049,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("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 +1078,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - utils.logMsg("EMBY", "Found cached backdrop.", 2) + log("Found cached backdrop.", 2) # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1083,7 +1089,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - utils.logMsg("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 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..da0a5108 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,66 @@ 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.addonId = clientinfo.ClientInfo().getAddonId() + self.doUtils = downloadutils.DownloadUtils().downloadUrl 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 + dialog = xbmcgui.Dialog() ##### 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)) + server_confirm = dialog.yesno( + heading=lang(29999), + line1=lang(33034), + 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) - - url = "%s/emby/Users/Public?format=json" % server - result = self.doUtils.downloadUrl(url, authenticate=False) + log("Getting user list.", 1) + + result = self.doUtils("%s/emby/Users/Public?format=json" % server, 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,66 +95,55 @@ 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 = 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) + 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: - 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( - 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: - 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?") + heading=lang(29999), + line1=lang(33039)) 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: 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: - 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 +157,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/itemtypes.py b/resources/lib/itemtypes.py index 18c90517..934efcee 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, kodiSQL ################################################################################################## @@ -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)) @@ -102,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: @@ -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): @@ -192,7 +190,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) @@ -204,19 +202,31 @@ 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=lang(29999), + line1="%s %s. %s" % (lang(33047), path, lang(33048))) + if resp: + window('emby_shouldStop', value="true") + return False + + return True + def contentPop(self, name, time=5000): 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) @@ -272,11 +282,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 +300,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 +348,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 +360,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 +377,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 +397,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 +417,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 +527,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 +541,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,9 +566,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - self.logMsg( - "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 @@ -585,7 +582,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 +598,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 +624,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 +664,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 +682,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 +723,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 +743,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 +769,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 +869,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 +901,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 +927,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 +990,8 @@ class TVShows(Items): artwork = self.artwork API = api.API(item) - if utils.settings('syncEmptyShows') == "false" and not item['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 +1002,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 +1019,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 +1027,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 +1064,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 +1076,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 +1094,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 +1165,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 +1214,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 +1232,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,10 +1256,9 @@ 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 - seriesName = item['SeriesName'] season = item.get('ParentIndexNumber') episode = item.get('IndexNumber', -1) @@ -1320,7 +1294,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) @@ -1337,22 +1311,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 @@ -1368,7 +1331,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): @@ -1402,7 +1365,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) @@ -1505,7 +1468,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: @@ -1524,7 +1487,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: @@ -1562,7 +1525,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 @@ -1652,14 +1615,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): @@ -1667,7 +1630,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): @@ -1676,7 +1639,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): @@ -1685,12 +1648,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): @@ -1750,7 +1713,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') @@ -1777,13 +1740,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) @@ -1831,7 +1794,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') @@ -1862,13 +1825,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) @@ -2001,7 +1964,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() @@ -2048,27 +2011,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 = ?" @@ -2091,7 +2042,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) @@ -2104,27 +2055,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: @@ -2306,7 +2257,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 @@ -2314,8 +2265,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 @@ -2345,7 +2296,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 @@ -2413,21 +2364,21 @@ 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): + 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/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..16baa487 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 d3a441dd..e31a83cb 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() @@ -333,9 +320,9 @@ 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, 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 @@ -553,23 +541,22 @@ 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 - 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: @@ -580,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 @@ -604,12 +591,12 @@ 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 ##### 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'] @@ -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: @@ -655,8 +642,8 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( - heading="Emby for Kodi", - message="%s %s..." % (utils.language(33019), viewName)) + heading=lang(29999), + 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: @@ -701,8 +688,8 @@ class LibrarySync(threading.Thread): # Get items per view if pdialog: pdialog.update( - heading="Emby for Kodi", - message="%s %s..." % (utils.language(33020), view['name'])) + heading=lang(29999), + 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 @@ -756,8 +743,8 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( - heading="Emby for Kodi", - message="%s %s..." % (utils.language(33021), itemtype)) + heading=lang(29999), + 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,25 +898,22 @@ 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", + 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): - 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,13 +931,13 @@ 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)) + resp = dialog.yesno(lang(29999), lang(33022)) if not resp: - self.logMsg("Database version is out of date! USER IGNORED!", 0) - dialog.ok("Emby for Kodi", lang(33023)) + log("Database version is out of date! USER IGNORED!", 0) + dialog.ok(lang(29999), lang(33023)) else: utils.reset() @@ -967,24 +951,24 @@ 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 " "to know which Kodi versions are supported.", 0) dialog.ok( - heading="Emby for Kodi", + heading=lang(29999), line1=lang(33024)) 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: @@ -1077,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) @@ -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[:] @@ -1121,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']: @@ -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: @@ -1200,8 +1183,8 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", - message="%s %s..." % (utils.language(33028), viewName)) + heading=lang(29999), + 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: @@ -1287,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) @@ -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[:] @@ -1332,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) @@ -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[:] @@ -1363,7 +1345,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) @@ -1373,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 @@ -1419,8 +1402,8 @@ class ManualSync(LibrarySync): for data_type in ['artists', 'albums', 'songs']: if pdialog: pdialog.update( - heading="Emby for Kodi", - message="%s %s..." % (utils.language(33031), data_type)) + heading=lang(29999), + message="%s %s..." % (lang(33031), data_type)) if data_type != "artists": all_embyitems = process[data_type][0](basic=True, dialog=pdialog) else: @@ -1445,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[:] @@ -1466,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..66da24a1 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 +log = Logging('MusicTools').log ################################################################################################# # 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) - 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/playbackutils.py b/resources/lib/playbackutils.py index 4fbbc636..4da87f27 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,7 +265,7 @@ 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 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/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..d364bbb3 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,25 +117,25 @@ 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 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) @@ -151,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) @@ -192,27 +185,26 @@ 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') + 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 @@ -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 @@ -325,11 +316,10 @@ 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): - lang = utils.language dialog = xbmcgui.Dialog() # For transcoding only # Present the list of audio to select from diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 3121a07a..5e62425e 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 @@ -518,23 +539,36 @@ 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: - self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST") - elif favourite == False: - self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, 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") - elif not deletelike and like is False: - self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, 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}/FavoriteItems/%s?format=json" % itemid + doUtils(url, action_type="POST") + elif not favourite: + url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?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 | favourite: %s" % (itemid, favourite), 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") + + def deleteItem(self, itemid): + + url = "{server}/emby/Items/%s?format=json" % itemId + self.doUtils(url, action_type="DELETE") \ No newline at end of file diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index f068b772..c5d9caeb 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(lang(29999), 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,37 +335,38 @@ 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: 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']) - 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..74f216a7 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 @@ -20,66 +20,82 @@ import xbmcgui import xbmcvfs ################################################################################################# +# Main methods + +class Logging(): + + LOGGINGCLASS = None -def logMsg(title, msg, level=1): + def __init__(self, classname=""): - # Get the logLevel set in UserClient - try: - logLevel = int(window('emby_logLevel')) - except ValueError: - logLevel = 0 + self.LOGGINGCLASS = classname - if logLevel >= level: + def log(self, msg, level=1): - 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'))) + self.logMsg("EMBY %s" % self.LOGGINGCLASS, msg, level) -def window(property, value=None, clear=False, windowid=10000): + 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(windowid) - - #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")''' + 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 +114,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 +130,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,141 +164,10 @@ def setScreensaver(value): 'value': value } } - logMsg("EMBY", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1) + result = xbmc.executeJSONRPC(json.dumps(query)) + log("Toggling screensaver: %s %s" % (value, result), 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 - -def convertdate(date): +def convertDate(date): try: date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") except TypeError: @@ -344,6 +232,139 @@ 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(language(29999), language(33074)): + return + + # first stop any db sync + window('emby_shouldStop', value="true") + count = 10 + while window('emby_dbScan') == "true": + log("Sync is running, will retry: %s..." % count) + count -= 1 + if count == 0: + dialog.ok(language(29999), language(33085)) + 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(language(29999), language(33086)) + 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(language(29999), language(33087)) + 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=language(29999), line1=language(33088)) + xbmc.executebuiltin('RestartApp') + def sourcesXML(): # To make Master lock compatible path = xbmc.translatePath("special://profile/").decode('utf-8') @@ -401,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 @@ -413,17 +434,16 @@ 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( - 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) @@ -431,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 @@ -473,7 +487,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) @@ -481,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) @@ -501,7 +515,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 +523,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 +531,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 +549,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 +571,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..87d1e012 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,11 +77,10 @@ class WebSocket_Client(threading.Thread): command = data['PlayCommand'] pl = playlist.Playlist() - dialog = xbmcgui.Dialog() 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) @@ -97,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) @@ -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=lang(29999), + 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/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 b54a4a7c..311e00a9 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 @@ -177,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'))), @@ -252,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, @@ -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):