Connection manager, part 1

This commit is contained in:
tomkat83 2017-07-01 12:32:23 +02:00
parent b7243e291e
commit 32ace844aa
13 changed files with 1677 additions and 1609 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,11 +3,12 @@ from logging import getLogger
from urllib import urlencode from urllib import urlencode
from ast import literal_eval from ast import literal_eval
from urlparse import urlparse, parse_qsl from urlparse import urlparse, parse_qsl
from urllib import quote_plus
import re import re
from copy import deepcopy from copy import deepcopy
import downloadutils import downloadutils
from utils import settings from utils import settings, tryEncode
from variables import PLEX_TO_KODI_TIMEFACTOR from variables import PLEX_TO_KODI_TIMEFACTOR
############################################################################### ###############################################################################
@ -434,7 +435,7 @@ def delete_item_from_pms(plexid):
return False return False
def get_PMS_settings(url, token): def get_pms_settings(url, token):
""" """
Retrieve the PMS' settings via <url>/:/ Retrieve the PMS' settings via <url>/:/
@ -445,3 +446,42 @@ def get_PMS_settings(url, token):
authenticate=False, authenticate=False,
verifySSL=False, verifySSL=False,
headerOptions={'X-Plex-Token': token} if token else None) headerOptions={'X-Plex-Token': token} if token else None)
def get_transcode_image_path(self, key, AuthToken, path, width, height):
"""
Transcode Image support
parameters:
key
AuthToken
path - source path of current XML: path[srcXML]
width
height
result:
final path to image file
"""
# external address - can we get a transcoding request for external images?
if key.startswith('http://') or key.startswith('https://'):
path = key
elif key.startswith('/'): # internal full path.
path = 'http://127.0.0.1:32400' + key
else: # internal path, add-on
path = 'http://127.0.0.1:32400' + path + '/' + key
path = tryEncode(path)
# This is bogus (note the extra path component) but ATV is stupid when it
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
# lenient...
transcodePath = '/photo/:/transcode/' + \
str(width) + 'x' + str(height) + '/' + quote_plus(path)
args = dict()
args['width'] = width
args['height'] = height
args['url'] = path
if not AuthToken == '':
args['X-Plex-Token'] = AuthToken
return transcodePath + '?' + urlencode(args)

View file

@ -0,0 +1 @@
# Dummy file to make this directory a package.

View file

@ -0,0 +1,449 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from xbmc import sleep, executebuiltin
from utils import window, settings, dialog, language as lang, tryEncode
from clientinfo import getXArgsDeviceInfo
from downloadutils import DownloadUtils
import variables as v
import state
###############################################################################
log = getLogger("PLEX."+__name__)
###############################################################################
def my_plex_sign_in(username, password, options):
"""
MyPlex Sign In
parameters:
username - Plex forum name, MyPlex login, or email address
password
options - dict() of PlexConnect-options as received from aTV -
necessary: PlexConnectUDID
result:
username
authtoken - token for subsequent communication with MyPlex
"""
# create POST request
xml = DownloadUtils().downloadUrl(
'https://plex.tv/users/sign_in.xml',
action_type='POST',
headerOptions=getXArgsDeviceInfo(options),
authenticate=False,
auth=(username, password))
try:
xml.attrib
except AttributeError:
log.error('Could not sign in to plex.tv')
return ('', '')
el_username = xml.find('username')
el_authtoken = xml.find('authentication-token')
if el_username is None or \
el_authtoken is None:
username = ''
authtoken = ''
else:
username = el_username.text
authtoken = el_authtoken.text
return (username, authtoken)
def check_plex_tv_pin(identifier):
"""
Checks with plex.tv whether user entered the correct PIN on plex.tv/pin
Returns False if not yet done so, or the XML response file as etree
"""
# Try to get a temporary token
xml = DownloadUtils().downloadUrl(
'https://plex.tv/pins/%s.xml' % identifier,
authenticate=False)
try:
temp_token = xml.find('auth_token').text
except:
log.error("Could not find token in plex.tv answer")
return False
if not temp_token:
return False
# Use temp token to get the final plex credentials
xml = DownloadUtils().downloadUrl('https://plex.tv/users/account',
authenticate=False,
parameters={'X-Plex-Token': temp_token})
return xml
def get_plex_pin():
"""
For plex.tv sign-in: returns 4-digit code and identifier as 2 str
"""
code = None
identifier = None
# Download
xml = DownloadUtils().downloadUrl('https://plex.tv/pins.xml',
authenticate=False,
action_type="POST")
try:
xml.attrib
except:
log.error("Error, no PIN from plex.tv provided")
return None, None
code = xml.find('code').text
identifier = xml.find('id').text
log.info('Successfully retrieved code and id from plex.tv')
return code, identifier
def get_plex_login_password():
"""
Signs in to plex.tv.
plexLogin, authtoken = get_plex_login_password()
Input: nothing
Output:
plexLogin plex.tv username
authtoken token for plex.tv
Also writes 'plexLogin' and 'token_plex.tv' to Kodi settings file
If not logged in, empty strings are returned for both.
"""
retrievedPlexLogin = ''
plexLogin = 'dummy'
authtoken = ''
while retrievedPlexLogin == '' and plexLogin != '':
# Enter plex.tv username. Or nothing to cancel.
plexLogin = dialog('input',
lang(29999) + lang(39300),
type='{alphanum}')
if plexLogin != "":
# Enter password for plex.tv user
plexPassword = dialog('input',
lang(39301) + plexLogin,
type='{alphanum}',
option='{hide_input}')
retrievedPlexLogin, authtoken = my_plex_sign_in(
plexLogin,
plexPassword,
{'X-Plex-Client-Identifier': window('plex_client_Id')})
log.debug("plex.tv username and token: %s, %s"
% (plexLogin, authtoken))
if plexLogin == '':
# Could not sign in user
dialog('ok', lang(29999), lang(39302) + plexLogin)
# Write to Kodi settings file
settings('plexLogin', value=retrievedPlexLogin)
settings('plexToken', value=authtoken)
return (retrievedPlexLogin, authtoken)
def plex_tv_sign_in_with_pin():
"""
Prompts user to sign in by visiting https://plex.tv/pin
Writes to Kodi settings file. Also returns:
{
'plexhome': 'true' if Plex Home, 'false' otherwise
'username':
'avatar': URL to user avator
'token':
'plexid': Plex user ID
'homesize': Number of Plex home users (defaults to '1')
}
Returns False if authentication did not work.
"""
code, identifier = get_plex_pin()
if not code:
# Problems trying to contact plex.tv. Try again later
dialog('ok', lang(29999), lang(39303))
return False
# Go to https://plex.tv/pin and enter the code:
# Or press No to cancel the sign in.
answer = dialog('yesno',
lang(29999),
lang(39304) + "\n\n",
code + "\n\n",
lang(39311))
if not answer:
return False
count = 0
# Wait for approx 30 seconds (since the PIN is not visible anymore :-))
while count < 30:
xml = check_plex_tv_pin(identifier)
if xml is not False:
break
# Wait for 1 seconds
sleep(1000)
count += 1
if xml is False:
# Could not sign in to plex.tv Try again later
dialog('ok', lang(29999), lang(39305))
return False
# Parse xml
userid = xml.attrib.get('id')
home = xml.get('home', '0')
if home == '1':
home = 'true'
else:
home = 'false'
username = xml.get('username', '')
avatar = xml.get('thumb', '')
token = xml.findtext('authentication-token')
homeSize = xml.get('homeSize', '1')
result = {
'plexhome': home,
'username': username,
'avatar': avatar,
'token': token,
'plexid': userid,
'homesize': homeSize
}
settings('plexLogin', username)
settings('plexToken', token)
settings('plexhome', home)
settings('plexid', userid)
settings('plexAvatar', avatar)
settings('plexHomeSize', homeSize)
# Let Kodi log into plex.tv on startup from now on
settings('myplexlogin', 'true')
settings('plex_status', value=lang(39227))
return result
def list_plex_home_users(token):
"""
Returns a list for myPlex home users for the current plex.tv account.
Input:
token for plex.tv
Output:
List of users, where one entry is of the form:
"id": userId,
"admin": '1'/'0',
"guest": '1'/'0',
"restricted": '1'/'0',
"protected": '1'/'0',
"email": email,
"title": title,
"username": username,
"thumb": thumb_url
}
If any value is missing, None is returned instead (or "" from plex.tv)
If an error is encountered, False is returned
"""
xml = DownloadUtils.downloadUrl('https://plex.tv/api/home/users/',
authenticate=False,
headerOptions={'X-Plex-Token': token})
try:
xml.attrib
except:
log.error('Download of Plex home users failed.')
return False
users = []
for user in xml:
users.append(user.attrib)
return users
def switch_home_user(userId, pin, token, machineIdentifier):
"""
Retrieves Plex home token for a Plex home user.
Returns False if unsuccessful
Input:
userId id of the Plex home user
pin PIN of the Plex home user, if protected
token token for plex.tv
Output:
{
'username'
'usertoken' Might be empty strings if no token found
for the machineIdentifier that was chosen
}
settings('userid') and settings('username') with new plex token
"""
log.info('Switching to user %s' % userId)
url = 'https://plex.tv/api/home/users/' + userId + '/switch'
if pin:
url += '?pin=' + pin
answer = DownloadUtils.downloadUrl(
url,
authenticate=False,
action_type="POST",
headerOptions={'X-Plex-Token': token})
try:
answer.attrib
except:
log.error('Error: plex.tv switch HomeUser change failed')
return False
username = answer.attrib.get('title', '')
token = answer.attrib.get('authenticationToken', '')
# Write to settings file
settings('username', username)
settings('accessToken', token)
settings('userid', answer.attrib.get('id', ''))
settings('plex_restricteduser',
'true' if answer.attrib.get('restricted', '0') == '1'
else 'false')
state.RESTRICTED_USER = True if \
answer.attrib.get('restricted', '0') == '1' else False
# Get final token to the PMS we've chosen
url = 'https://plex.tv/api/resources?includeHttps=1'
xml = DownloadUtils.downloadUrl(url,
authenticate=False,
headerOptions={'X-Plex-Token': token})
try:
xml.attrib
except:
log.error('Answer from plex.tv not as excepted')
# Set to empty iterable list for loop
xml = []
found = 0
log.debug('Our machineIdentifier is %s' % machineIdentifier)
for device in xml:
identifier = device.attrib.get('clientIdentifier')
log.debug('Found a Plex machineIdentifier: %s' % identifier)
if (identifier in machineIdentifier or
machineIdentifier in identifier):
found += 1
token = device.attrib.get('accessToken')
result = {
'username': username,
}
if found == 0:
log.info('No tokens found for your server! Using empty string')
result['usertoken'] = ''
else:
result['usertoken'] = token
log.info('Plex.tv switch HomeUser change successfull for user %s'
% username)
return result
def ChoosePlexHomeUser(plexToken):
"""
Let's user choose from a list of Plex home users. Will switch to that
user accordingly.
Returns a dict:
{
'username': Unicode
'userid': '' Plex ID of the user
'token': '' User's token
'protected': True if PIN is needed, else False
}
Will return False if something went wrong (wrong PIN, no connection)
"""
# Get list of Plex home users
users = list_plex_home_users(plexToken)
if not users:
log.error("User download failed.")
return False
userlist = []
userlistCoded = []
for user in users:
username = user['title']
userlist.append(username)
# To take care of non-ASCII usernames
userlistCoded.append(tryEncode(username))
usernumber = len(userlist)
username = ''
usertoken = ''
trials = 0
while trials < 3:
if usernumber > 1:
# Select user
user_select = dialog('select',
lang(29999) + lang(39306),
userlistCoded)
if user_select == -1:
log.info("No user selected.")
settings('username', value='')
executebuiltin('Addon.OpenSettings(%s)'
% v.ADDON_ID)
return False
# Only 1 user received, choose that one
else:
user_select = 0
selected_user = userlist[user_select]
log.info("Selected user: %s" % selected_user)
user = users[user_select]
# Ask for PIN, if protected:
pin = None
if user['protected'] == '1':
log.debug('Asking for users PIN')
pin = dialog('input',
lang(39307) + selected_user,
'',
type='{numeric}',
option='{hide_input}')
# User chose to cancel
# Plex bug: don't call url for protected user with empty PIN
if not pin:
trials += 1
continue
# Switch to this Plex Home user, if applicable
result = switch_home_user(
user['id'],
pin,
plexToken,
settings('plex_machineIdentifier'))
if result:
# Successfully retrieved username: break out of while loop
username = result['username']
usertoken = result['usertoken']
break
# Couldn't get user auth
else:
trials += 1
# Could not login user, please try again
if not dialog('yesno',
lang(29999),
lang(39308) + selected_user,
lang(39309)):
# User chose to cancel
break
if not username:
log.error('Failed signing in a user to plex.tv')
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
return False
return {
'username': username,
'userid': user['id'],
'protected': True if user['protected'] == '1' else False,
'token': usertoken
}
def get_user_artwork_url(username):
"""
Returns the URL for the user's Avatar. Or False if something went
wrong.
"""
plexToken = settings('plexToken')
users = list_plex_home_users(plexToken)
url = ''
# If an error is encountered, set to False
if not users:
log.info("Couldnt get user from plex.tv. No URL for user avatar")
return False
for user in users:
if username in user['title']:
url = user['thumb']
log.debug("Avatar url for user %s is: %s" % (username, url))
return url

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################################## ###############################################################################
import logging from logging import getLogger
import xbmc import xbmc
import xbmcgui import xbmcgui
import connect.connectionmanager as connectionmanager
from utils import language as lang from utils import language as lang
################################################################################################## ###############################################################################
log = logging.getLogger("EMBY."+__name__) log = getLogger("PLEX."+__name__)
CONN_STATE = connectionmanager.ConnectionState
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -30,7 +28,7 @@ BUSY = 204
EMBY_CONNECT = 205 EMBY_CONNECT = 205
MANUAL_SERVER = 206 MANUAL_SERVER = 206
################################################################################################## ###############################################################################
class ServerConnect(xbmcgui.WindowXMLDialog): class ServerConnect(xbmcgui.WindowXMLDialog):
@ -43,11 +41,6 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
_connect_login = False _connect_login = False
_manual_server = False _manual_server = False
def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
def set_args(self, **kwargs): def set_args(self, **kwargs):
# connect_manager, username, user_image, servers, emby_connect # connect_manager, username, user_image, servers, emby_connect
for key, value in kwargs.iteritems(): for key, value in kwargs.iteritems():
@ -65,7 +58,6 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
def is_manual_server(self): def is_manual_server(self):
return self._manual_server return self._manual_server
def onInit(self): def onInit(self):
self.message = self.getControl(MESSAGE) self.message = self.getControl(MESSAGE)
@ -77,13 +69,13 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
server_type = "wifi" if server.get('ExchangeToken') else "network" server_type = "wifi" if server.get('ExchangeToken') else "network"
self.list_.addItem(self._add_listitem(server['Name'], server['Id'], server_type)) self.list_.addItem(self._add_listitem(server['Name'], server['Id'], server_type))
self.getControl(USER_NAME).setLabel("%s %s" % (lang(33000), self.username.decode('utf-8'))) self.getControl(USER_NAME).setLabel("%s %s" % ('Switch plex.tv user', self.username.decode('utf-8')))
if self.user_image is not None: if self.user_image is not None:
self.getControl(USER_IMAGE).setImage(self.user_image) self.getControl(USER_IMAGE).setImage(self.user_image)
if not self.emby_connect: # Change connect user if not self.emby_connect: # Change connect user
self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+lang(30618)+"[/B][/UPPERCASE]") self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+'plex.tv user change'+"[/B][/UPPERCASE]")
if self.servers: if self.servers:
self.setFocus(self.list_) self.setFocus(self.list_)

