parent
eb6b1fbe48
commit
607fdab326
4 changed files with 219 additions and 174 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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():
|
||||
|
|
Loading…
Reference in a new issue