From cbb44e4ccf551dae1860ceb5ee7a5e45b85ddf9f Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 6 May 2017 17:04:09 +0200 Subject: [PATCH] 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')