View file

@ -142,13 +142,16 @@ class DownloadUtils():
def downloadUrl(self, url, action_type="GET", postBody=None, def downloadUrl(self, url, action_type="GET", postBody=None,
parameters=None, authenticate=True, headerOptions=None, parameters=None, authenticate=True, headerOptions=None,
verifySSL=True, timeout=None, return_response=False): verifySSL=True, timeout=None, return_response=False,
auth=None):
""" """
Override SSL check with verifySSL=False Override SSL check with verifySSL=False
If authenticate=True, existing request session will be used/started If authenticate=True, existing request session will be used/started
Otherwise, 'empty' request will be made Otherwise, 'empty' request will be made
auth=None or auth=('user', 'password')
Returns: Returns:
None If an error occured None If an error occured
True If connection worked but no body was received True If connection worked but no body was received
@ -190,6 +193,8 @@ class DownloadUtils():
kwargs['params'] = parameters kwargs['params'] = parameters
if timeout is not None: if timeout is not None:
kwargs['timeout'] = timeout kwargs['timeout'] = timeout
if auth is not None:
kwargs['auth'] = auth
# ACTUAL DOWNLOAD HAPPENING HERE # ACTUAL DOWNLOAD HAPPENING HERE
try: try:

View file

@ -37,9 +37,9 @@ def chooseServer():
""" """
log.info("Choosing PMS server requested, starting") log.info("Choosing PMS server requested, starting")
import initialsetup from connectmanager import ConnectManager
setup = initialsetup.InitialSetup() connectmanager = ConnectManager()
server = setup.PickPMS(showDialog=True) server = connectmanager.pick_pms(show_dialog=True)
if server is None: if server is None:
log.error('We did not connect to a new PMS, aborting') log.error('We did not connect to a new PMS, aborting')
plex_command('SUSPEND_USER_CLIENT', 'False') plex_command('SUSPEND_USER_CLIENT', 'False')
@ -47,7 +47,7 @@ def chooseServer():
return return
log.info("User chose server %s" % server['name']) log.info("User chose server %s" % server['name'])
setup.WritePMStoSettings(server) connectmanager.write_pms_to_settings(server)
if not __LogOut(): if not __LogOut():
return return
@ -85,8 +85,8 @@ def togglePlexTV():
plex_command('PLEX_USERNAME', '') plex_command('PLEX_USERNAME', '')
else: else:
log.info('Login to plex.tv') log.info('Login to plex.tv')
import initialsetup from connectmanager import ConnectManager
initialsetup.InitialSetup().PlexTVSignIn() ConnectManager().plex_tv_signin()
dialog('notification', dialog('notification',
lang(29999), lang(29999),
lang(39221), lang(39221),

View file

@ -1,512 +1,136 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from logging import getLogger
from xbmc import executebuiltin
import logging from utils import settings, language as lang, advancedsettings_xml, dialog
import xbmc from connectmanager import ConnectManager
import xbmcgui
from utils import settings, window, language as lang, tryEncode, \
advancedsettings_xml
import downloadutils
from userclient import UserClient
from PlexAPI import PlexAPI
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
import state import state
from migration import check_migration from migration import check_migration
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__)
log = logging.getLogger("PLEX."+__name__)
############################################################################### ###############################################################################
class InitialSetup(): def setup(self):
"""
Initial setup. Run once upon startup.
def __init__(self): Check server, user, direct paths, music, direct stream if not direct
log.debug('Entering initialsetup class') path.
self.doUtils = downloadutils.DownloadUtils().downloadUrl """
self.plx = PlexAPI() log.info("Initial setup called")
self.dialog = xbmcgui.Dialog() connectmanager = ConnectManager()
self.server = UserClient().getServer() # Get current Kodi video cache setting
self.serverid = settings('plex_machineIdentifier') cache, _ = advancedsettings_xml(['cache', 'memorysize'])
# Get Plex credentials from settings file, if they exist if cache is None:
plexdict = self.plx.GetPlexLoginFromSettings() # Kodi default cache
self.myplexlogin = plexdict['myplexlogin'] == 'true' cache = '20971520'
self.plexLogin = plexdict['plexLogin'] else:
self.plexToken = plexdict['plexToken'] cache = str(cache.text)
self.plexid = plexdict['plexid'] log.info('Current Kodi video memory cache in bytes: %s' % cache)
# Token for the PMS, not plex.tv settings('kodi_video_cache', value=cache)
self.pms_token = settings('accessToken')
if self.plexToken:
log.debug('Found a plex.tv token in the settings')
def PlexTVSignIn(self): # Do we need to migrate stuff?
""" check_migration()
Signs (freshly) in to plex.tv (will be saved to file settings)
Returns True if successful, or False if not # Optionally sign into plex.tv. Will not be called on very first run
""" # as plexToken will be ''
result = self.plx.PlexTvSignInWithPin() settings('plex_status', value=lang(39226))
if result: if connectmanager.plexToken and connectmanager.myplexlogin:
self.plexLogin = result['username'] connectmanager.check_plex_tv_signin()
self.plexToken = result['token']
self.plexid = result['plexid']
return True
return False
def CheckPlexTVSignIn(self): # If a Plex server IP has already been set
""" # return only if the right machine identifier is found
Checks existing connection to plex.tv. If not, triggers sign in if connectmanager.server:
log.info("PMS is already set: %s. Checking now..." % self.server)
Returns True if signed in, False otherwise if connectmanager.check_pms():
""" log.info("Using PMS %s with machineIdentifier %s"
answer = True % (self.server, self.serverid))
chk = self.plx.CheckConnection('plex.tv', token=self.plexToken) connectmanager.write_pms_settings(self.server, self.pms_token)
if chk in (401, 403):
# HTTP Error: unauthorized. Token is no longer valid
log.info('plex.tv connection returned HTTP %s' % str(chk))
# Delete token in the settings
settings('plexToken', value='')
settings('plexLogin', value='')
# Could not login, please try again
self.dialog.ok(lang(29999), lang(39009))
answer = self.PlexTVSignIn()
elif chk is False or chk >= 400:
# Problems connecting to plex.tv. Network or internet issue?
log.info('Problems connecting to plex.tv; connection returned '
'HTTP %s' % str(chk))
self.dialog.ok(lang(29999), lang(39010))
answer = False
else:
log.info('plex.tv connection with token successful')
settings('plex_status', value=lang(39227))
# Refresh the info from Plex.tv
xml = self.doUtils('https://plex.tv/users/account',
authenticate=False,
headerOptions={'X-Plex-Token': self.plexToken})
try:
self.plexLogin = xml.attrib['title']
except (AttributeError, KeyError):
log.error('Failed to update Plex info from plex.tv')
else:
settings('plexLogin', value=self.plexLogin)
home = 'true' if xml.attrib.get('home') == '1' else 'false'
settings('plexhome', value=home)
settings('plexAvatar', value=xml.attrib.get('thumb'))
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
log.info('Updated Plex info from plex.tv')
return answer
def CheckPMS(self):
"""
Check the PMS that was set in file settings.
Will return False if we need to reconnect, because:
PMS could not be reached (no matter the authorization)
machineIdentifier did not match
Will also set the PMS machineIdentifier in the file settings if it was
not set before
"""
answer = True
chk = self.plx.CheckConnection(self.server, verifySSL=False)
if chk is False:
log.warn('Could not reach PMS %s' % self.server)
answer = False
if answer is True and not self.serverid:
log.info('No PMS machineIdentifier found for %s. Trying to '
'get the PMS unique ID' % self.server)
self.serverid = GetMachineIdentifier(self.server)
if self.serverid is None:
log.warn('Could not retrieve machineIdentifier')
answer = False
else:
settings('plex_machineIdentifier', value=self.serverid)
elif answer is True:
tempServerid = GetMachineIdentifier(self.server)
if tempServerid != self.serverid:
log.warn('The current PMS %s was expected to have a '
'unique machineIdentifier of %s. But we got '
'%s. Pick a new server to be sure'
% (self.server, self.serverid, tempServerid))
answer = False
return answer
def _getServerList(self):
"""
Returns a list of servers from GDM and possibly plex.tv
"""
self.plx.discoverPMS(xbmc.getIPAddress(),
plexToken=self.plexToken)
serverlist = self.plx.returnServerList(self.plx.g_PMS)
log.debug('PMS serverlist: %s' % serverlist)
return serverlist
def _checkServerCon(self, server):
"""
Checks for server's connectivity. Returns CheckConnection result
"""
# Re-direct via plex if remote - will lead to the correct SSL
# certificate
if server['local'] == '1':
url = '%s://%s:%s' \
% (server['scheme'], server['ip'], server['port'])
# Deactive SSL verification if the server is local!
verifySSL = False
else:
url = server['baseURL']
verifySSL = True
chk = self.plx.CheckConnection(url,
token=server['accesstoken'],
verifySSL=verifySSL)
return chk
def PickPMS(self, showDialog=False):
"""
Searches for PMS in local Lan and optionally (if self.plexToken set)
also on plex.tv
showDialog=True: let the user pick one
showDialog=False: automatically pick PMS based on machineIdentifier
Returns the picked PMS' detail as a dict:
{
'name': friendlyName, the Plex server's name
'address': ip:port
'ip': ip, without http/https
'port': port
'scheme': 'http'/'https', nice for checking for secure connections
'local': '1'/'0', Is the server a local server?
'owned': '1'/'0', Is the server owned by the user?
'machineIdentifier': id, Plex server machine identifier
'accesstoken': token Access token to this server
'baseURL': baseURL scheme://ip:port
'ownername' Plex username of PMS owner
}
or None if unsuccessful
"""
server = None
# If no server is set, let user choose one
if not self.server or not self.serverid:
showDialog = True
if showDialog is True:
server = self._UserPickPMS()
else:
server = self._AutoPickPMS()
if server is not None:
self._write_PMS_settings(server['baseURL'], server['accesstoken'])
return server
def _write_PMS_settings(self, url, token):
"""
Sets certain settings for server by asking for the PMS' settings
Call with url: scheme://ip:port
"""
xml = get_PMS_settings(url, token)
try:
xml.attrib
except AttributeError:
log.error('Could not get PMS settings for %s' % url)
return
for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion':
settings('plex_allows_mediaDeletion',
value=entry.attrib.get('value', 'true'))
window('plex_allows_mediaDeletion',
value=entry.attrib.get('value', 'true'))
def _AutoPickPMS(self):
"""
Will try to pick PMS based on machineIdentifier saved in file settings
but only once
Returns server or None if unsuccessful
"""
httpsUpdated = False
checkedPlexTV = False
server = None
while True:
if httpsUpdated is False:
serverlist = self._getServerList()
for item in serverlist:
if item.get('machineIdentifier') == self.serverid:
server = item
if server is None:
name = settings('plex_servername')
log.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is '
'offline' % (self.serverid, name))
return
chk = self._checkServerCon(server)
if chk == 504 and httpsUpdated is False:
# Not able to use HTTP, try HTTPs for now
server['scheme'] = 'https'
httpsUpdated = True
continue
if chk == 401:
log.warn('Not yet authorized for Plex server %s'
% server['name'])
if self.CheckPlexTVSignIn() is True:
if checkedPlexTV is False:
# Try again
checkedPlexTV = True
httpsUpdated = False
continue
else:
log.warn('Not authorized even though we are signed '
' in to plex.tv correctly')
self.dialog.ok(lang(29999), '%s %s'
% (lang(39214),
tryEncode(server['name'])))
return
else:
return
# Problems connecting
elif chk >= 400 or chk is False:
log.warn('Problems connecting to server %s. chk is %s'
% (server['name'], chk))
return
log.info('We found a server to automatically connect to: %s'
% server['name'])
return server
def _UserPickPMS(self):
"""
Lets user pick his/her PMS from a list
Returns server or None if unsuccessful
"""
httpsUpdated = False
while True:
if httpsUpdated is False:
serverlist = self._getServerList()
# Exit if no servers found
if len(serverlist) == 0:
log.warn('No plex media servers found!')
self.dialog.ok(lang(29999), lang(39011))
return
# Get a nicer list
dialoglist = []
for server in serverlist:
if server['local'] == '1':
# server is in the same network as client.
# Add"local"
msg = lang(39022)
else:
# Add 'remote'
msg = lang(39054)
if server.get('ownername'):
# Display username if its not our PMS
dialoglist.append('%s (%s, %s)'
% (server['name'],
server['ownername'],
msg))
else:
dialoglist.append('%s (%s)'
% (server['name'], msg))
# Let user pick server from a list
resp = self.dialog.select(lang(39012), dialoglist)
if resp == -1:
# User cancelled
return
server = serverlist[resp]
chk = self._checkServerCon(server)
if chk == 504 and httpsUpdated is False:
# Not able to use HTTP, try HTTPs for now
serverlist[resp]['scheme'] = 'https'
httpsUpdated = True
continue
httpsUpdated = False
if chk == 401:
log.warn('Not yet authorized for Plex server %s'
% server['name'])
# Please sign in to plex.tv
self.dialog.ok(lang(29999),
lang(39013) + server['name'],
lang(39014))
if self.PlexTVSignIn() is False:
# Exit while loop if user cancels
return
# Problems connecting
elif chk >= 400 or chk is False:
# Problems connecting to server. Pick another server?
answ = self.dialog.yesno(lang(29999),
lang(39015))
# Exit while loop if user chooses No
if not answ:
return
# Otherwise: connection worked!
else:
return server
def WritePMStoSettings(self, server):
"""
Saves server to file settings. server is a dict of the form:
{
'name': friendlyName, the Plex server's name
'address': ip:port
'ip': ip, without http/https
'port': port
'scheme': 'http'/'https', nice for checking for secure connections
'local': '1'/'0', Is the server a local server?
'owned': '1'/'0', Is the server owned by the user?
'machineIdentifier': id, Plex server machine identifier
'accesstoken': token Access token to this server
'baseURL': baseURL scheme://ip:port
'ownername' Plex username of PMS owner
}
"""
settings('plex_machineIdentifier', server['machineIdentifier'])
settings('plex_servername', server['name'])
settings('plex_serverowned',
'true' if server['owned'] == '1'
else 'false')
# Careful to distinguish local from remote PMS
if server['local'] == '1':
scheme = server['scheme']
settings('ipaddress', server['ip'])
settings('port', server['port'])
log.debug("Setting SSL verify to false, because server is "
"local")
settings('sslverify', 'false')
else:
baseURL = server['baseURL'].split(':')
scheme = baseURL[0]
settings('ipaddress', baseURL[1].replace('//', ''))
settings('port', baseURL[2])
log.debug("Setting SSL verify to true, because server is not "
"local")
settings('sslverify', 'true')
if scheme == 'https':
settings('https', 'true')
else:
settings('https', 'false')
# And finally do some logging
log.debug("Writing to Kodi user settings file")
log.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
% (server['machineIdentifier'], server['ip'],
server['port'], server['scheme']))
def setup(self):
"""
Initial setup. Run once upon startup.
Check server, user, direct paths, music, direct stream if not direct
path.
"""
log.info("Initial setup called.")
dialog = self.dialog
# Get current Kodi video cache setting
cache, _ = advancedsettings_xml(['cache', 'memorysize'])
if cache is None:
# Kodi default cache
cache = '20971520'
else:
cache = str(cache.text)
log.info('Current Kodi video memory cache in bytes: %s' % cache)
settings('kodi_video_cache', value=cache)
# Do we need to migrate stuff?
check_migration()
# Optionally sign into plex.tv. Will not be called on very first run
# as plexToken will be ''
settings('plex_status', value=lang(39226))
if self.plexToken and self.myplexlogin:
self.CheckPlexTVSignIn()
# If a Plex server IP has already been set
# return only if the right machine identifier is found
if self.server:
log.info("PMS is already set: %s. Checking now..." % self.server)
if self.CheckPMS():
log.info("Using PMS %s with machineIdentifier %s"
% (self.server, self.serverid))
self._write_PMS_settings(self.server, self.pms_token)
return
# If not already retrieved myplex info, optionally let user sign in
# to plex.tv. This DOES get called on very first install run
if not self.plexToken and self.myplexlogin:
self.PlexTVSignIn()
server = self.PickPMS()
if server is not None:
# Write our chosen server to Kodi settings file
self.WritePMStoSettings(server)
# User already answered the installation questions
if settings('InstallQuestionsAnswered') == 'true':
return return
# Additional settings where the user needs to choose # If not already retrieved myplex info, optionally let user sign in
# Direct paths (\\NAS\mymovie.mkv) or addon (http)? # to plex.tv. This DOES get called on very first install run
goToSettings = False if not connectmanager.plexToken and connectmanager.myplexlogin:
if dialog.yesno(lang(29999), connectmanager.plex_tv_signin()
lang(39027),
lang(39028),
nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)"):
log.debug("User opted to use direct paths.")
settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True
# Are you on a system where you would like to replace paths
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
if dialog.yesno(heading=lang(29999), line1=lang(39033)):
log.debug("User chose to replace paths with smb")
else:
settings('replaceSMB', value="false")
# complete replace all original Plex library paths with custom SMB server = connectmanager.connectmanager.pick_pms()
if dialog.yesno(heading=lang(29999), line1=lang(39043)): if server is not None:
log.debug("User chose custom smb paths") # Write our chosen server to Kodi settings file
settings('remapSMB', value="true") connectmanager.write_pms_to_settings(server)
# Please enter your custom smb paths in the settings under
# "Sync Options" and then restart Kodi
dialog.ok(heading=lang(29999), line1=lang(39044))
goToSettings = True
# Go to network credentials? # User already answered the installation questions
if dialog.yesno(heading=lang(29999), if settings('InstallQuestionsAnswered') == 'true':
line1=lang(39029), return
line2=lang(39030)):
log.debug("Presenting network credentials dialog.")
from utils import passwordsXML
passwordsXML()
# Disable Plex music?
if dialog.yesno(heading=lang(29999), line1=lang(39016)):
log.debug("User opted to disable Plex music library.")
settings('enableMusic', value="false")
# Download additional art from FanArtTV # Additional settings where the user needs to choose
if dialog.yesno(heading=lang(29999), line1=lang(39061)): # Direct paths (\\NAS\mymovie.mkv) or addon (http)?
log.debug("User opted to use FanArtTV") goToSettings = False
settings('FanartTV', value="true") if dialog('yesno',
# Do you want to replace your custom user ratings with an indicator of lang(29999),
# how many versions of a media item you posses? lang(39027),
if dialog.yesno(heading=lang(29999), line1=lang(39718)): lang(39028),
log.debug("User opted to replace user ratings with version number") nolabel="Addon (Default)",
settings('indicate_media_versions', value="true") yeslabel="Native (Direct Paths)"):
log.debug("User opted to use direct paths.")
settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True
# Are you on a system where you would like to replace paths
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
if dialog('yesno', heading=lang(29999), line1=lang(39033)):
log.debug("User chose to replace paths with smb")
else:
settings('replaceSMB', value="false")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and # complete replace all original Plex library paths with custom SMB
# "Parents Movies", be sure to check https://goo.gl/JFtQV9 if dialog('yesno', heading=lang(29999), line1=lang(39043)):
dialog.ok(heading=lang(29999), line1=lang(39076)) log.debug("User chose custom smb paths")
settings('remapSMB', value="true")
# Please enter your custom smb paths in the settings under
# "Sync Options" and then restart Kodi
dialog('ok', heading=lang(29999), line1=lang(39044))
goToSettings = True
# Need to tell about our image source for collections: themoviedb.org # Go to network credentials?
dialog.ok(heading=lang(29999), line1=lang(39717)) if dialog('yesno',
# Make sure that we only ask these questions upon first installation heading=lang(29999),
settings('InstallQuestionsAnswered', value='true') line1=lang(39029),
line2=lang(39030)):
log.debug("Presenting network credentials dialog.")
from utils import passwordsXML
passwordsXML()
# Disable Plex music?
if dialog('yesno', heading=lang(29999), line1=lang(39016)):
log.debug("User opted to disable Plex music library.")
settings('enableMusic', value="false")
if goToSettings is False: # Download additional art from FanArtTV
# Open Settings page now? You will need to restart! if dialog('yesno', heading=lang(29999), line1=lang(39061)):
goToSettings = dialog.yesno(heading=lang(29999), line1=lang(39017)) log.debug("User opted to use FanArtTV")
if goToSettings: settings('FanartTV', value="true")
state.PMS_STATUS = 'Stop' # Do you want to replace your custom user ratings with an indicator of
xbmc.executebuiltin( # how many versions of a media item you posses?
'Addon.OpenSettings(plugin.video.plexkodiconnect)') if dialog('yesno', heading=lang(29999), line1=lang(39718)):
log.debug("User opted to replace user ratings with version number")
settings('indicate_media_versions', value="true")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and
# "Parents Movies", be sure to check https://goo.gl/JFtQV9
dialog('ok', heading=lang(29999), line1=lang(39076))
# Need to tell about our image source for collections: themoviedb.org
dialog('ok', heading=lang(29999), line1=lang(39717))
# Make sure that we only ask these questions upon first installation
settings('InstallQuestionsAnswered', value='true')
if goToSettings is False:
# Open Settings page now? You will need to restart!
goToSettings = dialog('yesno', heading=lang(29999), line1=lang(39017))
if goToSettings:
state.PMS_STATUS = 'Stop'
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')

