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:
with XmlKodiSetting('advancedsettings.xml',
force_create=True,
top_element='advancedsettings') as xml:
# Get current Kodi video cache setting # Get current Kodi video cache setting
cache, _ = advancedsettings_xml(['cache', 'memorysize']) 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:
parent = xml.set_setting(['audio', 'excludefromscan'])
for path in paths: for path in paths:
for element in root: for element in parent:
if element.text == path: if element.text == path:
# Path already excluded # Path already excluded
break break
else: else:
changed = True LOG.info('New Plex music library detected: %s', path)
write_xml = True xml.set_setting(['audio', 'excludefromscan', 'regexp'],
log.info('New Plex music library detected: %s' % path) value=path, check_existing=False)
element = etree.Element(tag='regexp') # We only need to reboot if we ADD new paths!
element.text = path reboot = xml.write_xml
root.append(element) # Delete obsolete entries
for element in parent:
# Delete obsolete entries (unlike above, we don't change 'changed' to not
# enforce a restart)
for element in root:
for path in paths: for path in paths:
if element.text == path: if element.text == path:
break break
else: else:
log.info('Deleting Plex music library from advancedsettings: %s' LOG.info('Deleting music library from advancedsettings: %s',
% element.text) element.text)
root.remove(element) parent.remove(element)
write_xml = True except (ParseError, IOError):
LOG.error('Could not adjust advancedsettings.xml')
if write_xml is True: reboot = False
indent(tree.getroot()) if reboot is True:
tree.write('%sadvancedsettings.xml' % v.KODI_PROFILE, encoding="UTF-8") # 'New Plex music library detected. Sorry, but we need to
return changed # restart Kodi now due to the changes made.'
reboot_kodi(lang(39711))
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,31 +623,78 @@ 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:
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 Returns an etree element's subelement. Creates one if not exist
""" """
@ -650,81 +703,86 @@ def __setSubElement(element, subelement):
answ = etree.SubElement(element, subelement) answ = etree.SubElement(element, subelement)
return answ return answ
def get_setting(self, node_list):
def advancedsettings_xml(node_list, new_value=None, attrib=None,
force_create=False):
""" """
Returns node_list is a list of node names starting from the outside, ignoring
etree element, tree the outter advancedsettings.
or Example nodelist=['video', 'busydialogdelayms'] for the following xml
None, None 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:
<busydialogdelayms>750</busydialogdelayms> <busydialogdelayms>750</busydialogdelayms>
for the following example xml: for the following example xml:
<?xml version="1.0" encoding="UTF-8" ?>
<advancedsettings> <advancedsettings>
<video> <video>
<busydialogdelayms>750</busydialogdelayms> <busydialogdelayms>750</busydialogdelayms>
</video> </video>
</advancedsettings> </advancedsettings>
If new_value is set, '750' will be replaced accordingly, returning the new 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
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:
<busydialogdelayms>750</busydialogdelayms>
for the following example xml:
<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 etree Element. Advancedsettings might be generated if it did not exist
already already
If the dict attrib is set, the Element's attributs will be appended If the dict attrib is set, the Element's attributs will be appended
accordingly accordingly
force_create=True will forcibly create the key even if no value is provided 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
""" """
path = '%sadvancedsettings.xml' % KODI_PROFILE attrib = attrib or {}
try: value = value or ''
tree = etree.parse(path) if check_existing is True:
except IOError: old = self.get_setting(node_list)
# Document is blank or missing if old is not None:
if new_value is None and attrib is None and force_create is False: already_set = True
log.debug('Could not parse advancedsettings.xml, returning None') if old.text.strip() != value:
return None, None already_set = False
# Create topmost xml entry elif old.attrib != attrib:
tree = etree.ElementTree(element=etree.Element('advancedsettings')) already_set = False
except etree.ParseError: if already_set is True:
log.error('Error parsing %s' % path) log.debug('Element has already been found')
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit return old
# {1} and correct your file!" # Need to set new setting, indeed
dialog('ok', language(29999), language(39716).format( self.write_xml = True
'advancedsettings.xml', element = self.root
'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 = self._set_sub_element(element, node)
if element is None:
break
return element, tree
# Setting new values. Get correct element first
for node in node_list:
element = __setSubElement(element, node)
# Write new values # Write new values
element.text = new_value or '' element.text = value
if attrib is not None: if attrib:
for key, attribute in attrib.iteritems(): for key, attribute in attrib.iteritems():
element.set(key, attribute) element.set(key, attribute)
# Indent and make readable return element
indent(root)
# Safe the changed xml
tree.write(path, encoding="UTF-8")
return element, tree
def sourcesXML(): def sourcesXML():