Merge branch 'hotfixes' into develop

This commit is contained in:
tomkat83 2016-03-24 12:35:37 +01:00
commit ed5483e2ae
12 changed files with 154 additions and 57 deletions

View file

@ -70,7 +70,8 @@ class Main:
'reConnect': entrypoint.reConnect, 'reConnect': entrypoint.reConnect,
'delete': entrypoint.deleteItem, 'delete': entrypoint.deleteItem,
'browseplex': entrypoint.BrowsePlexContent, 'browseplex': entrypoint.BrowsePlexContent,
'ondeck': entrypoint.getOnDeck 'ondeck': entrypoint.getOnDeck,
'chooseServer': entrypoint.chooseServer
} }
if "/extrafanart" in sys.argv[0]: if "/extrafanart" in sys.argv[0]:

View file

@ -297,7 +297,7 @@
<string id="30532">Duration of the video library pop up (in seconds)</string> <string id="30532">Duration of the video library pop up (in seconds)</string>
<string id="30533">Duration of the music library pop up (in seconds)</string> <string id="30533">Duration of the music library pop up (in seconds)</string>
<string id="30534">Server messages</string> <string id="30534">Server messages</string>
<string id="30535">Generate a new device Id</string> <string id="30535">[COLOR yellow]Generate a new unique device Id (e.g. when cloning Kodi)[/COLOR]</string>
<string id="30536">Users must log in every time when Kodi restarts</string> <string id="30536">Users must log in every time when Kodi restarts</string>
<string id="30537">Restart Kodi if you make changes</string> <string id="30537">Restart Kodi if you make changes</string>
<string id="30538">Complete Re-Sync necessary</string> <string id="30538">Complete Re-Sync necessary</string>
@ -392,6 +392,7 @@
<string id="39047">On Deck: Append show title to episode</string> <string id="39047">On Deck: Append show title to episode</string>
<string id="39048">On Deck: Append season- and episode-number (e.g. S3E2)</string> <string id="39048">On Deck: Append season- and episode-number (e.g. S3E2)</string>
<string id="39049">Nothing works? Try a full reset!</string> <string id="39049">Nothing works? Try a full reset!</string>
<string id="39050">[COLOR yellow]Choose Plex Server from a list[/COLOR]</string>
<!-- Plex Entrypoint.py --> <!-- Plex Entrypoint.py -->

View file

@ -24,6 +24,7 @@
<string id="30521">Bei Wiederaufnahme zurückspulen (in Sekunden)</string> <string id="30521">Bei Wiederaufnahme zurückspulen (in Sekunden)</string>
<string id="30505">[COLOR yellow]Anzahl Login-Versuche zurücksetzen[/COLOR]</string> <string id="30505">[COLOR yellow]Anzahl Login-Versuche zurücksetzen[/COLOR]</string>
<string id="30535">[COLOR yellow]Neue einzigartige Geräte-ID generieren (z.B. wenn Kodi geklont wurde)[/COLOR]</string>
<string id="30536">Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden</string> <string id="30536">Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden</string>
<string id="30537">Bei Änderungen Kodi neu starten</string> <string id="30537">Bei Änderungen Kodi neu starten</string>
<string id="30538">Komplette Neusynchronisierung nötig</string> <string id="30538">Komplette Neusynchronisierung nötig</string>
@ -329,6 +330,7 @@
<string id="39047">"Aktuell": Serien- an Episoden-Titel anfügen</string> <string id="39047">"Aktuell": Serien- an Episoden-Titel anfügen</string>
<string id="39048">"Aktuell": Staffel und Episode anfügen (z.B. S3E2)</string> <string id="39048">"Aktuell": Staffel und Episode anfügen (z.B. S3E2)</string>
<string id="39049">Nichts funktioniert? Setze mal alles zurück!</string> <string id="39049">Nichts funktioniert? Setze mal alles zurück!</string>
<string id="39050">[COLOR yellow]Plex Server aus Liste auswählen[/COLOR]</string>
<!-- Plex Entrypoint.py --> <!-- Plex Entrypoint.py -->
<string id="39200">Plex Home Benutzer abmelden: </string> <string id="39200">Plex Home Benutzer abmelden: </string>

