diff --git a/addon.xml b/addon.xml index cf14026e..f6a82d42 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 03f8f2c7..baa14c63 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +version 2.2.8 +- Fix to photos not displaying directories without picutres. +- Fix to grouped views causing crash + version 2.2.7 - Prevent Kodi screensaver during the initial sync diff --git a/default.py b/default.py index e4b5173b..0cc69761 100644 --- a/default.py +++ b/default.py @@ -51,7 +51,6 @@ class Main: 'reset': utils.reset, 'resetauth': entrypoint.resetAuth, - 'extrafanart': entrypoint.getExtraFanArt, 'play': entrypoint.doPlayback, 'passwords': utils.passwordsXML, 'adduser': entrypoint.addUser, @@ -70,8 +69,15 @@ class Main: 'doPlexTvLogin': entrypoint.doPlexTvLogin } - if "extrafanart" in sys.argv[0]: - entrypoint.getExtraFanArt() + if "/extrafanart" in sys.argv[0]: + embypath = sys.argv[2][1:] + embyid = params.get('id',[""])[0] + entrypoint.getExtraFanArt(embyid,embypath) + + 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) if modes.get(mode): # Simple functions diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index ee753cee..51e5c48c 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -364,6 +364,7 @@ Failed to authenticate. Did you login to plex.tv? [COLOR yellow]Log into plex.tv[/COLOR] Automatically log into plex.tv on startup + Enable constant background sync (restart Kodi!) diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index 4c44a3af..d42cb99a 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -295,6 +295,7 @@ Plex Media Server Authentifizierung fehlgeschlagen. Haben Sie sich bei plex.tv eingeloggt? [COLOR yellow]Bei plex.tv einloggen[/COLOR] Automatisch beim Starten bei plex.tv einloggen + Laufende Synchronisierung im Hintergrund aktivieren (Neustart!) Plex Home Benutzer wechseln diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index a8330722..a3d68ea8 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -79,7 +79,6 @@ class PlexAPI(): self.deviceName = client.getDeviceName() self.plexversion = client.getVersion() self.platform = client.getPlatform() - self.user = utils.window('plex_username') self.userId = utils.window('emby_currUser') self.token = utils.window('emby_accessToken%s' % self.userId) self.server = utils.window('emby_server%s' % self.userId) @@ -95,13 +94,15 @@ class PlexAPI(): 'plexid': utils.settings('plexid'), 'myplexlogin': utils.settings('myplexlogin') + plexLogin is unicode or empty unicode string u'' + Returns empty strings '' for a setting if not found. myplexlogin is 'true' if user opted to log into plex.tv (the default) plexhome is 'true' if plex home is used (the default) """ return { - 'plexLogin': utils.settings('plexLogin'), + 'plexLogin': utils.settings('plexLogin').decode('utf-8'), 'plexToken': utils.settings('plexToken'), 'plexhome': utils.settings('plexhome'), 'plexid': utils.settings('plexid'), @@ -127,32 +128,31 @@ class PlexAPI(): retrievedPlexLogin = '' plexLogin = 'dummy' authtoken = '' + dialog = xbmcgui.Dialog() while retrievedPlexLogin == '' and plexLogin != '': - dialog = xbmcgui.Dialog() # Enter plex.tv username. Or nothing to cancel. plexLogin = dialog.input( - self.addonName + string(39300), + self.addonName.encode('utf-8') + string(39300).encode('utf-8'), type=xbmcgui.INPUT_ALPHANUM, ) if plexLogin != "": - dialog = xbmcgui.Dialog() # Enter password for plex.tv user plexPassword = dialog.input( - string(39301) + plexLogin, + string(39301).encode('utf-8') + plexLogin.encode('utf-8'), type=xbmcgui.INPUT_ALPHANUM, option=xbmcgui.ALPHANUM_HIDE_INPUT ) retrievedPlexLogin, authtoken = self.MyPlexSignIn( plexLogin, plexPassword, - {'X-Plex-Client-Identifier': self.clientId} - ) + {'X-Plex-Client-Identifier': self.clientId}) self.logMsg("plex.tv username and token: %s, %s" % (plexLogin, authtoken), 1) if plexLogin == '': - dialog = xbmcgui.Dialog() # Could not sign in user - dialog.ok(self.addonName, string(39302) + plexLogin) + dialog.ok(self.addonName, + string(39302).encode('utf-8') + + plexLogin.encode('utf-8')) # Write to Kodi settings file utils.settings('plexLogin', value=retrievedPlexLogin) utils.settings('plexToken', value=authtoken) @@ -177,12 +177,12 @@ class PlexAPI(): dialog = xbmcgui.Dialog() if not code: # Problems trying to contact plex.tv. Try again later - dialog.ok(self.addonName, string(39303)) + dialog.ok(self.addonName, string(39303).encode('utf-8')) return False # Go to https://plex.tv/pin and enter the code: answer = dialog.yesno(self.addonName, - string(39304) + "\n\n", - code) + (string(39304) + "\n\n").encode('utf-8'), + code.encode('utf-8')) if not answer: return False count = 0 @@ -196,7 +196,7 @@ class PlexAPI(): count += 1 if not xml: # Could not sign in to plex.tv Try again later - dialog.ok(self.addonName, string(39305)) + dialog.ok(self.addonName, string(39305).encode('utf-8')) return False # Parse xml userid = xml.attrib.get('id') @@ -259,11 +259,11 @@ class PlexAPI(): try: code = xml.find('code').text identifier = xml.find('id').text + self.logMsg('Successfully retrieved code and id from plex.tv', 1) + return code, identifier except: self.logMsg("Error, no PIN from plex.tv provided", -1) - self.logMsg("plex.tv/pin: Code is: %s" % code, 2) - self.logMsg("plex.tv/pin: Identifier is: %s" % identifier, 2) - return code, identifier + return None, None def TalkToPlexServer(self, url, talkType="GET", verify=True, token=None): """ @@ -345,8 +345,8 @@ class PlexAPI(): sslverify = True else: sslverify = False - self.logMsg("Checking connection to server %s with header %s and " - "sslverify=%s" % (url, header, sslverify), 1) + self.logMsg("Checking connection to server %s with sslverify=%s" + % (url, sslverify), 1) timeout = (3, 10) if 'plex.tv' in url: url = 'https://plex.tv/api/home/users' @@ -814,7 +814,6 @@ class PlexAPI(): 'X-Plex-Version': self.plexversion, 'X-Plex-Client-Identifier': self.clientId, 'X-Plex-Provides': 'player', - 'X-Plex-Username': self.user, 'X-Plex-Client-Capabilities': 'protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac,dts{bitrate:800000&channels:8},ac3{bitrate:800000&channels:8}', 'X-Plex-Client-Profile-Extra': 'add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=*&audioCodec=dca,ac3)', } @@ -1042,6 +1041,8 @@ class PlexAPI(): self.logMsg("No URL for user avatar.", 1) return False for user in users: + self.logMsg('type user: %s, type username: %s' + % (type(user['title']), type(username))) if username in user['title']: url = user['thumb'] self.logMsg("Avatar url for user %s is: %s" % (username, url), 1) @@ -1060,6 +1061,7 @@ class PlexAPI(): Will return empty strings if failed. """ string = self.__language__ + dialog = xbmcgui.Dialog() plexLogin = utils.settings('plexLogin') plexToken = utils.settings('plexToken') @@ -1074,19 +1076,21 @@ class PlexAPI(): return ('', '', '') userlist = [] + userlistCoded = [] for user in users: username = user['title'] userlist.append(username) + userlistCoded.append(username.encode('utf-8')) usernumber = len(userlist) usertoken = '' # Plex home not in use: only 1 user returned trials = 0 while trials < 3: if usernumber > 1: - dialog = xbmcgui.Dialog() # Select user - user_select = dialog.select(self.addonName + string(39306), - userlist) + user_select = dialog.select( + (self.addonName + string(39306)).encode('utf-8'), + userlistCoded) if user_select == -1: self.logMsg("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' @@ -1101,11 +1105,10 @@ class PlexAPI(): user = users[user_select] # Ask for PIN, if protected: if user['protected'] == '1': - dialog = xbmcgui.Dialog() # Please enter pin for user self.logMsg('Asking for users PIN', 1) pin = dialog.input( - string(39307) + selected_user, + (string(39307) + selected_user).encode('utf-8'), type=xbmcgui.INPUT_NUMERIC, option=xbmcgui.ALPHANUM_HIDE_INPUT) # User chose to cancel @@ -1114,6 +1117,9 @@ class PlexAPI(): else: pin = None # Switch to this Plex Home user, if applicable + # Plex bug: don't call url for protected user with empty PIN + if user['protected'] == '1' and not pin: + break username, usertoken = self.PlexSwitchHomeUser( user['id'], pin, @@ -1122,12 +1128,11 @@ class PlexAPI(): ) # Couldn't get user auth if not username: - dialog = xbmcgui.Dialog() # Could not login user, please try again if not dialog.yesno( self.addonName, - string(39308) + selected_user, - string(39309) + (string(39308) + selected_user).encode('utf-8'), + string(39309).encode('utf-8') ): # User chose to cancel break @@ -1158,8 +1163,7 @@ class PlexAPI(): url = 'https://plex.tv/api/home/users/' + userId + '/switch' if pin: url += '?pin=' + pin - self.logMsg('Switching to user %s with url %s and machineId %s' - % (userId, url, machineId), 0) + self.logMsg('Switching to user %s' % userId, 0) answer = self.TalkToPlexServer(url, talkType="POST", token=token) if not answer: self.logMsg('Error: plex.tv switch HomeUser change failed', -1) @@ -1188,7 +1192,7 @@ class PlexAPI(): self.logMsg("username: %s, token: xxxx. " "Saving to window and file settings" % username, 0) utils.window('emby_currUser', value=userId) - utils.settings('userId%s' % username, value=userId) + utils.settings('userId', value=userId) utils.settings('username', value=username) utils.window('emby_accessToken%s' % userId, value=token) return (username, token) @@ -1411,7 +1415,6 @@ class API(): self.part = 0 self.clientinfo = clientinfo.ClientInfo() self.clientId = self.clientinfo.getDeviceId() - self.user = utils.window('plex_username') self.userId = utils.window('emby_currUser') self.server = utils.window('emby_server%s' % self.userId) self.token = utils.window('emby_accessToken%s' % self.userId) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index ca9af7ea..9bface89 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -162,7 +162,8 @@ class Artwork(): import xbmcaddon string = xbmcaddon.Addon().getLocalizedString - if not xbmcgui.Dialog().yesno("Image Texture Cache", string(39250)): + if not xbmcgui.Dialog().yesno( + "Image Texture Cache", string(39250).encode('utf-8')): return self.logMsg("Doing Image Cache Sync", 1) @@ -171,11 +172,12 @@ class Artwork(): dialog.create("Emby for Kodi", "Image Cache Sync") # ask to rest all existing or not - if xbmcgui.Dialog().yesno("Image Texture Cache", string(39251), ""): + if xbmcgui.Dialog().yesno( + "Image Texture Cache", string(39251).encode('utf-8'), ""): self.logMsg("Resetting all cache data first", 1) # Remove all existing textures first path = xbmc.translatePath("special://thumbnails/").decode('utf-8') - if xbmcvfs.exists(path): + if utils.IfExists(path): allDirs, allFiles = xbmcvfs.listdir(path) for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path+dir) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 7046b9c1..5080bccd 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -5,7 +5,6 @@ import json import os import sys -import urlparse import xbmc import xbmcaddon @@ -156,7 +155,7 @@ def resetAuth(): string = xbmcaddon.Addon().getLocalizedString resp = xbmcgui.Dialog().yesno( heading="Warning", - line1=string(39206)) + line1=string(39206).encode('utf-8')) if resp == 1: utils.logMsg("PLEX", "Reset login attempts.", 1) utils.window('emby_serverStatus', value="Auth") @@ -226,14 +225,14 @@ def resetDeviceId(): "Failed to generate a new device Id: %s" % e, 1) dialog.ok( heading=addonName, - line1=language(33032)) + line1=language(33032).encode('utf-8')) else: utils.logMsg(addonName, "Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( heading=addonName, - line1=language(33033)) + line1=language(33033).encode('utf-8')) xbmc.executebuiltin('RestartApp') ##### ADD ADDITIONAL USERS ##### @@ -399,7 +398,7 @@ def getThemeMedia(): library = xbmc.translatePath( "special://profile/addon_data/plugin.video.plexkodiconnect/library/").decode('utf-8') # Create library directory - if not xbmcvfs.exists(library): + if not utils.IfExists(library): xbmcvfs.mkdir(library) # Set custom path for user @@ -572,10 +571,10 @@ def BrowseContent(viewname, type="", folderid=""): if not folderid: views = emby.getViews(type) for view in views: - if view.get("name") == viewname: + if view.get("name") == viewname.decode('utf-8'): folderid = view.get("id") - utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname, type, folderid, filter)) + utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), type.decode('utf-8'), folderid.decode('utf-8'), filter.decode('utf-8'))) #set the correct params for the content type #only proceed if we have a folderid if folderid: @@ -584,7 +583,7 @@ def BrowseContent(viewname, type="", folderid=""): itemtype = "Video,Folder,PhotoAlbum" elif type.lower() == "photos": xbmcplugin.setContent(int(sys.argv[1]), 'files') - itemtype = "Photo,PhotoAlbum" + itemtype = "Photo,PhotoAlbum,Folder" else: itemtype = "" @@ -610,14 +609,13 @@ def BrowseContent(viewname, type="", folderid=""): li = createListItemFromEmbyItem(item,art,doUtils) if item.get("IsFolder") == True: #for folders we add an additional browse request, passing the folderId - path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0], viewname, type, item.get("Id")) + path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0].decode('utf-8'), viewname.decode('utf-8'), type.decode('utf-8'), item.get("Id").decode('utf-8')) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True) else: #playable item, set plugin path and mediastreams xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) if filter == "recent": xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) else: @@ -626,6 +624,8 @@ def BrowseContent(viewname, type="", folderid=""): xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) + xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) + ##### CREATE LISTITEM FROM EMBY METADATA ##### def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()): API = api.API(item) @@ -1066,28 +1066,45 @@ def getRecentEpisodes(tagname, limit): xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) +##### GET VIDEO EXTRAS FOR LISTITEM ##### +def getVideoFiles(embyId,embyPath): + #returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc. + emby = embyserver.Read_EmbyServer() + if not embyId: + if "plugin.video.emby" in embyPath: + embyId = embyPath.split("/")[-2] + if embyId: + item = emby.getItem(embyId) + putils = playutils.PlayUtils(item) + if putils.isDirectPlay(): + #only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside + filelocation = putils.directPlay() + if not filelocation.endswith("/"): + filelocation = filelocation.rpartition("/")[0] + dirs, files = xbmcvfs.listdir(filelocation) + for file in files: + file = filelocation + file + li = xbmcgui.ListItem(file, path=file) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li) + for dir in dirs: + dir = filelocation + dir + li = xbmcgui.ListItem(dir, path=dir) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True) + xbmcplugin.endOfDirectory(int(sys.argv[1])) + ##### GET EXTRAFANART FOR LISTITEM ##### -def getExtraFanArt(): +def getExtraFanArt(embyId,embyPath): emby = embyserver.Read_EmbyServer() art = artwork.Artwork() - embyId = "" # Get extrafanart for listitem # will be called by skinhelper script to get the extrafanart try: # for tvshows we get the embyid just from the path - if xbmc.getCondVisibility("Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes)"): - itemPath = xbmc.getInfoLabel("ListItem.Path").decode('utf-8') - if "plugin.video.plexkodiconnect" in itemPath: - embyId = itemPath.split("/")[-2] - else: - #for movies we grab the emby id from the params - itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath").decode('utf-8') - if "plugin.video.plexkodiconnect" in itemPath: - params = urlparse.parse_qs(itemPath) - embyId = params.get('id') - if embyId: embyId = embyId[0] + if not embyId: + if "plugin.video.emby" in embyPath: + embyId = embyPath.split("/")[-2] if embyId: #only proceed if we actually have a emby id @@ -1131,7 +1148,7 @@ def getExtraFanArt(): url=fanartFile, listitem=li) except Exception as e: - utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 1) + utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0) # Always do endofdirectory to prevent errors in the logs xbmcplugin.endOfDirectory(int(sys.argv[1])) @@ -1142,6 +1159,6 @@ def RunLibScan(mode): # Server is not online, do not run the sync string = xbmcaddon.Addon().getLocalizedString xbmcgui.Dialog().ok(heading=addonName, - line1=string(39205)) + line1=string(39205).encode('utf-8')) else: utils.window('plex_runLibScan', value='full') diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 3544149d..1853695c 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -20,10 +20,6 @@ import PlexAPI class InitialSetup(): def __init__(self): - - self.addon = xbmcaddon.Addon() - self.__language__ = self.addon.getLocalizedString - self.clientInfo = clientinfo.ClientInfo() self.addonId = self.clientInfo.getAddonId() self.doUtils = downloadutils.DownloadUtils() @@ -36,7 +32,7 @@ class InitialSetup(): Check server, user, direct paths, music, direct stream if not direct path. """ - string = self.__language__ + string = xbmcaddon.Addon().getLocalizedString # SERVER INFO ##### self.logMsg("Initial setup called.", 0) server = self.userClient.getServer() @@ -63,7 +59,7 @@ class InitialSetup(): # Could not login, please try again dialog.ok( self.addonName, - string(39009) + string(39009).encode('utf-8') ) result = self.plx.PlexTvSignInWithPin() if result: @@ -74,7 +70,7 @@ class InitialSetup(): # Problems connecting to plex.tv. Network or internet issue? dialog.ok( self.addonName, - string(39010) + string(39010).encode('utf-8') ) # If a Plex server IP has already been set, return. if server and forcePlexTV is False: @@ -92,36 +88,40 @@ class InitialSetup(): plexToken = result['token'] plexid = result['plexid'] # Get g_PMS list of servers (saved to plx.g_PMS) + httpsUpdated = False while True: - tokenDict = {'MyPlexToken': plexToken} if plexToken else {} - # Populate g_PMS variable with the found Plex servers - self.plx.discoverPMS(clientId, - None, - xbmc.getIPAddress(), - tokenDict=tokenDict) - self.logMsg("Result of setting g_PMS variable: %s" - % self.plx.g_PMS, 2) - isconnected = False - serverlist = self.plx.returnServerList(clientId, self.plx.g_PMS) - # Let user pick server from a list - # Get a nicer list - dialoglist = [] - # Exit if no servers found - if len(serverlist) == 0: - dialog.ok( - self.addonName, - string(39011) - ) - break - for server in serverlist: - if server['local'] == '1': - # server is in the same network as client. Add "local" - dialoglist.append(str(server['name']) + string(39022)) - else: - dialoglist.append(str(server['name'])) - resp = dialog.select( - string(39012), - dialoglist) + if httpsUpdated is False: + tokenDict = {'MyPlexToken': plexToken} if plexToken else {} + # Populate g_PMS variable with the found Plex servers + self.plx.discoverPMS(clientId, + None, + xbmc.getIPAddress(), + tokenDict=tokenDict) + self.logMsg("Result of setting g_PMS variable: %s" + % self.plx.g_PMS, 1) + isconnected = False + serverlist = self.plx.returnServerList( + clientId, self.plx.g_PMS) + self.logMsg('PMS serverlist: %s' % serverlist) + # Let user pick server from a list + # Get a nicer list + dialoglist = [] + # Exit if no servers found + if len(serverlist) == 0: + dialog.ok( + self.addonName, + string(39011).encode('utf-8') + ) + break + for server in serverlist: + if server['local'] == '1': + # server is in the same network as client. Add "local" + dialoglist.append( + server['name'].encode('utf-8') + + string(39022).encode('utf-8')) + else: + dialoglist.append(server['name'].encode('utf-8')) + resp = dialog.select(string(39012).encode('utf-8'), dialoglist) server = serverlist[resp] activeServer = server['machineIdentifier'] url = server['scheme'] + '://' + server['ip'] + ':' + \ @@ -136,13 +136,19 @@ class InitialSetup(): self.logMsg("Setting SSL verify to true, because server is " "not local", 1) chk = self.plx.CheckConnection(url, server['accesstoken']) - # Unauthorized + if chk == 504 and httpsUpdated is False: + # Not able to use HTTP, try HTTPs for now + serverlist[resp]['scheme'] = 'https' + httpsUpdated = True + continue + httpsUpdated = False if chk == 401: # Not yet authorized for Plex server # Please sign in to plex.tv dialog.ok(self.addonName, - string(39013) + str(server['name']), - string(39014)) + string(39013).encode('utf-8') + + server['name'].encode('utf-8'), + string(39014).encode('utf-8')) result = self.plx.PlexTvSignInWithPin() if result: plexLogin = result['username'] @@ -154,7 +160,8 @@ class InitialSetup(): # Problems connecting elif chk >= 400 or chk is False: # Problems connecting to server. Pick another server? - resp = dialog.yesno(self.addonName, string(39015)) + resp = dialog.yesno(self.addonName, + string(39015).encode('utf-8')) # Exit while loop if user chooses No if not resp: break @@ -166,7 +173,7 @@ class InitialSetup(): # Enter Kodi settings instead if dialog.yesno( heading=self.addonName, - line1=string(39016)): + line1=string(39016).encode('utf-8')): self.logMsg("User opted to disable Plex music library.", 1) utils.settings('enableMusic', value="false") xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) @@ -202,12 +209,12 @@ class InitialSetup(): if dialog.yesno( heading=self.addonName, - line1=string(39016)): + line1=string(39016).encode('utf-8')): self.logMsg("User opted to disable Plex music library.", 1) utils.settings('enableMusic', value="false") if dialog.yesno( heading=self.addonName, - line1=string(39017)): + line1=string(39017).encode('utf-8')): xbmc.executebuiltin( 'Addon.OpenSettings(plugin.video.plexkodiconnect)') diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 4e685462..2382f26c 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -164,10 +164,10 @@ class ThreadedShowSyncInfo(Thread): threadStopped = self.threadStopped downloadLock = self.locks[0] processLock = self.locks[1] - dialog.create("%s: Sync %s: %s items" - % (self.addonName.encode('utf-8'), - self.itemType.encode('utf-8'), - str(total)), + dialog.create(("%s: Sync %s: %s items" + % (self.addonName, + self.itemType, + str(total))).encode('utf-8'), "Starting") global getMetadataCount global processMetadataCount @@ -188,9 +188,9 @@ class ThreadedShowSyncInfo(Thread): try: dialog.update( percentage, - message="Downloaded: %s. Processed: %s: %s" - % (getMetadataProgress, processMetadataProgress, - viewName.decode('utf-8'))) + message=("Downloaded: %s. Processed: %s: %s" + % (getMetadataProgress, processMetadataProgress, + viewName))).encode('utf-8') except: # Wierd formating of the string viewName?!? pass @@ -225,6 +225,9 @@ class LibrarySync(Thread): utils.settings('dbSyncIndicator') == 'true' else False self.enableMusic = True if utils.settings('enableMusic') == "true" \ else False + self.enableBackgroundSync = True if utils.settings( + 'enableBackgroundSync') == "true" \ + else False Thread.__init__(self) @@ -236,7 +239,7 @@ class LibrarySync(Thread): return xbmcgui.Dialog().notification( heading=self.addonName, - message=message, + message=message.encode('utf-8'), icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", sound=False) @@ -1253,7 +1256,7 @@ class LibrarySync(Thread): time=3000, sound=True) window('emby_dbScan', clear=True) - else: + elif self.enableBackgroundSync: # Run full lib scan approx every 30min if count >= 1800: count = 0 diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index 92fbaf66..c58cb245 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -214,7 +214,7 @@ def getSongTags(file): except Exception as e: #file in use ? - logMsg("Exception in getSongTags %s" %e,0) + utils.logMsg("Exception in getSongTags", str(e),0) rating = None #remove tempfile if needed.... diff --git a/resources/lib/player.py b/resources/lib/player.py index b0372da3..24a5a037 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -523,7 +523,10 @@ class Player(xbmc.Player): # Plex: never delete offerDelete = False if percentComplete >= markPlayedAt and offerDelete: - resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) + resp = xbmcgui.Dialog().yesno( + lang(30091).encode('utf-8'), + lang(33015).encode('utf-8'), + autoclose=120000) if not resp: log("User skipped deletion.", 1) continue diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index 116921c5..9d0334ee 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -8,7 +8,6 @@ from urllib import urlencode import xbmc import xbmcgui -import playbackutils import embydb_functions as embydb import read_embyserver as embyserver import utils diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 9aaa9516..8a382f1a 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -343,7 +343,7 @@ class PlayUtils(): #audioStreamsChannelsList[audioNum] = stream.attrib['channels'] audioStreamsList.append(index) - audioStreams.append(track) + audioStreams.append(track.encode('utf-8')) audioNum += 1 # Subtitles @@ -367,11 +367,11 @@ class PlayUtils(): downloadableStreams.append(index) subtitleStreamsList.append(index) - subtitleStreams.append(track) + subtitleStreams.append(track.encode('utf-8')) subNum += 1 if audioNum > 1: - resp = dialog.select(lang(33013), audioStreams) + resp = dialog.select(lang(33013).encode('utf-8'), audioStreams) if resp > -1: # User selected audio playurlprefs['audioStreamID'] = audioStreamsList[resp] @@ -384,7 +384,7 @@ class PlayUtils(): playurlprefs['audioBoost'] = utils.settings('audioBoost') if subNum > 1: - resp = dialog.select(lang(33014), subtitleStreams) + resp = dialog.select(lang(33014).encode('utf-8'), subtitleStreams) if resp == 0: # User selected no subtitles playurlprefs["skipSubtitles"] = 1 diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index d92d05c7..9f907e73 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -262,14 +262,14 @@ class Read_EmbyServer(): retry = 0 while utils.window('emby_online') != "true": # Wait server to come back online - if retry == 3: + if retry == 5: log("Unable to reconnect to server. Abort process.", 1) - return + return items retry += 1 if xbmc.Monitor().waitForAbort(1): # Abort was requested while waiting. - return + return items else: # Request succeeded index += jump @@ -321,18 +321,18 @@ class Read_EmbyServer(): # Filter view types continue - # 11/10/2015 Review key, when it's added to server. Currently unavailable. - itemtype = item.get('OriginalCollectionType', item.get('CollectionType')) + # 3/4/2016 OriginalCollectionType is added + itemtype = item.get('OriginalCollectionType', item.get('CollectionType', "mixed")) # 11/29/2015 Remove this once OriginalCollectionType is added to stable server. # Assumed missing is mixed then. - if itemtype is None: + '''if itemtype is None: url = "{server}/emby/Library/MediaFolders?format=json" result = doUtils(url) for folder in result['Items']: if itemId == folder['Id']: - itemtype = folder.get('CollectionType', "mixed") + itemtype = folder.get('CollectionType', "mixed")''' if name not in ('Collections', 'Trailers'): diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 2a31f58e..c5a073ef 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -54,12 +54,15 @@ class UserClient(threading.Thread): self.AdditionalUser = additionalUsers.split(',') def getUsername(self): + """ + Returns username as unicode + """ - username = utils.settings('username') + username = utils.settings('username').decode('utf-8') if not username: self.logMsg("No username saved, trying to get Plex username", 0) - username = utils.settings('plexLogin') + username = utils.settings('plexLogin').decode('utf-8') if not username: self.logMsg("Also no Plex username found", 0) return "" @@ -84,33 +87,31 @@ class UserClient(threading.Thread): if username is None: username = self.getUsername() w_userId = window('emby_currUser') - s_userId = settings('userId%s' % username) + s_userId = settings('userId') # Verify the window property if w_userId: if not s_userId: # Save access token if it's missing from settings - settings('userId%s' % username, value=w_userId) - log("Returning userId from WINDOW for username: %s UserId: %s" - % (username, w_userId), 1) + settings('userId', value=w_userId) + log("Returning userId %s from WINDOW for username %s" + % (w_userId, username), 0) return w_userId # Verify the settings elif s_userId: - log("Returning userId from SETTINGS for username: %s userId: %s" - % (username, s_userId), 1) + log("Returning userId %s from SETTINGS for username %s" + % (w_userId, username), 0) return s_userId # No userId found - else: - log("No userId saved for username: %s. Trying to get Plex ID" - % username, 0) - plexId = settings('plexid') - if not plexId: - log('Also no Plex ID found in settings', 0) - return '' - log('Using Plex ID %s as userid for username: %s' - % (plexId, username)) - settings('userId%s' % username, value=plexId) - return plexId + log("No userId saved. Trying to get Plex to use instead", 0) + plexId = settings('plexid') + if not plexId: + log('Also no Plex ID found in settings', 0) + return '' + log('Using Plex ID %s as userid for username %s' + % (plexId, username), 0) + settings('userId', value=plexId) + return plexId def getServer(self, prefix=True): @@ -157,14 +158,14 @@ class UserClient(threading.Thread): if not s_token: # Save access token if it's missing from settings settings('accessToken', value=w_token) - log("Returning accessToken from WINDOW for username: %s accessToken: %s" - % (username, w_token), 2) + log("Returning accessToken from WINDOW for username: %s " + "accessToken: xxxx" % username, 2) return w_token # Verify the settings elif s_token: - log("Returning accessToken from SETTINGS for username: %s accessToken: %s" - % (username, s_token), 2) - window('emby_accessToken%s' % username, value=s_token) + log("Returning accessToken from SETTINGS for username: %s " + "accessToken: xxxx" % username, 2) + window('emby_accessToken%s' % userId, value=s_token) return s_token else: log("No token found.", 1) @@ -244,7 +245,9 @@ class UserClient(threading.Thread): log("Access is granted.", 1) self.HasAccess = True window('emby_serverStatus', clear=True) - xbmcgui.Dialog().notification(self.addonName, utils.language(33007)) + xbmcgui.Dialog().notification( + self.addonName, + utils.language(33007).encode('utf-8')) def loadCurrUser(self, authenticated=False): self.logMsg('Loading current user', 0) @@ -284,7 +287,6 @@ class UserClient(threading.Thread): window('plex_username', value=username) window('emby_accessToken%s' % userId, value=self.currToken) window('emby_server%s' % userId, value=self.currServer) - window('emby_server_%s' % userId, value=self.getServer(prefix=False)) window('plex_machineIdentifier', value=self.machineIdentifier) window('emby_serverStatus', clear=True) @@ -359,9 +361,8 @@ class UserClient(threading.Thread): # Check connection if plx.CheckConnection(server, accessToken) == 200: self.currUser = username - dialog = xbmcgui.Dialog() settings('accessToken', value=accessToken) - settings('userId%s' % username, value=userId) + settings('userId', value=userId) log("User authenticated with an access token", 1) if self.loadCurrUser(authenticated=True) is False: # Something went really wrong, return and try again @@ -372,7 +373,7 @@ class UserClient(threading.Thread): if username: dialog.notification( heading=self.addonName, - message="Welcome %s" % username.decode('utf-8'), + message=("Welcome " + username).encode('utf-8'), icon="special://home/addons/plugin.video.plexkodiconnect/icon.png") else: dialog.notification( @@ -384,13 +385,14 @@ class UserClient(threading.Thread): else: self.logMsg("Error: user authentication failed.", -1) settings('accessToken', value="") - settings('userId%s' % username, value="") + settings('userId', value="") # Give attempts at entering password / selecting user if self.retry >= 5: log("Too many retries.", 1) window('emby_serverStatus', value="Stop") - dialog.ok(lang(33001), lang(39023)) + dialog.ok(lang(33001).encode('utf-8'), + lang(39023).encode('utf-8')) xbmc.executebuiltin( 'Addon.OpenSettings(plugin.video.plexkodiconnect)') diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 3f7257a9..d2db3fdf 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -25,6 +25,27 @@ import xbmcvfs addonName = xbmcaddon.Addon().getAddonInfo('name') +def IfExists(path): + """ + Kodi's xbmcvfs.exists is broken - it caches the results for directories. + + path: path to a directory (with a slash at the end) + + Returns True if path exists, else false + """ + dummyfile = os.path.join(path, 'dummyfile.txt').encode('utf-8') + try: + etree.ElementTree(etree.Element('test')).write(dummyfile) + except: + # folder does not exist yet + answer = False + else: + # Folder exists. Delete file again. + xbmcvfs.delete(dummyfile) + answer = True + return answer + + def LogTime(func): """ Decorator for functions and methods to log the time it took to run the code @@ -207,18 +228,26 @@ def window(property, value=None, clear=False, windowid=10000): if clear: WINDOW.clearProperty(property) elif value is not None: + # Takes unicode or string by default! WINDOW.setProperty(property, value) else: #getproperty returns string so convert to unicode - return WINDOW.getProperty(property)#.decode("utf-8") + return WINDOW.getProperty(property) def settings(setting, value=None): - # Get or add addon setting + """ + Get or add addon setting. + + Settings needs to be string + Value can either be unicode or string + """ addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect') - + if value is not None: + # Takes string or unicode by default! addon.setSetting(setting, value) else: - return addon.getSetting(setting) #returns unicode object + # Returns unicode by default! + return addon.getSetting(setting) def language(stringid): # Central string retrieval @@ -410,8 +439,8 @@ def reset(): 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) + xbmcvfs.delete(dataPath.encode('utf-8')) + logMsg("PLEX", "Deleting: settings.xml", 1) dialog.ok( heading="Emby for Kodi", @@ -668,9 +697,9 @@ def passwordsXML(): sound=False) def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): - # Tagname is in unicode - actions: add or delete - tagname = tagname.encode('utf-8') - + """ + Feed with tagname as unicode + """ path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') if viewtype == "mixed": plname = "%s - %s" % (tagname, mediatype) @@ -680,15 +709,15 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): xsppath = "%sPlex %s.xsp" % (path, viewid) # Create the playlist directory - if not xbmcvfs.exists(path): + if not xbmcvfs.exists(path.encode('utf-8')): logMsg("PLEX", "Creating directory: %s" % path, 1) - xbmcvfs.mkdirs(path) + xbmcvfs.mkdirs(path.encode('utf-8')) # Only add the playlist if it doesn't already exists - if xbmcvfs.exists(xsppath): - + if xbmcvfs.exists(xsppath.encode('utf-8')): + logMsg('Path %s does exist' % xsppath, 1) if delete: - xbmcvfs.delete(xsppath) + xbmcvfs.delete(xsppath.encode('utf-8')) logMsg("PLEX", "Successfully removed playlist: %s." % tagname, 1) return @@ -699,21 +728,22 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): } logMsg("Plex", "Writing playlist file to: %s" % xsppath, 1) try: - f = xbmcvfs.File(xsppath, 'w') + f = xbmcvfs.File(xsppath.encode('utf-8'), 'wb') except: logMsg("Plex", "Failed to create playlist: %s" % xsppath, -1) return else: - f.write( + f.write(( '\n' '\n\t' 'Plex %s\n\t' 'all\n\t' '\n\t\t' '%s\n\t' - '' + '\n' '' % (itemtypes.get(mediatype, mediatype), plname, tagname)) + .encode('utf-8')) f.close() logMsg("Plex", "Successfully added playlist: %s" % tagname) @@ -721,26 +751,26 @@ def deletePlaylists(): # Clean up the playlists path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') - dirs, files = xbmcvfs.listdir(path) + dirs, files = xbmcvfs.listdir(path.encode('utf-8')) for file in files: - if file.decode('utf-8').startswith('Emby'): - xbmcvfs.delete("%s%s" % (path, file)) + if file.decode('utf-8').startswith('Plex'): + xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8')) def deleteNodes(): # Clean up video nodes import shutil path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - dirs, files = xbmcvfs.listdir(path) + dirs, files = xbmcvfs.listdir(path.encode('utf-8')) for dir in dirs: - if dir.decode('utf-8').startswith('Emby'): + if dir.decode('utf-8').startswith('Plex'): try: shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) except: - logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8')) + logMsg("PLEX", "Failed to delete directory: %s" % dir.decode('utf-8')) for file in files: - if file.decode('utf-8').startswith('emby'): + if file.decode('utf-8').startswith('plex'): try: - xbmcvfs.delete("%s%s" % (path, file.decode('utf-8'))) + xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8')) except: - logMsg("EMBY", "Failed to file: %s" % file.decode('utf-8')) \ No newline at end of file + logMsg("PLEX", "Failed to file: %s" % file.decode('utf-8')) \ No newline at end of file diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index 40865369..12768ad1 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -4,6 +4,7 @@ import shutil import xml.etree.ElementTree as etree +from os import path as ospath import xbmc import xbmcvfs @@ -41,7 +42,6 @@ class VideoNodes(object): return root def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): - # Plex: reassign mediatype due to Kodi inner workings mediatypes = { 'movie': 'movies', @@ -65,24 +65,29 @@ class VideoNodes(object): "special://profile/library/video/Plex-%s/" % dirname).decode('utf-8') # Verify the video directory - if not xbmcvfs.exists(path): + # KODI BUG + # Kodi caches the result of exists for directories + # so try creating a file + if utils.IfExists(path) is False: shutil.copytree( src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) - xbmcvfs.exists(path) # Create the node directory - if not xbmcvfs.exists(nodepath) and not mediatype == "photo": - # We need to copy over the default items - xbmcvfs.mkdirs(nodepath) - else: - if delete: - dirs, files = xbmcvfs.listdir(nodepath) - for file in files: - xbmcvfs.delete(nodepath + file) + if mediatype != "photo": + if utils.IfExists(nodepath) is False: + # folder does not exist yet + self.logMsg('Creating folder %s' % nodepath, 1) + xbmcvfs.mkdirs(nodepath.encode('utf-8')) + if delete: + dirs, files = xbmcvfs.listdir(nodepath.encode('utf-8')) + for file in files: + xbmcvfs.delete( + (nodepath + file.decode('utf-8')).encode('utf-8')) - self.logMsg("Sucessfully removed videonode: %s." % tagname, 1) - return + self.logMsg("Sucessfully removed videonode: %s." + % tagname, 1) + return # Create index entry nodeXML = "%sindex.xml" % nodepath @@ -239,7 +244,7 @@ class VideoNodes(object): # To do: add our photos nodes to kodi picture sources somehow continue - if xbmcvfs.exists(nodeXML): + if xbmcvfs.exists(nodeXML.encode('utf-8')): # Don't recreate xml if already exists continue diff --git a/resources/settings.xml b/resources/settings.xml index c12a66d3..517ec5f9 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -51,6 +51,7 @@ + diff --git a/service.py b/service.py index de478698..0074b26e 100644 --- a/service.py +++ b/service.py @@ -171,9 +171,8 @@ class Service(): self.welcome_msg = False xbmcgui.Dialog().notification( heading=self.addonName, - message=("%s %s" - % (lang(33000), - user.currUser.decode('utf-8'))), + message=("%s %s" % (lang(33000), user.currUser) + ).encode('utf-8'), icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", time=2000, sound=False) @@ -226,8 +225,8 @@ class Service(): window('emby_online', value="false") xbmcgui.Dialog().notification( - heading=lang(33001), - message="%s %s" % (self.addonName, lang(33002)), + heading=lang(33001).encode('utf-8'), + message=("%s %s" % (self.addonName, lang(33002))).encode('utf-8'), icon="special://home/addons/plugin.video." "plexkodiconnect/icon.png", sound=False) @@ -245,7 +244,7 @@ class Service(): # Alert the user that server is online. xbmcgui.Dialog().notification( heading=self.addonName, - message=lang(33003), + message=lang(33003).encode('utf-8'), icon="special://home/addons/plugin.video." "plexkodiconnect/icon.png", time=2000,