View file

@ -14,6 +14,9 @@ from utils import window, settings, language as lang, thread_methods
import downloadutils import downloadutils
import PlexAPI import PlexAPI
from connectmanager import check_connection
from connect.plex_tv import get_user_artwork_url
from PlexFunctions import GetMachineIdentifier from PlexFunctions import GetMachineIdentifier
import state import state
@ -106,7 +109,7 @@ class UserClient(threading.Thread):
log.debug('Setting user preferences') log.debug('Setting user preferences')
# Only try to get user avatar if there is a token # Only try to get user avatar if there is a token
if self.currToken: if self.currToken:
url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser) url = get_user_artwork_url(self.currUser)
if url: if url:
window('PlexUserImage', value=url) window('PlexUserImage', value=url)
# Set resume point max # Set resume point max
@ -130,9 +133,9 @@ class UserClient(threading.Thread):
if self.currServer is None: if self.currServer is None:
return False return False
log.debug('Testing validity of current token') log.debug('Testing validity of current token')
res = PlexAPI.PlexAPI().CheckConnection(self.currServer, res = check_connection(self.currServer,
token=self.currToken, token=self.currToken,
verifySSL=self.ssl) verifySSL=self.ssl)
if res is False: if res is False:
# PMS probably offline # PMS probably offline
return False return False