View file

@ -29,17 +29,6 @@ http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem
http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-python http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-python
(and others...) (and others...)
""" """
# Specific to PlexDB:
import clientinfo
import utils
import downloadutils
import xbmcaddon
import xbmcgui
import xbmc
import xbmcvfs
import struct import struct
import time import time
import urllib2 import urllib2
@ -52,13 +41,21 @@ import Queue
import traceback import traceback
import requests import requests
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from uuid import uuid4
import re import re
import json import json
from urllib import urlencode, quote_plus, unquote from urllib import urlencode, quote_plus, unquote
from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled import clientinfo
import utils
import downloadutils
import xbmcaddon
import xbmcgui
import xbmc
import xbmcvfs
from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled
# Disable requests logging # Disable requests logging
from requests.packages.urllib3.exceptions import InsecureRequestWarning from requests.packages.urllib3.exceptions import InsecureRequestWarning
@ -835,8 +832,6 @@ class PlexAPI():
'X-Plex-Version': self.plexversion, 'X-Plex-Version': self.plexversion,
'X-Plex-Client-Identifier': self.clientId, 'X-Plex-Client-Identifier': self.clientId,
'X-Plex-Provides': 'player', 'X-Plex-Provides': 'player',
'X-Plex-Client-Capabilities': 'protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac,dts{bitrate:800000&channels:8},ac3{bitrate:800000&channels:8}',
'X-Plex-Client-Profile-Extra': 'add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=*&audioCodec=dca,ac3)',
} }
if self.token: if self.token:
@ -2173,15 +2168,17 @@ class API():
transcodePath = self.server + \ transcodePath = self.server + \
'/video/:/transcode/universal/start.m3u8?' '/video/:/transcode/universal/start.m3u8?'
args = { args = {
'copyts': 1, 'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
'session': str(uuid4()),
'fastSeek': 1,
'path': path, 'path': path,
'mediaIndex': 0, # Probably refering to XML reply sheme 'mediaIndex': 0, # Probably refering to XML reply sheme
'partIndex': self.part, 'partIndex': self.part,
'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls' # 'copyts': 1,
'session': self.clientId,
# 'offset': 0, # Resume point # 'offset': 0, # Resume point
'fastSeek': 1
} }
# Seem like PHT to let the PMS use the transcoding profile
xargs['X-Plex-Device'] = 'Plex Home Theater'
# Currently not used! # Currently not used!
if action == "DirectStream": if action == "DirectStream":
@ -2199,9 +2196,7 @@ class API():
args.update(quality) args.update(quality)
args.update(argsUpdate) args.update(argsUpdate)
url = transcodePath + \ url = transcodePath + urlencode(xargs) + '&' + urlencode(args)
urlencode(xargs) + '&' + \
urlencode(args)
return url return url
def externalSubs(self, playurl): def externalSubs(self, playurl):

View file

