Fix instance chaos with Plex Companion
- Should fix PKC startup issues, among others
This commit is contained in:
parent
50b25ccf73
commit
43dc83ae57
8 changed files with 413 additions and 322 deletions
|
@ -7,22 +7,25 @@ import xbmc
|
||||||
|
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import utils
|
import utils
|
||||||
from plexbmchelper import listener, plexgdm, subscribers
|
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||||
from plexbmchelper.settings import settings
|
httppersist
|
||||||
|
|
||||||
|
|
||||||
@utils.logging
|
@utils.logging
|
||||||
|
@utils.ThreadMethodsAdditionalSuspend('emby_serverStatus')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class PlexCompanion(threading.Thread):
|
class PlexCompanion(threading.Thread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.port = int(utils.settings('companionPort'))
|
|
||||||
ci = clientinfo.ClientInfo()
|
ci = clientinfo.ClientInfo()
|
||||||
self.clientId = ci.getDeviceId()
|
self.clientId = ci.getDeviceId()
|
||||||
self.deviceName = ci.getDeviceName()
|
self.deviceName = ci.getDeviceName()
|
||||||
self.logMsg("----===## Starting PlexBMC Helper ##===----", 1)
|
|
||||||
|
self.port = int(utils.settings('companionPort'))
|
||||||
|
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
|
||||||
|
|
||||||
# Start GDM for server/client discovery
|
# Start GDM for server/client discovery
|
||||||
self.client = plexgdm.plexgdm(debug=settings['gdm_debug'])
|
self.client = plexgdm.plexgdm(
|
||||||
|
debug=utils.settings('companionGDMDebugging'))
|
||||||
self.client.clientDetails(self.clientId, # UUID
|
self.client.clientDetails(self.clientId, # UUID
|
||||||
self.deviceName, # clientName
|
self.deviceName, # clientName
|
||||||
self.port,
|
self.port,
|
||||||
|
@ -34,23 +37,38 @@ class PlexCompanion(threading.Thread):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Cache for quicker while loops
|
||||||
|
log = self.logMsg
|
||||||
|
client = self.client
|
||||||
|
threadStopped = self.threadStopped
|
||||||
|
threadSuspended = self.threadSuspended
|
||||||
start_count = 0
|
start_count = 0
|
||||||
window = utils.window
|
|
||||||
|
# Start up instances
|
||||||
|
requestMgr = httppersist.RequestMgr()
|
||||||
|
jsonClass = functions.jsonClass(requestMgr)
|
||||||
|
subscriptionManager = subscribers.SubscriptionManager(
|
||||||
|
jsonClass, requestMgr)
|
||||||
|
|
||||||
|
# Start up httpd
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
httpd = listener.ThreadedHTTPServer(
|
httpd = listener.ThreadedHTTPServer(
|
||||||
|
client,
|
||||||
|
subscriptionManager,
|
||||||
|
jsonClass,
|
||||||
('', self.port),
|
('', self.port),
|
||||||
listener.MyHandler)
|
listener.MyHandler)
|
||||||
httpd.timeout = 0.95
|
httpd.timeout = 0.95
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
self.logMsg("Unable to start PlexCompanion. Traceback:", -1)
|
log("Unable to start PlexCompanion. Traceback:", -1)
|
||||||
self.logMsg(traceback.print_exc(), -1)
|
log(traceback.print_exc(), -1)
|
||||||
|
|
||||||
xbmc.sleep(3000)
|
xbmc.sleep(3000)
|
||||||
|
|
||||||
if start_count == 3:
|
if start_count == 3:
|
||||||
self.logMsg("Error: Unable to start web helper.", -1)
|
log("Error: Unable to start web helper.", -1)
|
||||||
httpd = False
|
httpd = False
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -59,15 +77,15 @@ class PlexCompanion(threading.Thread):
|
||||||
if not httpd:
|
if not httpd:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.client.start_all()
|
client.start_all()
|
||||||
|
|
||||||
message_count = 0
|
message_count = 0
|
||||||
is_running = False
|
while not threadStopped():
|
||||||
while not self.threadStopped():
|
|
||||||
# If we are not authorized, sleep
|
# If we are not authorized, sleep
|
||||||
# Otherwise, we trigger a download which leads to a
|
# Otherwise, we trigger a download which leads to a
|
||||||
# re-authorizations
|
# re-authorizations
|
||||||
while self.threadSuspended() or window('emby_serverStatus'):
|
while threadSuspended():
|
||||||
if self.threadStopped():
|
if threadStopped():
|
||||||
break
|
break
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
try:
|
try:
|
||||||
|
@ -76,31 +94,29 @@ class PlexCompanion(threading.Thread):
|
||||||
message_count += 1
|
message_count += 1
|
||||||
|
|
||||||
if message_count > 100:
|
if message_count > 100:
|
||||||
if self.client.check_client_registration():
|
if client.check_client_registration():
|
||||||
self.logMsg("Client is still registered", 1)
|
log("Client is still registered", 1)
|
||||||
else:
|
else:
|
||||||
self.logMsg("Client is no longer registered", 1)
|
log("Client is no longer registered", 1)
|
||||||
self.logMsg("Plex Companion still running on port %s"
|
log("Plex Companion still running on port %s"
|
||||||
% self.port, 1)
|
% self.port, 1)
|
||||||
message_count = 0
|
message_count = 0
|
||||||
|
|
||||||
if not is_running:
|
# Get and set servers
|
||||||
self.logMsg("Plex Companion has started", 0)
|
subscriptionManager.serverlist = client.getServerList()
|
||||||
is_running = True
|
|
||||||
|
|
||||||
subscribers.subMgr.notify()
|
subscriptionManager.notify()
|
||||||
settings['serverList'] = self.client.getServerList()
|
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(50)
|
||||||
except:
|
except:
|
||||||
self.logMsg("Error in loop, continuing anyway", 1)
|
log("Error in loop, continuing anyway", 1)
|
||||||
self.logMsg(traceback.format_exc(), 1)
|
log(traceback.format_exc(), 1)
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(50)
|
||||||
|
|
||||||
self.client.stop_all()
|
client.stop_all()
|
||||||
try:
|
try:
|
||||||
httpd.socket.shutdown(socket.SHUT_RDWR)
|
httpd.socket.shutdown(socket.SHUT_RDWR)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
httpd.socket.close()
|
httpd.socket.close()
|
||||||
self.logMsg("----===## Plex Companion stopped ##===----", 0)
|
log("----===## Plex Companion stopped ##===----", 0)
|
||||||
|
|
|
@ -1,26 +1,35 @@
|
||||||
import base64
|
import base64
|
||||||
import inspect
|
|
||||||
import json
|
import json
|
||||||
import string
|
import string
|
||||||
import traceback
|
|
||||||
import xbmc
|
import xbmc
|
||||||
from settings import settings
|
import settings
|
||||||
from httppersist import requests
|
from utils import logMsg
|
||||||
|
|
||||||
|
|
||||||
def xbmc_photo():
|
def xbmc_photo():
|
||||||
return "photo"
|
return "photo"
|
||||||
|
|
||||||
|
|
||||||
def xbmc_video():
|
def xbmc_video():
|
||||||
return "video"
|
return "video"
|
||||||
|
|
||||||
|
|
||||||
def xbmc_audio():
|
def xbmc_audio():
|
||||||
return "audio"
|
return "audio"
|
||||||
|
|
||||||
|
|
||||||
def plex_photo():
|
def plex_photo():
|
||||||
return "photo"
|
return "photo"
|
||||||
|
|
||||||
|
|
||||||
def plex_video():
|
def plex_video():
|
||||||
return "video"
|
return "video"
|
||||||
|
|
||||||
|
|
||||||
def plex_audio():
|
def plex_audio():
|
||||||
return "music"
|
return "music"
|
||||||
|
|
||||||
|
|
||||||
def xbmc_type(plex_type):
|
def xbmc_type(plex_type):
|
||||||
if plex_type == plex_photo():
|
if plex_type == plex_photo():
|
||||||
return xbmc_photo()
|
return xbmc_photo()
|
||||||
|
@ -28,7 +37,8 @@ def xbmc_type(plex_type):
|
||||||
return xbmc_video()
|
return xbmc_video()
|
||||||
elif plex_type == plex_audio():
|
elif plex_type == plex_audio():
|
||||||
return xbmc_audio()
|
return xbmc_audio()
|
||||||
|
|
||||||
|
|
||||||
def plex_type(xbmc_type):
|
def plex_type(xbmc_type):
|
||||||
if xbmc_type == xbmc_photo():
|
if xbmc_type == xbmc_photo():
|
||||||
return plex_photo()
|
return plex_photo()
|
||||||
|
@ -37,6 +47,7 @@ def plex_type(xbmc_type):
|
||||||
elif xbmc_type == xbmc_audio():
|
elif xbmc_type == xbmc_audio():
|
||||||
return plex_audio()
|
return plex_audio()
|
||||||
|
|
||||||
|
|
||||||
def getPlatform():
|
def getPlatform():
|
||||||
if xbmc.getCondVisibility('system.platform.osx'):
|
if xbmc.getCondVisibility('system.platform.osx'):
|
||||||
return "MacOSX"
|
return "MacOSX"
|
||||||
|
@ -50,140 +61,23 @@ def getPlatform():
|
||||||
return "RaspberryPi"
|
return "RaspberryPi"
|
||||||
elif xbmc.getCondVisibility('system.platform.linux'):
|
elif xbmc.getCondVisibility('system.platform.linux'):
|
||||||
return "Linux"
|
return "Linux"
|
||||||
elif xbmc.getCondVisibility('system.platform.android'):
|
elif xbmc.getCondVisibility('system.platform.android'):
|
||||||
return "Android"
|
return "Android"
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
def printDebug( msg, functionname=True ):
|
|
||||||
if settings['debug']:
|
|
||||||
if functionname is False:
|
|
||||||
print str(msg)
|
|
||||||
else:
|
|
||||||
print "PleXBMC Helper -> " + inspect.stack()[1][3] + ": " + str(msg)
|
|
||||||
|
|
||||||
""" communicate with XBMC """
|
|
||||||
def jsonrpc(action, arguments = {}):
|
|
||||||
""" put some JSON together for the JSON-RPC APIv6 """
|
|
||||||
if action.lower() == "sendkey":
|
|
||||||
request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : self.arguments[0], "done" : False }} )
|
|
||||||
elif action.lower() == "ping":
|
|
||||||
request=json.dumps({ "jsonrpc" : "2.0",
|
|
||||||
"id" : 1 ,
|
|
||||||
"method" : "JSONRPC.Ping" })
|
|
||||||
elif action.lower() == "playmedia":
|
|
||||||
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
|
|
||||||
"?mode=companion&arguments=%s"
|
|
||||||
% arguments)
|
|
||||||
return True
|
|
||||||
elif arguments:
|
|
||||||
request=json.dumps({ "id" : 1,
|
|
||||||
"jsonrpc" : "2.0",
|
|
||||||
"method" : action,
|
|
||||||
"params" : arguments})
|
|
||||||
else:
|
|
||||||
request=json.dumps({ "id" : 1,
|
|
||||||
"jsonrpc" : "2.0",
|
|
||||||
"method" : action})
|
|
||||||
|
|
||||||
printDebug("Sending request to XBMC without network stack: %s" % request)
|
|
||||||
result = parseJSONRPC(xbmc.executeJSONRPC(request))
|
|
||||||
|
|
||||||
if not result and settings['webserver_enabled']:
|
|
||||||
# xbmc.executeJSONRPC appears to fail on the login screen, but going
|
|
||||||
# through the network stack works, so let's try the request again
|
|
||||||
result = parseJSONRPC(requests.post(
|
|
||||||
"127.0.0.1",
|
|
||||||
settings['port'],
|
|
||||||
"/jsonrpc",
|
|
||||||
request,
|
|
||||||
{ 'Content-Type' : 'application/json',
|
|
||||||
'Authorization' : 'Basic ' + string.strip(base64.encodestring(settings['user'] + ':' + settings['passwd'])) }))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parseJSONRPC(jsonraw):
|
|
||||||
if not jsonraw:
|
|
||||||
printDebug("Empty response from XBMC")
|
|
||||||
return {}
|
|
||||||
else:
|
|
||||||
printDebug("Response from XBMC: %s" % jsonraw)
|
|
||||||
parsed=json.loads(jsonraw)
|
|
||||||
if parsed.get('error', False):
|
|
||||||
print "XBMC returned an error: %s" % parsed.get('error')
|
|
||||||
return parsed.get('result', {})
|
|
||||||
|
|
||||||
def getXMLHeader():
|
def getXMLHeader():
|
||||||
return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\n"
|
return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\n"
|
||||||
|
|
||||||
|
|
||||||
def getOKMsg():
|
def getOKMsg():
|
||||||
return getXMLHeader() + '<Response code="200" status="OK" />'
|
return getXMLHeader() + '<Response code="200" status="OK" />'
|
||||||
|
|
||||||
def getPlexHeaders():
|
|
||||||
h = {
|
|
||||||
"Content-type": "application/x-www-form-urlencoded",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"X-Plex-Version": settings['version'],
|
|
||||||
"X-Plex-Client-Identifier": settings['uuid'],
|
|
||||||
"X-Plex-Provides": "player",
|
|
||||||
"X-Plex-Product": "PlexKodiConnect",
|
|
||||||
"X-Plex-Device-Name": settings['client_name'],
|
|
||||||
"X-Plex-Platform": "XBMC",
|
|
||||||
"X-Plex-Model": getPlatform(),
|
|
||||||
"X-Plex-Device": "PC",
|
|
||||||
}
|
|
||||||
if settings['myplex_user']:
|
|
||||||
h["X-Plex-Username"] = settings['myplex_user']
|
|
||||||
return h
|
|
||||||
|
|
||||||
def getServerByHost(host):
|
|
||||||
list = settings['serverList']
|
|
||||||
if len(list) == 1:
|
|
||||||
return list[0]
|
|
||||||
for server in list:
|
|
||||||
if server.get('serverName') in host or server.get('server') in host:
|
|
||||||
return server
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def getPlayers():
|
|
||||||
info = jsonrpc("Player.GetActivePlayers") or []
|
|
||||||
ret = {}
|
|
||||||
for player in info:
|
|
||||||
player['playerid'] = int(player['playerid'])
|
|
||||||
ret[player['type']] = player
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def getPlayerIds():
|
|
||||||
ret = []
|
|
||||||
for player in getPlayers().values():
|
|
||||||
ret.append(player['playerid'])
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def getVideoPlayerId(players = False):
|
|
||||||
if players is None:
|
|
||||||
players = getPlayers()
|
|
||||||
return players.get(xbmc_video(), {}).get('playerid', None)
|
|
||||||
|
|
||||||
def getAudioPlayerId(players = False):
|
|
||||||
if players is None:
|
|
||||||
players = getPlayers()
|
|
||||||
return players.get(xbmc_audio(), {}).get('playerid', None)
|
|
||||||
|
|
||||||
def getPhotoPlayerId(players = False):
|
|
||||||
if players is None:
|
|
||||||
players = getPlayers()
|
|
||||||
return players.get(xbmc_photo(), {}).get('playerid', None)
|
|
||||||
|
|
||||||
def getVolume():
|
|
||||||
answ = jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] })
|
|
||||||
vol = str(answ.get('volume', 100))
|
|
||||||
mute = ("0", "1")[answ.get('muted', False)]
|
|
||||||
return (vol, mute)
|
|
||||||
|
|
||||||
def timeToMillis(time):
|
def timeToMillis(time):
|
||||||
return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds']
|
return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds']
|
||||||
|
|
||||||
|
|
||||||
def millisToTime(t):
|
def millisToTime(t):
|
||||||
millis = int(t)
|
millis = int(t)
|
||||||
seconds = millis / 1000
|
seconds = millis / 1000
|
||||||
|
@ -194,5 +88,118 @@ def millisToTime(t):
|
||||||
millis = millis % 1000
|
millis = millis % 1000
|
||||||
return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis}
|
return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis}
|
||||||
|
|
||||||
|
|
||||||
def textFromXml(element):
|
def textFromXml(element):
|
||||||
return element.firstChild.data
|
return element.firstChild.data
|
||||||
|
|
||||||
|
|
||||||
|
class jsonClass():
|
||||||
|
|
||||||
|
def __init__(self, requestMgr):
|
||||||
|
self.settings = settings.getSettings()
|
||||||
|
self.requestMgr = requestMgr
|
||||||
|
|
||||||
|
def jsonrpc(self, action, arguments={}):
|
||||||
|
""" put some JSON together for the JSON-RPC APIv6 """
|
||||||
|
if action.lower() == "sendkey":
|
||||||
|
request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : arguments[0], "done" : False }} )
|
||||||
|
elif action.lower() == "ping":
|
||||||
|
request=json.dumps({ "jsonrpc" : "2.0",
|
||||||
|
"id" : 1 ,
|
||||||
|
"method" : "JSONRPC.Ping" })
|
||||||
|
elif action.lower() == "playmedia":
|
||||||
|
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
|
||||||
|
"?mode=companion&arguments=%s"
|
||||||
|
% arguments)
|
||||||
|
return True
|
||||||
|
elif arguments:
|
||||||
|
request=json.dumps({ "id" : 1,
|
||||||
|
"jsonrpc" : "2.0",
|
||||||
|
"method" : action,
|
||||||
|
"params" : arguments})
|
||||||
|
else:
|
||||||
|
request=json.dumps({ "id" : 1,
|
||||||
|
"jsonrpc" : "2.0",
|
||||||
|
"method" : action})
|
||||||
|
|
||||||
|
logMsg("PlexCompanion",
|
||||||
|
"Sending request to XBMC without network stack: %s"
|
||||||
|
% request, 2)
|
||||||
|
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
|
||||||
|
|
||||||
|
if not result and self.settings['webserver_enabled']:
|
||||||
|
# xbmc.executeJSONRPC appears to fail on the login screen, but going
|
||||||
|
# through the network stack works, so let's try the request again
|
||||||
|
result = self.parseJSONRPC(self.requestMgr.post(
|
||||||
|
"127.0.0.1",
|
||||||
|
self.settings['port'],
|
||||||
|
"/jsonrpc",
|
||||||
|
request,
|
||||||
|
{ 'Content-Type' : 'application/json',
|
||||||
|
'Authorization' : 'Basic ' + string.strip(base64.encodestring(self.settings['user'] + ':' + self.settings['passwd'])) }))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getPlexHeaders(self):
|
||||||
|
h = {
|
||||||
|
"Content-type": "application/x-www-form-urlencoded",
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
"X-Plex-Version": self.settings['version'],
|
||||||
|
"X-Plex-Client-Identifier": self.settings['uuid'],
|
||||||
|
"X-Plex-Provides": "player",
|
||||||
|
"X-Plex-Product": "PlexKodiConnect",
|
||||||
|
"X-Plex-Device-Name": self.settings['client_name'],
|
||||||
|
"X-Plex-Platform": "Kodi",
|
||||||
|
"X-Plex-Model": getPlatform(),
|
||||||
|
"X-Plex-Device": "PC",
|
||||||
|
}
|
||||||
|
if self.settings['myplex_user']:
|
||||||
|
h["X-Plex-Username"] = self.settings['myplex_user']
|
||||||
|
return h
|
||||||
|
|
||||||
|
def parseJSONRPC(self, jsonraw):
|
||||||
|
if not jsonraw:
|
||||||
|
logMsg("PlexCompanion", "Empty response from XBMC", 1)
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
logMsg("PlexCompanion", "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)
|
||||||
|
return parsed.get('result', {})
|
||||||
|
|
||||||
|
def getPlayers(self):
|
||||||
|
info = self.jsonrpc("Player.GetActivePlayers") or []
|
||||||
|
ret = {}
|
||||||
|
for player in info:
|
||||||
|
player['playerid'] = int(player['playerid'])
|
||||||
|
ret[player['type']] = player
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def getPlayerIds(self):
|
||||||
|
ret = []
|
||||||
|
for player in self.getPlayers().values():
|
||||||
|
ret.append(player['playerid'])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def getVideoPlayerId(self, players=False):
|
||||||
|
if players is None:
|
||||||
|
players = self.getPlayers()
|
||||||
|
return players.get(xbmc_video(), {}).get('playerid', None)
|
||||||
|
|
||||||
|
def getAudioPlayerId(self, players=False):
|
||||||
|
if players is None:
|
||||||
|
players = self.getPlayers()
|
||||||
|
return players.get(xbmc_audio(), {}).get('playerid', None)
|
||||||
|
|
||||||
|
def getPhotoPlayerId(self, players=False):
|
||||||
|
if players is None:
|
||||||
|
players = self.getPlayers()
|
||||||
|
return players.get(xbmc_photo(), {}).get('playerid', None)
|
||||||
|
|
||||||
|
def getVolume(self):
|
||||||
|
answ = self.jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] })
|
||||||
|
vol = str(answ.get('volume', 100))
|
||||||
|
mute = ("0", "1")[answ.get('muted', False)]
|
||||||
|
return (vol, mute)
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import httplib
|
import httplib
|
||||||
import traceback
|
import traceback
|
||||||
import string
|
import string
|
||||||
|
import errno
|
||||||
|
from socket import error as socket_error
|
||||||
|
from utils import logging
|
||||||
|
|
||||||
|
|
||||||
|
@logging
|
||||||
class RequestMgr:
|
class RequestMgr:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.conns = {}
|
self.conns = {}
|
||||||
|
@ -9,24 +14,24 @@ class RequestMgr:
|
||||||
def getConnection(self, protocol, host, port):
|
def getConnection(self, protocol, host, port):
|
||||||
conn = self.conns.get(protocol+host+str(port), False)
|
conn = self.conns.get(protocol+host+str(port), False)
|
||||||
if not conn:
|
if not conn:
|
||||||
if protocol=="https":
|
if protocol == "https":
|
||||||
conn = httplib.HTTPSConnection(host, port)
|
conn = httplib.HTTPSConnection(host, port)
|
||||||
else:
|
else:
|
||||||
conn = httplib.HTTPConnection(host, port)
|
conn = httplib.HTTPConnection(host, port)
|
||||||
self.conns[protocol+host+str(port)] = conn
|
self.conns[protocol+host+str(port)] = conn
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def closeConnection(self, protocol, host, port):
|
def closeConnection(self, protocol, host, port):
|
||||||
conn = self.conns.get(protocol+host+str(port), False)
|
conn = self.conns.get(protocol+host+str(port), False)
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
conn.close()
|
||||||
self.conns.pop(protocol+host+str(port), None)
|
self.conns.pop(protocol+host+str(port), None)
|
||||||
|
|
||||||
def dumpConnections(self):
|
def dumpConnections(self):
|
||||||
for conn in self.conns.values():
|
for conn in self.conns.values():
|
||||||
conn.close()
|
conn.close()
|
||||||
self.conns = {}
|
self.conns = {}
|
||||||
|
|
||||||
def post(self, host, port, path, body, header={}, protocol="http"):
|
def post(self, host, port, path, body, header={}, protocol="http"):
|
||||||
conn = None
|
conn = None
|
||||||
try:
|
try:
|
||||||
|
@ -35,27 +40,33 @@ class RequestMgr:
|
||||||
conn.request("POST", path, body, header)
|
conn.request("POST", path, body, header)
|
||||||
data = conn.getresponse()
|
data = conn.getresponse()
|
||||||
if int(data.status) >= 400:
|
if int(data.status) >= 400:
|
||||||
print "HTTP response error: " + str(data.status)
|
self.logMsg("HTTP response error: %s" % str(data.status), -1)
|
||||||
# this should return false, but I'm hacking it since iOS returns 404 no matter what
|
# this should return false, but I'm hacking it since iOS
|
||||||
|
# returns 404 no matter what
|
||||||
return data.read() or True
|
return data.read() or True
|
||||||
else:
|
else:
|
||||||
return data.read() or True
|
return data.read() or True
|
||||||
except:
|
except socket_error as serr:
|
||||||
print "Unable to connect to %s\nReason:" % host
|
# Ignore remote close and connection refused (e.g. shutdown PKC)
|
||||||
traceback.print_exc()
|
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)
|
self.conns.pop(protocol+host+str(port), None)
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
conn.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getwithparams(self, host, port, path, params, header={}, protocol="http"):
|
def getwithparams(self, host, port, path, params, header={},
|
||||||
|
protocol="http"):
|
||||||
newpath = path + '?'
|
newpath = path + '?'
|
||||||
pairs = []
|
pairs = []
|
||||||
for key in params:
|
for key in params:
|
||||||
pairs.append(str(key)+'='+str(params[key]))
|
pairs.append(str(key)+'='+str(params[key]))
|
||||||
newpath += string.join(pairs, '&')
|
newpath += string.join(pairs, '&')
|
||||||
return self.get(host, port, newpath, header, protocol)
|
return self.get(host, port, newpath, header, protocol)
|
||||||
|
|
||||||
def get(self, host, port, path, header={}, protocol="http"):
|
def get(self, host, port, path, header={}, protocol="http"):
|
||||||
try:
|
try:
|
||||||
conn = self.getConnection(protocol, host, port)
|
conn = self.getConnection(protocol, host, port)
|
||||||
|
@ -63,14 +74,13 @@ class RequestMgr:
|
||||||
conn.request("GET", path, headers=header)
|
conn.request("GET", path, headers=header)
|
||||||
data = conn.getresponse()
|
data = conn.getresponse()
|
||||||
if int(data.status) >= 400:
|
if int(data.status) >= 400:
|
||||||
print "HTTP response error: " + str(data.status)
|
self.logMsg("HTTP response error: %s" % str(data.status), -1)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return data.read() or True
|
return data.read() or True
|
||||||
except:
|
except:
|
||||||
print "Unable to connect to %s\nReason: %s" % (host, traceback.print_exc())
|
self.logMsg("Unable to connect to %s\nReason:" % host, -1)
|
||||||
|
self.logMsg(traceback.print_exc(), -1)
|
||||||
self.conns.pop(protocol+host+str(port), None)
|
self.conns.pop(protocol+host+str(port), None)
|
||||||
conn.close()
|
conn.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
requests = RequestMgr()
|
|
||||||
|
|
|
@ -4,38 +4,56 @@ import xbmc
|
||||||
from SocketServer import ThreadingMixIn
|
from SocketServer import ThreadingMixIn
|
||||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||||
from urlparse import urlparse, parse_qs
|
from urlparse import urlparse, parse_qs
|
||||||
from settings import settings
|
import settings
|
||||||
from functions import *
|
from functions import *
|
||||||
from subscribers import subMgr
|
from utils import logging
|
||||||
|
|
||||||
|
|
||||||
|
@logging
|
||||||
class MyHandler(BaseHTTPRequestHandler):
|
class MyHandler(BaseHTTPRequestHandler):
|
||||||
protocol_version = 'HTTP/1.1'
|
protocol_version = 'HTTP/1.1'
|
||||||
def log_message(s, format, *args):
|
|
||||||
# I have my own logging, suppressing BaseHTTPRequestHandler's
|
def __init__(self, *args, **kwargs):
|
||||||
#printDebug(format % args)
|
self.serverlist = []
|
||||||
return True
|
self.settings = settings.getSettings()
|
||||||
|
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def getServerByHost(self, host):
|
||||||
|
if len(self.serverlist) == 1:
|
||||||
|
return self.serverlist[0]
|
||||||
|
for server in self.serverlist:
|
||||||
|
if (server.get('serverName') in host or
|
||||||
|
server.get('server') in host):
|
||||||
|
return server
|
||||||
|
return {}
|
||||||
|
|
||||||
def do_HEAD(s):
|
def do_HEAD(s):
|
||||||
printDebug( "Serving HEAD request..." )
|
s.logMsg("Serving HEAD request...", 2)
|
||||||
s.answer_request(0)
|
s.answer_request(0)
|
||||||
|
|
||||||
def do_GET(s):
|
def do_GET(s):
|
||||||
printDebug( "Serving GET request..." )
|
s.logMsg("Serving GET request...", 2)
|
||||||
s.answer_request(1)
|
s.answer_request(1)
|
||||||
|
|
||||||
def do_OPTIONS(s):
|
def do_OPTIONS(s):
|
||||||
s.send_response(200)
|
s.send_response(200)
|
||||||
s.send_header('Content-Length', '0')
|
s.send_header('Content-Length', '0')
|
||||||
s.send_header('X-Plex-Client-Identifier', settings['uuid'])
|
s.send_header('X-Plex-Client-Identifier', s.settings['uuid'])
|
||||||
s.send_header('Content-Type', 'text/plain')
|
s.send_header('Content-Type', 'text/plain')
|
||||||
s.send_header('Connection', 'close')
|
s.send_header('Connection', 'close')
|
||||||
s.send_header('Access-Control-Max-Age', '1209600')
|
s.send_header('Access-Control-Max-Age', '1209600')
|
||||||
s.send_header('Access-Control-Allow-Origin', '*')
|
s.send_header('Access-Control-Allow-Origin', '*')
|
||||||
s.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, HEAD')
|
s.send_header('Access-Control-Allow-Methods',
|
||||||
s.send_header('Access-Control-Allow-Headers', 'x-plex-version, x-plex-platform-version, x-plex-username, x-plex-client-identifier, x-plex-target-client-identifier, x-plex-device-name, x-plex-platform, x-plex-product, accept, x-plex-device')
|
'POST, GET, OPTIONS, DELETE, PUT, HEAD')
|
||||||
|
s.send_header('Access-Control-Allow-Headers',
|
||||||
|
'x-plex-version, x-plex-platform-version, '
|
||||||
|
'x-plex-username, x-plex-client-identifier, '
|
||||||
|
'x-plex-target-client-identifier, x-plex-device-name, '
|
||||||
|
'x-plex-platform, x-plex-product, accept, x-plex-device')
|
||||||
s.end_headers()
|
s.end_headers()
|
||||||
s.wfile.close()
|
s.wfile.close()
|
||||||
|
|
||||||
def response(s, body, headers = {}, code = 200):
|
def response(s, body, headers={}, code=200):
|
||||||
try:
|
try:
|
||||||
s.send_response(code)
|
s.send_response(code)
|
||||||
for key in headers:
|
for key in headers:
|
||||||
|
@ -49,73 +67,76 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def answer_request(s, sendData):
|
def answer_request(s, sendData):
|
||||||
|
s.serverlist = s.server.client.getServerList()
|
||||||
|
s.subMgr = s.server.subscriptionManager
|
||||||
|
s.js = s.server.jsonClass
|
||||||
try:
|
try:
|
||||||
request_path=s.path[1:]
|
request_path = s.path[1:]
|
||||||
request_path=re.sub(r"\?.*","",request_path)
|
request_path = re.sub(r"\?.*", "", request_path)
|
||||||
url = urlparse(s.path)
|
url = urlparse(s.path)
|
||||||
paramarrays = parse_qs(url.query)
|
paramarrays = parse_qs(url.query)
|
||||||
params = {}
|
params = {}
|
||||||
for key in paramarrays:
|
for key in paramarrays:
|
||||||
params[key] = paramarrays[key][0]
|
params[key] = paramarrays[key][0]
|
||||||
printDebug ( "request path is: [%s]" % ( request_path,) )
|
s.logMsg("request path is: [%s]" % (request_path,), 2)
|
||||||
printDebug ( "params are: %s" % params )
|
s.logMsg("params are: %s" % params, 2)
|
||||||
subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False))
|
s.subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False))
|
||||||
if request_path=="version":
|
if request_path=="version":
|
||||||
s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % settings['version'])
|
s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % s.settings['version'])
|
||||||
elif request_path=="verify":
|
elif request_path=="verify":
|
||||||
result=jsonrpc("ping")
|
result=s.js.jsonrpc("ping")
|
||||||
s.response("XBMC JSON connection test:\r\n"+result)
|
s.response("XBMC JSON connection test:\r\n"+result)
|
||||||
elif "resources" == request_path:
|
elif "resources" == request_path:
|
||||||
resp = getXMLHeader()
|
resp = getXMLHeader()
|
||||||
resp += "<MediaContainer>"
|
resp += "<MediaContainer>"
|
||||||
resp += "<Player"
|
resp += "<Player"
|
||||||
resp += ' title="%s"' % settings['client_name']
|
resp += ' title="%s"' % s.settings['client_name']
|
||||||
resp += ' protocol="plex"'
|
resp += ' protocol="plex"'
|
||||||
resp += ' protocolVersion="1"'
|
resp += ' protocolVersion="1"'
|
||||||
resp += ' protocolCapabilities="navigation,playback,timeline"'
|
resp += ' protocolCapabilities="navigation,playback,timeline"'
|
||||||
resp += ' machineIdentifier="%s"' % settings['uuid']
|
resp += ' machineIdentifier="%s"' % s.settings['uuid']
|
||||||
resp += ' product="PlexKodiConnect"'
|
resp += ' product="PlexKodiConnect"'
|
||||||
resp += ' platform="%s"' % getPlatform()
|
resp += ' platform="%s"' % getPlatform()
|
||||||
resp += ' platformVersion="%s"' % settings['plexbmc_version']
|
resp += ' platformVersion="%s"' % s.settings['plexbmc_version']
|
||||||
resp += ' deviceClass="pc"'
|
resp += ' deviceClass="pc"'
|
||||||
resp += "/>"
|
resp += "/>"
|
||||||
resp += "</MediaContainer>"
|
resp += "</MediaContainer>"
|
||||||
printDebug("crafted resources response: %s" % resp)
|
s.logMsg("crafted resources response: %s" % resp, 2)
|
||||||
s.response(resp, getPlexHeaders())
|
s.response(resp, s.js.getPlexHeaders())
|
||||||
elif "/subscribe" in request_path:
|
elif "/subscribe" in request_path:
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
protocol = params.get('protocol', False)
|
protocol = params.get('protocol', False)
|
||||||
host = s.client_address[0]
|
host = s.client_address[0]
|
||||||
port = params.get('port', False)
|
port = params.get('port', False)
|
||||||
uuid = s.headers.get('X-Plex-Client-Identifier', "")
|
uuid = s.headers.get('X-Plex-Client-Identifier', "")
|
||||||
commandID = params.get('commandID', 0)
|
commandID = params.get('commandID', 0)
|
||||||
subMgr.addSubscriber(protocol, host, port, uuid, commandID)
|
s.subMgr.addSubscriber(protocol, host, port, uuid, commandID)
|
||||||
elif "/poll" in request_path:
|
elif "/poll" in request_path:
|
||||||
if params.get('wait', False) == '1':
|
if params.get('wait', False) == '1':
|
||||||
xbmc.sleep(950)
|
xbmc.sleep(950)
|
||||||
commandID = params.get('commandID', 0)
|
commandID = params.get('commandID', 0)
|
||||||
s.response(re.sub(r"INSERTCOMMANDID", str(commandID), subMgr.msg(getPlayers())), {
|
s.response(re.sub(r"INSERTCOMMANDID", str(commandID), s.subMgr.msg(s.js.getPlayers())), {
|
||||||
'X-Plex-Client-Identifier': settings['uuid'],
|
'X-Plex-Client-Identifier': s.settings['uuid'],
|
||||||
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
|
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Content-Type': 'text/xml'
|
'Content-Type': 'text/xml'
|
||||||
})
|
})
|
||||||
elif "/unsubscribe" in request_path:
|
elif "/unsubscribe" in request_path:
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
uuid = s.headers.get('X-Plex-Client-Identifier', False) or s.client_address[0]
|
uuid = s.headers.get('X-Plex-Client-Identifier', False) or s.client_address[0]
|
||||||
subMgr.removeSubscriber(uuid)
|
s.subMgr.removeSubscriber(uuid)
|
||||||
elif request_path == "player/playback/setParameters":
|
elif request_path == "player/playback/setParameters":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
if 'volume' in params:
|
if 'volume' in params:
|
||||||
volume = int(params['volume'])
|
volume = int(params['volume'])
|
||||||
printDebug("adjusting the volume to %s%%" % volume)
|
s.logMsg("adjusting the volume to %s%%" % volume, 2)
|
||||||
jsonrpc("Application.SetVolume", {"volume": volume})
|
s.js.jsonrpc("Application.SetVolume", {"volume": volume})
|
||||||
elif "/playMedia" in request_path:
|
elif "/playMedia" in request_path:
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
offset = params.get('viewOffset', params.get('offset', "0"))
|
offset = params.get('viewOffset', params.get('offset', "0"))
|
||||||
protocol = params.get('protocol', "http")
|
protocol = params.get('protocol', "http")
|
||||||
address = params.get('address', s.client_address[0])
|
address = params.get('address', s.client_address[0])
|
||||||
server = getServerByHost(address)
|
server = s.getServerByHost(address)
|
||||||
port = params.get('port', server.get('port', '32400'))
|
port = params.get('port', server.get('port', '32400'))
|
||||||
try:
|
try:
|
||||||
containerKey = urlparse(params.get('containerKey')).path
|
containerKey = urlparse(params.get('containerKey')).path
|
||||||
|
@ -127,74 +148,88 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
playQueueID = ''
|
playQueueID = ''
|
||||||
|
|
||||||
jsonrpc("playmedia", params)
|
s.js.jsonrpc("playmedia", params)
|
||||||
subMgr.lastkey = params['key']
|
s.subMgr.lastkey = params['key']
|
||||||
subMgr.containerKey = containerKey
|
s.subMgr.containerKey = containerKey
|
||||||
subMgr.playQueueID = playQueueID
|
s.subMgr.playQueueID = playQueueID
|
||||||
subMgr.server = server.get('server', 'localhost')
|
s.subMgr.server = server.get('server', 'localhost')
|
||||||
subMgr.port = port
|
s.subMgr.port = port
|
||||||
subMgr.protocol = protocol
|
s.subMgr.protocol = protocol
|
||||||
subMgr.notify()
|
s.subMgr.notify()
|
||||||
elif request_path == "player/playback/play":
|
elif request_path == "player/playback/play":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True})
|
s.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True})
|
||||||
elif request_path == "player/playback/pause":
|
elif request_path == "player/playback/pause":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False})
|
s.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False})
|
||||||
elif request_path == "player/playback/stop":
|
elif request_path == "player/playback/stop":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.Stop", {"playerid" : playerid})
|
s.js.jsonrpc("Player.Stop", {"playerid" : playerid})
|
||||||
elif request_path == "player/playback/seekTo":
|
elif request_path == "player/playback/seekTo":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))})
|
s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))})
|
||||||
subMgr.notify()
|
s.subMgr.notify()
|
||||||
elif request_path == "player/playback/stepForward":
|
elif request_path == "player/playback/stepForward":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"})
|
s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"})
|
||||||
subMgr.notify()
|
s.subMgr.notify()
|
||||||
elif request_path == "player/playback/stepBack":
|
elif request_path == "player/playback/stepBack":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"})
|
s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"})
|
||||||
subMgr.notify()
|
s.subMgr.notify()
|
||||||
elif request_path == "player/playback/skipNext":
|
elif request_path == "player/playback/skipNext":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"})
|
s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"})
|
||||||
subMgr.notify()
|
s.subMgr.notify()
|
||||||
elif request_path == "player/playback/skipPrevious":
|
elif request_path == "player/playback/skipPrevious":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
for playerid in getPlayerIds():
|
for playerid in s.js.getPlayerIds():
|
||||||
jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"})
|
s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"})
|
||||||
subMgr.notify()
|
s.subMgr.notify()
|
||||||
elif request_path == "player/navigation/moveUp":
|
elif request_path == "player/navigation/moveUp":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Up")
|
s.js.jsonrpc("Input.Up")
|
||||||
elif request_path == "player/navigation/moveDown":
|
elif request_path == "player/navigation/moveDown":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Down")
|
s.js.jsonrpc("Input.Down")
|
||||||
elif request_path == "player/navigation/moveLeft":
|
elif request_path == "player/navigation/moveLeft":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Left")
|
s.js.jsonrpc("Input.Left")
|
||||||
elif request_path == "player/navigation/moveRight":
|
elif request_path == "player/navigation/moveRight":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Right")
|
s.js.jsonrpc("Input.Right")
|
||||||
elif request_path == "player/navigation/select":
|
elif request_path == "player/navigation/select":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Select")
|
s.js.jsonrpc("Input.Select")
|
||||||
elif request_path == "player/navigation/home":
|
elif request_path == "player/navigation/home":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Home")
|
s.js.jsonrpc("Input.Home")
|
||||||
elif request_path == "player/navigation/back":
|
elif request_path == "player/navigation/back":
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), s.js.getPlexHeaders())
|
||||||
jsonrpc("Input.Back")
|
s.js.jsonrpc("Input.Back")
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
|
|
||||||
|
def __init__(self, client, subscriptionManager, jsonClass,
|
||||||
|
*args, **kwargs):
|
||||||
|
"""
|
||||||
|
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
|
||||||
|
date serverlist without instantiating anything
|
||||||
|
|
||||||
|
same for SubscriptionManager and jsonClass
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
self.subscriptionManager = subscriptionManager
|
||||||
|
self.jsonClass = jsonClass
|
||||||
|
HTTPServer.__init__(self, *args, **kwargs)
|
||||||
|
|
|
@ -333,7 +333,7 @@ class plexgdm:
|
||||||
if discovery_count > self.discovery_interval:
|
if discovery_count > self.discovery_interval:
|
||||||
self.discover()
|
self.discover()
|
||||||
discovery_count=0
|
discovery_count=0
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(500)
|
||||||
|
|
||||||
def start_discovery(self, daemon = False):
|
def start_discovery(self, daemon = False):
|
||||||
if not self._discovery_is_running:
|
if not self._discovery_is_running:
|
||||||
|
|
|
@ -1,48 +1,46 @@
|
||||||
import xbmc
|
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
guisettingsXML = utils.guisettingsXML()
|
|
||||||
|
|
||||||
|
|
||||||
def getGUI(name):
|
def getGUI(name):
|
||||||
|
guisettingsXML = utils.guisettingsXML()
|
||||||
try:
|
try:
|
||||||
ans = list(guisettingsXML.iter(name))[0].text
|
ans = list(guisettingsXML.iter(name))[0].text
|
||||||
if ans is None:
|
if ans is None:
|
||||||
ans = ''
|
ans = ''
|
||||||
return ans
|
|
||||||
except:
|
except:
|
||||||
return ""
|
ans = ''
|
||||||
|
return ans
|
||||||
|
|
||||||
addon = xbmcaddon.Addon()
|
|
||||||
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
|
|
||||||
|
|
||||||
settings['debug'] = utils.settings('companionDebugging')
|
def getSettings():
|
||||||
settings['gdm_debug'] = utils.settings('companionGDMDebugging')
|
settings = {}
|
||||||
|
addon = xbmcaddon.Addon()
|
||||||
|
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
|
||||||
|
|
||||||
# Transform 'true' into True because of the way Kodi's file settings work
|
settings['debug'] = utils.settings('companionDebugging')
|
||||||
kodiSettingsList = ['debug', 'gdm_debug']
|
settings['gdm_debug'] = utils.settings('companionGDMDebugging')
|
||||||
for entry in kodiSettingsList:
|
|
||||||
if settings[entry] == 'true':
|
|
||||||
settings[entry] = True
|
|
||||||
else:
|
|
||||||
settings[entry] = False
|
|
||||||
|
|
||||||
settings['client_name'] = plexbmc.getSetting('deviceName')
|
# 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
|
||||||
|
|
||||||
# XBMC web server settings
|
settings['client_name'] = plexbmc.getSetting('deviceName')
|
||||||
xbmc.sleep(5000)
|
|
||||||
settings['webserver_enabled'] = (getGUI('webserver') == "true")
|
|
||||||
settings['port'] = int(getGUI('webserverport'))
|
|
||||||
settings['user'] = getGUI('webserverusername')
|
|
||||||
settings['passwd'] = getGUI('webserverpassword')
|
|
||||||
|
|
||||||
settings['uuid'] = plexbmc.getSetting('plex_client_Id')
|
# XBMC web server settings
|
||||||
|
settings['webserver_enabled'] = (getGUI('webserver') == "true")
|
||||||
|
settings['port'] = int(getGUI('webserverport'))
|
||||||
|
settings['user'] = getGUI('webserverusername')
|
||||||
|
settings['passwd'] = getGUI('webserverpassword')
|
||||||
|
|
||||||
settings['version'] = plexbmc.getAddonInfo('version')
|
settings['uuid'] = plexbmc.getSetting('plex_client_Id')
|
||||||
settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
|
|
||||||
settings['myplex_user'] = plexbmc.getSetting('username')
|
settings['version'] = plexbmc.getAddonInfo('version')
|
||||||
settings['serverList'] = []
|
settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
|
||||||
settings['myport'] = addon.getSetting('companionPort')
|
settings['myplex_user'] = plexbmc.getSetting('username')
|
||||||
|
settings['myport'] = addon.getSetting('companionPort')
|
||||||
|
return settings
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
# from xml.dom.minidom import parseString
|
|
||||||
from functions import *
|
from functions import *
|
||||||
from settings import settings
|
|
||||||
from httppersist import requests
|
|
||||||
|
|
||||||
from xbmc import Player
|
from xbmc import Player
|
||||||
# import xbmcgui
|
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from utils import window
|
from utils import window, logging
|
||||||
import PlexFunctions as pf
|
import PlexFunctions as pf
|
||||||
|
|
||||||
|
|
||||||
|
@logging
|
||||||
class SubscriptionManager:
|
class SubscriptionManager:
|
||||||
def __init__(self):
|
def __init__(self, jsonClass, RequestMgr):
|
||||||
|
self.serverlist = []
|
||||||
self.subscribers = {}
|
self.subscribers = {}
|
||||||
self.info = {}
|
self.info = {}
|
||||||
self.lastkey = ""
|
self.lastkey = ""
|
||||||
|
@ -27,8 +26,20 @@ class SubscriptionManager:
|
||||||
self.download = downloadutils.DownloadUtils()
|
self.download = downloadutils.DownloadUtils()
|
||||||
self.xbmcplayer = Player()
|
self.xbmcplayer = Player()
|
||||||
|
|
||||||
|
self.js = jsonClass
|
||||||
|
self.RequestMgr = RequestMgr
|
||||||
|
|
||||||
|
def getServerByHost(self, host):
|
||||||
|
if len(self.serverlist) == 1:
|
||||||
|
return self.serverlist[0]
|
||||||
|
for server in self.serverlist:
|
||||||
|
if (server.get('serverName') in host or
|
||||||
|
server.get('server') in host):
|
||||||
|
return server
|
||||||
|
return {}
|
||||||
|
|
||||||
def getVolume(self):
|
def getVolume(self):
|
||||||
self.volume, self.mute = getVolume()
|
self.volume, self.mute = self.js.getVolume()
|
||||||
|
|
||||||
def msg(self, players):
|
def msg(self, players):
|
||||||
msg = getXMLHeader()
|
msg = getXMLHeader()
|
||||||
|
@ -45,9 +56,9 @@ class SubscriptionManager:
|
||||||
else:
|
else:
|
||||||
self.mainlocation = "navigation"
|
self.mainlocation = "navigation"
|
||||||
msg += ' location="%s">' % self.mainlocation
|
msg += ' location="%s">' % self.mainlocation
|
||||||
msg += self.getTimelineXML(getAudioPlayerId(players), plex_audio())
|
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
|
||||||
msg += self.getTimelineXML(getPhotoPlayerId(players), plex_photo())
|
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
|
||||||
msg += self.getTimelineXML(getVideoPlayerId(players), plex_video())
|
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
||||||
msg += "\r\n</MediaContainer>"
|
msg += "\r\n</MediaContainer>"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
@ -88,7 +99,7 @@ class SubscriptionManager:
|
||||||
ret += ' location="%s"' % (self.mainlocation)
|
ret += ' location="%s"' % (self.mainlocation)
|
||||||
ret += ' key="%s"' % (self.lastkey)
|
ret += ' key="%s"' % (self.lastkey)
|
||||||
ret += ' ratingKey="%s"' % (self.lastratingkey)
|
ret += ' ratingKey="%s"' % (self.lastratingkey)
|
||||||
serv = getServerByHost(self.server)
|
serv = self.getServerByHost(self.server)
|
||||||
if info.get('playQueueID'):
|
if info.get('playQueueID'):
|
||||||
self.containerKey = "/playQueues/%s" % info.get('playQueueID')
|
self.containerKey = "/playQueues/%s" % info.get('playQueueID')
|
||||||
ret += ' playQueueID="%s"' % info.get('playQueueID')
|
ret += ' playQueueID="%s"' % info.get('playQueueID')
|
||||||
|
@ -124,7 +135,7 @@ class SubscriptionManager:
|
||||||
|
|
||||||
def notify(self, event = False):
|
def notify(self, event = False):
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
players = getPlayers()
|
players = self.js.getPlayers()
|
||||||
# fetch the message, subscribers or not, since the server
|
# fetch the message, subscribers or not, since the server
|
||||||
# will need the info anyway
|
# will need the info anyway
|
||||||
msg = self.msg(players)
|
msg = self.msg(players)
|
||||||
|
@ -152,32 +163,39 @@ class SubscriptionManager:
|
||||||
params['state'] = info['state']
|
params['state'] = info['state']
|
||||||
params['time'] = info['time']
|
params['time'] = info['time']
|
||||||
params['duration'] = info['duration']
|
params['duration'] = info['duration']
|
||||||
serv = getServerByHost(self.server)
|
serv = self.getServerByHost(self.server)
|
||||||
url = serv.get('protocol', 'http') + '://' \
|
url = serv.get('protocol', 'http') + '://' \
|
||||||
+ serv.get('server', 'localhost') + ':' \
|
+ serv.get('server', 'localhost') + ':' \
|
||||||
+ serv.get('port', '32400') + "/:/timeline"
|
+ serv.get('port', '32400') + "/:/timeline"
|
||||||
self.download.downloadUrl(url, type="GET", parameters=params)
|
self.download.downloadUrl(url, type="GET", parameters=params)
|
||||||
# requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
|
# requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
|
||||||
printDebug("params: %s" % params)
|
self.logMsg("params: %s" % params, 2)
|
||||||
printDebug("players: %s" % players)
|
self.logMsg("players: %s" % players, 2)
|
||||||
printDebug("sent server notification with state = %s" % params['state'])
|
self.logMsg("sent server notification with state = %s"
|
||||||
|
% params['state'], 2)
|
||||||
|
|
||||||
def controllable(self):
|
def controllable(self):
|
||||||
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
|
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
|
||||||
|
|
||||||
def addSubscriber(self, protocol, host, port, uuid, commandID):
|
def addSubscriber(self, protocol, host, port, uuid, commandID):
|
||||||
sub = Subscriber(protocol, host, port, uuid, commandID)
|
sub = Subscriber(protocol,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
uuid,
|
||||||
|
commandID,
|
||||||
|
self,
|
||||||
|
self.RequestMgr)
|
||||||
with threading.RLock():
|
with threading.RLock():
|
||||||
self.subscribers[sub.uuid] = sub
|
self.subscribers[sub.uuid] = sub
|
||||||
return sub
|
return sub
|
||||||
|
|
||||||
def removeSubscriber(self, uuid):
|
def removeSubscriber(self, uuid):
|
||||||
with threading.RLock():
|
with threading.RLock():
|
||||||
for sub in self.subscribers.values():
|
for sub in self.subscribers.values():
|
||||||
if sub.uuid == uuid or sub.host == uuid:
|
if sub.uuid == uuid or sub.host == uuid:
|
||||||
sub.cleanup()
|
sub.cleanup()
|
||||||
del self.subscribers[sub.uuid]
|
del self.subscribers[sub.uuid]
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
with threading.RLock():
|
with threading.RLock():
|
||||||
for sub in self.subscribers.values():
|
for sub in self.subscribers.values():
|
||||||
|
@ -189,8 +207,8 @@ class SubscriptionManager:
|
||||||
info = {}
|
info = {}
|
||||||
try:
|
try:
|
||||||
# get info from the player
|
# get info from the player
|
||||||
props = jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]})
|
props = self.js.jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]})
|
||||||
printDebug(jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}))
|
self.logMsg(self.js.jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}), 2)
|
||||||
info['time'] = timeToMillis(props['time'])
|
info['time'] = timeToMillis(props['time'])
|
||||||
info['duration'] = timeToMillis(props['totaltime'])
|
info['duration'] = timeToMillis(props['totaltime'])
|
||||||
info['state'] = ("paused", "playing")[int(props['speed'])]
|
info['state'] = ("paused", "playing")[int(props['speed'])]
|
||||||
|
@ -214,8 +232,11 @@ class SubscriptionManager:
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
@logging
|
||||||
class Subscriber:
|
class Subscriber:
|
||||||
def __init__(self, protocol, host, port, uuid, commandID):
|
def __init__(self, protocol, host, port, uuid, commandID,
|
||||||
|
subMgr, RequestMgr):
|
||||||
self.protocol = protocol or "http"
|
self.protocol = protocol or "http"
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port or 32400
|
self.port = port or 32400
|
||||||
|
@ -224,12 +245,18 @@ class Subscriber:
|
||||||
self.navlocationsent = False
|
self.navlocationsent = False
|
||||||
self.age = 0
|
self.age = 0
|
||||||
self.download = downloadutils.DownloadUtils()
|
self.download = downloadutils.DownloadUtils()
|
||||||
|
self.subMgr = subMgr
|
||||||
|
self.RequestMgr = RequestMgr
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.uuid == other.uuid
|
return self.uuid == other.uuid
|
||||||
|
|
||||||
def tostr(self):
|
def tostr(self):
|
||||||
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
|
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
requests.closeConnection(self.protocol, self.host, self.port)
|
self.RequestMgr.closeConnection(self.protocol, self.host, self.port)
|
||||||
|
|
||||||
def send_update(self, msg, is_nav):
|
def send_update(self, msg, is_nav):
|
||||||
self.age += 1
|
self.age += 1
|
||||||
if not is_nav:
|
if not is_nav:
|
||||||
|
@ -239,7 +266,8 @@ class Subscriber:
|
||||||
else:
|
else:
|
||||||
self.navlocationsent = True
|
self.navlocationsent = True
|
||||||
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
|
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
|
||||||
printDebug("sending xml to subscriber %s: %s" % (self.tostr(), msg))
|
self.logMsg("sending xml to subscriber %s: %s"
|
||||||
|
% (self.tostr(), msg), 2)
|
||||||
url = self.protocol + '://' + self.host + ':' + self.port \
|
url = self.protocol + '://' + self.host + ':' + self.port \
|
||||||
+ "/:/timeline"
|
+ "/:/timeline"
|
||||||
t = threading.Thread(target=self.threadedSend, args=(url, msg))
|
t = threading.Thread(target=self.threadedSend, args=(url, msg))
|
||||||
|
@ -255,6 +283,4 @@ class Subscriber:
|
||||||
postBody=msg,
|
postBody=msg,
|
||||||
type="POSTXML")
|
type="POSTXML")
|
||||||
if response in [False, None, 401]:
|
if response in [False, None, 401]:
|
||||||
subMgr.removeSubscriber(self.uuid)
|
self.subMgr.removeSubscriber(self.uuid)
|
||||||
|
|
||||||
subMgr = SubscriptionManager()
|
|
||||||
|
|
|
@ -322,8 +322,7 @@ class Service():
|
||||||
delay = int(utils.settings('startupDelay'))
|
delay = int(utils.settings('startupDelay'))
|
||||||
|
|
||||||
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
||||||
# Plex: add 10 seconds just for good measure
|
if delay and xbmc.Monitor().waitForAbort(delay):
|
||||||
if delay and xbmc.Monitor().waitForAbort(delay+10):
|
|
||||||
# Start the service
|
# Start the service
|
||||||
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue