Integration of UserClient.

This commit is contained in:
angelblue05 2015-04-03 05:13:01 -05:00
parent 6f3e793561
commit 1b20863cf9
4 changed files with 488 additions and 129 deletions

View file

@ -7,83 +7,103 @@ import xbmcgui
import xbmcaddon
import json
import threading
from datetime import datetime
from DownloadUtils import DownloadUtils
import urllib
import sys
import socket
import threading
from datetime import datetime
from DownloadUtils import DownloadUtils
from UserClient import UserClient
from ClientInformation import ClientInformation
#define our global download utils
logLevel = 1
###########################################################################
class ConnectionManager():
addonSettings = None
__addon__ = xbmcaddon.Addon(id='plugin.video.emby')
__addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') )
__language__ = __addon__.getLocalizedString
clientInfo = ClientInformation()
userClient = UserClient()
doUtils = DownloadUtils()
def printDebug(self, msg, level = 1):
if(logLevel >= level):
if(logLevel == 2):
logLevel = 0
addon = None
WINDOW = xbmcgui.Window(10000)
def __init__(self):
clientInfo = self.clientInfo
self.addonId = clientInfo.getAddonId()
self.addonName = clientInfo.getAddonName()
self.addon = xbmcaddon.Addon(id=self.addonId)
self.__language__ = self.addon.getLocalizedString
def logMsg(self, msg, level=1):
addonName = self.addonName
className = self.__class__.__name__
s_logLevel = self.userClient.getLogLevel()
# Attempt to change logLevel live
if (self.logLevel != s_logLevel):
self.logLevel = s_logLevel
if (self.logLevel >= level):
try:
xbmc.log("%s %s -> %s" % (addonName, className, str(msg)))
except UnicodeEncodeError:
try:
xbmc.log("emby " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg))
except UnicodeEncodeError:
xbmc.log("emby " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
else:
try:
xbmc.log("emby " + str(level) + " -> " + str(msg))
except UnicodeEncodeError:
xbmc.log("emby " + str(level) + " -> " + str(msg.encode('utf-8')))
xbmc.log("%s %s -> %s" % (addonName, className, str(msg.encode('utf-8'))))
except:
pass
def checkServer(self):
WINDOW = xbmcgui.Window( 10000 )
WINDOW.setProperty("Server_Checked", "True")
self.WINDOW.setProperty("Server_Checked", "True")
self.logMsg("Connection Manager Called")
self.printDebug ("emby Connection Manager Called")
self.addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
port = self.addonSettings.getSetting('port')
host = self.addonSettings.getSetting('ipaddress')
addon = self.addon
server = self.userClient.getServer()
if(len(host) != 0 and host != "<none>"):
self.printDebug ("emby server already set")
if (server != ""):
self.logMsg("Server already set", 2)
return
serverInfo = self.getServerDetails()
prefix,ip,port = serverInfo.split(":")
if(serverInfo == None):
self.printDebug ("emby getServerDetails failed")
if (serverInfo == None):
self.logMsg("getServerDetails failed", 1)
return
index = serverInfo.find(":")
setServer = xbmcgui.Dialog().yesno(self.__language__(30167), "Proceed with the following server?", self.__language__(30169) + serverInfo)
if(index <= 0):
self.printDebug ("emby getServerDetails data not correct : " + serverInfo)
if setServer == 1:
self.logMsg("Server selected. Saving information.", 1)
addon.setSetting("ipaddress", ip.replace("/", ""))
addon.setSetting("port", port)
# If https is enabled
if (prefix == 'https'):
addon.setSetting('https', "true")
else:
self.logMsg("No server selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
return
server_address = serverInfo[:index]
server_port = serverInfo[index+1:]
self.printDebug ("emby detected server info " + server_address + " : " + server_port)
# Get List of public users
self.logMsg("Getting user list", 1)
server = ip.replace("/", "") + ":" + port
xbmcgui.Dialog().ok(self.__language__(30167), self.__language__(30168), self.__language__(30169) + server_address, self.__language__(30030) + server_port)
# get a list of users
self.printDebug ("Getting user list")
jsonData = None
downloadUtils = DownloadUtils()
try:
jsonData = downloadUtils.downloadUrl(server_address + ":" + server_port + "/mediabrowser/Users/Public?format=json")
jsonData = self.doUtils.downloadUrl(serverInfo + "/mediabrowser/Users/Public?format=json", authenticate=False)
except Exception, msg:
error = "Get User unable to connect to " + server_address + ":" + server_port + " : " + str(msg)
error = "Get User unable to connect to " + server + " : " + str(msg)
xbmc.log (error)
return ""
if(jsonData == False):
if (jsonData == False):
return
self.printDebug("jsonData : " + str(jsonData), level=2)
self.logMsg("jsonData : " + str(jsonData), level=2)
result = json.loads(jsonData)
names = []
@ -95,55 +115,48 @@ class ConnectionManager():
name = name + " (Secure)"
names.append(name)
self.printDebug ("User List : " + str(names))
self.printDebug ("User List : " + str(userList))
self.logMsg("User List: " + str(names))
self.logMsg("User List: " + str(userList))
return_value = xbmcgui.Dialog().select(self.__language__(30200), names)
if(return_value > -1):
selected_user = userList[return_value]
self.printDebug("Setting Selected User : " + selected_user)
self.addonSettings.setSetting("port", server_port)
self.addonSettings.setSetting("ipaddress", server_address)
self.addonSettings.setSetting("username", selected_user)
downloadUtils.authenticate()
self.logMsg("Setting Selected User: %s" % selected_user)
self.addon.setSetting("username", selected_user)
return
else:
xbmc.log("No user selected.")
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
return
def getServerDetails(self):
self.printDebug("Getting Server Details from Network")
self.logMsg("Getting Server Details from Network")
MESSAGE = "who is MediaBrowserServer?"
#MULTI_GROUP = ("224.3.29.71", 7359)
#MULTI_GROUP = ("127.0.0.1", 7359)
MULTI_GROUP = ("<broadcast>", 7359)
MESSAGE = "who is EmbyServer?"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(6.0)
#ttl = struct.pack('b', 20)
#sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
xbmc.log("MutliGroup : " + str(MULTI_GROUP));
xbmc.log("Sending UDP Data : " + MESSAGE);
self.logMsg("MutliGroup : %s" % str(MULTI_GROUP));
self.logMsg("Sending UDP Data: %s" % MESSAGE);
sock.sendto(MESSAGE, MULTI_GROUP)
try:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
xbmc.log("Received Response : " + data)
if(data[0:18] == "MediaBrowserServer"):
xbmc.log("Found Server : " + data[19:])
return data[19:]
self.logMsg("Received Response: %s" % data)
# Get the address
data = json.loads(data)
return data['Address']
except:
xbmc.log("No UDP Response")
self.logMsg("No UDP Response")
pass
return None

View file

@ -19,6 +19,7 @@ import traceback
class DownloadUtils():
WINDOW = xbmcgui.Window(10000)
logLevel = 0
addonSettings = None
getString = None
@ -49,30 +50,16 @@ class DownloadUtils():
def getServer(self, prefix=True):
# For https support
addon = self.addon
HTTPS = addon.getSetting('https')
host = addon.getSetting('ipaddress')
port = addon.getSetting('port')
server = host + ":" + port
return server
'''
if len(server) < 2:
self.logMsg("No server information saved.")
return ""
WINDOW = self.WINDOW
username = WINDOW.getProperty("currUser")
if prefix:
server = WINDOW.getProperty("server%s" % username)
else:
server = WINDOW.getProperty("server_%s" % username)
return server
# If https is true
if prefix and (HTTPS == "true"):
server = "https://%s" % server
return server
# If https is false
elif prefix and (HTTPS == "false"):
server = "http://%s" % server
return server
# If only the host:port is required
elif (prefix == False):
return server
'''
def getUserId(self, suppress=True):
WINDOW = xbmcgui.Window( 10000 )
@ -146,7 +133,7 @@ class DownloadUtils():
self.downloadUrl(url, postBody=stringdata, type="POST")
def authenticate(self, retreive=True):
'''def authenticate(self, retreive=True):
WINDOW = xbmcgui.Window(10000)
self.addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
@ -227,15 +214,13 @@ class DownloadUtils():
WINDOW.setProperty("userid" + username, "")
self.addonSettings.setSetting("AccessToken" + username, "")
self.addonSettings.setSetting("userid" + username, "")
return ""
return "" '''
def imageUrl(self, id, type, index, width, height):
port = self.addonSettings.getSetting('port')
host = self.addonSettings.getSetting('ipaddress')
server = host + ":" + port
server = self.getServer()
return "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + str(index) + "/e3ab56fe27d389446754d0fb04910a34/original/" + str(width) + "/" + str(height) + "/0"
return "%s/mediabrowser/Items/%s/Images/%s/%s//e3ab56fe27d389446754d0fb04910a34/original/%s/%s/0" % (server, id, type, index, width, height)
def getAuthHeader(self, authenticate=True):
clientInfo = ClientInformation()
@ -244,6 +229,7 @@ class DownloadUtils():
deviceName = self.addonSettings.getSetting('deviceName')
deviceName = deviceName.replace("\"", "_")
username = self.WINDOW.getProperty("currUser")
if(authenticate == False):
authString = "MediaBrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
@ -254,7 +240,7 @@ class DownloadUtils():
authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString}
authToken = self.authenticate()
authToken = self.WINDOW.getProperty("accessToken%s" % username)
if(authToken != ""):
headers["X-MediaBrowser-Token"] = authToken
@ -278,14 +264,20 @@ class DownloadUtils():
self.TrackLog = self.TrackLog + "HTTP_API_CALL : " + url + stackString + "\r"
link = ""
https = None
try:
if url[0:4] == "http":
if url[0:5] == "https":
serversplit = 2
urlsplit = 3
elif url[0:4] == "http":
serversplit = 2
urlsplit = 3
else:
serversplit = 0
urlsplit = 1
https = self.addonSettings.getSetting('https')
server = url.split('/')[serversplit]
urlPath = "/"+"/".join(url.split('/')[urlsplit:])
@ -300,7 +292,12 @@ class DownloadUtils():
head = self.getAuthHeader(authenticate)
self.logMsg("HEADERS : " + str(head), level=2)
conn = httplib.HTTPConnection(server, timeout=5)
if (https == 'false'):
#xbmc.log("Https disabled.")
conn = httplib.HTTPConnection(server, timeout=5)
elif (https == 'true'):
#xbmc.log("Https enabled.")
conn = httplib.HTTPSConnection(server, timeout=5)
# make the connection and send the request
if(postBody != None):
@ -356,22 +353,23 @@ class DownloadUtils():
return data.getheader('Location')
elif int(data.status) == 401:
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
xbmc.log(error)
username = self.addonSettings.getSetting("username")
WINDOW = xbmcgui.Window(10000)
WINDOW.setProperty("AccessToken" + username, "")
WINDOW.setProperty("userid" + username, "")
self.addonSettings.setSetting("AccessToken" + username, "")
self.addonSettings.setSetting("userid" + username, "")
xbmcgui.Dialog().ok(self.getString(30135), self.getString(30044), "Reason : " + str(data.reason))
try:
conn.close()
except:
status = WINDOW.getProperty("Server_status")
# Prevent multiple re-auth
if (status == "401") or (status == "Auth"):
pass
return ""
else:
# Tell UserClient token has been revoked.
WINDOW.setProperty("Server_status", "401")
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
xbmc.log(error)
#xbmcgui.Dialog().ok(self.getString(30135),"Reason: %s" % data.reason) #self.getString(30044),
try:
conn.close()
except:
pass
return ""
elif int(data.status) >= 400:
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)

337
resources/lib/UserClient.py Normal file
View file

@ -0,0 +1,337 @@
#################################################################################################
# UserClient thread
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import threading
import hashlib
import json as json
import KodiMonitor
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
class UserClient(threading.Thread):
clientInfo = ClientInformation()
doUtils = DownloadUtils()
stopClient = False
logLevel = 0
addon = None
auth = True
retry = 0
WINDOW = xbmcgui.Window(10000)
currUser = None
currUserId = None
currServer = None
currToken = None
AdditionalUser = []
def __init__(self, *args):
clientInfo = self.clientInfo
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
self.addonId = clientInfo.getAddonId()
self.addonName = clientInfo.getAddonName()
self.addon = xbmcaddon.Addon(id=self.addonId)
self.logMsg("|---- Starting UserClient ----|", 0)
threading.Thread.__init__(self, *args)
def logMsg(self, msg, level=1):
addonName = self.addonName
className = self.__class__.__name__
if (self.logLevel != self.getLogLevel()):
xbmc.log("Adjusting logLevel to %i" % self.getLogLevel())
self.logLevel = self.getLogLevel()
if (self.logLevel >= level):
try:
xbmc.log("%s %s -> %s" % (addonName, className, str(msg)))
except UnicodeEncodeError:
try:
xbmc.log("%s %s -> %s" % (addonName, className, str(msg.encode('utf-8'))))
except:
pass
def getUsername(self):
username = self.addon.getSetting('username')
if (username == ""):
self.logMsg("No username saved.", 2)
return ""
return username
def getLogLevel(self):
logLevel = int(self.addon.getSetting('logLevel'))
return logLevel
def getUserId(self):
username = self.getUsername()
w_userId = self.WINDOW.getProperty('userId%s' % username)
s_userId = self.addon.getSetting('userId%s' % username)
# Verify the window property
if (w_userId != ""):
self.logMsg("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2)
return w_userId
# Verify the settings
elif (s_userId != ""):
self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2)
return s_userId
# No userId found
else:
self.logMsg("No userId saved for username: %s." % username)
return
def getServer(self, prefix=True):
# For https support
addon = self.addon
HTTPS = addon.getSetting('https')
host = addon.getSetting('ipaddress')
port = addon.getSetting('port')
server = host + ":" + port
if host == "":
self.logMsg("No server information saved.", 2)
return ""
# If https is true
if prefix and (HTTPS == "true"):
server = "https://%s" % server
return server
# If https is false
elif prefix and (HTTPS == "false"):
server = "http://%s" % server
return server
# If only the host:port is required
elif (prefix == False):
return server
def getToken(self):
username = self.getUsername()
w_token = self.WINDOW.getProperty('accessToken%s' % username)
s_token = self.addon.getSetting('accessToken%s' % username)
# Verify the window property
if (w_token != ""):
self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2)
return w_token
# Verify the settings
elif (s_token != ""):
self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2)
self.WINDOW.setProperty('accessToken%s' % username, s_token)
return s_token
else:
self.logMsg("No token found.")
return ""
def getPublicUsers(self):
server = self.getServer()
# Get public Users
url = "%s/mediabrowser/Users/Public?format=json" % server
jsonData = self.doUtils.downloadUrl(url, authenticate=False)
users = []
if (jsonData != ""):
users = json.loads(jsonData)
return users
def loadCurrUser(self):
WINDOW = self.WINDOW
username = self.getUsername()
# Only to be used if token exists
self.currUserId = self.getUserId()
self.currServer = self.getServer()
self.currToken = self.getToken()
# Set to windows property
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
WINDOW.setProperty("server%s" % username, self.currServer)
WINDOW.setProperty("server_%s" % username, self.getServer(prefix=False))
WINDOW.setProperty("userId%s" % username, self.currUserId)
self.currUser = username
def authenticate(self):
WINDOW = self.WINDOW
addon = self.addon
username = self.getUsername()
server = self.getServer()
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile'))
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
# If there's no settings.xml
if (hasSettings == 0):
self.logMsg("No settings.xml found.")
self.auth = False
return
# If no user information
if (server == "") or (username == ""):
self.logMsg("Missing server information.")
self.auth = False
return
# If there's a token
if (self.getToken() != ""):
self.loadCurrUser()
self.logMsg("Current user: %s" % self.currUser, 0)
self.logMsg("Current userId: %s" % self.currUserId, 0)
self.logMsg("Current accessToken: %s" % self.currToken, 0)
return
users = self.getPublicUsers()
password = ""
# Find user in list
for user in users:
name = user.get("Name")
userHasPassword = False
if (username == name):
# Verify if user has a password
if (user.get("HasPassword") == True):
userHasPassword = True
# If user has password
if (userHasPassword):
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if password == "":
self.logMsg("No password entered.", 0)
#addon.setSetting("username", "")
self.WINDOW.setProperty("Server_status", "Stop")
self.auth = False
#self.WINDOW.setProperty("Server_status", "")
return
break
else:
# Manual login, user is hidden
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
sha1 = hashlib.sha1(password)
sha1 = sha1.hexdigest()
# Authenticate username and password
url = "%s/mediabrowser/Users/AuthenticateByName?format=json" % server
messageData = "username=%s&password=%s" % (username, sha1)
resp = self.doUtils.downloadUrl(url, postBody=messageData, type="POST", authenticate=False)
result = None
accessToken = None
try:
self.logMsg("Auth_Reponse: %s" % resp, 1)
result = json.loads(resp)
accessToken = result.get("AccessToken")
except:
pass
if (result != None and accessToken != None):
self.currUser = username
userId = result.get("User").get("Id")
addon.setSetting("accessToken%s" % username, accessToken)
addon.setSetting("userId%s" % username, userId)
self.logMsg("User Authenticated: %s" % accessToken)
self.loadCurrUser()
self.WINDOW.setProperty("Server_status", "")
self.retry = 0
return
else:
self.logMsg("User authentication failed.")
addon.setSetting("accessToken%s" % username, "")
addon.setSetting("userId%s" % username, "")
xbmcgui.Dialog().ok("Error Connecting", "Wrong password.")
# Give two attempts at entering password
self.retry += 1
if self.retry == 2:
self.logMsg("Too many retries. Please restart Kodi.")
self.WINDOW.setProperty("Server_status", "Stop")
self.auth = False
return
def resetClient(self):
if self.currToken != None:
# In case of 401, removed saved token
self.addon.setSetting("accessToken%s" % self.currUser, "")
self.WINDOW.setProperty("accessToken%s" % self.currUser, "")
self.currToken = None
self.logMsg("User token has been removed.", 1)
self.auth = True
self.currUser = None
return
def run(self):
while not self.KodiMonitor.abortRequested():
# Get the latest addon settings
self.addon = xbmcaddon.Addon(id=self.addonId)
if (self.WINDOW.getProperty("Server_status") != ""):
status = self.WINDOW.getProperty("Server_status")
if status == "401":
self.WINDOW.setProperty("Server_status", "Auth")
# Revoked token
self.resetClient()
if self.auth and (self.currUser == None):
status = self.WINDOW.getProperty("Server_status")
if (status == "") or (status == "Auth"):
self.auth = False
self.authenticate()
if (self.auth == False) and (self.currUser == None):
# Only if there's information found to login
server = self.getServer()
username = self.getUsername()
status = self.WINDOW.getProperty("Server_status")
# If user didn't enter a password when prompted
if status == "Stop":
pass
elif (server != "") and (username != ""):
self.logMsg("Server found: %s" % server)
self.logMsg("Username found: %s" % username)
self.auth = True
# If stopping the client didn't work
if self.stopClient == True:
break
self.logMsg("|---- UserClient Stopped ----|", 0)
def stopClient(self):
# As last resort
self.stopClient = True

View file

@ -16,27 +16,30 @@ from LibrarySync import LibrarySync
from Player import Player
from DownloadUtils import DownloadUtils
from ConnectionManager import ConnectionManager
from ClientInformation import ClientInformation
from WebSocketClient import WebSocketThread
from UserClient import UserClient
librarySync = LibrarySync()
class Service():
newWebSocketThread = None
newUserClient = None
clientInfo = ClientInformation()
def __init__(self, *args ):
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
utils.logMsg("MB3 Sync Service", "starting Monitor",0)
xbmc.log("======== START %s ========" % self.clientInfo.getAddonName())
pass
def ServiceEntryPoint(self):
ConnectionManager().checkServer()
DownloadUtils().authenticate(retreive=True)
player = Player()
lastProgressUpdate = datetime.today()
interval_FullSync = 600
@ -45,6 +48,8 @@ class Service():
cur_seconds_fullsync = interval_FullSync
cur_seconds_incrsync = interval_IncrementalSync
user = UserClient()
player = Player()
ws = WebSocketThread()
while not self.KodiMonitor.abortRequested():
@ -74,8 +79,11 @@ class Service():
xbmc.log("MB3 Sync Service -> Exception in Playback Monitor Service : " + str(e))
pass
else:
if (self.newUserClient == None):
self.newUserClient = "Started"
user.start()
# background worker for database sync
if DownloadUtils().authenticate(retreive=False) != "":
if (user.currUser != None):
# Correctly launch the websocket, if user manually launches the add-on
if (self.newWebSocketThread == None):
@ -115,6 +123,9 @@ class Service():
if (self.newWebSocketThread != None):
ws.stopClient()
if (self.newUserClient != None):
user.stopClient()
#start the service
Service().ServiceEntryPoint()