diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py
index 92b1eafc..767f1abc 100644
--- a/resources/lib/initialsetup.py
+++ b/resources/lib/initialsetup.py
@@ -2,12 +2,13 @@
###############################################################################
from logging import getLogger
from Queue import Queue
+from xml.etree.ElementTree import ParseError
import xbmc
import xbmcgui
from utils import settings, window, language as lang, tryEncode, \
- advancedsettings_xml
+ XmlKodiSetting, reboot_kodi
import downloadutils
from userclient import UserClient
@@ -402,17 +403,28 @@ class InitialSetup():
"""
LOG.info("Initial setup called.")
dialog = self.dialog
-
- # Get current Kodi video cache setting
- cache, _ = advancedsettings_xml(['cache', 'memorysize'])
+ try:
+ with XmlKodiSetting('advancedsettings.xml',
+ force_create=True,
+ top_element='advancedsettings') as xml:
+ # Get current Kodi video cache setting
+ cache = xml.get_setting(['cache', 'memorysize'])
+ # Disable foreground "Loading media information from files"
+ # (still used by Kodi, even though the Wiki says otherwise)
+ xml.set_setting(['musiclibrary', 'backgroundupdate'],
+ value='true')
+ # Disable cleaning of library - not compatible with PKC
+ xml.set_setting(['videolibrary', 'cleanonupdate'],
+ value='false')
+ reboot = xml.write_xml
+ except ParseError:
+ cache = None
+ reboot = False
# Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520'
LOG.info('Current Kodi video memory cache in bytes: %s', cache)
settings('kodi_video_cache', value=cache)
- # Disable foreground "Loading media information from files"
- # (still used by Kodi, even though the Wiki says otherwise)
- advancedsettings_xml(['musiclibrary', 'backgroundupdate'],
- new_value='true')
+
# Do we need to migrate stuff?
check_migration()
# Optionally sign into plex.tv. Will not be called on very first run
@@ -436,6 +448,8 @@ class InitialSetup():
LOG.info("Using PMS %s with machineIdentifier %s",
self.server, self.serverid)
self._write_PMS_settings(self.server, self.pms_token)
+ if reboot is True:
+ reboot_kodi()
return
# If not already retrieved myplex info, optionally let user sign in
@@ -450,6 +464,8 @@ class InitialSetup():
# User already answered the installation questions
if settings('InstallQuestionsAnswered') == 'true':
+ if reboot is True:
+ reboot_kodi()
return
# Additional settings where the user needs to choose
@@ -517,3 +533,5 @@ class InitialSetup():
state.PMS_STATUS = 'Stop'
xbmc.executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
+ elif reboot is True:
+ reboot_kodi()
diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py
index 28d173b0..82f21f4b 100644
--- a/resources/lib/librarysync.py
+++ b/resources/lib/librarysync.py
@@ -458,14 +458,8 @@ class LibrarySync(Thread):
Compare the views to Plex
"""
if state.DIRECT_PATHS is True and state.ENABLE_MUSIC 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', heading='{plex}', line1=lang(39711))
- from xbmc import executebuiltin
- executebuiltin('RestartApp')
- return False
+ # Will reboot Kodi is new library detected
+ music.excludefromscan_music_folders()
self.views = []
vnodes = self.vnodes
diff --git a/resources/lib/music.py b/resources/lib/music.py
index ab229f0e..136c0a7a 100644
--- a/resources/lib/music.py
+++ b/resources/lib/music.py
@@ -1,57 +1,33 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from re import compile as re_compile
-import xml.etree.ElementTree as etree
+from xml.etree.ElementTree import ParseError
-from utils import advancedsettings_xml, indent, tryEncode
+from utils import XmlKodiSetting, reboot_kodi, language as lang
from PlexFunctions import get_plex_sections
from PlexAPI import API
import variables as v
###############################################################################
-log = getLogger("PLEX."+__name__)
+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():
+def 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
+ Reboots Kodi if new library detected
"""
- changed = False
- write_xml = False
xml = get_plex_sections()
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
- log.error('Could not get Plex sections')
+ LOG.error('Could not get Plex sections')
return
# Build paths
paths = []
@@ -66,39 +42,38 @@ def set_excludefromscan_music_folders():
typus=v.PLEX_TYPE_ARTIST,
omitCheck=True)
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, encoding="UTF-8")
- return changed
+ try:
+ with XmlKodiSetting('advancedsettings.xml',
+ force_create=True,
+ top_element='advancedsettings') as xml:
+ parent = xml.set_setting(['audio', 'excludefromscan'])
+ for path in paths:
+ for element in parent:
+ if element.text == path:
+ # Path already excluded
+ break
+ else:
+ LOG.info('New Plex music library detected: %s', path)
+ xml.set_setting(['audio', 'excludefromscan', 'regexp'],
+ value=path, check_existing=False)
+ # We only need to reboot if we ADD new paths!
+ reboot = xml.write_xml
+ # Delete obsolete entries
+ for element in parent:
+ for path in paths:
+ if element.text == path:
+ break
+ else:
+ LOG.info('Deleting music library from advancedsettings: %s',
+ element.text)
+ parent.remove(element)
+ except (ParseError, IOError):
+ LOG.error('Could not adjust advancedsettings.xml')
+ reboot = False
+ if reboot is True:
+ # 'New Plex music library detected. Sorry, but we need to
+ # restart Kodi now due to the changes made.'
+ reboot_kodi(lang(39711))
def __turn_to_regex(path):
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 6dfdd941..68ab881b 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -37,6 +37,17 @@ ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
# Main methods
+def reboot_kodi(message=None):
+ """
+ Displays an OK prompt with 'Kodi will now restart to apply the changes'
+ Kodi will then reboot.
+
+ Set optional custom message
+ """
+ message = message or language(33033)
+ dialog('ok', heading='{plex}', line1=message)
+ xbmc.executebuiltin('RestartApp')
+
def window(property, value=None, clear=False, windowid=10000):
"""
Get or set window property - thread safe!
@@ -457,12 +468,7 @@ def reset():
dataPath = "%ssettings.xml" % addondir
log.info("Deleting: settings.xml")
remove(dataPath)
-
- # Kodi will now restart to apply the changes.
- dialog('ok',
- heading='{plex} %s ' % language(30132),
- line1=language(33033))
- xbmc.executebuiltin('RestartApp')
+ reboot_kodi()
def profiling(sortby="cumulative"):
@@ -617,114 +623,166 @@ def guisettingsXML():
return root
-def __setXMLTag(element, tag, value, attrib=None):
+class XmlKodiSetting(object):
"""
- Looks for an element's subelement and sets its value.
- If "subelement" does not exist, create it using attrib and value.
+ Used to load a Kodi XML settings file from special://profile as an etree
+ object to read settings or set them. Usage:
+ with XmlKodiSetting(filename,
+ path=None,
+ force_create=False,
+ top_element=None) as xml:
+ xml.get_setting('test')
- element : etree element
- tag : unicode for subelement
- value : unicode
- attrib : dict; will use etree attrib method
+ filename [str]: filename of the Kodi settings file under
+ path [str]: if set, replace special://profile path with custom
+ path
+ force_create: will create the XML file if it does not exist
+ top_element [str]: Name of the top xml element; used if xml does not
+ yet exist
- Returns the subelement
+ Raises IOError if the file does not exist or is empty and force_create
+ has been set to False.
+ Raises etree.ParseError if the file could not be parsed by etree
+
+ xml.write_xml Set to True if we need to write the XML to disk
"""
- subelement = element.find(tag)
- if subelement is None:
- # Setting does not exist yet; create it
- if attrib is None:
- etree.SubElement(element, tag).text = value
+ def __init__(self, filename, path=None, force_create=False,
+ top_element=None):
+ self.filename = filename
+ if path is None:
+ self.path = join(KODI_PROFILE, filename)
else:
- etree.SubElement(element, tag, attrib=attrib).text = value
- else:
- subelement.text = value
- return subelement
+ self.path = join(path, filename)
+ self.force_create = force_create
+ self.top_element = top_element
+ self.tree = None
+ self.root = None
+ self.write_xml = False
+ def __enter__(self):
+ try:
+ self.tree = etree.parse(self.path)
+ except IOError:
+ # Document is blank or missing
+ if self.force_create is False:
+ log.debug('%s does not seem to exist; not creating', self.path)
+ # This will abort __enter__
+ self.__exit__(IOError, None, None)
+ # Create topmost xml entry
+ self.tree = etree.ElementTree(
+ element=etree.Element(self.top_element))
+ self.write_xml = True
+ except etree.ParseError:
+ log.error('Error parsing %s', self.path)
+ # "Kodi cannot parse {0}. PKC will not function correctly. Please
+ # visit {1} and correct your file!"
+ dialog('ok', language(29999), language(39716).format(
+ self.filename,
+ 'http://kodi.wiki'))
+ self.__exit__(etree.ParseError, None, None)
+ self.root = self.tree.getroot()
+ return self
-def __setSubElement(element, subelement):
- """
- Returns an etree element's subelement. Creates one if not exist
- """
- answ = element.find(subelement)
- if answ is None:
- answ = etree.SubElement(element, subelement)
- return answ
+ def __exit__(self, e_typ, e_val, trcbak):
+ if e_typ:
+ raise
+ # Only safe to file if we did not botch anything
+ if self.write_xml is True:
+ # Indent and make readable
+ indent(self.root)
+ # Safe the changed xml
+ self.tree.write(self.path, encoding="UTF-8")
+ @staticmethod
+ def _set_sub_element(element, subelement):
+ """
+ Returns an etree element's subelement. Creates one if not exist
+ """
+ answ = element.find(subelement)
+ if answ is None:
+ answ = etree.SubElement(element, subelement)
+ return answ
-def advancedsettings_xml(node_list, new_value=None, attrib=None,
- force_create=False):
- """
- Returns
- etree element, tree
- or
- None, None
+ def get_setting(self, node_list):
+ """
+ node_list is a list of node names starting from the outside, ignoring
+ the outter advancedsettings.
+ Example nodelist=['video', 'busydialogdelayms'] for the following xml
+ would return the etree Element:
- node_list is a list of node names starting from the outside, ignoring the
- outter advancedsettings. Example nodelist=['video', 'busydialogdelayms']
- for the following xml would return the etree Element:
-
- 750
-
- for the following example xml:
-
-
-
-
-
- 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
+ for the following example xml:
- 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' % KODI_PROFILE
- try:
- tree = etree.parse(path)
- except IOError:
- # Document is blank or missing
- if new_value is None and attrib is None and force_create is False:
- log.debug('Could not parse advancedsettings.xml, returning None')
- return None, None
- # Create topmost xml entry
- tree = etree.ElementTree(element=etree.Element('advancedsettings'))
- except etree.ParseError:
- log.error('Error parsing %s' % path)
- # "Kodi cannot parse {0}. PKC will not function correctly. Please visit
- # {1} and correct your file!"
- dialog('ok', language(29999), language(39716).format(
- 'advancedsettings.xml',
- 'http://kodi.wiki/view/Advancedsettings.xml'))
- return None, None
- root = tree.getroot()
- element = root
-
- # Reading values
- if new_value is None and attrib is None and force_create is False:
+ Returns the etree element or None if not found
+ """
+ element = self.root
for node in node_list:
element = element.find(node)
if element is None:
break
- return element, tree
+ return element
- # Setting new values. Get correct element first
- for node in node_list:
- 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
- tree.write(path, encoding="UTF-8")
- return element, tree
+ def set_setting(self, node_list, value=None, attrib=None,
+ check_existing=True):
+ """
+ node_list is a list of node names starting from the outside, ignoring
+ the outter advancedsettings.
+ Example nodelist=['video', 'busydialogdelayms'] for the following xml
+ would return the etree Element:
+
+ 750
+
+ for the following example xml:
+
+
+
+
+
+ value, e.g. '750' will be set 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
+
+ If check_existing is True, it will return the FIRST matching element of
+ node_list. Set to False if there are several elements of the same tag!
+
+ Returns the (last) etree element
+ """
+ attrib = attrib or {}
+ value = value or ''
+ if check_existing is True:
+ old = self.get_setting(node_list)
+ if old is not None:
+ already_set = True
+ if old.text.strip() != value:
+ already_set = False
+ elif old.attrib != attrib:
+ already_set = False
+ if already_set is True:
+ log.debug('Element has already been found')
+ return old
+ # Need to set new setting, indeed
+ self.write_xml = True
+ element = self.root
+ for node in node_list:
+ element = self._set_sub_element(element, node)
+ # Write new values
+ element.text = value
+ if attrib:
+ for key, attribute in attrib.iteritems():
+ element.set(key, attribute)
+ return element
def sourcesXML():