View file

@ -34,6 +34,26 @@ log = logging.getLogger("PLEX."+__name__)
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect') ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
# For use with xbmcgui.dialog
ICONS = {
'{plex}': 'special://home/addons/plugin.video.plexkodiconnect/icon.png',
'{info}': xbmcgui.NOTIFICATION_INFO,
'{warning}': xbmcgui.NOTIFICATION_WARNING,
'{error}': xbmcgui.NOTIFICATION_ERROR
}
TYPES = {
'{alphanum}': xbmcgui.INPUT_ALPHANUM,
'{numeric}': xbmcgui.INPUT_NUMERIC,
'{date}': xbmcgui.INPUT_DATE,
'{time}': xbmcgui.INPUT_TIME,
'{ipaddress}': xbmcgui.INPUT_IPADDRESS,
'{password}': xbmcgui.INPUT_PASSWORD
}
OPTIONS = {
'{hide_input}': xbmcgui.ALPHANUM_HIDE_INPUT,
'{password_verify}': xbmcgui.PASSWORD_VERIFY
}
############################################################################### ###############################################################################
# Main methods # Main methods
@ -154,27 +174,19 @@ def dialog(typus, *args, **kwargs):
type='{ipaddress}' xbmcgui.INPUT_IPADDRESS (format: #.#.#.#) type='{ipaddress}' xbmcgui.INPUT_IPADDRESS (format: #.#.#.#)
type='{password}' xbmcgui.INPUT_PASSWORD type='{password}' xbmcgui.INPUT_PASSWORD
(return md5 hash of input, input is masked) (return md5 hash of input, input is masked)
Input Options:
option='{hide_input}': xbmcgui.ALPHANUM_HIDE_INPUT
option='{password_verify}': xbmcgui.PASSWORD_VERIFY
""" """
d = xbmcgui.Dialog() d = xbmcgui.Dialog()
if "icon" in kwargs: if "icon" in kwargs:
types = { for key, value in ICONS.iteritems():
'{plex}': 'special://home/addons/plugin.video.plexkodiconnect/icon.png',
'{info}': xbmcgui.NOTIFICATION_INFO,
'{warning}': xbmcgui.NOTIFICATION_WARNING,
'{error}': xbmcgui.NOTIFICATION_ERROR
}
for key, value in types.iteritems():
kwargs['icon'] = kwargs['icon'].replace(key, value) kwargs['icon'] = kwargs['icon'].replace(key, value)
if 'type' in kwargs: if 'type' in kwargs:
types = { kwargs['type'] = TYPES[kwargs['type']]
'{alphanum}': xbmcgui.INPUT_ALPHANUM, if 'option' in kwargs:
'{numeric}': xbmcgui.INPUT_NUMERIC, kwargs['option'] = OPTIONS[kwargs['option']]
'{date}': xbmcgui.INPUT_DATE,
'{time}': xbmcgui.INPUT_TIME,
'{ipaddress}': xbmcgui.INPUT_IPADDRESS,
'{password}': xbmcgui.INPUT_PASSWORD
}
kwargs['type'] = types[kwargs['type']]
if "heading" in kwargs: if "heading" in kwargs:
kwargs['heading'] = kwargs['heading'].replace("{plex}", kwargs['heading'] = kwargs['heading'].replace("{plex}",
language(29999)) language(29999))

View file

@ -26,6 +26,7 @@ _ADDON = Addon()
ADDON_NAME = 'PlexKodiConnect' ADDON_NAME = 'PlexKodiConnect'
ADDON_ID = 'plugin.video.plexkodiconnect' ADDON_ID = 'plugin.video.plexkodiconnect'
ADDON_VERSION = _ADDON.getAddonInfo('version') ADDON_VERSION = _ADDON.getAddonInfo('version')
ADDON_PATH = tryDecode(_ADDON.getAddonInfo('path'))
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1) KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])

