diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index f9173a57..61cfe113 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -316,7 +316,7 @@ class UserClient(threading.Thread): elif self.getToken(): result = self.loadCurrUser() - if result == False: + if result is False: pass else: self.logMsg("Current user: %s" % self.currUser, 1) @@ -355,11 +355,10 @@ class UserClient(threading.Thread): sha1 = sha1.hexdigest() # Authenticate username and password - url = "%s/emby/Users/AuthenticateByName?format=json" % server data = {'username': username, 'password': sha1} self.logMsg(data, 2) - result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False) + result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, type="POST", authenticate=False) try: self.logMsg("Auth response: %s" % result, 1) @@ -373,9 +372,8 @@ class UserClient(threading.Thread): self.currUser = username dialog.notification("Emby for Kodi", "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) - userId = result['User']['Id'] settings('accessToken', value=accessToken) - settings('userId%s' % username, value=userId) + settings('userId%s' % username, value=result['User']['Id']) self.logMsg("User Authenticated: %s" % accessToken, 1) self.loadCurrUser(authenticated=True) window('emby_serverStatus', clear=True) @@ -399,12 +397,10 @@ class UserClient(threading.Thread): def resetClient(self): self.logMsg("Reset UserClient authentication.", 1) - userId = self.getUserId() - if self.currToken is not None: # In case of 401, removed saved token utils.settings('accessToken', value="") - utils.window('emby_accessToken%s' % userId, clear=True) + utils.window('emby_accessToken%s' % self.getUserId(), clear=True) self.currToken = None self.logMsg("User token has been removed.", 1) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index c9b3474f..7b26e1d0 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -1,577 +1,564 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import cProfile -import inspect -import json -import pstats -import sqlite3 -import StringIO -import os -from datetime import datetime, time -import time -import unicodedata -import xml.etree.ElementTree as etree - -import xbmc -import xbmcaddon -import xbmcgui -import xbmcvfs - -################################################################################################# - - -def logMsg(title, msg, level=1): - - # Get the logLevel set in UserClient - try: - logLevel = int(window('emby_logLevel')) - except ValueError: - logLevel = 0 - - if logLevel >= level: - - if logLevel == 2: # inspect.stack() is expensive - try: - xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg)) - except UnicodeEncodeError: - xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8'))) - else: - try: - xbmc.log("%s -> %s" % (title, msg)) - except UnicodeEncodeError: - xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) - -def window(property, value=None, clear=False, windowid=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")''' - - 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") - -def settings(setting, value=None): - # Get or add addon setting - addon = xbmcaddon.Addon(id='plugin.video.emby') - - if value is not None: - addon.setSetting(setting, value) - else: - return addon.getSetting(setting) #returns unicode object - -def language(stringid): - # Central string retrieval - addon = xbmcaddon.Addon(id='plugin.video.emby') - string = addon.getLocalizedString(stringid) #returns unicode object - return string - -def kodiSQL(type="video"): - - if type == "emby": - dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8') - elif type == "music": - dbPath = getKodiMusicDBPath() - elif type == "texture": - dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8') - else: - dbPath = getKodiVideoDBPath() - - connection = sqlite3.connect(dbPath) - return connection - -def getKodiVideoDBPath(): - - kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2] - dbVersion = { - - "13": 78, # Gotham - "14": 90, # Helix - "15": 93, # Isengard - "16": 99 # Jarvis - } - - dbPath = xbmc.translatePath( - "special://database/MyVideos%s.db" - % dbVersion.get(kodibuild, "")).decode('utf-8') - return dbPath - -def getKodiMusicDBPath(): - - kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2] - dbVersion = { - - "13": 46, # Gotham - "14": 48, # Helix - "15": 52, # Isengard - "16": 56 # Jarvis - } - - dbPath = xbmc.translatePath( - "special://database/MyMusic%s.db" - % dbVersion.get(kodibuild, "")).decode('utf-8') - return dbPath - -def getScreensaver(): - # Get the current screensaver value - query = { - - 'jsonrpc': "2.0", - 'id': 0, - 'method': "Settings.getSettingValue", - 'params': { - - 'setting': "screensaver.mode" - } - } - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - screensaver = result['result']['value'] - - return screensaver - -def setScreensaver(value): - # Toggle the screensaver - query = { - - 'jsonrpc': "2.0", - 'id': 0, - 'method': "Settings.setSettingValue", - 'params': { - - 'setting': "screensaver.mode", - 'value': value - } - } - result = xbmc.executeJSONRPC(json.dumps(query)) - logMsg("EMBY", "Toggling screensaver: %s %s" % (value, result), 1) - -def reset(): - - dialog = xbmcgui.Dialog() - - resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") - if resp == 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", "Removed 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): - try: - date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") - except TypeError: - # TypeError: attribute of type 'NoneType' is not callable - # Known Kodi/python error - date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6])) - - return date - -def normalize_nodes(text): - # For video nodes - text = text.replace(":", "") - text = text.replace("/", "-") - text = text.replace("\\", "-") - text = text.replace("<", "") - text = text.replace(">", "") - text = text.replace("*", "") - text = text.replace("?", "") - text = text.replace('|', "") - text = text.replace('(', "") - text = text.replace(')', "") - text = text.strip() - # Remove dots from the last character as windows can not have directories - # with dots at the end - text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') - - return text - -def normalize_string(text): - # For theme media, do not modify unless - # modified in TV Tunes - text = text.replace(":", "") - text = text.replace("/", "-") - text = text.replace("\\", "-") - text = text.replace("<", "") - text = text.replace(">", "") - text = text.replace("*", "") - text = text.replace("?", "") - text = text.replace('|', "") - text = text.strip() - # Remove dots from the last character as windows can not have directories - # with dots at the end - text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') - - return text - -def indent(elem, level=0): - # Prettify xml trees - i = "\n" + level*" " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - indent(elem, level+1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - -def sourcesXML(): - # To make Master lock compatible - path = xbmc.translatePath("special://profile/").decode('utf-8') - xmlpath = "%ssources.xml" % path - - try: - xmlparse = etree.parse(xmlpath) - except: # Document is blank or missing - root = etree.Element('sources') - else: - root = xmlparse.getroot() - - - video = root.find('video') - if video is None: - video = etree.SubElement(root, 'video') - etree.SubElement(video, 'default', attrib={'pathversion': "1"}) - - # Add elements - count = 2 - for source in root.findall('.//path'): - if source.text == "smb://": - count -= 1 - - if count == 0: - # sources already set - break - else: - # Missing smb:// occurences, re-add. - for i in range(0, count): - source = etree.SubElement(video, 'source') - etree.SubElement(source, 'name').text = "Emby" - etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://" - etree.SubElement(source, 'allowsharing').text = "true" - # Prettify and write to file - try: - indent(root) - except: pass - etree.ElementTree(root).write(xmlpath) - -def passwordsXML(): - - # To add network credentials - path = xbmc.translatePath("special://userdata/").decode('utf-8') - xmlpath = "%spasswords.xml" % path - - try: - xmlparse = etree.parse(xmlpath) - except: # Document is blank or missing - root = etree.Element('passwords') - else: - root = xmlparse.getroot() - - dialog = xbmcgui.Dialog() - credentials = settings('networkCreds') - if credentials: - # Present user with options - option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"]) - - if option < 0: - # User cancelled dialog - return - - elif option == 1: - # User selected remove - iterator = root.getiterator('passwords') - - for paths in iterator: - for path in paths: - if path.find('.//from').text == "smb://%s/" % credentials: - paths.remove(path) - logMsg("EMBY", "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) - - settings('networkCreds', value="") - xbmcgui.Dialog().notification( - heading="Emby for Kodi", - message="%s removed from passwords.xml" % credentials, - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000, - sound=False) - return - - elif option == 0: - # User selected to modify - server = dialog.input("Modify the computer name or ip address", 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") - if not server: - return - - # Network username - user = dialog.input("Enter the network username") - if not user: - return - # Network password - password = dialog.input( - heading="Enter the network password", - option=xbmcgui.ALPHANUM_HIDE_INPUT) - if not password: - return - - # Add elements - for path in root.findall('.//path'): - if path.find('.//from').text.lower() == "smb://%s/" % server.lower(): - # Found the server, rewrite credentials - path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server) - break - else: - # Server not found, add it. - path = etree.SubElement(root, 'path') - etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server - topath = "smb://%s:%s@%s/" % (user, password, server) - etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath - # Force Kodi to see the credentials without restarting - xbmcvfs.exists(topath) - - # Add credentials - settings('networkCreds', value="%s" % server) - logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1) - # Prettify and write to file - try: - indent(root) - except: pass - etree.ElementTree(root).write(xmlpath) - - dialog.notification( - heading="Emby for Kodi", - message="%s added to passwords.xml" % server, - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000, - sound=False) - -def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): - # Tagname is in unicode - actions: add or delete - tagname = tagname.encode('utf-8') - - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') - if viewtype == "mixed": - plname = "%s - %s" % (tagname, mediatype) - xsppath = "%sEmby %s - %s.xsp" % (path, viewid, mediatype) - else: - plname = tagname - xsppath = "%sEmby %s.xsp" % (path, viewid) - - # Create the playlist directory - if not xbmcvfs.exists(path): - logMsg("EMBY", "Creating directory: %s" % path, 1) - xbmcvfs.mkdirs(path) - - # Only add the playlist if it doesn't already exists - if xbmcvfs.exists(xsppath): - - if delete: - xbmcvfs.delete(xsppath) - logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1) - - return - - # Using write process since there's no guarantee the xml declaration works with etree - itemtypes = { - 'homevideos': "movies" - } - logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1) - try: - f = xbmcvfs.File(xsppath, 'w') - except: - logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1) - return - else: - f.write( - '\n' - '\n\t' - 'Emby %s\n\t' - 'all\n\t' - '\n\t\t' - '%s\n\t' - '' - '' - % (itemtypes.get(mediatype, mediatype), plname, tagname)) - f.close() - logMsg("EMBY", "Successfully added playlist: %s" % tagname) - -def deletePlaylists(): - - # Clean up the playlists - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') - dirs, files = xbmcvfs.listdir(path) - for file in files: - if file.decode('utf-8').startswith('Emby'): - xbmcvfs.delete("%s%s" % (path, file)) - -def deleteNodes(): - - # Clean up video nodes - import shutil - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - dirs, files = xbmcvfs.listdir(path) - for dir in dirs: - if dir.decode('utf-8').startswith('Emby'): - try: - shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) - except: - logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8')) - for file in files: - if file.decode('utf-8').startswith('emby'): - try: - xbmcvfs.delete("%s%s" % (path, file.decode('utf-8'))) - except: +# -*- coding: utf-8 -*- + +################################################################################################# + +import cProfile +import inspect +import json +import pstats +import sqlite3 +import StringIO +import os +from datetime import datetime, time +import time +import unicodedata +import xml.etree.ElementTree as etree + +import xbmc +import xbmcaddon +import xbmcgui +import xbmcvfs + +################################################################################################# + + +def logMsg(title, msg, level=1): + + # Get the logLevel set in UserClient + try: + logLevel = int(window('emby_logLevel')) + except ValueError: + logLevel = 0 + + if logLevel >= level: + + if logLevel == 2: # inspect.stack() is expensive + try: + xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg)) + except UnicodeEncodeError: + xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8'))) + else: + try: + xbmc.log("%s -> %s" % (title, msg)) + except UnicodeEncodeError: + xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) + +def window(property, value=None, clear=False, windowid=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")''' + + 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") + +def settings(setting, value=None): + # Get or add addon setting + if value is not None: + xbmcaddon.Addon(id='plugin.video.metaman').setSetting(setting, value) + else: + return xbmcaddon.Addon(id='plugin.video.metaman').getSetting(setting) #returns unicode object + +def language(stringid): + # Central string retrieval + string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(stringid) #returns unicode object + return string + +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') + else: + dbPath = getKodiVideoDBPath() + + connection = sqlite3.connect(dbPath) + return connection + +def getKodiVideoDBPath(): + + dbVersion = { + + "13": 78, # Gotham + "14": 90, # Helix + "15": 93, # Isengard + "16": 99 # Jarvis + } + + dbPath = xbmc.translatePath( + "special://database/MyVideos%s.db" + % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') + return dbPath + +def getKodiMusicDBPath(): + + dbVersion = { + + "13": 46, # Gotham + "14": 48, # Helix + "15": 52, # Isengard + "16": 56 # Jarvis + } + + dbPath = xbmc.translatePath( + "special://database/MyMusic%s.db" + % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') + return dbPath + +def getScreensaver(): + # Get the current screensaver value + query = { + + 'jsonrpc': "2.0", + 'id': 0, + 'method': "Settings.getSettingValue", + 'params': { + + 'setting': "screensaver.mode" + } + } + return json.loads(xbmc.executeJSONRPC(json.dumps(query)))['result']['value'] + +def setScreensaver(value): + # Toggle the screensaver + query = { + + 'jsonrpc': "2.0", + 'id': 0, + 'method': "Settings.setSettingValue", + 'params': { + + 'setting': "screensaver.mode", + 'value': value + } + } + logMsg("EMBY", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1) + +def reset(): + + dialog = xbmcgui.Dialog() + + if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0: + return + + # first stop any db sync + window('emby_shouldStop', value="true") + count = 10 + while window('emby_dbScan') == "true": + logMsg("EMBY", "Sync is running, will retry: %s..." % count) + count -= 1 + if count == 0: + dialog.ok("Warning", "Could not stop the database from running. Try again.") + return + xbmc.sleep(1000) + + # Clean up the playlists + deletePlaylists() + + # Clean up the video nodes + deleteNodes() + + # Wipe the kodi databases + logMsg("EMBY", "Resetting the Kodi video database.", 0) + connection = kodiSQL('video') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tablename = row[0] + if tablename != "version": + cursor.execute("DELETE FROM " + tablename) + connection.commit() + cursor.close() + + if settings('enableMusic') == "true": + logMsg("EMBY", "Resetting the Kodi music database.") + connection = kodiSQL('music') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tablename = row[0] + if tablename != "version": + cursor.execute("DELETE FROM " + tablename) + connection.commit() + cursor.close() + + # Wipe the emby database + logMsg("EMBY", "Resetting the Emby database.", 0) + connection = kodiSQL('emby') + cursor = connection.cursor() + cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor.fetchall() + for row in rows: + tablename = row[0] + if tablename != "version": + cursor.execute("DELETE FROM " + tablename) + cursor.execute('DROP table IF EXISTS emby') + cursor.execute('DROP table IF EXISTS view') + connection.commit() + cursor.close() + + # Offer to wipe cached thumbnails + resp = dialog.yesno("Warning", "Removed 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): + try: + date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") + except TypeError: + # TypeError: attribute of type 'NoneType' is not callable + # Known Kodi/python error + date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6])) + + return date + +def normalize_nodes(text): + # For video nodes + text = text.replace(":", "") + text = text.replace("/", "-") + text = text.replace("\\", "-") + text = text.replace("<", "") + text = text.replace(">", "") + text = text.replace("*", "") + text = text.replace("?", "") + text = text.replace('|', "") + text = text.replace('(', "") + text = text.replace(')', "") + text = text.strip() + # Remove dots from the last character as windows can not have directories + # with dots at the end + text = text.rstrip('.') + text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') + + return text + +def normalize_string(text): + # For theme media, do not modify unless + # modified in TV Tunes + text = text.replace(":", "") + text = text.replace("/", "-") + text = text.replace("\\", "-") + text = text.replace("<", "") + text = text.replace(">", "") + text = text.replace("*", "") + text = text.replace("?", "") + text = text.replace('|', "") + text = text.strip() + # Remove dots from the last character as windows can not have directories + # with dots at the end + text = text.rstrip('.') + text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') + + return text + +def indent(elem, level=0): + # Prettify xml trees + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def sourcesXML(): + # To make Master lock compatible + path = xbmc.translatePath("special://profile/").decode('utf-8') + xmlpath = "%ssources.xml" % path + + try: + xmlparse = etree.parse(xmlpath) + except: # Document is blank or missing + root = etree.Element('sources') + else: + root = xmlparse.getroot() + + + video = root.find('video') + if video is None: + video = etree.SubElement(root, 'video') + etree.SubElement(video, 'default', attrib={'pathversion': "1"}) + + # Add elements + count = 2 + for source in root.findall('.//path'): + if source.text == "smb://": + count -= 1 + + if count == 0: + # sources already set + break + else: + # Missing smb:// occurences, re-add. + for i in range(0, count): + source = etree.SubElement(video, 'source') + etree.SubElement(source, 'name').text = "Emby" + etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://" + etree.SubElement(source, 'allowsharing').text = "true" + # Prettify and write to file + try: + indent(root) + except: pass + etree.ElementTree(root).write(xmlpath) + +def passwordsXML(): + + # To add network credentials + path = xbmc.translatePath("special://userdata/").decode('utf-8') + xmlpath = "%spasswords.xml" % path + + try: + xmlparse = etree.parse(xmlpath) + except: # Document is blank or missing + root = etree.Element('passwords') + else: + root = xmlparse.getroot() + + dialog = xbmcgui.Dialog() + credentials = settings('networkCreds') + if credentials: + # Present user with options + option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"]) + + if option < 0: + # User cancelled dialog + return + + elif option == 1: + # User selected remove + for paths in root.getiterator('passwords'): + for path in paths: + if path.find('.//from').text == "smb://%s/" % credentials: + paths.remove(path) + logMsg("EMBY", "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) + + settings('networkCreds', value="") + xbmcgui.Dialog().notification( + heading="Emby for Kodi", + message="%s removed from passwords.xml" % credentials, + icon="special://home/addons/plugin.video.emby/icon.png", + time=1000, + sound=False) + return + + elif option == 0: + # User selected to modify + server = dialog.input("Modify the computer name or ip address", 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") + if not server: + return + + # Network username + user = dialog.input("Enter the network username") + if not user: + return + # Network password + password = dialog.input( + heading="Enter the network password", + option=xbmcgui.ALPHANUM_HIDE_INPUT) + if not password: + return + + # Add elements + for path in root.findall('.//path'): + if path.find('.//from').text.lower() == "smb://%s/" % server.lower(): + # Found the server, rewrite credentials + path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server) + break + else: + # Server not found, add it. + path = etree.SubElement(root, 'path') + etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server + topath = "smb://%s:%s@%s/" % (user, password, server) + etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath + # Force Kodi to see the credentials without restarting + xbmcvfs.exists(topath) + + # Add credentials + settings('networkCreds', value="%s" % server) + logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1) + # Prettify and write to file + try: + indent(root) + except: pass + etree.ElementTree(root).write(xmlpath) + + dialog.notification( + heading="Emby for Kodi", + message="%s added to passwords.xml" % server, + icon="special://home/addons/plugin.video.emby/icon.png", + time=1000, + sound=False) + +def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): + # Tagname is in unicode - actions: add or delete + tagname = tagname.encode('utf-8') + + path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + if viewtype == "mixed": + plname = "%s - %s" % (tagname, mediatype) + xsppath = "%sEmby %s - %s.xsp" % (path, viewid, mediatype) + else: + plname = tagname + xsppath = "%sEmby %s.xsp" % (path, viewid) + + # Create the playlist directory + if not xbmcvfs.exists(path): + logMsg("EMBY", "Creating directory: %s" % path, 1) + xbmcvfs.mkdirs(path) + + # Only add the playlist if it doesn't already exists + if xbmcvfs.exists(xsppath): + + if delete: + xbmcvfs.delete(xsppath) + logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1) + + return + + # Using write process since there's no guarantee the xml declaration works with etree + itemtypes = { + 'homevideos': "movies" + } + logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1) + try: + f = xbmcvfs.File(xsppath, 'w') + except: + logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1) + return + else: + f.write( + '\n' + '\n\t' + 'Emby %s\n\t' + 'all\n\t' + '\n\t\t' + '%s\n\t' + '' + '' + % (itemtypes.get(mediatype, mediatype), plname, tagname)) + f.close() + logMsg("EMBY", "Successfully added playlist: %s" % tagname) + +def deletePlaylists(): + + # Clean up the playlists + path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + dirs, files = xbmcvfs.listdir(path) + for file in files: + if file.decode('utf-8').startswith('Emby'): + xbmcvfs.delete("%s%s" % (path, file)) + +def deleteNodes(): + + # Clean up video nodes + import shutil + path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + dirs, files = xbmcvfs.listdir(path) + for dir in dirs: + if dir.decode('utf-8').startswith('Emby'): + try: + shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) + except: + logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8')) + 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')) \ No newline at end of file