@ -383,7 +383,7 @@ def GetPlexPlaylist(itemid, librarySectionUUID, mediatype='movie'):
try: try:
xml[0].tag xml[0].tag
except (IndexError, TypeError, AttributeError): except (IndexError, TypeError, AttributeError):
logMsg("Error retrieving metadata for %s" % url, -1) logMsg(title, "Error retrieving metadata for %s" % url, -1)
return None return None
return xml return xml
@ -425,16 +425,16 @@ def PMSHttpsEnabled(url):
headers={}, headers={},
timeout=(3, 10)) timeout=(3, 10))
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
logMsg("Server is offline or cannot be reached. Url: %s, " logMsg(title, "Server is offline or cannot be reached. Url: %s"
"Error message: %s" % (url, e), -1) ", Error message: %s" % (url, e), -1)
return None return None
except requests.exceptions.ReadTimeout: except requests.exceptions.ReadTimeout:
logMsg("Server timeout reached for Url %s" % url, -1) logMsg(title, "Server timeout reached for Url %s" % url, -1)
return None return None
else: else:
answer = False answer = False
except requests.exceptions.ReadTimeout: except requests.exceptions.ReadTimeout:
logMsg("Server timeout reached for Url %s" % url, -1) logMsg(title, "Server timeout reached for Url %s" % url, -1)
return None return None
if res.status_code == requests.codes.ok: if res.status_code == requests.codes.ok:
return answer return answer
@ -442,6 +442,26 @@ def PMSHttpsEnabled(url):
return None return None
def GetMachineIdentifier(url):
"""
Returns the unique PMS machine identifier of url
Returns None if something went wrong
"""
xml = downloadutils.DownloadUtils().downloadUrl(
url + '/identity', type="GET")
try:
xml.attrib
except:
logMsg(title, 'Could not get the PMS machineIdentifier for %s'
% url, -1)
return None
machineIdentifier = xml.attrib.get('machineIdentifier')
logMsg(title, 'Found machineIdentifier %s for %s'
% (machineIdentifier, url), 1)
return machineIdentifier
def scrobble(ratingKey, state): def scrobble(ratingKey, state):
""" """
Tells the PMS to set an item's watched state to state="watched" or Tells the PMS to set an item's watched state to state="watched" or
@ -458,4 +478,4 @@ def scrobble(ratingKey, state):
else: else:
return return
downloadutils.DownloadUtils().downloadUrl(url, type="GET") downloadutils.DownloadUtils().downloadUrl(url, type="GET")
logMsg("Toggled watched state for Plex item %s" % ratingKey, 1) logMsg(title, "Toggled watched state for Plex item %s" % ratingKey, 1)

View file

@ -69,7 +69,7 @@ class ClientInfo():
If id does not exist, create one and save in Kodi settings file. If id does not exist, create one and save in Kodi settings file.
""" """
if reset: if reset is True:
utils.window('plex_client_Id', clear=True) utils.window('plex_client_Id', clear=True)
utils.settings('plex_client_Id', value="") utils.settings('plex_client_Id', value="")
@ -78,7 +78,8 @@ class ClientInfo():
return clientId return clientId
clientId = utils.settings('plex_client_Id') clientId = utils.settings('plex_client_Id')
if clientId: # Because Kodi appears to cache file settings!!
if clientId != "" and reset is False:
utils.window('plex_client_Id', value=clientId) utils.window('plex_client_Id', value=clientId)
self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1) self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1)
return clientId return clientId

View file

@ -73,36 +73,100 @@ def plexCompanion(fullurl, params):
title, "Not knowing what to do for now - no playQueue sent", -1) title, "Not knowing what to do for now - no playQueue sent", -1)
def reConnect(): def chooseServer():
""" """
Triggers login to plex.tv and re-authorization Lets user choose from list of PMS (signs out & signs in)
""" """
string = xbmcaddon.Addon().getLocalizedString string = xbmcaddon.Addon().getLocalizedString
utils.logMsg("entrypoint reConnect", utils.logMsg(title, "Choosing PMS server requested, starting", 0)
"Connection resets requested", 0)
dialog = xbmcgui.Dialog() dialog = xbmcgui.Dialog()
# Resetting, please wait # Resetting, please wait
dialog.notification( dialog.notification(
heading=addonName, heading=addonName,
message=string(39207), message=string(39207),
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
time=2000, time=3000,
sound=False) sound=False)
# Pause library sync thread - user needs to be auth in order to sync # Pause library sync thread - user needs to be auth in order to sync
utils.window('suspend_LibraryThread', value='true') utils.window('suspend_LibraryThread', value='true')
# Wait max for 5 seconds for all lib scans to finish # Wait max for 25 seconds for all lib scans to finish
counter = 0 counter = 0
while utils.window('emby_dbScan') == 'true': while utils.window('emby_dbScan') == 'true':
if counter > 500: if counter > 500:
# Failed to reset PMS and plex.tv connects. Try to restart Kodi. # Failed to reset PMS and plex.tv connects. Try to restart Kodi.
dialog.ok(heading=addonName, dialog.ok(addonName,
message=string(39208)) string(39208))
# Resuming threads, just in case # Resuming threads, just in case
utils.window('suspend_LibraryThread', clear=True) utils.window('suspend_LibraryThread', clear=True)
# Abort reConnection utils.logMsg(title, "Could not stop library sync, aborting", -1)
return return
counter += 1 counter += 1
xbmc.sleep(50) xbmc.sleep(50)
utils.logMsg(title, "Successfully stopped library sync", 0)
# Reset connection details
utils.settings('plex_machineIdentifier', value="")
utils.settings('plex_servername', value="")
utils.settings('https', value="")
utils.settings('ipaddress', value="")
utils.settings('port', value="")
# Log out currently signed in user:
utils.window('emby_serverStatus', value="401")
# Above method needs to have run its course! Hence wait
counter = 0
while utils.window('emby_serverStatus') == "401":
if counter > 100:
dialog.ok(addonName,
string(39208))
utils.logMsg(title, "Could not sign out, aborting", -1)
return
counter += 1
xbmc.sleep(50)
# Suspend the user client during procedure
utils.window('suspend_Userclient', value='true')
import initialsetup
initialsetup.InitialSetup().setup(chooseServer=True)
# Request lib sync to get user view data (e.g. watched/unwatched)
utils.window('plex_runLibScan', value='full')
# Restart user client
utils.window('suspend_Userclient', clear=True)
utils.logMsg(title, "Choosing new PMS complete", 0)
def reConnect():
"""
Triggers login to plex.tv and re-authorization
"""
string = xbmcaddon.Addon().getLocalizedString
utils.logMsg(title, "Connection resets requested", 0)
dialog = xbmcgui.Dialog()
# Resetting, please wait
dialog.notification(
heading=addonName,
message=string(39207),
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
time=3000,
sound=False)
# Pause library sync thread - user needs to be auth in order to sync
utils.window('suspend_LibraryThread', value='true')
# Wait max for 25 seconds for all lib scans to finish
counter = 0
while utils.window('emby_dbScan') == 'true':
if counter > 500:
# Failed to reset PMS and plex.tv connects. Try to restart Kodi.
dialog.ok(addonName,
string(39208))
# Resuming threads, just in case
utils.window('suspend_LibraryThread', clear=True)
utils.logMsg(title, "Could not stop library sync, aborting", -1)
return
counter += 1
xbmc.sleep(50)
utils.logMsg(title, "Successfully stopped library sync", 0)
# Delete plex credentials in settings # Delete plex credentials in settings
utils.settings('myplexlogin', value="true") utils.settings('myplexlogin', value="true")
@ -126,9 +190,9 @@ def reConnect():
counter = 0 counter = 0
while utils.window('emby_serverStatus') == "401": while utils.window('emby_serverStatus') == "401":
if counter > 100: if counter > 100:
dialog.ok(heading=addonName, dialog.ok(addonName,
message=string(39208)) string(39208))
# Abort reConnection utils.logMsg(title, "Could not sign out, aborting", -1)
return return
counter += 1 counter += 1
xbmc.sleep(50) xbmc.sleep(50)
@ -141,6 +205,7 @@ def reConnect():
utils.window('plex_runLibScan', value='full') utils.window('plex_runLibScan', value='full')
# Restart user client # Restart user client
utils.window('suspend_Userclient', clear=True) utils.window('suspend_Userclient', clear=True)
utils.logMsg(title, "Complete reconnection to plex.tv and PMS complete", 0)
def PassPlaylist(xml, resume=None): def PassPlaylist(xml, resume=None):
@ -288,9 +353,8 @@ def resetDeviceId():
dialog = xbmcgui.Dialog() dialog = xbmcgui.Dialog()
language = utils.language language = utils.language
deviceId_old = utils.window('emby_deviceId') deviceId_old = utils.window('plex_client_Id')
try: try:
utils.window('emby_deviceId', clear=True)
deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True)
except Exception as e: except Exception as e:
utils.logMsg(addonName, utils.logMsg(addonName,

View file

@ -26,7 +26,7 @@ class InitialSetup():
self.userClient = userclient.UserClient() self.userClient = userclient.UserClient()
self.plx = PlexAPI.PlexAPI() self.plx = PlexAPI.PlexAPI()
def setup(self, forcePlexTV=False): def setup(self, forcePlexTV=False, chooseServer=False):
""" """
Initial setup. Run once upon startup. Initial setup. Run once upon startup.
Check server, user, direct paths, music, direct stream if not direct Check server, user, direct paths, music, direct stream if not direct
@ -51,7 +51,8 @@ class InitialSetup():
# 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
# as plexToken will be '' # as plexToken will be ''
if (plexToken and myplexlogin == 'true' and forcePlexTV is False): if (plexToken and myplexlogin == 'true' and forcePlexTV is False
and chooseServer is False):
chk = self.plx.CheckConnection('plex.tv', plexToken) chk = self.plx.CheckConnection('plex.tv', plexToken)
# HTTP Error: unauthorized. Token is no longer valid # HTTP Error: unauthorized. Token is no longer valid
if chk == 401 or chk == 403: if chk == 401 or chk == 403:
@ -92,7 +93,7 @@ class InitialSetup():
self.logMsg('Failed to update Plex info from plex.tv', -1) self.logMsg('Failed to update Plex info from plex.tv', -1)
# If a Plex server IP has already been set, return. # If a Plex server IP has already been set, return.
if server and forcePlexTV is False: if server and forcePlexTV is False and chooseServer is False:
self.logMsg("Server is already set.", 0) self.logMsg("Server is already set.", 0)
self.logMsg("url: %s, Plex machineIdentifier: %s" self.logMsg("url: %s, Plex machineIdentifier: %s"
% (server, serverid), 0) % (server, serverid), 0)
@ -100,7 +101,8 @@ class InitialSetup():
# If not already retrieved myplex info, optionally let user sign in # If not already retrieved myplex info, optionally let user sign in
# to plex.tv. This DOES get called on very first install run # to plex.tv. This DOES get called on very first install run
if ((not plexToken and myplexlogin == 'true') or forcePlexTV): if ((not plexToken and myplexlogin == 'true' and chooseServer is False)
or forcePlexTV):
result = self.plx.PlexTvSignInWithPin() result = self.plx.PlexTvSignInWithPin()
if result: if result:
plexLogin = result['username'] plexLogin = result['username']
@ -231,7 +233,7 @@ class InitialSetup():
# self.logMsg("User opted to use direct paths.", 1) # self.logMsg("User opted to use direct paths.", 1)
# utils.settings('useDirectPaths', value="1") # utils.settings('useDirectPaths', value="1")
if forcePlexTV: if forcePlexTV is True or chooseServer is True:
return return
goToSettings = False goToSettings = False

View file

@ -184,7 +184,7 @@ class PlaybackUtils():
# For transcoding only, ask for audio/subs pref # For transcoding only, ask for audio/subs pref
if window('emby_%s.playmethod' % playurl) == "Transcode": if window('emby_%s.playmethod' % playurl) == "Transcode":
window('emby_%s.playmethod' % playurl, clear=True) window('emby_%s.playmethod' % playurl, clear=True)
playurl = playutils.audioSubsPref(playurl, listitem) playurl = playutils.audioSubsPref(listitem, playurl)
window('emby_%s.playmethod' % playurl, value="Transcode") window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl) listitem.setPath(playurl)

View file

@ -321,22 +321,24 @@ class PlayUtils():
# Set part where we're at # Set part where we're at
self.API.setPartNumber(part) self.API.setPartNumber(part)
if part is None:
part = 0
try: try:
mediastreams = self.item[0][part] mediastreams = self.item[0][part]
except (TypeError, KeyError, IndexError): except (TypeError, KeyError, IndexError):
return return url
audioNum = 0 audioNum = 0
# Remember 'no subtitles' # Remember 'no subtitles'
subNum = 1 subNum = 1
for stream in mediastreams: for stream in mediastreams:
# Since Emby returns all possible tracks together, have to sort them. # Since Emby returns all possible tracks together, have to sort them.
index = stream.attrib['id'] index = stream.attrib.get('id')
type = stream.attrib['streamType'] type = stream.attrib.get('streamType')
# Audio # Audio
if type == "2": if type == "2":
codec = stream.attrib['codec'] codec = stream.attrib.get('codec')
channelLayout = stream.attrib.get('audioChannelLayout', "") channelLayout = stream.attrib.get('audioChannelLayout', "")
try: try:
@ -356,7 +358,7 @@ class PlayUtils():
try: try:
track = "%s %s" % (subNum+1, stream.attrib['language']) track = "%s %s" % (subNum+1, stream.attrib['language'])
except: except:
track = "%s 'unknown' (%s)" % (subNum+1, stream.attrib['codec']) track = "%s 'unknown' (%s)" % (subNum+1, stream.attrib.get('codec'))
default = stream.attrib.get('default') default = stream.attrib.get('default')
forced = stream.attrib.get('forced') forced = stream.attrib.get('forced')

View file

@ -13,6 +13,7 @@ import utils
import downloadutils import downloadutils
import PlexAPI import PlexAPI
from PlexFunctions import GetMachineIdentifier
############################################################################### ###############################################################################
@ -76,11 +77,11 @@ class UserClient(threading.Thread):
settings = utils.settings settings = utils.settings
# Original host # Original host
self.machineIdentifier = settings('plex_machineIdentifier')
self.servername = settings('plex_servername') self.servername = settings('plex_servername')
HTTPS = settings('https') == "true" HTTPS = settings('https') == "true"
host = settings('ipaddress') host = settings('ipaddress')
port = settings('port') port = settings('port')
self.machineIdentifier = settings('plex_machineIdentifier')
server = host + ":" + port server = host + ":" + port
@ -94,6 +95,12 @@ class UserClient(threading.Thread):
# If https is false # If https is false
elif prefix and not HTTPS: elif prefix and not HTTPS:
server = "http://%s" % server server = "http://%s" % server
# User entered IP; we need to get the machineIdentifier
if self.machineIdentifier == '' and prefix is True:
self.machineIdentifier = GetMachineIdentifier(server)
if self.machineIdentifier is None:
self.machineIdentifier = ''
settings('plex_machineIdentifier', value=self.machineIdentifier)
self.logMsg('Returning active server: %s' % server) self.logMsg('Returning active server: %s' % server)
return server return server

View file

@ -2,6 +2,7 @@
<settings> <settings>
<category label="30014"><!-- Connection --> <category label="30014"><!-- Connection -->
<!-- Primary address --> <!-- Primary address -->
<setting label="39050" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=chooseServer)" option="close" /><!-- Choose Plex Server from a list -->
<setting id="ipaddress" label="30000" type="text" default="" /> <setting id="ipaddress" label="30000" type="text" default="" />
<setting id="port" label="30030" type="number" default="32400" /> <setting id="port" label="30030" type="number" default="32400" />
<setting id="https" label="30243" type="bool" default="false" /> <setting id="https" label="30243" type="bool" default="false" />
@ -122,6 +123,7 @@
<setting id="connectMsg" type="bool" label="30249" default="true" /> <setting id="connectMsg" type="bool" label="30249" default="true" />
<setting label="39018" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=repair)" option="close" /> <!-- Repair local database (force update all content) --> <setting label="39018" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=repair)" option="close" /> <!-- Repair local database (force update all content) -->
<setting label="39020" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=texturecache)" option="close" /> <!-- Cache all images to Kodi texture cache --> <setting label="39020" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=texturecache)" option="close" /> <!-- Cache all images to Kodi texture cache -->
<setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=deviceid)" /><!-- Reset device id uuid -->
<setting label="39021" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=thememedia)" option="close" visible="false" /> <!-- Sync Plex Theme Media to Kodi --> <setting label="39021" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=thememedia)" option="close" visible="false" /> <!-- Sync Plex Theme Media to Kodi -->
<setting type="lsep" label="39049" /><!-- Nothing works? Try a full reset --> <setting type="lsep" label="39049" /><!-- Nothing works? Try a full reset -->
<setting label="39019" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=reset)" option="close" /> <!-- Partial or full reset of Database and PKC --> <setting label="39019" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=reset)" option="close" /> <!-- Partial or full reset of Database and PKC -->