jellyfin-kodi/resources/lib/downloadutils.py
sfaulds 4096e0ad5c small fix for server details being missing, stop crash and just try connection
dont return None for server details being missing, throw as we need to get to the botton of this
2017-01-30 16:05:41 +11:00

395 lines
14 KiB
Python

# -*- coding: utf-8 -*-
##################################################################################################
import json
import logging
import requests
import xbmcgui
import clientinfo
import connect.connectionmanager as connectionmanager
from utils import window, settings, language as lang
##################################################################################################
# Disable requests logging
from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
log = logging.getLogger("EMBY."+__name__)
##################################################################################################
class DownloadUtils(object):
# Borg - multiple instances, shared state
_shared_state = {}
# Requests session
session = {}
session_requests = None
servers = {} # Multi server setup
default_timeout = 30
def __init__(self):
self.__dict__ = self._shared_state
self.client_info = clientinfo.ClientInfo()
def set_session(self, **kwargs):
# Reserved for userclient only
info = {}
for key in kwargs:
info[key] = kwargs[key]
self.session.update(info)
window('emby_server.json', value=self.session)
log.debug("Set info for server %s: %s", self.session['ServerId'], self.session)
def add_server(self, server, ssl):
# Reserved for userclient only
server_id = server['Id']
info = {
'UserId': server['UserId'],
'Server': connectionmanager.getServerAddress(server, server['LastConnectionMode']),
'Token': server['AccessToken'],
'SSL': ssl
}
for server_info in self.servers:
if server_info == server_id:
server_info.update(info)
# Set window prop
self._set_server_properties(server_id, server['Name'], info)
log.info("updating %s to available servers: %s", server_id, self.servers)
break
else:
self.servers[server_id] = info
self._set_server_properties(server_id, server['Name'], json.dumps(info))
log.info("adding %s to available servers: %s", server_id, self.servers)
def reset_server(self, server_id):
# Reserved for userclient only
for server in self.servers:
if server['ServerId'] == server_id:
self.servers.pop(server)
window('emby_server%s.json' % server_id, clear=True)
window('emby_server%s.name' % server_id, clear=True)
log.info("removing %s from available servers", server_id)
@staticmethod
def _set_server_properties(server_id, name, info):
window('emby_server%s.json' % server_id, value=info)
window('emby_server%s.name' % server_id, value=name)
def post_capabilities(self, device_id):
# Post settings to session
url = "{server}/emby/Sessions/Capabilities/Full?format=json"
data = {
'PlayableMediaTypes': "Audio,Video",
'SupportsMediaControl': True,
'SupportedCommands': (
"MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
"Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
"GoHome,PageUp,NextLetter,GoToSearch,"
"GoToSettings,PageDown,PreviousLetter,TakeScreenshot,"
"VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage,"
"SetAudioStreamIndex,SetSubtitleStreamIndex,"
"Mute,Unmute,SetVolume,"
"Play,Playstate,PlayNext"
)
}
self.downloadUrl(url, postBody=data, action_type="POST")
log.debug("Posted capabilities to %s", self.session['Server'])
# Attempt at getting sessionId
url = "{server}/emby/Sessions?DeviceId=%s&format=json" % device_id
try:
result = self.downloadUrl(url)
session_id = result[0]['Id']
except Exception as error:
log.error("Failed to retrieve the session id: " + str(error))
else:
log.info("SessionId: %s", session_id)
window('emby_sessionId', value=session_id)
# Post any permanent additional users
additional_users = settings('additionalUsers')
if additional_users:
additional_users = additional_users.split(',')
log.info("List of permanent users added to the session: %s", additional_users)
# Get the user list from server to get the userId
url = "{server}/emby/Users?format=json"
result = self.downloadUrl(url)
for additional in additional_users:
add_user = additional.decode('utf-8').lower()
# Compare to server users to list of permanent additional users
for user in result:
username = user['Name'].lower()
if username in add_user:
user_id = user['Id']
url = ("{server}/emby/Sessions/%s/Users/%s?format=json"
% (session_id, user_id))
self.downloadUrl(url, postBody={}, action_type="POST")
def start_session(self):
# User is identified from this point
# Attach authenticated header to the session
session = requests.Session()
session.headers = self.get_header()
session.verify = self.session['SSL']
# Retry connections to the server
session.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
session.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
self.session_requests = session
log.info("requests session started on: %s", self.session['Server'])
def stop_session(self):
try:
self.session_requests.close()
except Exception as error:
log.error(error)
log.warn("requests session could not be terminated")
def get_header(self, server_id=None, authenticate=True):
device_name = self.client_info.get_device_name().encode('utf-8')
device_id = self.client_info.get_device_id()
version = self.client_info.get_version()
if authenticate:
user = self._get_session_info(server_id)
user_id = user['UserId']
token = user['Token']
auth = (
'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
% (user_id, device_name, device_id, version)
)
header = {
'Authorization': auth,
'X-MediaBrowser-Token': token
}
else:
auth = (
'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
% (device_name, device_id, version)
)
header = {'Authorization': auth}
header.update({
'Content-type': 'application/json',
'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*',
})
return header
def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None,
authenticate=True, server_id=None):
log.debug("===== ENTER downloadUrl =====")
kwargs = {}
try:
# Ensure server info is loaded
self._ensure_server(server_id)
server = self.session if server_id is None else self.servers[server_id]
if server is None or server.get("Server", None) is None or server.get("UserId", None) is None:
log.info("Aborting download, Server Details Error: %s url=%s" % (server, url))
exc = Exception("Aborting download, Server Details Error: %s url=%s" % (server, url))
#exc.quiet = True
raise exc
if server_id is None and self.session_requests is not None: # Main server
session = self.session_requests
else:
session = requests
kwargs.update({
'verify': server['SSL'],
'headers': self.get_header(server_id, authenticate)
})
# Replace for the real values
url = url.replace("{server}", server['Server'])
url = url.replace("{UserId}", server['UserId'])
# does the URL look ok
if url.startswith('/'):
exc = Exception("URL Error: " + url)
exc.quiet = True
raise exc
##### PREPARE REQUEST #####
kwargs.update({
'url': url,
'timeout': self.default_timeout,
'json': postBody,
'params': parameters
})
##### THE RESPONSE #####
log.debug(kwargs)
response = self._requests(action_type, session, **kwargs)
#response = requests.get('http://httpbin.org/status/400')
if response.status_code == 204:
# No body in the response
log.debug("====== 204 Success ======")
# Read response to release connection
response.content
if action_type == "GET":
raise Warning("Response Code 204: No Content for GET request")
else:
# this is probably valid for DELETE and PUT
return None
elif response.status_code == requests.codes.ok:
# UNICODE - JSON object
json_data = response.json()
log.debug("====== 200 Success ======")
log.debug("Response: %s", json_data)
return json_data
else: # Bad status code
log.error("=== Bad status response: %s ===", response.status_code)
response.raise_for_status()
##### EXCEPTIONS #####
except requests.exceptions.SSLError as error:
log.error("invalid SSL certificate for: %s", url)
error.quiet = True
raise
except requests.exceptions.ConnectTimeout as error:
log.error("ConnectTimeout at: %s", url)
error.quiet = True
raise
except requests.exceptions.ReadTimeout as error:
log.error("ReadTimeout at: %s", url)
error.quiet = True
raise
except requests.exceptions.ConnectionError as error:
# Make the addon aware of status
if window('emby_online') != "false":
log.error("Server unreachable at: %s", url)
window('emby_online', value="false")
error.quiet = True
raise
except requests.exceptions.HTTPError as error:
if response.status_code == 400:
log.error("Malformed request: %s", error)
error.quiet = True
raise
if response.status_code == 401:
# Unauthorized
status = window('emby_serverStatus')
if 'X-Application-Error-Code' in response.headers:
# Emby server errors
if response.headers['X-Application-Error-Code'] == "ParentalControl":
# Parental control - access restricted
if status != "restricted":
xbmcgui.Dialog().notification(heading=lang(29999),
message="Access restricted.",
icon=xbmcgui.NOTIFICATION_ERROR,
time=5000)
window('emby_serverStatus', value="restricted")
exc = Exception("restricted: " + str(error))
exc.quiet = True
raise exc
elif (response.headers['X-Application-Error-Code'] ==
"UnauthorizedAccessException"):
# User tried to do something his emby account doesn't allow
exc = Exception("UnauthorizedAccessException: " + str(error))
exc.quiet = True
raise exc
elif status not in ("401", "Auth"):
# Tell userclient token has been revoked.
window('emby_serverStatus', value="401")
log.error("HTTP Error: %s", error)
xbmcgui.Dialog().notification(heading="Error connecting",
message="Unauthorized.",
icon=xbmcgui.NOTIFICATION_ERROR)
exc = Exception("401: " + str(error))
exc.quiet = True
raise exc
except requests.exceptions.RequestException as error:
log.error("unknown error connecting to: %s", url)
raise
def _ensure_server(self, server_id=None):
if server_id is None and self.session_requests is None:
if not self.session:
server = self._get_session_info()
self.session = server
elif server_id and server_id not in self.servers:
if server_id not in self.servers:
server = self._get_session_info(server_id)
self.servers[server_id] = server
return True
@classmethod
def _get_session_info(cls, server_id=None):
info = {
'UserId': "",
'Server': "",
'Token': "",
'SSL': False
}
if server_id is None: # Main server
server = window('emby_server.json')
else: # Other connect servers
server = window('emby_server%s.json' % server_id)
if server:
info.update(server)
return info
@classmethod
def _requests(cls, action, session, **kwargs):
if action == "GET":
response = session.get(**kwargs)
elif action == "POST":
response = session.post(**kwargs)
elif action == "DELETE":
response = session.delete(**kwargs)
return response