From 56ec6d6ec1e922aec8733400a741853302024240 Mon Sep 17 00:00:00 2001 From: croneter Date: Thu, 3 Mar 2016 17:29:14 +0100 Subject: [PATCH 01/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f9601277..ce6826d4 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ 7. Once you're succesfully authenticated to your Plex server, the initial sync will start. 8. The first sync of the Plex server to local Kodi database may take a LONG time. With my setup (~400 movies, ~600 episodes, couple of Test music albums and a very powerful NAS), sync take approximately 5 minutes. 9. Once the full sync is done, you can browse your media in Kodi, syncs will be automatically done in the background. +10. Restart! Again, this is beta. You have been warned. It's a given that you will need to fully resync and reset your setup on a regular basis. From 897780f059b2071405f4d8b5a31b87e67a33278f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 3 Mar 2016 23:32:33 -0600 Subject: [PATCH 02/21] Fix views GroupedFolders sometimes return empty, even if the user's views are grouped (seems like a bug). Added a failsafe. --- resources/lib/librarysync.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 4ac5cdea..b40d5507 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -382,8 +382,9 @@ class LibrarySync(threading.Thread): result = doUtils(url) grouped_views = result['Items'] ordered_views = emby.getViews(sortedlist=True) - sorted_views = [] + all_views = [] for view in ordered_views: + all_views.append(view['name']) if view['type'] == "music": continue @@ -392,13 +393,6 @@ class LibrarySync(threading.Thread): sorted_views.append(view['name']) log("Sorted views: %s" % sorted_views, 1) - try: - groupedFolders = self.user.userSettings['Configuration']['GroupedFolders'] - except TypeError: - url = "{server}/emby/Users/{UserId}?format=json" - result = doUtils(url) - groupedFolders = result['Configuration']['GroupedFolders'] - # total nodes for window properties vnodes.clearProperties() totalnodes = len(sorted_views) + 0 @@ -427,12 +421,12 @@ class LibrarySync(threading.Thread): foldername = folder['name'] viewtype = folder['type'] - if folderid in groupedFolders: + if foldername not in all_views: # Media folders are grouped into userview url = "{server}/emby/Users/{UserId}/Items?format=json" params = { - 'ParentId': folderid, + 'Recursive': True, 'Limit': 1, 'IncludeItemTypes': emby_mediatypes[mediatype] } # Get one item from server using the folderid @@ -460,6 +454,12 @@ class LibrarySync(threading.Thread): sorted_views.append(foldername) log("Couldn't find corresponding grouped view: %s" % sorted_views, 1) + # Failsafe + try: + sorted_views.index(foldername) + except ValueError: + sorted_views.append(foldername) + # Get current media folders from emby database view = emby_db.getView_byId(folderid) try: From 48950820792fa9176c466908654f8df708a430c9 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 3 Mar 2016 23:54:53 -0600 Subject: [PATCH 03/21] Fix typo Accidentally pasted over line --- resources/lib/librarysync.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index b40d5507..4b885ca9 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -383,6 +383,7 @@ class LibrarySync(threading.Thread): grouped_views = result['Items'] ordered_views = emby.getViews(sortedlist=True) all_views = [] + sorted_views = [] for view in ordered_views: all_views.append(view['name']) if view['type'] == "music": From b05033c9b848f47a23224a8c0ccfcc4cd5b9c535 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 4 Mar 2016 03:00:25 -0600 Subject: [PATCH 04/21] Fix unicode and invalid handle --- resources/lib/entrypoint.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 652d089f..d6d03cab 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -456,10 +456,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: @@ -494,14 +494,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: @@ -510,6 +509,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) From c04a0bab64da34369d64798b1f18054da475d2bb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 4 Mar 2016 04:08:42 -0600 Subject: [PATCH 05/21] update url --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 246aea0f..5fd8a5a3 100644 --- a/addon.xml +++ b/addon.xml @@ -28,7 +28,7 @@ en GNU GENERAL PUBLIC LICENSE. Version 2, June 1991 - http://mediabrowser.tv/ + http://emby.media/ Welcome to Emby for Kodi A whole new way to manage and view your media library. The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Emby - the most powerful fully open source multi-client media metadata indexer and server. Emby for Kodi is the absolute best way to enjoy the incredible Kodi playback engine combined with the power of Emby's centralized database. Features: Direct integration with the Kodi library for native Kodi speed Instant synchronization with the Emby server Full support for Movie, TV and Music collections Emby Server direct stream and transcoding support - use Kodi when you are away from home! From 3c36af932a6f626b4f10c80e62c01bd887b63dd4 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Fri, 4 Mar 2016 14:03:15 +0100 Subject: [PATCH 06/21] fix extrafanart for homescreen widgets --- default.py | 4 +++- resources/lib/entrypoint.py | 19 +++++-------------- resources/lib/musicutils.py | 2 +- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/default.py b/default.py index eeb99c00..24e845cc 100644 --- a/default.py +++ b/default.py @@ -67,7 +67,9 @@ class Main: } if "extrafanart" in sys.argv[0]: - entrypoint.getExtraFanArt() + embypath = sys.argv[2][1:] + embyid = params.get('id',[""])[0] + entrypoint.getExtraFanArt(embyid,embypath) if modes.get(mode): # Simple functions diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 652d089f..5fe9ea67 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -951,27 +951,18 @@ def getRecentEpisodes(tagname, limit): xbmcplugin.endOfDirectory(handle=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.emby" 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.emby" 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 @@ -1015,7 +1006,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])) \ No newline at end of file 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.... From 36007a1e7d46ad2358bd47f2dd1812cba9b45370 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Fri, 4 Mar 2016 22:00:24 +0100 Subject: [PATCH 07/21] preparation of videoextras --- default.py | 8 ++++++-- resources/lib/entrypoint.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/default.py b/default.py index 24e845cc..40c4edf4 100644 --- a/default.py +++ b/default.py @@ -50,7 +50,6 @@ class Main: 'reset': utils.reset, 'resetauth': entrypoint.resetAuth, - 'extrafanart': entrypoint.getExtraFanArt, 'play': entrypoint.doPlayback, 'passwords': utils.passwordsXML, 'adduser': entrypoint.addUser, @@ -66,10 +65,15 @@ class Main: 'deviceid': entrypoint.resetDeviceId } - if "extrafanart" in sys.argv[0]: + 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/lib/entrypoint.py b/resources/lib/entrypoint.py index e3307543..8e8e252c 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -951,6 +951,32 @@ 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(embyId,embyPath): From 04acfb32003d5e8f080e7deb8f6161ce17e3054a Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 4 Mar 2016 17:20:24 -0600 Subject: [PATCH 08/21] Fix type folders in photos If "photo album" has no pictures in it, it's returned as type "Folder" --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index d6d03cab..6a1cf19e 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -468,7 +468,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 = "" From ad0f33a09ba17f3de3f0e8e578875f6e297f373b Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 4 Mar 2016 17:23:07 -0600 Subject: [PATCH 09/21] Removed the verification for mixed content Since OriginalCollectionType is now implemented server side. --- resources/lib/read_embyserver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index c277d79c..962c464e 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -332,18 +332,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'): From 0f2f6dbcafb668932418c8d8844ab764fca976f7 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 4 Mar 2016 17:47:37 -0600 Subject: [PATCH 10/21] Version bump 2.2.8 --- addon.xml | 2 +- changelog.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 5fd8a5a3..f87c512d 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 From 948015d43368052fbe9894a4b6e6e4e2370cdb57 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 6 Mar 2016 17:21:29 -0600 Subject: [PATCH 11/21] Fix potential error due to timeout --- resources/lib/read_embyserver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 962c464e..5c61b969 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -273,14 +273,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 From 0afa304f523f5674c5c44098c8c4fe06ba340af7 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 09:47:30 +0100 Subject: [PATCH 12/21] utils.Window to return unicode in all cases --- resources/lib/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 3f7257a9..f539545f 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -207,18 +207,21 @@ 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 unicode(WINDOW.getProperty(property)) def settings(setting, value=None): # Get or add addon setting 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 From c6503f8a8ef016abbae85f00838f35fd7f98b4b0 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 13:01:45 +0100 Subject: [PATCH 13/21] Enable non-ASCI PMS server name and username --- resources/lib/PlexAPI.py | 57 ++++++++++++++++--------------- resources/lib/initialsetup.py | 36 ++++++++++---------- resources/lib/userclient.py | 64 ++++++++++++++++++----------------- resources/lib/utils.py | 11 ++++-- service.py | 10 +++--- 5 files changed, 92 insertions(+), 86 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index a8330722..a4b64f90 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): """ @@ -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 @@ -1122,12 +1125,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 @@ -1188,7 +1190,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 +1413,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/initialsetup.py b/resources/lib/initialsetup.py index 3544149d..4405ca9b 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: @@ -110,18 +106,18 @@ class InitialSetup(): if len(serverlist) == 0: dialog.ok( self.addonName, - string(39011) + 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(str(server['name']) + string(39022)) + dialoglist.append( + server['name'].encode('utf-8') + + string(39022).encode('utf-8')) else: - dialoglist.append(str(server['name'])) - resp = dialog.select( - string(39012), - dialoglist) + 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'] + ':' + \ @@ -141,8 +137,9 @@ class InitialSetup(): # 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 +151,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 +164,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 +200,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/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 f539545f..52f9c81f 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -210,12 +210,17 @@ def window(property, value=None, clear=False, windowid=10000): # Takes unicode or string by default! WINDOW.setProperty(property, value) else: #getproperty returns string so convert to unicode - return unicode(WINDOW.getProperty(property)) + 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) diff --git a/service.py b/service.py index de478698..26dda9c1 100644 --- a/service.py +++ b/service.py @@ -172,8 +172,8 @@ class Service(): xbmcgui.Dialog().notification( heading=self.addonName, message=("%s %s" - % (lang(33000), - user.currUser.decode('utf-8'))), + % (lang(33000).encode('utf-8'), + user.currUser.encode('utf-8'))), icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", time=2000, sound=False) @@ -226,8 +226,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 +245,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, From 4996d7122f9de631b52b28c682aca01fc6c8fd35 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 13:10:38 +0100 Subject: [PATCH 14/21] Bugfix: enforce PIN for protected users Bugfix for Plex.tv bug: don't allow protected user to get a token if pin= in url is omitted --- resources/lib/PlexAPI.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index a4b64f90..ce17c6c4 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -1117,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, @@ -1160,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) From 398ef64d96b927ebc6954574eb4881d7e401923c Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 13:38:45 +0100 Subject: [PATCH 15/21] More encoding fixes for dialogs --- resources/lib/artwork.py | 6 ++++-- resources/lib/entrypoint.py | 8 ++++---- resources/lib/librarysync.py | 16 ++++++++-------- resources/lib/player.py | 5 ++++- resources/lib/playlist.py | 1 - resources/lib/playutils.py | 8 ++++---- service.py | 7 +++---- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index ca9af7ea..a8012e6e 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,7 +172,8 @@ 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') diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 07f8b84f..e7be13cd 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -156,7 +156,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 +226,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 ##### @@ -1160,6 +1160,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/librarysync.py b/resources/lib/librarysync.py index 4e685462..e895f42d 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 @@ -236,7 +236,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) 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/service.py b/service.py index 26dda9c1..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).encode('utf-8'), - user.currUser.encode('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) @@ -227,7 +226,7 @@ class Service(): xbmcgui.Dialog().notification( heading=lang(33001).encode('utf-8'), - message="%s %s" % (self.addonName, lang(33002).encode('utf-8')), + message=("%s %s" % (self.addonName, lang(33002))).encode('utf-8'), icon="special://home/addons/plugin.video." "plexkodiconnect/icon.png", sound=False) From c0f20678568f7403c9007b7f9be1f8a6ee2960ad Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 14:10:47 +0100 Subject: [PATCH 16/21] Don't log header if checking connection to a PMS --- resources/lib/PlexAPI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index ce17c6c4..a3d68ea8 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -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' From dc2ae721e61e80496540047bd4e8fcccb5bf3e0f Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 15:31:07 +0100 Subject: [PATCH 17/21] Allow non-ASCI library names --- resources/lib/utils.py | 45 +++++++++++++++++++------------------ resources/lib/videonodes.py | 14 ++++++------ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 52f9c81f..4c11baac 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -418,8 +418,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", @@ -676,9 +676,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) @@ -688,15 +688,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 @@ -707,21 +707,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) @@ -729,26 +730,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..f5f68f9c 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -65,21 +65,21 @@ class VideoNodes(object): "special://profile/library/video/Plex-%s/" % dirname).decode('utf-8') # Verify the video directory - if not xbmcvfs.exists(path): + if not xbmcvfs.exists(path.encode('utf-8')): 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) + xbmcvfs.exists(path.encode('utf-8')) # Create the node directory - if not xbmcvfs.exists(nodepath) and not mediatype == "photo": + if not xbmcvfs.exists(nodepath.encode('utf-8')) and not mediatype == "photo": # We need to copy over the default items - xbmcvfs.mkdirs(nodepath) + xbmcvfs.mkdirs(nodepath.encode('utf-8')) else: if delete: - dirs, files = xbmcvfs.listdir(nodepath) + dirs, files = xbmcvfs.listdir(nodepath.encode('utf-8')) for file in files: - xbmcvfs.delete(nodepath + file) + xbmcvfs.delete((nodepath + file).encode('utf-8')) self.logMsg("Sucessfully removed videonode: %s." % tagname, 1) return @@ -239,7 +239,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 From cbfa41de99537f94a7b7d6e728f1d0a65fc0382a Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 16:16:57 +0100 Subject: [PATCH 18/21] Fix refreshing playlists and videonodes --- resources/lib/videonodes.py | 44 ++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index f5f68f9c..cbcaad42 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,42 @@ class VideoNodes(object): "special://profile/library/video/Plex-%s/" % dirname).decode('utf-8') # Verify the video directory - if not xbmcvfs.exists(path.encode('utf-8')): + # KODI BUG + # Kodi caches the result of exists for directories + # so try creating a file + dummyfile = ospath.join(path, 'dummyfile.txt').encode('utf-8') + try: + etree.ElementTree(etree.Element('test')).write(dummyfile) + except: + # path does not exist yet 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.encode('utf-8')) + else: + # path exists - delete dummy file + xbmcvfs.delete(dummyfile) # Create the node directory - if not xbmcvfs.exists(nodepath.encode('utf-8')) and not mediatype == "photo": - # We need to copy over the default items - xbmcvfs.mkdirs(nodepath.encode('utf-8')) - else: - if delete: - dirs, files = xbmcvfs.listdir(nodepath.encode('utf-8')) - for file in files: - xbmcvfs.delete((nodepath + file).encode('utf-8')) + if mediatype != "photo": + dummyfile = ospath.join(nodepath, 'dummyfile.txt').encode('utf-8') + try: + etree.ElementTree(etree.Element('test')).write(dummyfile) + except: + # folder does not exist yet + self.logMsg('Creating folder %s' % nodepath, 1) + xbmcvfs.mkdirs(nodepath.encode('utf-8')) + else: + # path exists - delete dummy file + xbmcvfs.delete(dummyfile) + 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 From a258f969ab8c1b599a7156e30dd4adffa8361bca Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 16:30:50 +0100 Subject: [PATCH 19/21] Dedicated function to check whether directory exists --- resources/lib/artwork.py | 2 +- resources/lib/entrypoint.py | 3 +-- resources/lib/utils.py | 21 +++++++++++++++++++++ resources/lib/videonodes.py | 17 ++--------------- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index a8012e6e..9bface89 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -177,7 +177,7 @@ class Artwork(): 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 e7be13cd..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 @@ -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 diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 4c11baac..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 diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index cbcaad42..12768ad1 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -68,30 +68,17 @@ class VideoNodes(object): # KODI BUG # Kodi caches the result of exists for directories # so try creating a file - dummyfile = ospath.join(path, 'dummyfile.txt').encode('utf-8') - try: - etree.ElementTree(etree.Element('test')).write(dummyfile) - except: - # path does not exist yet + 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')) - else: - # path exists - delete dummy file - xbmcvfs.delete(dummyfile) # Create the node directory if mediatype != "photo": - dummyfile = ospath.join(nodepath, 'dummyfile.txt').encode('utf-8') - try: - etree.ElementTree(etree.Element('test')).write(dummyfile) - except: + if utils.IfExists(nodepath) is False: # folder does not exist yet self.logMsg('Creating folder %s' % nodepath, 1) xbmcvfs.mkdirs(nodepath.encode('utf-8')) - else: - # path exists - delete dummy file - xbmcvfs.delete(dummyfile) if delete: dirs, files = xbmcvfs.listdir(nodepath.encode('utf-8')) for file in files: From e9e1de6e3d7a3a638d55aec104df6237fa649a80 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 17:11:54 +0100 Subject: [PATCH 20/21] HTTPS! --- resources/lib/initialsetup.py | 69 ++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 4405ca9b..1853695c 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -88,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).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) + 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'] + ':' + \ @@ -132,7 +136,12 @@ 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 From 68e91b192b612efa5dc613d568edcab84e6a4a42 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 7 Mar 2016 17:22:55 +0100 Subject: [PATCH 21/21] Setting to turn off constant background sync --- resources/language/English/strings.xml | 1 + resources/language/German/strings.xml | 1 + resources/lib/librarysync.py | 5 ++++- resources/settings.xml | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) 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/librarysync.py b/resources/lib/librarysync.py index e895f42d..2382f26c 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -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) @@ -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/settings.xml b/resources/settings.xml index c12a66d3..517ec5f9 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -51,6 +51,7 @@ +