Connection manager, part 1
This commit is contained in:
parent
b7243e291e
commit
32ace844aa
13 changed files with 1677 additions and 1609 deletions
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||||
|
|
1
resources/lib/connect/__init__.py
Normal file
1
resources/lib/connect/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Dummy file to make this directory a package.
|
449
resources/lib/connect/plex_tv.py
Normal file
449
resources/lib/connect/plex_tv.py
Normal 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
|
1010
resources/lib/connectmanager.py
Normal file
1010
resources/lib/connectmanager.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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_)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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)')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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])
|
||||||
|
|
15
service.py
15
service.py
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue