More Plex Companion cleanup

No dedicated logging anymore
This commit is contained in:
tomkat83 2016-04-05 10:57:30 +02:00
parent 43dc83ae57
commit 43b0af936f
8 changed files with 222 additions and 218 deletions

View File

@ -5,10 +5,9 @@ import socket
import xbmc
import clientinfo
import utils
from plexbmchelper import listener, plexgdm, subscribers, functions, \
httppersist
httppersist, settings
@utils.logging
@ -16,21 +15,12 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \
@utils.ThreadMethods
class PlexCompanion(threading.Thread):
def __init__(self):
ci = clientinfo.ClientInfo()
self.clientId = ci.getDeviceId()
self.deviceName = ci.getDeviceName()
self.port = int(utils.settings('companionPort'))
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
self.settings = settings.getSettings()
# Start GDM for server/client discovery
self.client = plexgdm.plexgdm(
debug=utils.settings('companionGDMDebugging'))
self.client.clientDetails(self.clientId, # UUID
self.deviceName, # clientName
self.port,
self.addonName,
'1.0') # Version
self.client = plexgdm.plexgdm()
self.client.clientDetails(self.settings)
self.logMsg("Registration string is: %s "
% self.client.getClientDetails(), 1)
@ -46,7 +36,7 @@ class PlexCompanion(threading.Thread):
# Start up instances
requestMgr = httppersist.RequestMgr()
jsonClass = functions.jsonClass(requestMgr)
jsonClass = functions.jsonClass(requestMgr, self.settings)
subscriptionManager = subscribers.SubscriptionManager(
jsonClass, requestMgr)
@ -57,7 +47,8 @@ class PlexCompanion(threading.Thread):
client,
subscriptionManager,
jsonClass,
('', self.port),
self.settings,
('', self.settings['myport']),
listener.MyHandler)
httpd.timeout = 0.95
break
@ -99,7 +90,7 @@ class PlexCompanion(threading.Thread):
else:
log("Client is no longer registered", 1)
log("Plex Companion still running on port %s"
% self.port, 1)
% self.settings['myport'], 1)
message_count = 0
# Get and set servers
@ -108,7 +99,7 @@ class PlexCompanion(threading.Thread):
subscriptionManager.notify()
xbmc.sleep(50)
except:
log("Error in loop, continuing anyway", 1)
log("Error in loop, continuing anyway", 0)
log(traceback.format_exc(), 1)
xbmc.sleep(50)

View File

@ -44,19 +44,20 @@ class ClientInfo():
return deviceName
def getPlatform(self):
if xbmc.getCondVisibility('system.platform.osx'):
return "OSX"
return "MacOSX"
elif xbmc.getCondVisibility('system.platform.atv2'):
return "ATV2"
return "AppleTV2"
elif xbmc.getCondVisibility('system.platform.ios'):
return "iOS"
elif xbmc.getCondVisibility('system.platform.windows'):
return "Windows"
elif xbmc.getCondVisibility('system.platform.raspberrypi'):
return "RaspberryPi"
elif xbmc.getCondVisibility('system.platform.linux'):
return "Linux/RPi"
elif xbmc.getCondVisibility('system.platform.android'):
return "Linux/Android"
return "Linux"
elif xbmc.getCondVisibility('system.platform.android'):
return "Android"
else:
return "Unknown"

View File

@ -2,8 +2,7 @@ import base64
import json
import string
import xbmc
import settings
from utils import logMsg
from utils import logging
def xbmc_photo():
@ -48,24 +47,6 @@ def plex_type(xbmc_type):
return plex_audio()
def getPlatform():
if xbmc.getCondVisibility('system.platform.osx'):
return "MacOSX"
elif xbmc.getCondVisibility('system.platform.atv2'):
return "AppleTV2"
elif xbmc.getCondVisibility('system.platform.ios'):
return "iOS"
elif xbmc.getCondVisibility('system.platform.windows'):
return "Windows"
elif xbmc.getCondVisibility('system.platform.raspberrypi'):
return "RaspberryPi"
elif xbmc.getCondVisibility('system.platform.linux'):
return "Linux"
elif xbmc.getCondVisibility('system.platform.android'):
return "Android"
return "Unknown"
def getXMLHeader():
return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\n"
@ -93,10 +74,11 @@ def textFromXml(element):
return element.firstChild.data
@logging
class jsonClass():
def __init__(self, requestMgr):
self.settings = settings.getSettings()
def __init__(self, requestMgr, settings):
self.settings = settings
self.requestMgr = requestMgr
def jsonrpc(self, action, arguments={}):
@ -122,9 +104,8 @@ class jsonClass():
"jsonrpc" : "2.0",
"method" : action})
logMsg("PlexCompanion",
"Sending request to XBMC without network stack: %s"
% request, 2)
self.logMsg("Sending request to XBMC without network stack: %s"
% request, 2)
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
if not result and self.settings['webserver_enabled']:
@ -159,14 +140,13 @@ class jsonClass():
def parseJSONRPC(self, jsonraw):
if not jsonraw:
logMsg("PlexCompanion", "Empty response from XBMC", 1)
self.logMsg("Empty response from XBMC", 1)
return {}
else:
logMsg("PlexCompanion", "Response from XBMC: %s" % jsonraw, 2)
self.logMsg("Response from XBMC: %s" % jsonraw, 2)
parsed=json.loads(jsonraw)
if parsed.get('error', False):
logMsg("PlexCompanion", "XBMC returned an error: %s"
% parsed.get('error'), -1)
self.logMsg("XBMC returned an error: %s" % parsed.get('error'), -1)
return parsed.get('result', {})
def getPlayers(self):

View File

@ -78,9 +78,13 @@ class RequestMgr:
return False
else:
return data.read() or True
except:
self.logMsg("Unable to connect to %s\nReason:" % host, -1)
self.logMsg(traceback.print_exc(), -1)
except socket_error as serr:
# Ignore remote close and connection refused (e.g. shutdown PKC)
if serr.errno in (errno.WSAECONNABORTED, errno.WSAECONNREFUSED):
pass
else:
self.logMsg("Unable to connect to %s\nReason:" % host, -1)
self.logMsg(traceback.print_exc(), -1)
self.conns.pop(protocol+host+str(port), None)
conn.close()
return False

View File

@ -1,10 +1,11 @@
import re
import traceback
import xbmc
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urlparse import urlparse, parse_qs
import settings
from xbmc import sleep
from functions import *
from utils import logging
@ -14,9 +15,9 @@ class MyHandler(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
def __init__(self, *args, **kwargs):
self.serverlist = []
self.settings = settings.getSettings()
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
self.serverlist = []
self.settings = self.server.settings
def getServerByHost(self, host):
if len(self.serverlist) == 1:
@ -113,7 +114,7 @@ class MyHandler(BaseHTTPRequestHandler):
s.subMgr.addSubscriber(protocol, host, port, uuid, commandID)
elif "/poll" in request_path:
if params.get('wait', False) == '1':
xbmc.sleep(950)
sleep(950)
commandID = params.get('commandID', 0)
s.response(re.sub(r"INSERTCOMMANDID", str(commandID), s.subMgr.msg(s.js.getPlayers())), {
'X-Plex-Client-Identifier': s.settings['uuid'],
@ -221,7 +222,7 @@ class MyHandler(BaseHTTPRequestHandler):
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
def __init__(self, client, subscriptionManager, jsonClass,
def __init__(self, client, subscriptionManager, jsonClass, settings,
*args, **kwargs):
"""
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
@ -232,4 +233,5 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
self.client = client
self.subscriptionManager = subscriptionManager
self.jsonClass = jsonClass
self.settings = settings
HTTPServer.__init__(self, *args, **kwargs)

View File

@ -26,28 +26,25 @@ __author__ = 'DHJ (hippojay) <plex@h-jay.com>'
import socket
import struct
import sys
import re
import threading
import time
import urllib2
import xbmc
from xbmc import sleep
import downloadutils
from PlexFunctions import PMSHttpsEnabled
from utils import window
from utils import window, logging
@logging
class plexgdm:
def __init__(self, debug=False):
def __init__(self):
self.discover_message = 'M-SEARCH * HTTP/1.0'
self.client_header = '* HTTP/1.0'
self.client_data = None
self.client_id = None
self._multicast_address = '239.0.0.250'
self.discover_group = (self._multicast_address, 32414)
self.client_register_group = (self._multicast_address, 32413)
@ -55,92 +52,130 @@ class plexgdm:
self.server_list = []
self.discovery_interval = 120
self._discovery_is_running = False
self._registration_is_running = False
self.discovery_complete = False
self.client_registered = False
self.debug = debug
self.download = downloadutils.DownloadUtils().downloadUrl
def __printDebug(self, message, level=1):
if self.debug:
print "PlexGDM: %s" % message
def clientDetails(self, settings):
self.client_data = (
"Content-Type: plex/media-player\r\n"
"Resource-Identifier: %s\r\n"
"Name: %s\r\n"
"Port: %s\r\n"
"Product: %s\r\n"
"Version: %s\r\n"
"Protocol: plex\r\n"
"Protocol-Version: 1\r\n"
"Protocol-Capabilities: timeline,playback,navigation,"
"mirror,playqueues\r\n"
"Device-Class: HTPC"
) % (
settings['uuid'],
settings['client_name'],
settings['myport'],
settings['addonName'],
settings['version']
)
self.client_id = settings['uuid']
def clientDetails(self, c_id, c_name, c_post, c_product, c_version):
self.client_data = "Content-Type: plex/media-player\r\nResource-Identifier: %s\r\nName: %s\r\nPort: %s\r\nProduct: %s\r\nVersion: %s\r\nProtocol: plex\r\nProtocol-Version: 1\r\nProtocol-Capabilities: timeline,playback,navigation,mirror,playqueues\r\nDevice-Class: HTPC" % ( c_id, c_name, c_post, c_product, c_version )
self.client_id = c_id
def getClientDetails(self):
if not self.client_data:
self.__printDebug("Client data has not been initialised. Please use PlexGDM.clientDetails()")
self.logMsg("Client data has not been initialised. Please use "
"PlexGDM.clientDetails()", -1)
return self.client_data
def client_update (self):
update_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
#Set socket reuse, may not work on all OSs.
def client_update(self):
update_sock = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM,
socket.IPPROTO_UDP)
# Set socket reuse, may not work on all OSs.
try:
update_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
update_sock.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR,
1)
except:
pass
#Attempt to bind to the socket to recieve and send data. If we can;t do this, then we cannot send registration
# Attempt to bind to the socket to recieve and send data. If we cant
# do this, then we cannot send registration
try:
update_sock.bind(('0.0.0.0',self.client_update_port))
update_sock.bind(('0.0.0.0', self.client_update_port))
except:
self.__printDebug( "Error: Unable to bind to port [%s] - client will not be registered" % self.client_update_port, 0)
return
update_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
status = update_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self._multicast_address) + socket.inet_aton('0.0.0.0'))
self.logMsg("Unable to bind to port [%s] - client will not be "
"registered" % self.client_update_port, -1)
return
update_sock.setsockopt(socket.IPPROTO_IP,
socket.IP_MULTICAST_TTL,
255)
update_sock.setsockopt(socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(
self._multicast_address) +
socket.inet_aton('0.0.0.0'))
update_sock.setblocking(0)
self.__printDebug("Sending registration data: HELLO %s\r\n%s" % (self.client_header, self.client_data), 3)
#Send initial client registration
self.logMsg("Sending registration data: HELLO %s\r\n%s"
% (self.client_header, self.client_data), 2)
# Send initial client registration
try:
update_sock.sendto("HELLO %s\r\n%s" % (self.client_header, self.client_data), self.client_register_group)
update_sock.sendto("HELLO %s\r\n%s"
% (self.client_header, self.client_data),
self.client_register_group)
except:
self.__printDebug( "Error: Unable to send registeration message" , 0)
#Now, listen for client discovery reguests and respond.
self.logMsg("Unable to send registration message", -1)
# Now, listen for client discovery reguests and respond.
while self._registration_is_running:
try:
data, addr = update_sock.recvfrom(1024)
self.__printDebug("Recieved UDP packet from [%s] containing [%s]" % (addr, data.strip()), 3)
except socket.error, e:
self.logMsg("Recieved UDP packet from [%s] containing [%s]"
% (addr, data.strip()), 2)
except socket.error:
pass
else:
if "M-SEARCH * HTTP/1." in data:
self.__printDebug("Detected client discovery request from %s. Replying" % ( addr ,) , 2)
self.logMsg("Detected client discovery request from %s. "
" Replying" % addr, 2)
try:
update_sock.sendto("HTTP/1.0 200 OK\r\n%s" % self.client_data, addr)
update_sock.sendto("HTTP/1.0 200 OK\r\n%s"
% self.client_data,
addr)
except:
self.__printDebug( "Error: Unable to send client update message",0)
self.__printDebug("Sending registration data: HTTP/1.0 200 OK\r\n%s" % (self.client_data), 3)
self.client_registered = True
xbmc.sleep(500)
self.logMsg("Unable to send client update message", -1)
self.__printDebug("Client Update loop stopped",1)
#When we are finished, then send a final goodbye message to deregister cleanly.
self.__printDebug("Sending registration data: BYE %s\r\n%s" % (self.client_header, self.client_data), 3)
self.logMsg("Sending registration data: HTTP/1.0 200 OK"
"\r\n%s" % self.client_data, 2)
self.client_registered = True
sleep(500)
self.logMsg("Client Update loop stopped", 1)
# When we are finished, then send a final goodbye message to
# deregister cleanly.
self.logMsg("Sending registration data: BYE %s\r\n%s"
% (self.client_header, self.client_data), 2)
try:
update_sock.sendto("BYE %s\r\n%s" % (self.client_header, self.client_data), self.client_register_group)
update_sock.sendto("BYE %s\r\n%s"
% (self.client_header, self.client_data),
self.client_register_group)
except:
self.__printDebug( "Error: Unable to send client update message" ,0)
self.logMsg("Unable to send client update message", -1)
self.client_registered = False
def check_client_registration(self):
if self.client_registered and self.discovery_complete:
if not self.server_list:
self.__printDebug("Server list is empty. Unable to check",2)
self.logMsg("Server list is empty. Unable to check", 1)
return False
try:
@ -151,36 +186,35 @@ class plexgdm:
scheme = server['protocol']
break
else:
self.__printDebug("Did not find our server!", 2)
self.logMsg("Did not find our server!", 0)
return False
self.__printDebug("Checking server [%s] on port [%s]" % (media_server, media_port) ,2)
self.logMsg("Checking server [%s] on port [%s]"
% (media_server, media_port), 2)
client_result = self.download(
'%s://%s:%s/clients' % (scheme, media_server, media_port))
# f = urllib2.urlopen('http://%s:%s/clients' % (media_server, media_port))
# client_result = f.read()
registered = False
for client in client_result:
if (client.attrib.get('machineIdentifier') ==
self.client_id):
registered = True
if registered:
self.__printDebug("Client registration successful",1)
self.__printDebug("Client data is: %s" % client_result, 3)
self.logMsg("Client registration successful", 1)
self.logMsg("Client data is: %s" % client_result, 2)
return True
else:
self.__printDebug("Client registration not found",1)
self.__printDebug("Client data is: %s" % client_result, 3)
self.logMsg("Client registration not found", 1)
self.logMsg("Client data is: %s" % client_result, 1)
except:
self.__printDebug("Unable to check status")
self.logMsg("Unable to check status", 0)
pass
return False
def getServerList (self):
def getServerList(self):
return self.server_list
def discover(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -194,17 +228,18 @@ class plexgdm:
returnData = []
try:
# Send data to the multicast group
self.__printDebug("Sending discovery messages: %s" % self.discover_message, 2)
sent = sock.sendto(self.discover_message, self.discover_group)
self.logMsg("Sending discovery messages: %s"
% self.discover_message, 2)
sock.sendto(self.discover_message, self.discover_group)
# Look for responses from all recipients
while True:
try:
data, server = sock.recvfrom(1024)
self.__printDebug("Received data from %s, %s" % server, 3)
self.__printDebug("Data received is:\r\n %s" % data, 3)
returnData.append( { 'from' : server,
'data' : data } )
self.logMsg("Received data from %s, %s" % server, 2)
self.logMsg("Data received is:\r\n %s" % data, 2)
returnData.append({'from': server,
'data': data})
except socket.timeout:
break
except:
@ -221,18 +256,16 @@ class plexgdm:
if returnData:
for response in returnData:
update = { 'server' : response.get('from')[0] }
update = {'server': response.get('from')[0]}
#Check if we had a positive HTTP response
# Check if we had a positive HTTP reponse
if "200 OK" in response.get('data'):
for each in response.get('data').split('\r\n'):
update['discovery'] = "auto"
update['owned']='1'
update['master']= 1
update['role']='master'
update['class']=None
update['owned'] = '1'
update['master'] = 1
update['role'] = 'master'
update['class'] = None
if "Content-Type:" in each:
update['content-type'] = each.split(':')[1].strip()
elif "Resource-Identifier:" in each:
@ -291,12 +324,12 @@ class plexgdm:
self.server_list = discovered_servers
if not self.server_list:
self.__printDebug("No servers have been discovered",1)
self.logMsg("No servers have been discovered", 0)
else:
self.__printDebug("Number of servers Discovered: %s" % len(self.server_list),1)
self.logMsg("Number of servers Discovered: %s"
% len(self.server_list), 2)
for items in self.server_list:
self.__printDebug("Server Discovered: %s" % items, 2)
self.logMsg("Server Discovered: %s" % items, 2)
def setInterval(self, interval):
self.discovery_interval = interval
@ -307,71 +340,54 @@ class plexgdm:
def stop_discovery(self):
if self._discovery_is_running:
self.__printDebug("Discovery shutting down", 1)
self.logMsg("Discovery shutting down", 0)
self._discovery_is_running = False
self.discover_t.join()
del self.discover_t
else:
self.__printDebug("Discovery not running", 1)
self.logMsg("Discovery not running", 0)
def stop_registration(self):
if self._registration_is_running:
self.__printDebug("Registration shutting down", 1)
self.logMsg("Registration shutting down", 0)
self._registration_is_running = False
self.register_t.join()
del self.register_t
else:
self.__printDebug("Registration not running", 1)
self.logMsg("Registration not running", 0)
def run_discovery_loop(self):
#Run initial discovery
# Run initial discovery
self.discover()
discovery_count=0
discovery_count = 0
while self._discovery_is_running:
discovery_count+=1
discovery_count += 1
if discovery_count > self.discovery_interval:
self.discover()
discovery_count=0
xbmc.sleep(500)
discovery_count = 0
sleep(500)
def start_discovery(self, daemon = False):
def start_discovery(self, daemon=False):
if not self._discovery_is_running:
self.__printDebug("Discovery starting up", 1)
self.logMsg("Discovery starting up", 0)
self._discovery_is_running = True
self.discover_t = threading.Thread(target=self.run_discovery_loop)
self.discover_t.setDaemon(daemon)
self.discover_t.start()
else:
self.__printDebug("Discovery already running", 1)
def start_registration(self, daemon = False):
self.logMsg("Discovery already running", 0)
def start_registration(self, daemon=False):
if not self._registration_is_running:
self.__printDebug("Registration starting up", 1)
self.logMsg("Registration starting up", 0)
self._registration_is_running = True
self.register_t = threading.Thread(target=self.client_update)
self.register_t.setDaemon(daemon)
self.register_t.start()
else:
self.__printDebug("Registration already running", 1)
def start_all(self, daemon = False):
self.logMsg("Registration already running", 0)
def start_all(self, daemon=False):
self.start_discovery(daemon)
self.start_registration(daemon)
#Example usage
if __name__ == '__main__':
client = plexgdm(debug=3)
client.clientDetails("Test-Name", "Test Client", "3003", "Test-App", "1.2.3")
client.start_all()
while not client.discovery_complete:
print "Waiting for results"
xbmc.sleep(1000)
xbmc.sleep(20000)
print client.getServerList()
if client.check_client_registration():
print "Successfully registered"
else:
print "Unsuccessfully registered"
client.stop_all()

View File

@ -1,11 +1,11 @@
import xbmcaddon
import utils
from utils import guisettingsXML, settings, logMsg
import clientinfo
def getGUI(name):
guisettingsXML = utils.guisettingsXML()
xml = guisettingsXML()
try:
ans = list(guisettingsXML.iter(name))[0].text
ans = list(xml.iter(name))[0].text
if ans is None:
ans = ''
except:
@ -14,33 +14,45 @@ def getGUI(name):
def getSettings():
settings = {}
addon = xbmcaddon.Addon()
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
client = clientinfo.ClientInfo()
options = {}
title = 'PlexCompanion Settings'
settings['debug'] = utils.settings('companionDebugging')
settings['gdm_debug'] = utils.settings('companionGDMDebugging')
options['gdm_debug'] = settings('companionGDMDebugging')
options['gdm_debug'] = True if options['gdm_debug'] == 'true' else False
# Transform 'true' into True because of the way Kodi's file settings work
kodiSettingsList = ['debug', 'gdm_debug']
for entry in kodiSettingsList:
if settings[entry] == 'true':
settings[entry] = True
else:
settings[entry] = False
options['client_name'] = settings('deviceName')
settings['client_name'] = plexbmc.getSetting('deviceName')
# XBMC web server options
options['webserver_enabled'] = (getGUI('webserver') == "true")
logMsg(title, 'Webserver is set to %s' % options['webserver_enabled'], 0)
webserverport = getGUI('webserverport')
try:
webserverport = int(webserverport)
logMsg(title, 'Using webserver port %s' % str(webserverport), 0)
except:
logMsg(title, 'No setting for webserver port found in guisettings.xml.'
'Using default fallback port 8080', 0)
webserverport = 8080
options['port'] = webserverport
# XBMC web server settings
settings['webserver_enabled'] = (getGUI('webserver') == "true")
settings['port'] = int(getGUI('webserverport'))
settings['user'] = getGUI('webserverusername')
settings['passwd'] = getGUI('webserverpassword')
options['user'] = getGUI('webserverusername')
options['passwd'] = getGUI('webserverpassword')
logMsg(title, 'Webserver username: %s, password: %s'
% (options['user'], options['passwd']), 1)
settings['uuid'] = plexbmc.getSetting('plex_client_Id')
settings['version'] = plexbmc.getAddonInfo('version')
settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
settings['myplex_user'] = plexbmc.getSetting('username')
settings['myport'] = addon.getSetting('companionPort')
return settings
options['addonName'] = client.getAddonName()
options['uuid'] = settings('plex_client_Id')
options['platform'] = client.getPlatform()
options['version'] = client.getVersion()
options['plexbmc_version'] = options['version']
options['myplex_user'] = settings('username')
try:
options['myport'] = int(settings('companionPort'))
logMsg(title, 'Using Plex Companion Port %s'
% str(options['myport']), 0)
except:
logMsg(title, 'Error getting Plex Companion Port from file settings. '
'Using fallback port 39005', -1)
options['myport'] = 39005
return options

View File

@ -43,8 +43,6 @@
<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
<setting id="companionDebugging" label="39006" type="bool" default="false" visible="eq(-4,true)"/>
<setting id="companionGDMDebugging" label="39007" type="bool" default="false" visible="eq(-5,true)"/>
</category>
<category label="30506"><!-- Sync Options -->
<setting type="lsep" label="30537" /><!-- Restart if you make changes -->