PlexKodiConnect/resources/lib/plexnet/plexconnection.py
2018-09-30 13:16:17 +02:00

213 lines
7.4 KiB
Python

import random
import http
import plexapp
import callback
import util
class ConnectionSource(int):
def init(self, name):
self.name = name
return self
def __repr__(self):
return self.name
class PlexConnection(object):
# Constants
STATE_UNKNOWN = "unknown"
STATE_UNREACHABLE = "unreachable"
STATE_REACHABLE = "reachable"
STATE_UNAUTHORIZED = "unauthorized"
STATE_INSECURE = "insecure_untested"
SOURCE_MANUAL = ConnectionSource(1).init('MANUAL')
SOURCE_DISCOVERED = ConnectionSource(2).init('DISCOVERED')
SOURCE_MANUAL_AND_DISCOVERED = ConnectionSource(3).init('MANUAL, DISCOVERED')
SOURCE_MYPLEX = ConnectionSource(4).init('MYPLEX')
SOURCE_MANUAL_AND_MYPLEX = ConnectionSource(5).init('MANUAL, MYPLEX')
SOURCE_DISCOVERED_AND_MYPLEX = ConnectionSource(6).init('DISCOVERED, MYPLEX')
SOURCE_ALL = ConnectionSource(7).init('ALL')
SCORE_REACHABLE = 4
SCORE_LOCAL = 2
SCORE_SECURE = 1
SOURCE_BY_VAL = {
1: SOURCE_MANUAL,
2: SOURCE_DISCOVERED,
3: SOURCE_MANUAL_AND_DISCOVERED,
4: SOURCE_MYPLEX,
5: SOURCE_MANUAL_AND_MYPLEX,
6: SOURCE_DISCOVERED_AND_MYPLEX,
7: SOURCE_ALL
}
def __init__(self, source, address, isLocal, token, isFallback=False):
self.state = self.STATE_UNKNOWN
self.sources = source
self.address = address
self.isLocal = isLocal
self.isSecure = address[:5] == 'https'
self.isFallback = isFallback
self.token = token
self.refreshed = True
self.score = 0
self.request = None
self.lastTestedAt = 0
self.hasPendingRequest = False
self.getScore(True)
def __eq__(self, other):
if not other:
return False
if self.__class__ != other.__class__:
return False
return self.address == other.address
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return "Connection: {0} local: {1} token: {2} sources: {3} state: {4}".format(
self.address,
self.isLocal,
util.hideToken(self.token),
repr(self.sources),
self.state
)
def __repr__(self):
return self.__str__()
def merge(self, other):
# plex.tv trumps all, otherwise assume newer is better
# ROKU: if (other.sources and self.SOURCE_MYPLEX) <> 0 then
if other.sources == self.SOURCE_MYPLEX:
self.token = other.token
else:
self.token = self.token or other.token
self.address = other.address
self.sources = self.SOURCE_BY_VAL[self.sources | other.sources]
self.isLocal = self.isLocal | other.isLocal
self.isSecure = other.isSecure
self.isFallback = self.isFallback or other.isFallback
self.refreshed = True
self.getScore(True)
def testReachability(self, server, allowFallback=False):
# Check if we will allow the connection test. If this is a fallback connection,
# then we will defer it until we "allowFallback" (test insecure connections
# after secure tests have completed and failed). Insecure connections will be
# tested if the policy "always" allows them, or if set to "same_network" and
# the current connection is local and server has (publicAddressMatches=1).
allowConnectionTest = not self.isFallback
if not allowConnectionTest:
insecurePolicy = plexapp.INTERFACE.getPreference("allow_insecure")
if insecurePolicy == "always" or (insecurePolicy == "same_network" and server.sameNetwork and self.isLocal):
allowConnectionTest = allowFallback
server.hasFallback = not allowConnectionTest
util.LOG(
'{0} for {1}'.format(
allowConnectionTest and "Continuing with insecure connection testing" or "Insecure connection testing is deferred", server
)
)
else:
util.LOG("Insecure connections not allowed. Ignore insecure connection test for {0}".format(server))
self.state = self.STATE_INSECURE
callable = callback.Callable(server.onReachabilityResult, [self], random.randint(0, 256))
callable.deferCall()
return True
if allowConnectionTest:
if not self.isSecure and (
not allowFallback and
server.hasSecureConnections() or
server.activeConnection and
server.activeConnection.state != self.STATE_REACHABLE and
server.activeConnection.isSecure
):
util.DEBUG_LOG("Invalid insecure connection test in progress")
self.request = http.HttpRequest(self.buildUrl(server, "/"))
context = self.request.createRequestContext("reachability", callback.Callable(self.onReachabilityResponse))
context.server = server
util.addPlexHeaders(self.request, server.getToken())
self.hasPendingRequest = plexapp.APP.startRequest(self.request, context)
return True
return False
def cancelReachability(self):
if self.request:
self.request.ignoreResponse = True
self.request.cancel()
def onReachabilityResponse(self, request, response, context):
self.hasPendingRequest = False
# It's possible we may have a result pending before we were able
# to cancel it, so we'll just ignore it.
# if request.ignoreResponse:
# return
if response.isSuccess():
data = response.getBodyXml()
if data is not None and context.server.collectDataFromRoot(data):
self.state = self.STATE_REACHABLE
else:
# This is unexpected, but treat it as unreachable
util.ERROR_LOG("Unable to parse root response from {0}".format(context.server))
self.state = self.STATE_UNREACHABLE
elif response.getStatus() == 401:
self.state = self.STATE_UNAUTHORIZED
else:
self.state = self.STATE_UNREACHABLE
self.getScore(True)
context.server.onReachabilityResult(self)
def buildUrl(self, server, path, includeToken=False):
if '://' in path:
url = path
else:
url = self.address + path
if includeToken:
# If we have a token, use it. Otherwise see if any other connections
# for this server have one. That will let us use a plex.tv token for
# something like a manually configured connection.
token = self.token or server.getToken()
if token:
url = http.addUrlParam(url, "X-Plex-Token=" + token)
return url
def simpleBuildUrl(self, server, path):
token = (self.token or server.getToken())
param = ''
if token:
param = '&X-Plex-Token={0}'.format(token)
return '{0}{1}{2}'.format(self.address, path, param)
def getScore(self, recalc=False):
if recalc:
self.score = 0
if self.state == self.STATE_REACHABLE:
self.score += self.SCORE_REACHABLE
if self.isSecure:
self.score += self.SCORE_SECURE
if self.isLocal:
self.score += self.SCORE_LOCAL
return self.score