Force-set some important Kodi settings

- Fixes #389
This commit is contained in:
tomkat83 2018-01-07 15:16:53 +01:00
parent eb6b1fbe48
commit 607fdab326
4 changed files with 219 additions and 174 deletions

View file

@ -2,12 +2,13 @@
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from Queue import Queue from Queue import Queue
from xml.etree.ElementTree import ParseError
import xbmc import xbmc
import xbmcgui import xbmcgui
from utils import settings, window, language as lang, tryEncode, \ from utils import settings, window, language as lang, tryEncode, \
advancedsettings_xml XmlKodiSetting, reboot_kodi
import downloadutils import downloadutils
from userclient import UserClient from userclient import UserClient
@ -402,17 +403,28 @@ class InitialSetup():
""" """
LOG.info("Initial setup called.") LOG.info("Initial setup called.")
dialog = self.dialog dialog = self.dialog
try:
# Get current Kodi video cache setting with XmlKodiSetting('advancedsettings.xml',
cache, _ = advancedsettings_xml(['cache', 'memorysize']) 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 # Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520' cache = str(cache.text) if cache is not None else '20971520'
LOG.info('Current Kodi video memory cache in bytes: %s', cache) LOG.info('Current Kodi video memory cache in bytes: %s', cache)
settings('kodi_video_cache', value=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? # Do we need to migrate stuff?
check_migration() check_migration()
# Optionally sign into plex.tv. Will not be called on very first run # 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", LOG.info("Using PMS %s with machineIdentifier %s",
self.server, self.serverid) self.server, self.serverid)
self._write_PMS_settings(self.server, self.pms_token) self._write_PMS_settings(self.server, self.pms_token)
if reboot is True:
reboot_kodi()
return return
# If not already retrieved myplex info, optionally let user sign in # If not already retrieved myplex info, optionally let user sign in
@ -450,6 +464,8 @@ class InitialSetup():
# User already answered the installation questions # User already answered the installation questions
if settings('InstallQuestionsAnswered') == 'true': if settings('InstallQuestionsAnswered') == 'true':
if reboot is True:
reboot_kodi()
return return
# Additional settings where the user needs to choose # Additional settings where the user needs to choose
@ -517,3 +533,5 @@ class InitialSetup():
state.PMS_STATUS = 'Stop' state.PMS_STATUS = 'Stop'
xbmc.executebuiltin( xbmc.executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)') 'Addon.OpenSettings(plugin.video.plexkodiconnect)')
elif reboot is True:
reboot_kodi()

View file

@ -458,14 +458,8 @@ class LibrarySync(Thread):
Compare the views to Plex Compare the views to Plex
""" """
if state.DIRECT_PATHS is True and state.ENABLE_MUSIC is True: if state.DIRECT_PATHS is True and state.ENABLE_MUSIC is True:
if music.set_excludefromscan_music_folders() is True: # Will reboot Kodi is new library detected
log.info('Detected new Music library - restarting now') music.excludefromscan_music_folders()
# '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
self.views = [] self.views = []
vnodes = self.vnodes vnodes = self.vnodes

View file

