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 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
try:
with XmlKodiSetting('advancedsettings.xml',
force_create=True,
top_element='advancedsettings') as xml:
# 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
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()

View file

@ -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

View file

@ -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)
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 root:
for element in parent:
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:
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 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
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):

View file

@ -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,31 +623,78 @@ 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):
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
"""
@ -650,81 +703,86 @@ def __setSubElement(element, subelement):
answ = etree.SubElement(element, subelement)
return answ
def advancedsettings_xml(node_list, new_value=None, attrib=None,
force_create=False):
def get_setting(self, node_list):
"""
Returns
etree element, tree
or
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:
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>
</video>
</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
already
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
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
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:
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 = element.find(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)
element = self._set_sub_element(element, node)
# Write new values
element.text = new_value or ''
if attrib is not None:
element.text = value
if attrib:
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
return element
def sourcesXML():