View file

@ -30,8 +30,7 @@ sys_path.append(_base_resource)
############################################################################### ###############################################################################
from utils import settings, window, language as lang, dialog, tryEncode, \ from utils import settings, window, language as lang, dialog, tryDecode
tryDecode
from userclient import UserClient from userclient import UserClient
import initialsetup import initialsetup
from kodimonitor import KodiMonitor from kodimonitor import KodiMonitor
@ -40,8 +39,8 @@ import videonodes
from websocket_client import PMS_Websocket, Alexa_Websocket from websocket_client import PMS_Websocket, Alexa_Websocket
import downloadutils import downloadutils
from playqueue import Playqueue from playqueue import Playqueue
from connectmanager import ConnectManager, check_connection
import PlexAPI
from PlexCompanion import PlexCompanion from PlexCompanion import PlexCompanion
from command_pipeline import Monitor_Window from command_pipeline import Monitor_Window
from playback_starter import Playback_Starter from playback_starter import Playback_Starter
@ -165,8 +164,6 @@ class Service():
if settings('enableTextureCache') == "true": if settings('enableTextureCache') == "true":
self.image_cache_thread = Image_Cache_Thread() self.image_cache_thread = Image_Cache_Thread()
plx = PlexAPI.PlexAPI()
welcome_msg = True welcome_msg = True
counter = 0 counter = 0
while not __stop_PKC(): while not __stop_PKC():
@ -260,7 +257,7 @@ class Service():
if server is False: if server is False:
# No server info set in add-on settings # No server info set in add-on settings
pass pass
elif plx.CheckConnection(server, verifySSL=True) is False: elif check_connection(server, verifySSL=True) is False:
# Server is offline or cannot be reached # Server is offline or cannot be reached
# Alert the user and suppress future warning # Alert the user and suppress future warning
if self.server_online: if self.server_online:
@ -279,10 +276,10 @@ class Service():
# Periodically check if the IP changed, e.g. per minute # Periodically check if the IP changed, e.g. per minute
if counter > 20: if counter > 20:
counter = 0 counter = 0
setup = initialsetup.InitialSetup() connectmanager = ConnectManager()
tmp = setup.PickPMS() tmp = connectmanager.pick_pms()
if tmp is not None: if tmp is not None:
setup.WritePMStoSettings(tmp) connectmanager.write_pms_to_settings(tmp)
else: else:
# Server is online # Server is online
counter = 0 counter = 0