@ -1,57 +1,33 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from logging import getLogger from logging import getLogger
from re import compile as re_compile 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 PlexFunctions import get_plex_sections
from PlexAPI import API from PlexAPI import API
import variables as v import variables as v
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''') REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
############################################################################### ###############################################################################
def get_current_music_folders(): def excludefromscan_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 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. to be excluded in the advancedsettings.xml from being scanned by Kodi.
Existing keys will be replaced 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() xml = get_plex_sections()
try: try:
xml[0].attrib xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
log.error('Could not get Plex sections') LOG.error('Could not get Plex sections')
return return
# Build paths # Build paths
paths = [] paths = []
@ -66,39 +42,38 @@ def set_excludefromscan_music_folders():
typus=v.PLEX_TYPE_ARTIST, typus=v.PLEX_TYPE_ARTIST,
omitCheck=True) omitCheck=True)
paths.append(__turn_to_regex(path)) paths.append(__turn_to_regex(path))
# Get existing advancedsettings try:
root, tree = advancedsettings_xml(['audio', 'excludefromscan'], with XmlKodiSetting('advancedsettings.xml',
force_create=True) force_create=True,
top_element='advancedsettings') as xml:
for path in paths: parent = xml.set_setting(['audio', 'excludefromscan'])
for element in root: for path in paths:
if element.text == path: for element in parent:
# Path already excluded if element.text == path:
break # Path already excluded
else: break
changed = True else:
write_xml = True LOG.info('New Plex music library detected: %s', path)
log.info('New Plex music library detected: %s' % path) xml.set_setting(['audio', 'excludefromscan', 'regexp'],
element = etree.Element(tag='regexp') value=path, check_existing=False)
element.text = path # We only need to reboot if we ADD new paths!
root.append(element) reboot = xml.write_xml
# Delete obsolete entries
# Delete obsolete entries (unlike above, we don't change 'changed' to not for element in parent:
# enforce a restart) for path in paths:
for element in root: if element.text == path:
for path in paths: break
if element.text == path: else:
break LOG.info('Deleting music library from advancedsettings: %s',
else: element.text)
log.info('Deleting Plex music library from advancedsettings: %s' parent.remove(element)
% element.text) except (ParseError, IOError):
root.remove(element) LOG.error('Could not adjust advancedsettings.xml')
write_xml = True reboot = False
if reboot is True:
if write_xml is True: # 'New Plex music library detected. Sorry, but we need to
indent(tree.getroot()) # restart Kodi now due to the changes made.'
tree.write('%sadvancedsettings.xml' % v.KODI_PROFILE, encoding="UTF-8") reboot_kodi(lang(39711))
return changed
def __turn_to_regex(path): def __turn_to_regex(path):

View file

@ -37,6 +37,17 @@ ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
# Main methods # 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): def window(property, value=None, clear=False, windowid=10000):
""" """
Get or set window property - thread safe! Get or set window property - thread safe!
@ -457,12 +468,7 @@ def reset():
dataPath = "%ssettings.xml" % addondir dataPath = "%ssettings.xml" % addondir
log.info("Deleting: settings.xml") log.info("Deleting: settings.xml")
remove(dataPath) remove(dataPath)
reboot_kodi()
# Kodi will now restart to apply the changes.
dialog('ok',
heading='{plex} %s ' % language(30132),
line1=language(33033))
xbmc.executebuiltin('RestartApp')
def profiling(sortby="cumulative"): def profiling(sortby="cumulative"):
@ -617,114 +623,166 @@ def guisettingsXML():
return root return root
def __setXMLTag(element, tag, value, attrib=None): class XmlKodiSetting(object):
""" """
Looks for an element's subelement and sets its value. Used to load a Kodi XML settings file from special://profile as an etree
If "subelement" does not exist, create it using attrib and value. 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 filename [str]: filename of the Kodi settings file under
tag : unicode for subelement path [str]: if set, replace special://profile path with custom
value : unicode path
attrib : dict; will use etree attrib method 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) def __init__(self, filename, path=None, force_create=False,
if subelement is None: top_element=None):
# Setting does not exist yet; create it self.filename = filename
if attrib is None: if path is None:
etree.SubElement(element, tag).text = value self.path = join(KODI_PROFILE, filename)
else: else:
etree.SubElement(element, tag, attrib=attrib).text = value self.path = join(path, filename)
else: self.force_create = force_create
subelement.text = value self.top_element = top_element
return subelement 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): def __exit__(self, e_typ, e_val, trcbak):
""" if e_typ:
Returns an etree element's subelement. Creates one if not exist raise
""" # Only safe to file if we did not botch anything
answ = element.find(subelement) if self.write_xml is True:
if answ is None: # Indent and make readable
answ = etree.SubElement(element, subelement) indent(self.root)
return answ # 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, def get_setting(self, node_list):
force_create=False): """
""" node_list is a list of node names starting from the outside, ignoring
Returns the outter advancedsettings.
etree element, tree Example nodelist=['video', 'busydialogdelayms'] for the following xml
or would return the etree Element:
None, None
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:
<busydialogdelayms>750</busydialogdelayms>
for the following example xml:
<?xml version="1.0" encoding="UTF-8" ?>
<advancedsettings>
<video>
<busydialogdelayms>750</busydialogdelayms> <busydialogdelayms>750</busydialogdelayms>
</video>
</advancedsettings>
If new_value is set, '750' will be replaced accordingly, returning the new for the following example xml:
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 <advancedsettings>
accordingly <video>
<busydialogdelayms>750</busydialogdelayms>
</video>
</advancedsettings>
force_create=True will forcibly create the key even if no value is provided Returns the etree element or None if not found
""" """
path = '%sadvancedsettings.xml' % KODI_PROFILE element = self.root
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:
for node in node_list: for node in node_list:
element = element.find(node) element = element.find(node)
if element is None: if element is None:
break break
return element, tree return element
# Setting new values. Get correct element first def set_setting(self, node_list, value=None, attrib=None,
for node in node_list: check_existing=True):
element = __setSubElement(element, node) """
# Write new values node_list is a list of node names starting from the outside, ignoring
element.text = new_value or '' the outter advancedsettings.
if attrib is not None: Example nodelist=['video', 'busydialogdelayms'] for the following xml
for key, attribute in attrib.iteritems(): would return the etree Element:
element.set(key, attribute)
# Indent and make readable <busydialogdelayms>750</busydialogdelayms>
indent(root)
# Safe the changed xml for the following example xml:
tree.write(path, encoding="UTF-8")
return element, tree <advancedsettings>
<video>
<busydialogdelayms>750</busydialogdelayms>
</video>
</advancedsettings>
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(): def sourcesXML():