From 60759ba625d0b9403a4329ab65dbeaa7c4c79931 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 1 May 2017 20:53:02 +0200 Subject: [PATCH 01/24] Update German translation --- .../resource.language.de_DE/strings.po | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/resources/language/resource.language.de_DE/strings.po b/resources/language/resource.language.de_DE/strings.po index b71de87b..46e92b0d 100644 --- a/resources/language/resource.language.de_DE/strings.po +++ b/resources/language/resource.language.de_DE/strings.po @@ -2037,3 +2037,34 @@ msgstr "" msgctxt "#39705" msgid "Use at your own risk" msgstr "Verwendung auf eigene Gefahr" + +# If user gets prompted to choose between several subtitles. Leave the number +# one at the beginning of the string! +msgctxt "#39706" +msgid "1 No subtitles" +msgstr "1 Untertitel deaktivieren" + +# If user gets prompted to choose between several audio/subtitle tracks and +# language is unknown +msgctxt "#39707" +msgid "unknown" +msgstr "unbekannt" + +# If user gets prompted to choose between several subtitles and Plex adds the +# "default" flag +msgctxt "#39708" +msgid "Default" +msgstr "Standart" + +# If user gets prompted to choose between several subtitles and Plex adds the +# "forced" flag +msgctxt "#39709" +msgid "Forced" +msgstr "Erzwungen" + +# If user gets prompted to choose between several subtitles the subtitle +# cannot be downloaded (has no 'key' attribute from the PMS), the subtitle +# needs to be burned in +msgctxt "#39710" +msgid "burn-in" +msgstr "einbrennen" From b2cd6e1156d3eb82516f1a35c4dc720b2224c416 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Wed, 3 May 2017 20:30:33 +0200 Subject: [PATCH 02/24] Avoid xbmcvfs entirely; use encoded paths - Fixes #286 --- resources/lib/PlexAPI.py | 23 +++--- resources/lib/artwork.py | 49 +++++-------- resources/lib/entrypoint.py | 80 ++++++++++----------- resources/lib/librarysync.py | 6 +- resources/lib/userclient.py | 10 +-- resources/lib/utils.py | 134 ++++++++++++----------------------- resources/lib/variables.py | 22 +++--- resources/lib/videonodes.py | 98 +++++++++++++------------ service.py | 5 +- 9 files changed, 181 insertions(+), 246 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index b466d1b6..e50760f5 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -39,11 +39,11 @@ import xml.etree.ElementTree as etree from re import compile as re_compile, sub from json import dumps from urllib import urlencode, quote_plus, unquote -from os import path as os_path +from os.path import basename, join, exists +from os import makedirs import xbmcgui from xbmc import sleep, executebuiltin -from xbmcvfs import exists, mkdirs import clientinfo as client from downloadutils import DownloadUtils @@ -2113,11 +2113,14 @@ class API(): if fanarttvimage not in data: continue for entry in data[fanarttvimage]: - if fanartcount < maxfanarts: - if exists(entry.get("url")): - allartworks['Backdrop'].append( - entry.get("url", "").replace(' ', '%20')) - fanartcount += 1 + if entry.get("url") is None: + continue + if fanartcount > maxfanarts: + break + if exists(tryEncode(entry['url'])): + allartworks['Backdrop'].append( + entry['url'].replace(' ', '%20')) + fanartcount += 1 return allartworks def getSetArtwork(self, parentInfo=False): @@ -2184,7 +2187,7 @@ class API(): # Get additional info (filename / languages) filename = None if 'file' in entry[0].attrib: - filename = os_path.basename(entry[0].attrib['file']) + filename = basename(entry[0].attrib['file']) # Languages of audio streams languages = [] for stream in entry[0]: @@ -2339,8 +2342,8 @@ class API(): Returns the path to the downloaded subtitle or None """ if not exists(v.EXTERNAL_SUBTITLE_TEMP_PATH): - mkdirs(v.EXTERNAL_SUBTITLE_TEMP_PATH) - path = os_path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename) + makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH) + path = join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename) r = DownloadUtils().downloadUrl(url, return_response=True) try: r.status_code diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 1cd6143b..be076403 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -4,16 +4,16 @@ import logging from json import dumps, loads import requests -from os import path as os_path +from os.path import exists +from shutil import rmtree from urllib import quote_plus, unquote from threading import Thread from Queue import Queue, Empty from xbmc import executeJSONRPC, sleep, translatePath -from xbmcvfs import listdir, delete from utils import window, settings, language as lang, kodiSQL, tryEncode, \ - tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop, dialog + ThreadMethods, ThreadMethodsAdditionalStop, dialog # Disable annoying requests warnings import requests.packages.urllib3 @@ -228,30 +228,21 @@ class Artwork(): if dialog('yesno', "Image Texture Cache", lang(39251)): log.info("Resetting all cache data first") # Remove all existing textures first - path = tryDecode(translatePath("special://thumbnails/")) - if IfExists(path): - allDirs, allFiles = listdir(path) - for dir in allDirs: - allDirs, allFiles = listdir(path+dir) - for file in allFiles: - if os_path.supports_unicode_filenames: - delete(os_path.join( - path + tryDecode(dir), - tryDecode(file))) - else: - delete(os_path.join( - tryEncode(path) + dir, - file)) + path = translatePath("special://thumbnails/") + if exists(path): + rmtree(path, ignore_errors=True) # remove all existing data from texture DB connection = kodiSQL('texture') cursor = connection.cursor() - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + query = 'SELECT tbl_name FROM sqlite_master WHERE type=?' + cursor.execute(query, ('table', )) rows = cursor.fetchall() for row in rows: tableName = row[0] if tableName != "version": - cursor.execute("DELETE FROM " + tableName) + query = "DELETE FROM ?" + cursor.execute(query, (tableName,)) connection.commit() connection.close() @@ -259,7 +250,8 @@ class Artwork(): connection = kodiSQL('video') cursor = connection.cursor() # dont include actors - cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") + query = "SELECT url FROM art WHERE media_type != ?" + cursor.execute(query, ('actor', )) result = cursor.fetchall() total = len(result) log.info("Image cache sync about to process %s video images" % total) @@ -286,7 +278,6 @@ class Artwork(): def addArtwork(self, artwork, kodiId, mediaType, cursor): # Kodi conversion table kodiart = { - 'Primary': ["thumb", "poster"], 'Banner': "banner", 'Logo': "clearlogo", @@ -307,7 +298,6 @@ class Artwork(): backdropsNumber = len(backdrops) query = ' '.join(( - "SELECT url", "FROM art", "WHERE media_id = ?", @@ -320,7 +310,6 @@ class Artwork(): if len(rows) > backdropsNumber: # More backdrops in database. Delete extra fanart. query = ' '.join(( - "DELETE FROM art", "WHERE media_id = ?", "AND media_type = ?", @@ -339,7 +328,7 @@ class Artwork(): cursor=cursor) if backdropsNumber > 1: - try: # Will only fail on the first try, str to int. + try: # Will only fail on the first try, str to int. index += 1 except TypeError: index = 1 @@ -438,14 +427,10 @@ class Artwork(): log.info("Could not find cached url.") else: # Delete thumbnail as well as the entry - thumbnails = tryDecode( - translatePath("special://thumbnails/%s" % cachedurl)) - log.debug("Deleting cached thumbnail: %s" % thumbnails) - try: - delete(thumbnails) - except Exception as e: - log.error('Could not delete cached artwork %s. Error: %s' - % (thumbnails, e)) + path = translatePath("special://thumbnails/%s" % cachedurl) + log.debug("Deleting cached thumbnail: %s" % path) + if exists(path): + rmtree(path, ignore_errors=True) cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() finally: diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 4e943658..dea40bfe 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- ############################################################################### import logging -from os import path as os_path +from shutil import copyfile +from os import walk, makedirs +from os.path import basename, join, exists from sys import argv from urllib import urlencode @@ -9,8 +11,8 @@ import xbmcplugin from xbmc import sleep, executebuiltin, translatePath from xbmcgui import ListItem -from utils import window, settings, language as lang, dialog, tryDecode,\ - tryEncode, CatchExceptions, JSONRPC +from utils import window, settings, language as lang, dialog, tryEncode, \ + CatchExceptions, JSONRPC import downloadutils from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \ @@ -486,6 +488,7 @@ def getVideoFiles(plexId, params): except: log.error('Could not get file path for item %s' % plexId) return xbmcplugin.endOfDirectory(HANDLE) + path = tryEncode(path) # Assign network protocol if path.startswith('\\\\'): path = path.replace('\\\\', 'smb://') @@ -493,28 +496,25 @@ def getVideoFiles(plexId, params): # Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv' elif '\\' in path: path = path.replace('\\', '\\\\') - # Directory only, get rid of filename (!! exists() needs / or \ at end) - path = path.replace(os_path.basename(path), '') - # Only proceed if we can access this folder - import xbmcvfs - if xbmcvfs.exists(path): - # Careful, returns encoded strings! - dirs, files = xbmcvfs.listdir(path) - for file in files: - file = path + tryDecode(file) - li = ListItem(file, path=file) - xbmcplugin.addDirectoryItem(handle=HANDLE, - url=tryEncode(file), - listitem=li) - for dir in dirs: - dir = path + tryDecode(dir) - li = ListItem(dir, path=dir) - xbmcplugin.addDirectoryItem(handle=HANDLE, - url=tryEncode(dir), - listitem=li, - isFolder=True) + # Directory only, get rid of filename + path = path.replace(basename(path), '') + if exists(path): + for root, dirs, files in walk(path): + for directory in dirs: + item_path = join(root, directory) + li = ListItem(item_path, path=item_path) + xbmcplugin.addDirectoryItem(handle=HANDLE, + url=item_path, + listitem=li, + isFolder=True) + for file in files: + item_path = join(root, file) + li = ListItem(item_path, path=item_path) + xbmcplugin.addDirectoryItem(handle=HANDLE, + url=file, + listitem=li) else: - log.warn('Kodi cannot access folder %s' % path) + log.error('Kodi cannot access folder %s' % path) xbmcplugin.endOfDirectory(HANDLE) @@ -525,7 +525,6 @@ def getExtraFanArt(plexid, plexPath): will be called by skinhelper script to get the extrafanart for tvshows we get the plexid just from the path """ - import xbmcvfs log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath)) if not plexid: if "plugin.video.plexkodiconnect" in plexPath: @@ -536,11 +535,10 @@ def getExtraFanArt(plexid, plexPath): # We need to store the images locally for this to work # because of the caching system in xbmc - fanartDir = tryDecode(translatePath( - "special://thumbnails/plex/%s/" % plexid)) - if not xbmcvfs.exists(fanartDir): + fanartDir = translatePath("special://thumbnails/plex/%s/" % plexid) + if not exists(fanartDir): # Download the images to the cache directory - xbmcvfs.mkdirs(tryEncode(fanartDir)) + makedirs(fanartDir) xml = GetPlexMetadata(plexid) if xml is None: log.error('Could not download metadata for %s' % plexid) @@ -550,29 +548,23 @@ def getExtraFanArt(plexid, plexPath): backdrops = api.getAllArtwork()['Backdrop'] for count, backdrop in enumerate(backdrops): # Same ordering as in artwork - if os_path.supports_unicode_filenames: - fanartFile = os_path.join(fanartDir, - "fanart%.3d.jpg" % count) - else: - fanartFile = os_path.join( - tryEncode(fanartDir), - tryEncode("fanart%.3d.jpg" % count)) + fanartFile = join(fanartDir, "fanart%.3d.jpg" % count) li = ListItem("%.3d" % count, path=fanartFile) xbmcplugin.addDirectoryItem( handle=HANDLE, url=fanartFile, listitem=li) - xbmcvfs.copy(backdrop, fanartFile) + copyfile(backdrop, fanartFile) else: log.info("Found cached backdrop.") # Use existing cached images - dirs, files = xbmcvfs.listdir(fanartDir) - for file in files: - fanartFile = os_path.join(fanartDir, tryDecode(file)) - li = ListItem(file, path=fanartFile) - xbmcplugin.addDirectoryItem(handle=HANDLE, - url=fanartFile, - listitem=li) + for root, dirs, files in walk(fanartDir): + for file in files: + fanartFile = join(root, file) + li = ListItem(file, path=fanartFile) + xbmcplugin.addDirectoryItem(handle=HANDLE, + url=fanartFile, + listitem=li) xbmcplugin.endOfDirectory(HANDLE) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 8fe14c61..8c1b77d5 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- - ############################################################################### - import logging from threading import Thread import Queue from random import shuffle +from os.path import exists import xbmc import xbmcgui -import xbmcvfs from utils import window, settings, getUnixTimestamp, sourcesXML,\ ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\ @@ -1505,7 +1503,7 @@ class LibrarySync(Thread): # Also runs when first installed # Verify the video database can be found videoDb = v.DB_VIDEO_PATH - if not xbmcvfs.exists(videoDb): + if not exists(videoDb): # Database does not exists log.error("The current Kodi version is incompatible " "to know which Kodi versions are supported.") diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index dcdcc784..ebd790bd 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -3,14 +3,15 @@ ############################################################################### import logging import threading +from os.path import exists import xbmc import xbmcgui import xbmcaddon -import xbmcvfs + from utils import window, settings, language as lang, ThreadMethods, \ - tryDecode, ThreadMethodsAdditionalSuspend + ThreadMethodsAdditionalSuspend import downloadutils import PlexAPI @@ -209,9 +210,8 @@ class UserClient(threading.Thread): return False # Get /profile/addon_data - addondir = tryDecode(xbmc.translatePath( - self.addon.getAddonInfo('profile'))) - hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir) + addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')) + hasSettings = exists("%ssettings.xml" % addondir) # If there's no settings.xml if not hasSettings: diff --git a/resources/lib/utils.py b/resources/lib/utils.py index e416fe59..1abfc8b1 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -13,12 +13,13 @@ from unicodedata import normalize import xml.etree.ElementTree as etree from functools import wraps from calendar import timegm -from os import path as os_path +from os.path import exists, join +from os import remove, makedirs, walk +from shutil import rmtree import xbmc import xbmcaddon import xbmcgui -import xbmcvfs from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \ DB_PLEX_PATH @@ -198,27 +199,6 @@ def DateToKodi(stamp): return localdate -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 = tryEncode(os_path.join(path, 'dummyfile.txt')) - 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 IntFromStr(string): """ Returns an int from string or the int 0 if something happened @@ -362,24 +342,14 @@ def reset(): line1=language(39602)): log.info("Resetting all cached artwork.") # Remove all existing textures first - path = tryDecode(xbmc.translatePath("special://thumbnails/")) - if xbmcvfs.exists(path): - allDirs, allFiles = xbmcvfs.listdir(path) - for dir in allDirs: - allDirs, allFiles = xbmcvfs.listdir(path+dir) - for file in allFiles: - if os_path.supports_unicode_filenames: - xbmcvfs.delete(os_path.join( - path + tryDecode(dir), - tryDecode(file))) - else: - xbmcvfs.delete(os_path.join( - tryEncode(path) + dir, - file)) + path = xbmc.translatePath("special://thumbnails/") + if exists(path): + rmtree(path, ignore_errors=True) # remove all existing data from texture DB connection = kodiSQL('texture') cursor = connection.cursor() - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + query = 'SELECT tbl_name FROM sqlite_master WHERE type=?' + cursor.execute(query, ("table", )) rows = cursor.fetchall() for row in rows: tableName = row[0] @@ -398,10 +368,10 @@ def reset(): line1=language(39603)): # Delete the settings addon = xbmcaddon.Addon() - addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile'))) + addondir = xbmc.translatePath(addon.getAddonInfo('profile')) dataPath = "%ssettings.xml" % addondir log.info("Deleting: settings.xml") - xbmcvfs.delete(tryEncode(dataPath)) + remove(dataPath) # Kodi will now restart to apply the changes. dialog('ok', @@ -664,12 +634,13 @@ def sourcesXML(): def passwordsXML(): # To add network credentials - path = tryDecode(xbmc.translatePath("special://userdata/")) + path = xbmc.translatePath("special://userdata/") xmlpath = "%spasswords.xml" % path try: xmlparse = etree.parse(xmlpath) - except: # Document is blank or missing + except: + # Document is blank or missing root = etree.Element('passwords') skipFind = True else: @@ -753,8 +724,6 @@ def passwordsXML(): etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server topath = "smb://%s:%s@%s/" % (user, password, server) etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath - # Force Kodi to see the credentials without restarting - xbmcvfs.exists(topath) # Add credentials settings('networkCreds', value="%s" % server) @@ -762,7 +731,8 @@ def passwordsXML(): # Prettify and write to file try: indent(root) - except: pass + except: + pass etree.ElementTree(root).write(xmlpath) # dialog.notification( @@ -776,7 +746,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): """ Feed with tagname as unicode """ - path = tryDecode(xbmc.translatePath("special://profile/playlists/video/")) + path = xbmc.translatePath("special://profile/playlists/video/") if viewtype == "mixed": plname = "%s - %s" % (tagname, mediatype) xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype) @@ -785,20 +755,20 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): xsppath = "%sPlex %s.xsp" % (path, viewid) # Create the playlist directory - if not xbmcvfs.exists(tryEncode(path)): + if not exists(path): log.info("Creating directory: %s" % path) - xbmcvfs.mkdirs(tryEncode(path)) + makedirs(path) # Only add the playlist if it doesn't already exists - if xbmcvfs.exists(tryEncode(xsppath)): + if exists(xsppath): log.info('Path %s does exist' % xsppath) if delete: - xbmcvfs.delete(tryEncode(xsppath)) + remove(xsppath) log.info("Successfully removed playlist: %s." % tagname) - return - # Using write process since there's no guarantee the xml declaration works with etree + # Using write process since there's no guarantee the xml declaration works + # with etree itemtypes = { 'homevideos': 'movies', 'movie': 'movies', @@ -806,51 +776,39 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): } log.info("Writing playlist file to: %s" % xsppath) try: - f = xbmcvfs.File(tryEncode(xsppath), 'wb') - except: + with open(xsppath, 'wb'): + tryEncode( + '\n' + '\n\t' + 'Plex %s\n\t' + 'all\n\t' + '\n\t\t' + '%s\n\t' + '\n' + '\n' + % (itemtypes.get(mediatype, mediatype), plname, tagname)) + except Exception as e: log.error("Failed to create playlist: %s" % xsppath) + log.error(e) return - else: - f.write(tryEncode( - '\n' - '\n\t' - 'Plex %s\n\t' - 'all\n\t' - '\n\t\t' - '%s\n\t' - '\n' - '\n' - % (itemtypes.get(mediatype, mediatype), plname, tagname))) - f.close() log.info("Successfully added playlist: %s" % tagname) def deletePlaylists(): - # Clean up the playlists - path = tryDecode(xbmc.translatePath("special://profile/playlists/video/")) - dirs, files = xbmcvfs.listdir(tryEncode(path)) - for file in files: - if tryDecode(file).startswith('Plex'): - xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file)))) + path = xbmc.translatePath("special://profile/playlists/video/") + for root, _, files in walk(path): + for file in files: + if file.startswith('Plex'): + remove(join(root, file)) def deleteNodes(): - # Clean up video nodes - import shutil - path = tryDecode(xbmc.translatePath("special://profile/library/video/")) - dirs, files = xbmcvfs.listdir(tryEncode(path)) - for dir in dirs: - if tryDecode(dir).startswith('Plex'): - try: - shutil.rmtree("%s%s" % (path, tryDecode(dir))) - except: - log.error("Failed to delete directory: %s" % tryDecode(dir)) - for file in files: - if tryDecode(file).startswith('plex'): - try: - xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file)))) - except: - log.error("Failed to file: %s" % tryDecode(file)) + path = xbmc.translatePath("special://profile/library/video/") + for root, dirs, _ in walk(path): + for directory in dirs: + if directory.startswith('Plex-'): + rmtree(join(root, directory)) + break ############################################################################### diff --git a/resources/lib/variables.py b/resources/lib/variables.py index 98c345f6..40f354ad 100644 --- a/resources/lib/variables.py +++ b/resources/lib/variables.py @@ -2,6 +2,8 @@ import xbmc from xbmcaddon import Addon +# Paths are in string, not unicode! + def tryDecode(string, encoding='utf-8'): """ @@ -27,7 +29,7 @@ ADDON_VERSION = _ADDON.getAddonInfo('version') KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1) KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion') -KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile")) +KODI_PROFILE = xbmc.translatePath("special://profile") if xbmc.getCondVisibility('system.platform.osx'): PLATFORM = "MacOSX" @@ -68,8 +70,8 @@ _DB_VIDEO_VERSION = { 17: 107, # Krypton 18: 107 # Leia } -DB_VIDEO_PATH = tryDecode(xbmc.translatePath( - "special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION])) +DB_VIDEO_PATH = xbmc.translatePath( + "special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]) _DB_MUSIC_VERSION = { 13: 46, # Gotham @@ -79,8 +81,8 @@ _DB_MUSIC_VERSION = { 17: 60, # Krypton 18: 60 # Leia } -DB_MUSIC_PATH = tryDecode(xbmc.translatePath( - "special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION])) +DB_MUSIC_PATH = xbmc.translatePath( + "special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]) _DB_TEXTURE_VERSION = { 13: 13, # Gotham @@ -90,13 +92,13 @@ _DB_TEXTURE_VERSION = { 17: 13, # Krypton 18: 13 # Leia } -DB_TEXTURE_PATH = tryDecode(xbmc.translatePath( - "special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION])) +DB_TEXTURE_PATH = xbmc.translatePath( + "special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION]) -DB_PLEX_PATH = tryDecode(xbmc.translatePath("special://database/plex.db")) +DB_PLEX_PATH = xbmc.translatePath("special://database/plex.db") -EXTERNAL_SUBTITLE_TEMP_PATH = tryDecode(xbmc.translatePath( - "special://profile/addon_data/%s/temp/" % ADDON_ID)) +EXTERNAL_SUBTITLE_TEMP_PATH = xbmc.translatePath( + "special://profile/addon_data/%s/temp/" % ADDON_ID) # Multiply Plex time by this factor to receive Kodi time diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index 3d16fd91..4ffc91e7 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- - ############################################################################### - import logging -import shutil +from shutil import copytree import xml.etree.ElementTree as etree import xbmc -import xbmcvfs +from os import remove, makedirs, listdir +from os.path import exists, isfile, join -from utils import window, settings, language as lang, IfExists, tryDecode, \ - tryEncode, indent, normalize_nodes +from utils import window, settings, language as lang, tryEncode, indent, \ + normalize_nodes import variables as v ############################################################################### @@ -18,6 +17,7 @@ import variables as v log = logging.getLogger("PLEX."+__name__) ############################################################################### +# Paths are strings, NOT unicode! class VideoNodes(object): @@ -61,36 +61,30 @@ class VideoNodes(object): else: dirname = viewid - path = tryDecode(xbmc.translatePath( - "special://profile/library/video/")) - nodepath = tryDecode(xbmc.translatePath( - "special://profile/library/video/Plex-%s/" % dirname)) + # Returns strings + path = xbmc.translatePath("special://profile/library/video/") + nodepath = xbmc.translatePath( + "special://profile/library/video/Plex-%s/" % dirname) if delete: - dirs, files = xbmcvfs.listdir(tryEncode(nodepath)) + files = [f for f in listdir(nodepath) if isfile(join(nodepath, f))] for file in files: - xbmcvfs.delete(tryEncode( - (nodepath + tryDecode(file)))) + remove(nodepath + file) log.info("Sucessfully removed videonode: %s." % tagname) return # Verify the video directory - # KODI BUG - # Kodi caches the result of exists for directories - # so try creating a file - if IfExists(path) is False: - shutil.copytree( - src=tryDecode(xbmc.translatePath( - "special://xbmc/system/library/video")), - dst=tryDecode(xbmc.translatePath( - "special://profile/library/video"))) + if exists(path) is False: + copytree( + src=xbmc.translatePath("special://xbmc/system/library/video"), + dst=xbmc.translatePath("special://profile/library/video")) # Create the node directory if mediatype != "photos": - if IfExists(nodepath) is False: + if exists(nodepath) is False: # folder does not exist yet log.debug('Creating folder %s' % nodepath) - xbmcvfs.mkdirs(tryEncode(nodepath)) + makedirs(nodepath) # Create index entry nodeXML = "%sindex.xml" % nodepath @@ -103,23 +97,29 @@ class VideoNodes(object): if mediatype == "photos": path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid) - + window('Plex.nodes.%s.index' % indexnumber, value=path) - + # Root if not mediatype == "photos": if viewtype == "mixed": specialtag = "%s-%s" % (tagname, mediatype) - root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0) + root = self.commonRoot(order=0, + label=specialtag, + tagname=tagname, + roottype=0) else: - root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) + root = self.commonRoot(order=0, + label=tagname, + tagname=tagname, + roottype=0) try: indent(root) - except: pass + except: + pass etree.ElementTree(root).write(nodeXML) nodetypes = { - '1': "all", '2': "recent", '3': "recentepisodes", @@ -255,7 +255,7 @@ class VideoNodes(object): path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid else: path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype) - + if mediatype == "photos": windowpath = "ActivateWindow(Pictures,%s,return)" % path else: @@ -264,7 +264,7 @@ class VideoNodes(object): windowpath = "ActivateWindow(Videos,%s,return)" % path else: windowpath = "ActivateWindow(Video,%s,return)" % path - + if nodetype == "all": if viewtype == "mixed": @@ -288,8 +288,8 @@ class VideoNodes(object): # to be created. # To do: add our photos nodes to kodi picture sources somehow continue - - if xbmcvfs.exists(tryEncode(nodeXML)): + + if exists(nodeXML): # Don't recreate xml if already exists continue @@ -370,15 +370,14 @@ class VideoNodes(object): try: indent(root) - except: pass + except: + pass etree.ElementTree(root).write(nodeXML) def singleNode(self, indexnumber, tagname, mediatype, itemtype): - tagname = tryEncode(tagname) cleantagname = normalize_nodes(tagname) - nodepath = tryDecode(xbmc.translatePath( - "special://profile/library/video/")) + nodepath = xbmc.translatePath("special://profile/library/video/") nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) path = "library://video/plex_%s.xml" % cleantagname if v.KODIVERSION >= 17: @@ -388,17 +387,13 @@ class VideoNodes(object): windowpath = "ActivateWindow(Video,%s,return)" % path # Create the video node directory - if not xbmcvfs.exists(nodepath): + if not exists(nodepath): # We need to copy over the default items - shutil.copytree( - src=tryDecode(xbmc.translatePath( - "special://xbmc/system/library/video")), - dst=tryDecode(xbmc.translatePath( - "special://profile/library/video"))) - xbmcvfs.exists(path) + copytree( + src=xbmc.translatePath("special://xbmc/system/library/video"), + dst=xbmc.translatePath("special://profile/library/video")) labels = { - 'Favorite movies': 30180, 'Favorite tvshows': 30181, 'channels': 30173 @@ -410,12 +405,15 @@ class VideoNodes(object): window('%s.content' % embynode, value=path) window('%s.type' % embynode, value=itemtype) - if xbmcvfs.exists(nodeXML): + if exists(nodeXML): # Don't recreate xml if already exists return if itemtype == "channels": - root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) + root = self.commonRoot(order=1, + label=label, + tagname=tagname, + roottype=2) etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels" else: root = self.commonRoot(order=1, label=label, tagname=tagname) @@ -425,7 +423,8 @@ class VideoNodes(object): try: indent(root) - except: pass + except: + pass etree.ElementTree(root).write(nodeXML) def clearProperties(self): @@ -433,7 +432,6 @@ class VideoNodes(object): log.info("Clearing nodes properties.") plexprops = window('Plex.nodes.total') propnames = [ - "index","path","title","content", "inprogress.content","inprogress.title", "inprogress.content","inprogress.path", diff --git a/service.py b/service.py index 1c7fc843..5d50fdad 100644 --- a/service.py +++ b/service.py @@ -164,12 +164,11 @@ class Service(): counter = 0 while not monitor.abortRequested(): - if tryDecode(window('plex_kodiProfile')) != kodiProfile: + if window('plex_kodiProfile') != kodiProfile: # Profile change happened, terminate this thread and others log.warn("Kodi profile was: %s and changed to: %s. " "Terminating old PlexKodiConnect thread." - % (kodiProfile, - tryDecode(window('plex_kodiProfile')))) + % (kodiProfile, window('plex_kodiProfile'))) break # Before proceeding, need to make sure: From 741129fcfec8569e3ee265d2787c37602ec685e9 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Wed, 3 May 2017 20:31:12 +0200 Subject: [PATCH 03/24] Update Czech translation --- .../resource.language.cs_CZ/strings.po | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/resources/language/resource.language.cs_CZ/strings.po b/resources/language/resource.language.cs_CZ/strings.po index f5343dd7..5aa709c0 100644 --- a/resources/language/resource.language.cs_CZ/strings.po +++ b/resources/language/resource.language.cs_CZ/strings.po @@ -1,13 +1,14 @@ # XBMC Media Center language file # Translators: # Croneter None , 2017 +# Michal Kuncl , 2017 msgid "" msgstr "" "Project-Id-Version: PlexKodiConnect\n" "Report-Msgid-Bugs-To: croneter@gmail.com\n" "POT-Creation-Date: 2017-04-15 13:13+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Croneter None , 2017\n" +"Last-Translator: Michal Kuncl , 2017\n" "Language-Team: Czech (Czech Republic) (https://www.transifex.com/croneter/teams/73837/cs_CZ/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1446,7 +1447,7 @@ msgid "" "CAUTION! If you choose \"Native\" mode , you might loose access to certain " "Plex features such as: Plex trailers and transcoding options. ALL Plex " "shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or " -"\\myNAS/mymovie.mkv)!" +"\\\\myNAS/mymovie.mkv)!" msgstr "" "POZOR! Pokud zvolíte \"Nativní\" režim, přijdete o některé funkce Plexu, " "jako např. ukázky, nebo možnosti překódování. Všechny knihovny Plexu musí " @@ -1482,20 +1483,20 @@ msgstr "" msgctxt "#39033" msgid "" -"Transform Plex UNC library paths \\myNas\\mymovie.mkv automatically to smb " -"paths, smb://myNas/mymovie.mkv? (recommended)" +"Transform Plex UNC library paths \\\\myNas\\mymovie.mkv automatically to smb" +" paths, smb://myNas/mymovie.mkv? (recommended)" msgstr "" "Automaticky změnit UNC cesty ke knihovně Plexu \\myNas\\mymovie.mkv na smb " "cesty smb://myNas/mymovie.mkv? (doporučeno)" msgctxt "#39034" -msgid "Replace Plex UNC paths \\myNas with smb://myNas" +msgid "Replace Plex UNC paths \\\\myNas with smb://myNas" msgstr "Nahradit UNC cesty z \\myNas na smb://myNas" msgctxt "#39035" msgid "" -"Replace Plex paths /volume1/media or \\myserver\\media with custom SMB paths" -" smb://NAS/mystuff" +"Replace Plex paths /volume1/media or \\\\myserver\\media with custom SMB " +"paths smb://NAS/mystuff" msgstr "" "Nahradit cesty /volume1/media nebo \\myserver\\media za vlastní cesty " "smb://NAS/mystuff" @@ -2009,3 +2010,34 @@ msgstr "" msgctxt "#39705" msgid "Use at your own risk" msgstr "Používejte na vlastní nebezpečí" + +# If user gets prompted to choose between several subtitles. Leave the number +# one at the beginning of the string! +msgctxt "#39706" +msgid "1 No subtitles" +msgstr "Žádné titulky" + +# If user gets prompted to choose between several audio/subtitle tracks and +# language is unknown +msgctxt "#39707" +msgid "unknown" +msgstr "neznámé" + +# If user gets prompted to choose between several subtitles and Plex adds the +# "default" flag +msgctxt "#39708" +msgid "Default" +msgstr "Výchozí" + +# If user gets prompted to choose between several subtitles and Plex adds the +# "forced" flag +msgctxt "#39709" +msgid "Forced" +msgstr "Vynucené" + +# If user gets prompted to choose between several subtitles the subtitle +# cannot be downloaded (has no 'key' attribute from the PMS), the subtitle +# needs to be burned in +msgctxt "#39710" +msgid "burn-in" +msgstr "Vpálit" From 7e39e6bdd45606c700a55685aea7dce6cd3d1d30 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Wed, 3 May 2017 20:39:00 +0200 Subject: [PATCH 04/24] Version bump --- README.md | 2 +- addon.xml | 8 ++++++-- changelog.txt | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2dc54eb6..a28b5a9a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![stable version](https://img.shields.io/badge/stable_version-1.7.7-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) -[![beta version](https://img.shields.io/badge/beta_version-1.7.9-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) +[![beta version](https://img.shields.io/badge/beta_version-1.7.10-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) diff --git a/addon.xml b/addon.xml index 1f8c79cb..70538562 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -44,7 +44,11 @@ Gebruik op eigen risico 使用風險由您自己承擔 Usar a su propio riesgo - version 1.7.9 (beta only) + version 1.7.10 (beta only) +- Avoid xbmcvfs entirely; use encoded paths +- Update Czech translation + +version 1.7.9 (beta only) - Big transcoding overhaul - Fix for not detecting external subtitle language - Change Plex transcoding profile to Android diff --git a/changelog.txt b/changelog.txt index aa2c9e14..642b5fc8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +version 1.7.10 (beta only) +- Avoid xbmcvfs entirely; use encoded paths +- Update Czech translation + version 1.7.9 (beta only) - Big transcoding overhaul - Fix for not detecting external subtitle language From a30f1ebc42ea0233a99d2886a56580cb9bf41b77 Mon Sep 17 00:00:00 2001 From: Andrea Cotza Date: Fri, 5 May 2017 10:01:26 +0200 Subject: [PATCH 05/24] Add support to Kodi 18.0-alpha1 --- resources/lib/variables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/variables.py b/resources/lib/variables.py index 40f354ad..fa3b8be5 100644 --- a/resources/lib/variables.py +++ b/resources/lib/variables.py @@ -68,7 +68,7 @@ _DB_VIDEO_VERSION = { 15: 93, # Isengard 16: 99, # Jarvis 17: 107, # Krypton - 18: 107 # Leia + 18: 108 # Leia } DB_VIDEO_PATH = xbmc.translatePath( "special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]) @@ -79,7 +79,7 @@ _DB_MUSIC_VERSION = { 15: 52, # Isengard 16: 56, # Jarvis 17: 60, # Krypton - 18: 60 # Leia + 18: 62 # Leia } DB_MUSIC_PATH = xbmc.translatePath( "special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]) From 3a878334b589ffb5519fd21af67d6dd06c8b13fd Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Fri, 5 May 2017 20:16:48 +0200 Subject: [PATCH 06/24] Fix PKC not storing network credentials correctly - Fixes #287 --- resources/lib/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 1abfc8b1..79a0bf49 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -16,6 +16,7 @@ from calendar import timegm from os.path import exists, join from os import remove, makedirs, walk from shutil import rmtree +from urllib import quote_plus import xbmc import xbmcaddon @@ -696,18 +697,19 @@ def passwordsXML(): server = dialog.input("Enter the server name or IP address") if not server: return + server = quote_plus(server) # Network username user = dialog.input("Enter the network username") if not user: return + user = quote_plus(user) # Network password password = dialog.input("Enter the network password", '', # Default input xbmcgui.INPUT_ALPHANUM, xbmcgui.ALPHANUM_HIDE_INPUT) # Need to url-encode the password - from urllib import quote_plus password = quote_plus(password) # Add elements. Annoying etree bug where findall hangs forever if skipFind is False: From c6fdf851a8c79f011fc771121fdf39dc4b052c15 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Fri, 5 May 2017 20:19:35 +0200 Subject: [PATCH 07/24] Version bump --- README.md | 2 +- addon.xml | 8 ++++++-- changelog.txt | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a28b5a9a..a0288621 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![stable version](https://img.shields.io/badge/stable_version-1.7.7-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) -[![beta version](https://img.shields.io/badge/beta_version-1.7.10-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) +[![beta version](https://img.shields.io/badge/beta_version-1.7.11-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) diff --git a/addon.xml b/addon.xml index 70538562..8815bd8c 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -44,7 +44,11 @@ Gebruik op eigen risico 使用風險由您自己承擔 Usar a su propio riesgo - version 1.7.10 (beta only) + version 1.7.11 (beta only) +- Add support to Kodi 18.0-alpha1 (thanks @CotzaDev) +- Fix PKC not storing network credentials correctly + +version 1.7.10 (beta only) - Avoid xbmcvfs entirely; use encoded paths - Update Czech translation diff --git a/changelog.txt b/changelog.txt index 642b5fc8..ee0b55a7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +version 1.7.11 (beta only) +- Add support to Kodi 18.0-alpha1 (thanks @CotzaDev) +- Fix PKC not storing network credentials correctly + version 1.7.10 (beta only) - Avoid xbmcvfs entirely; use encoded paths - Update Czech translation From 45bb93dfc92f07a8c966eae64dc6c0b4a0774087 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 08:30:20 +0200 Subject: [PATCH 08/24] Update Readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index a0288621..4df6560d 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Install PKC via the PlexKodiConnect Kodi repository below (we cannot use the off 2. **Compatibility**: * PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing. * PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library. - * If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths) + * If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths-Explained) ### Donations I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC. @@ -98,8 +98,6 @@ I'm not in any way affiliated with Plex. Thank you very much for a small donatio Solutions are unlikely due to the nature of these issues - A Plex Media Server "bug" leads to frequent and slow syncs, see [here for more info](https://github.com/croneter/PlexKodiConnect/issues/135) - *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details -- *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features -- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly and tell what language they are in. However, this is not the case if you use direct paths *Background Sync:* The Plex Server does not tell anyone of the following changes. Hence PKC cannot detect these changes instantly but will notice them only on full/delta syncs (standard settings is every 60 minutes) From 38cddbdb517fb84fa3774e3a6eca21f6254b48cd Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 08:33:24 +0200 Subject: [PATCH 09/24] Remove link to Crowdin.com --- crowdin.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index 85771861..00000000 --- a/crowdin.yml +++ /dev/null @@ -1,4 +0,0 @@ -files: - - source: /resources/language/English/*.* - translation: /resources/language/%language%/%original_file_name% - translate_attributes: '0' From 7f083272c09d0d87ef9dbc8ffb6521849a3bb19a Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 08:48:02 +0200 Subject: [PATCH 10/24] Fix UnicodeDecodeError on user switch --- service.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/service.py b/service.py index 5d50fdad..176607dd 100644 --- a/service.py +++ b/service.py @@ -30,7 +30,7 @@ sys_path.append(_base_resource) ############################################################################### -from utils import settings, window, language as lang, dialog, tryDecode +from utils import settings, window, language as lang, dialog, tryEncode from userclient import UserClient import initialsetup from kodimonitor import KodiMonitor @@ -164,11 +164,12 @@ class Service(): counter = 0 while not monitor.abortRequested(): - if window('plex_kodiProfile') != kodiProfile: + if tryEncode(window('plex_kodiProfile')) != kodiProfile: # Profile change happened, terminate this thread and others log.warn("Kodi profile was: %s and changed to: %s. " "Terminating old PlexKodiConnect thread." - % (kodiProfile, window('plex_kodiProfile'))) + % (kodiProfile, + tryEncode(window('plex_kodiProfile')))) break # Before proceeding, need to make sure: From ee85db457003104893460a95873c0e56a9df687a Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 09:35:05 +0200 Subject: [PATCH 11/24] Smarter function to set advancedsettings.xml values --- resources/lib/initialsetup.py | 8 +-- resources/lib/librarysync.py | 4 +- resources/lib/utils.py | 108 ++++++++++++++++++---------------- 3 files changed, 63 insertions(+), 57 deletions(-) diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 534241aa..b0aee77c 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -7,7 +7,7 @@ import xbmc import xbmcgui from utils import settings, window, language as lang, tryEncode, \ - get_advancessettings_xml_setting + advancessettings_xml import downloadutils from userclient import UserClient @@ -401,7 +401,7 @@ class InitialSetup(): dialog = self.dialog # Get current Kodi video cache setting - cache = get_advancessettings_xml_setting(['cache', 'memorysize']) + cache = advancessettings_xml(['cache', 'memorysize']) if cache is not None: cache = str(cache.text) else: @@ -478,8 +478,8 @@ class InitialSetup(): log.debug("User opted to disable Plex music library.") settings('enableMusic', value="false") else: - from utils import advancedSettingsXML - advancedSettingsXML() + from utils import advancedsettings_tweaks + advancedsettings_tweaks() # Download additional art from FanArtTV if dialog.yesno(heading=lang(29999), line1=lang(39061)): diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 8c1b77d5..017e4cf9 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -12,7 +12,7 @@ import xbmcgui from utils import window, settings, getUnixTimestamp, sourcesXML,\ ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\ setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\ - advancedSettingsXML, tryDecode, deletePlaylists, deleteNodes, \ + advancedsettings_tweaks, tryDecode, deletePlaylists, deleteNodes, \ ThreadMethodsAdditionalSuspend, create_actor_db_index, dialog import downloadutils import itemtypes @@ -1461,7 +1461,7 @@ class LibrarySync(Thread): self.initializeDBs() if self.enableMusic: - advancedSettingsXML() + advancedsettings_tweaks() if settings('FanartTV') == 'true': self.fanartthread.start() diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 79a0bf49..930fc3c4 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -451,21 +451,24 @@ def normalize_string(text): return text + def indent(elem, level=0): - # Prettify xml trees + """ + Prettifies xml trees. Pass the etree root in + """ i = "\n" + level*" " if len(elem): if not elem.text or not elem.text.strip(): - elem.text = i + " " + elem.text = i + " " if not elem.tail or not elem.tail.strip(): - elem.tail = i + elem.tail = i for elem in elem: - indent(elem, level+1) + indent(elem, level+1) if not elem.tail or not elem.tail.strip(): - elem.tail = i + elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i + elem.tail = i def guisettingsXML(): @@ -519,7 +522,7 @@ def __setSubElement(element, subelement): return answ -def get_advancessettings_xml_setting(node_list): +def advancessettings_xml(node_list, new_value=None, attrib=None): """ Returns the etree element for nodelist (if it exists) and None if not set @@ -529,7 +532,7 @@ def get_advancessettings_xml_setting(node_list): 750 - Example xml: + for the following example xml: @@ -537,60 +540,63 @@ def get_advancessettings_xml_setting(node_list): 750 + + If new_value is set, '750' will be replaced accordingly, returning the new + etree Element. Advancedsettings might be generated if it did not exist + already + + If the dict attrib is set, the Element's attributs will be appended + accordingly """ - path = tryDecode(xbmc.translatePath("special://profile/")) + path = '%sadvancedsettings.xml' % xbmc.translatePath("special://profile/") try: - xmlparse = etree.parse("%sadvancedsettings.xml" % path) - except: - log.debug('Could not parse advancedsettings.xml, returning None') - return - root = xmlparse.getroot() + xml = etree.parse(path) + except IOError: + # Document is blank or missing + if new_value is None and attrib is None: + log.debug('Could not parse advancedsettings.xml, returning None') + return + # Create topmost xml entry + root = etree.Element('advancedsettings') + else: + root = xml.getroot() + element = root + # Reading values + if new_value is None and attrib is None: + for node in node_list: + element = element.find(node) + if element is None: + break + return element + + # Setting new values. Get correct element first for node in node_list: - root = root.find(node) - if root is None: - break - return root + element = __setSubElement(element, node) + # Write new values + element.text = new_value or '' + if attrib is not None: + for key, attribute in attrib.iteritems(): + element.set(key, attribute) + # Indent and make readable + indent(root) + # Safe the changed xml + try: + xml.write(path) + except NameError: + etree.ElementTree(root).write(path) + return element -def advancedSettingsXML(): +def advancedsettings_tweaks(): """ Kodi tweaks Changes advancedsettings.xml, musiclibrary: backgroundupdate set to "true" - - Overrides guisettings.xml in Kodi userdata folder: - updateonstartup : set to "false" - usetags : set to "false" - findremotethumbs : set to "false" """ - path = tryDecode(xbmc.translatePath("special://profile/")) - xmlpath = "%sadvancedsettings.xml" % path - - try: - xmlparse = etree.parse(xmlpath) - except: - # Document is blank or missing - root = etree.Element('advancedsettings') - else: - root = xmlparse.getroot() - - music = __setSubElement(root, 'musiclibrary') - __setXMLTag(music, 'backgroundupdate', "true") - # __setXMLTag(music, 'updateonstartup', "false") - - # Subtag 'musicfiles' - # music = __setSubElement(root, 'musicfiles') - # __setXMLTag(music, 'usetags', "false") - # __setXMLTag(music, 'findremotethumbs', "false") - - # Prettify and write to file - try: - indent(root) - except: - pass - etree.ElementTree(root).write(xmlpath) + advancessettings_xml(['musiclibrary', 'backgroundupdate'], + new_value='true') def sourcesXML(): @@ -851,7 +857,7 @@ def LogTime(func): result = func(*args, **kwargs) elapsedtotal = datetime.now() - starttotal log.info('It took %s to run the function %s' - % (elapsedtotal, func.__name__)) + % (elapsedtotal, func.__name__)) return result return wrapper From d7bd6b0d131a14563745c12be27042b91e9517c2 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 09:45:21 +0200 Subject: [PATCH 12/24] Function to grab all Plex libraries from PMS --- resources/lib/PlexFunctions.py | 39 ++++------------------------------ resources/lib/librarysync.py | 8 +++---- 2 files changed, 7 insertions(+), 40 deletions(-) diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index 82e54cad..e6e9954e 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -258,43 +258,12 @@ def GetPlexOnDeck(viewId): return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId) -def GetPlexCollections(mediatype): +def get_plex_sections(): """ - Input: - mediatype String or list of strings with possible values - 'movie', 'show', 'artist', 'photo' - Output: - List with an entry of the form: - { - 'name': xxx Plex title for the media section - 'type': xxx Plex type: 'movie', 'show', 'artist', 'photo' - 'id': xxx Plex unique key for the section (1, 2, 3...) - 'uuid': xxx Other unique Plex key, e.g. - 74aec9f2-a312-4723-9436-de2ea43843c1 - } - Returns an empty list if nothing is found. + Returns all Plex sections (libraries) of the PMS as an etree xml """ - collections = [] - url = "{server}/library/sections" - xml = downloadutils.DownloadUtils().downloadUrl(url) - try: - xml.attrib - except AttributeError: - log.error('Could not download PMS sections for %s' % url) - return {} - for item in xml: - contentType = item['type'] - if contentType in mediatype: - name = item['title'] - contentId = item['key'] - uuid = item['uuid'] - collections.append({ - 'name': name, - 'type': contentType, - 'id': str(contentId), - 'uuid': uuid - }) - return collections + return downloadutils.DownloadUtils().downloadUrl( + '{server}/library/sections') def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie', diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 017e4cf9..c736317c 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -23,7 +23,7 @@ import videonodes import variables as v from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \ - GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus + GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus, get_plex_sections import PlexAPI from library_sync.get_metadata import Threaded_Get_Metadata from library_sync.process_metadata import Threaded_Process_Metadata @@ -126,8 +126,7 @@ class LibrarySync(Thread): # change in lastViewedAt # Get all Plex libraries - sections = downloadutils.DownloadUtils().downloadUrl( - "{server}/library/sections") + sections = get_plex_sections() try: sections.attrib except AttributeError: @@ -477,8 +476,7 @@ class LibrarySync(Thread): vnodes = self.vnodes # Get views - sections = downloadutils.DownloadUtils().downloadUrl( - "{server}/library/sections") + sections = get_plex_sections() try: sections.attrib except AttributeError: From ec6a526f0923854414c3ae1a4c4f5d042e814a20 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 10:03:35 +0200 Subject: [PATCH 13/24] Remove obsolete regex --- resources/lib/companion.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/resources/lib/companion.py b/resources/lib/companion.py index 652e5c48..9733acce 100644 --- a/resources/lib/companion.py +++ b/resources/lib/companion.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import logging -from re import compile as re_compile from xbmc import Player @@ -13,8 +12,6 @@ from PlexFunctions import GetPlexKeyNumber log = logging.getLogger("PLEX."+__name__) -REGEX_PLAYQUEUES = re_compile(r'''/playQueues/(\d+)$''') - ############################################################################### From cbb44e4ccf551dae1860ceb5ee7a5e45b85ddf9f Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 17:04:09 +0200 Subject: [PATCH 14/24] Major music overhaul: Direct Paths should now work! - Remember to always use Direct Paths with Music ;-) - Fixes #84 --- .../resource.language.en_gb/strings.po | 5 + resources/lib/initialsetup.py | 4 +- resources/lib/librarysync.py | 11 ++ resources/lib/music.py | 119 ++++++++++++++++++ resources/lib/utils.py | 34 ++--- 5 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 resources/lib/music.py diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 07db049c..f88e31b5 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1894,3 +1894,8 @@ msgstr "" msgctxt "#39710" msgid "burn-in" msgstr "" + +# Dialog text if PKC detected a new Music library and Kodi needs to be restarted +msgctxt "#39711" +msgid "New Plex music library detected. Sorry, but we need to restart Kodi now due to the changes made." +msgstr "" diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index b0aee77c..887c04a0 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -7,7 +7,7 @@ import xbmc import xbmcgui from utils import settings, window, language as lang, tryEncode, \ - advancessettings_xml + advancedsettings_xml import downloadutils from userclient import UserClient @@ -401,7 +401,7 @@ class InitialSetup(): dialog = self.dialog # Get current Kodi video cache setting - cache = advancessettings_xml(['cache', 'memorysize']) + cache, _ = advancedsettings_xml(['cache', 'memorysize']) if cache is not None: cache = str(cache.text) else: diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index c736317c..75c4217b 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -29,6 +29,7 @@ from library_sync.get_metadata import Threaded_Get_Metadata from library_sync.process_metadata import Threaded_Process_Metadata import library_sync.sync_info as sync_info from library_sync.fanart import Process_Fanart_Thread +import music ############################################################################### @@ -71,6 +72,7 @@ class LibrarySync(Thread): self.enableMusic = settings('enableMusic') == "true" self.enableBackgroundSync = settings( 'enableBackgroundSync') == "true" + self.direct_paths = settings('useDirectPaths') == '1' # Init for replacing paths window('remapSMB', value=settings('remapSMB')) @@ -295,6 +297,15 @@ class LibrarySync(Thread): } if self.enableMusic: process['music'] = self.PlexMusic + if self.direct_paths is True: + if music.set_excludefromscan_music_folders() is True: + log.info('Detected new Music library - restarting now') + # 'New Plex music library detected. Sorry, but we need to + # restart Kodi now due to the changes made.' + dialog('ok', lang(29999), lang(39711)) + from xbmc import executebuiltin + executebuiltin('RestartApp') + return False # Do the processing for itemtype in process: diff --git a/resources/lib/music.py b/resources/lib/music.py new file mode 100644 index 00000000..3374fa47 --- /dev/null +++ b/resources/lib/music.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from logging import getLogger +from re import compile as re_compile +import xml.etree.ElementTree as etree + +from utils import advancedsettings_xml, indent, tryEncode +from PlexFunctions import get_plex_sections +from PlexAPI import API +import variables as v + +############################################################################### +log = getLogger("PLEX."+__name__) + +REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''') +############################################################################### + + +def get_current_music_folders(): + """ + Returns a list of encoded strings as paths to the currently "blacklisted" + excludefromscan music folders in the advancedsettings.xml + """ + paths = [] + root, _ = advancedsettings_xml(['audio', 'excludefromscan']) + if root is None: + return paths + + for element in root: + try: + path = REGEX_MUSICPATH.findall(element.text)[0] + except IndexError: + log.error('Could not parse %s of xml element %s' + % (element.text, element.tag)) + continue + else: + paths.append(path) + return paths + + +def set_excludefromscan_music_folders(): + """ + Gets a complete list of paths for music libraries from the PMS. Sets them + to be excluded in the advancedsettings.xml from being scanned by Kodi. + Existing keys will be replaced + + Returns False if no new Plex libraries needed to be exluded, True otherwise + """ + changed = False + write_xml = False + xml = get_plex_sections() + try: + xml[0].attrib + except (TypeError, IndexError, AttributeError): + log.error('Could not get Plex sections') + return + # Build paths + paths = [] + api = API(item=None) + for library in xml: + if library.attrib['type'] != v.PLEX_TYPE_ARTIST: + # Only look at music libraries + continue + for location in library: + if location.tag == 'Location': + path = api.validatePlayurl(location.attrib['path'], + typus=v.PLEX_TYPE_ARTIST, + forceCheck=True) + path = tryEncode(path) + paths.append(__turn_to_regex(path)) + # Get existing advancedsettings + root, tree = advancedsettings_xml(['audio', 'excludefromscan'], + force_create=True) + + for path in paths: + for element in root: + if element.text == path: + # Path already excluded + break + else: + changed = True + write_xml = True + log.info('New Plex music library detected: %s' % path) + element = etree.Element(tag='regexp') + element.text = path + root.append(element) + + # Delete obsolete entries (unlike above, we don't change 'changed' to not + # enforce a restart) + for element in root: + for path in paths: + if element.text == path: + break + else: + log.info('Deleting Plex music library from advancedsettings: %s' + % element.text) + root.remove(element) + write_xml = True + + if write_xml is True: + indent(tree.getroot()) + tree.write('%sadvancedsettings.xml' % v.KODI_PROFILE) + return changed + + +def __turn_to_regex(path): + """ + Turns a path into regex expression to be fed to Kodi's advancedsettings.xml + """ + # Make sure we have a slash or backslash at the end of the path + if '/' in path: + if not path.endswith('/'): + path = '%s/' % path + else: + if not path.endswith('\\'): + path = '%s\\' % path + # Need to escape backslashes + path = path.replace('\\', '\\\\') + # Beginning of path only needs to be similar + return '^%s' % path diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 930fc3c4..57b8c341 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -23,7 +23,7 @@ import xbmcaddon import xbmcgui from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \ - DB_PLEX_PATH + DB_PLEX_PATH, KODI_PROFILE ############################################################################### @@ -522,9 +522,11 @@ def __setSubElement(element, subelement): return answ -def advancessettings_xml(node_list, new_value=None, attrib=None): +def advancedsettings_xml(node_list, new_value=None, attrib=None, + force_create=False): """ - Returns the etree element for nodelist (if it exists) and None if not set + Returns the etree element for nodelist (if it exists) and the tree. None if + not set node_list is a list of node names starting from the outside, ignoring the outter advancedsettings. Example nodelist=['video', 'busydialogdelayms'] @@ -547,28 +549,29 @@ def advancessettings_xml(node_list, new_value=None, attrib=None): If the dict attrib is set, the Element's attributs will be appended accordingly + + force_create=True will forcibly create the key even if no value is provided """ - path = '%sadvancedsettings.xml' % xbmc.translatePath("special://profile/") + path = '%sadvancedsettings.xml' % KODI_PROFILE try: - xml = etree.parse(path) + tree = etree.parse(path) except IOError: # Document is blank or missing - if new_value is None and attrib is None: + if new_value is None and attrib is None and force_create is False: log.debug('Could not parse advancedsettings.xml, returning None') return # Create topmost xml entry - root = etree.Element('advancedsettings') - else: - root = xml.getroot() + tree = etree.ElementTree(element=etree.Element('advancedsettings')) + root = tree.getroot() element = root # Reading values - if new_value is None and attrib is None: + if new_value is None and attrib is None and force_create is False: for node in node_list: element = element.find(node) if element is None: break - return element + return element, tree # Setting new values. Get correct element first for node in node_list: @@ -581,11 +584,8 @@ def advancessettings_xml(node_list, new_value=None, attrib=None): # Indent and make readable indent(root) # Safe the changed xml - try: - xml.write(path) - except NameError: - etree.ElementTree(root).write(path) - return element + tree.write(path) + return element, tree def advancedsettings_tweaks(): @@ -595,7 +595,7 @@ def advancedsettings_tweaks(): Changes advancedsettings.xml, musiclibrary: backgroundupdate set to "true" """ - advancessettings_xml(['musiclibrary', 'backgroundupdate'], + advancedsettings_xml(['musiclibrary', 'backgroundupdate'], new_value='true') From 3d7caec69a5552add81b05422dd6c5b87e67706f Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 17:22:29 +0200 Subject: [PATCH 15/24] Don't verify detected music paths --- resources/lib/PlexAPI.py | 7 +++++-- resources/lib/music.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index e50760f5..1c53396d 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -2509,7 +2509,8 @@ class API(): listItem.addStreamInfo( "video", {'duration': self.getRuntime()[1]}) - def validatePlayurl(self, path, typus, forceCheck=False, folder=False): + def validatePlayurl(self, path, typus, forceCheck=False, folder=False, + omitCheck=False): """ Returns a valid path for Kodi, e.g. with '\' substituted to '\\' in Unicode. Returns None if this is not possible @@ -2519,6 +2520,7 @@ class API(): forceCheck : Will always try to check validity of path Will also skip confirmation dialog if path not found folder : Set to True if path is a folder + omitCheck : Will entirely omit validity check if True """ if path is None: return None @@ -2532,7 +2534,8 @@ class API(): elif window('replaceSMB') == 'true': if path.startswith('\\\\'): path = 'smb:' + path.replace('\\', '/') - if window('plex_pathverified') == 'true' and forceCheck is False: + if ((window('plex_pathverified') == 'true' and forceCheck is False) or + omitCheck is True): return path # exist() needs a / or \ at the end to work for directories diff --git a/resources/lib/music.py b/resources/lib/music.py index 3374fa47..9ed2cb7b 100644 --- a/resources/lib/music.py +++ b/resources/lib/music.py @@ -64,7 +64,7 @@ def set_excludefromscan_music_folders(): if location.tag == 'Location': path = api.validatePlayurl(location.attrib['path'], typus=v.PLEX_TYPE_ARTIST, - forceCheck=True) + omitCheck=True) path = tryEncode(path) paths.append(__turn_to_regex(path)) # Get existing advancedsettings From ef0e2e2ba0d85f9b978ad735080426e0240d7ae1 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 18:36:24 +0200 Subject: [PATCH 16/24] Plex Companion: escape HTML --- resources/lib/playlist_func.py | 6 ++++-- resources/lib/utils.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index 5fb9289c..b3a9f143 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -4,7 +4,7 @@ from urlparse import parse_qsl, urlsplit import plexdb_functions as plexdb from downloadutils import DownloadUtils as DU -from utils import JSONRPC, tryEncode +from utils import JSONRPC, tryEncode, escape_html from PlexAPI import API ############################################################################### @@ -157,6 +157,8 @@ def playlist_item_from_xml(playlist, xml_video_element): item.plex_id = api.getRatingKey() item.ID = xml_video_element.attrib['%sItemID' % playlist.kind] item.guid = xml_video_element.attrib.get('guid') + if item.guid is not None: + item.guid = escape_html(item.guid) if item.plex_id: with plexdb.Get_Plex_DB() as plex_db: db_element = plex_db.getItem_byId(item.plex_id) @@ -336,7 +338,7 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None): # Get the guid for this item for plex_item in xml: if plex_item.attrib['%sItemID' % playlist.kind] == item.ID: - item.guid = plex_item.attrib['guid'] + item.guid = escape_html(plex_item.attrib['guid']) playlist.items.append(item) if pos == len(playlist.items) - 1: # Item was added at the end diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 57b8c341..752801cf 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -182,6 +182,23 @@ def tryDecode(string, encoding='utf-8'): return string +def escape_html(string): + """ + Escapes the following: + < to < + > to > + & to & + """ + escapes = { + '<': '<', + '>': '>', + '&': '&' + } + for key, value in escapes.iteritems(): + string = string.replace(key, value) + return string + + def DateToKodi(stamp): """ converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a From efb75c8f9934ee8ef67240e0b7edc6df0f11072e Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 18:38:44 +0200 Subject: [PATCH 17/24] Plex Companion: drop telling the 'location' --- resources/lib/plexbmchelper/subscribers.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index 463807fe..7294fea6 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -56,18 +56,6 @@ class SubscriptionManager: def msg(self, players): msg = getXMLHeader() msg += ' Date: Sat, 6 May 2017 18:39:38 +0200 Subject: [PATCH 18/24] Typos --- resources/lib/plexbmchelper/functions.py | 2 +- resources/lib/plexbmchelper/subscribers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/plexbmchelper/functions.py b/resources/lib/plexbmchelper/functions.py index cdda52fe..05b76392 100644 --- a/resources/lib/plexbmchelper/functions.py +++ b/resources/lib/plexbmchelper/functions.py @@ -57,7 +57,7 @@ def plex_type(xbmc_type): def getXMLHeader(): - return '\n' + return '\n' def getOKMsg(): diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index 7294fea6..3120a330 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -125,7 +125,7 @@ class SubscriptionManager: ret += ' subtitleStreamID="-1"' ret += ' audioStreamID="-1"' - ret += ' />' + ret += '/>' return ret def updateCommandID(self, uuid, commandID): From 9c48840b5492868e723ae52e96018bcf83092b21 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 18:42:05 +0200 Subject: [PATCH 19/24] Plex Companion: Add machineIdentifier --- resources/lib/plexbmchelper/subscribers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index 3120a330..ed4355c9 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -56,6 +56,7 @@ class SubscriptionManager: def msg(self, players): msg = getXMLHeader() msg += ' Date: Sat, 6 May 2017 18:42:43 +0200 Subject: [PATCH 20/24] Plex Companion: Add size of XML --- resources/lib/plexbmchelper/subscribers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index ed4355c9..557f761e 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -55,7 +55,7 @@ class SubscriptionManager: def msg(self, players): msg = getXMLHeader() - msg += ' Date: Sat, 6 May 2017 18:48:58 +0200 Subject: [PATCH 21/24] Typo --- resources/lib/plexbmchelper/subscribers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index 557f761e..22d77f64 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -56,7 +56,7 @@ class SubscriptionManager: def msg(self, players): msg = getXMLHeader() msg += ' Date: Sat, 6 May 2017 18:50:28 +0200 Subject: [PATCH 22/24] Typo --- resources/lib/plexbmchelper/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/plexbmchelper/functions.py b/resources/lib/plexbmchelper/functions.py index 05b76392..784a1e77 100644 --- a/resources/lib/plexbmchelper/functions.py +++ b/resources/lib/plexbmchelper/functions.py @@ -57,7 +57,7 @@ def plex_type(xbmc_type): def getXMLHeader(): - return '\n' + return '\n' def getOKMsg(): From 363682332737050fa6b2e48fa9e553dc22845bc2 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 18:52:50 +0200 Subject: [PATCH 23/24] Less logging --- resources/lib/kodidb_functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 5148fb47..2dd5ad90 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -851,7 +851,6 @@ class Kodidb_Functions(): if len(result) == 0: log.info('Did not find matching paths, abort') return - log.debug('Result: %s' % result) # Kodi seems to make ONE temporary entry; we only want the earlier, # permanent one if len(result) > 2: @@ -859,7 +858,6 @@ class Kodidb_Functions(): ' paths, aborting') return idFile = result[0] - log.debug('idFile: %s' % idFile) # Try movies first query = ' '.join(( From 96f3c062d04721b2966526a3104045a1d43a9501 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 19:01:38 +0200 Subject: [PATCH 24/24] Version bump --- README.md | 2 +- addon.xml | 11 +++++++++-- changelog.txt | 7 +++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4df6560d..e398413f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![stable version](https://img.shields.io/badge/stable_version-1.7.7-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) -[![beta version](https://img.shields.io/badge/beta_version-1.7.11-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) +[![beta version](https://img.shields.io/badge/beta_version-1.7.12-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) diff --git a/addon.xml b/addon.xml index 8815bd8c..2476c915 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -44,7 +44,14 @@ Gebruik op eigen risico 使用風險由您自己承擔 Usar a su propio riesgo - version 1.7.11 (beta only) + version 1.7.12 (beta only) +- Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database +- Some Plex Companion fixes +- Fix UnicodeDecodeError on user switch +- Remove link to Crowdin.com +- Update Readme + +version 1.7.11 (beta only) - Add support to Kodi 18.0-alpha1 (thanks @CotzaDev) - Fix PKC not storing network credentials correctly diff --git a/changelog.txt b/changelog.txt index ee0b55a7..03a188c2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +version 1.7.12 (beta only) +- Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database +- Some Plex Companion fixes +- Fix UnicodeDecodeError on user switch +- Remove link to Crowdin.com +- Update Readme + version 1.7.11 (beta only) - Add support to Kodi 18.0-alpha1 (thanks @CotzaDev) - Fix PKC not storing network credentials correctly