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