From 417b9fb9385300aaf5baa8feeaae049e74c6a93b Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Fri, 21 Feb 2020 00:42:50 +0000
Subject: [PATCH 1/8] Recreated Branch

---
 .gitignore                                   |   2 +
 jellyfin_kodi/jellyfin/api.py                | 104 ++++++-
 jellyfin_kodi/jellyfin/connection_manager.py | 307 ++++++-------------
 3 files changed, 196 insertions(+), 217 deletions(-)

diff --git a/.gitignore b/.gitignore
index b344f958..c79bce8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@ machine_guid
 .idea/
 .DS_Store
 .vscode/
+pyinstrument/
+pyinstrument_cext.so
diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index ba43c4cd..049e6035 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -1,5 +1,10 @@
-# -*- coding: utf-8 -*-
 from __future__ import division, absolute_import, print_function, unicode_literals
+import requests
+import json
+import logging
+from helper.utils import settings
+
+LOG = logging.getLogger('JELLYFIN.' + __name__)
 
 
 def jellyfin_url(client, handler):
@@ -35,6 +40,9 @@ class API(object):
     '''
     def __init__(self, client, *args, **kwargs):
         self.client = client
+        self.config = client.config
+        self.default_timeout = 5
+
 
     def _http(self, action, url, request={}):
         request.update({'type': action, 'handler': url})
@@ -344,3 +352,97 @@ class API(object):
         return self._delete("Videos/ActiveEncodings", params={
             'DeviceId': device_id
         })
+
+
+    #################################################################################################
+
+    # New API calls 
+
+    #################################################################################################
+    def get_default_headers(self):
+        auth = "MediaBrowser "
+        auth += "Client=%s, " % self.config.data['app.name'].encode('utf-8')
+        auth += "Device=%s, " % self.config.data['app.device_name'].encode('utf-8')
+        auth += "DeviceId=%s, " % self.config.data['app.device_id'].encode('utf-8')
+        auth += "Version=%s" % self.config.data['app.version'].encode('utf-8')
+
+        return {
+            "Accept": "application/json",
+            "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
+            "X-Application": "%s/%s" % (self.config.data['app.name'], self.config.data['app.version']),
+            "Accept-Charset": "UTF-8,*",
+            "Accept-encoding": "gzip",
+            "User-Agent": self.config.data['http.user_agent'] or "%s/%s" % (self.config.data['app.name'], self.config.data['app.version']),
+            "x-emby-authorization": auth
+        }
+
+    def send_request(self, url, path, method=None, timeout=None, headers=None, data=None):
+        if not timeout:
+            LOG.debug("No timeout set, using default")
+            timeout = self.default_timeout
+        if not headers:
+            LOG.debug("No headers set, using default")
+            headers = self.get_default_headers()
+        if not method:
+            LOG.debug("No method set, using default")
+            method = "get"  #Defaults to get request if none specified
+
+        request_method = getattr(requests, method.lower())
+        url = "%s/%s" % (url, path)
+        request_settings = {
+            "timeout": timeout,
+            "headers": headers,
+            "data": data
+        }
+
+        if not settings('sslverify'):
+            request_settings["verify"] = False
+
+        LOG.info("Sending %s request to %s" % (method, url))
+        LOG.debug(request_settings)
+
+        return request_method(url, **request_settings)
+
+
+    def login(self, server_url, username, password):
+        path = "Users/AuthenticateByName"
+        authData = {
+                    "username": username,
+                    "Pw": password or ""
+                }
+
+        headers = self.get_default_headers()
+        headers.update({'Content-type': "application/json"})
+
+        try:
+            LOG.info("Trying to login to %s/%s as %s" % (server_url, path, username))
+            response = self.send_request(server_url, path, method="post", headers=headers, data=json.dumps(authData))
+
+            if response.status_code == 200:
+                return response.json()
+            else:
+                LOG.error("Failed to login to server with status code: "+str(response.status_code))
+                LOG.error(response.text)
+                LOG.debug(headers)
+
+                return {}
+        except Exception as e: #Find exceptions for likely cases i.e, server timeout, etc
+            LOG.error(e) 
+
+        return {}
+
+    def validate_authentication_token(self, server):
+
+        url = "%s/%s" % (server['address'], "system/info")
+        authTokenHeader = {
+                    'X-MediaBrowser-Token': server['AccessToken']
+                }
+        headers = self.get_default_headers()
+        headers.update(authTokenHeader)
+
+        response = self.send_request(server['address'], "system/info", headers=headers)
+        return response.json() if response.status_code == 200 else {}
+
+    def get_public_info(self, server_address):
+        response = self.send_request(server_address, "system/info/public")
+        return response.json() if response.status_code == 200 else {}
\ No newline at end of file
diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py
index 2b0e4780..e66a763f 100644
--- a/jellyfin_kodi/jellyfin/connection_manager.py
+++ b/jellyfin_kodi/jellyfin/connection_manager.py
@@ -8,12 +8,14 @@ import logging
 import socket
 import time
 from datetime import datetime
-from distutils.version import LooseVersion
+from operator import itemgetter 
 
 import urllib3
 
 from .credentials import Credentials
 from .http import HTTP  # noqa: I201,I100
+from .api import API 
+import traceback
 
 #################################################################################################
 
@@ -29,11 +31,8 @@ CONNECTION_STATE = {
 
 class ConnectionManager(object):
 
-    min_server_version = "10.1.0"
-    server_version = min_server_version
     user = {}
     server_id = None
-    timeout = 10
 
     def __init__(self, client):
 
@@ -43,25 +42,14 @@ class ConnectionManager(object):
         self.config = client.config
         self.credentials = Credentials()
 
-        self.http = HTTP(client)
+        self.API = API(client)
 
-    def clear_data(self):
-
-        LOG.info("connection manager clearing data")
-
-        self.user = None
-        credentials = self.credentials.get_credentials()
-        credentials['Servers'] = list()
-        self.credentials.get_credentials(credentials)
-
-        self.config.auth(None, None)
-
-    def revoke_token(self):
+    def revoke_token(self): #Called once in http#L130
 
         LOG.info("revoking token")
 
         self['server']['AccessToken'] = None
-        self.credentials.get_credentials(self.credentials.get_credentials())
+        self.credentials.set_credentials(self.credentials.get())
 
         self.config.data['auth.token'] = None
 
@@ -70,7 +58,7 @@ class ConnectionManager(object):
         LOG.info("Begin getAvailableServers")
 
         # Clone the credentials
-        credentials = self.credentials.get_credentials()
+        credentials = self.credentials.get()
         found_servers = self._find_servers(self._server_discovery())
 
         if not found_servers and not credentials['Servers']:  # back out right away, no point in continuing
@@ -78,44 +66,64 @@ class ConnectionManager(object):
             return list()
 
         servers = list(credentials['Servers'])
-        self._merge_servers(servers, found_servers)
 
-        try:
-            servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True)
-        except TypeError:
-            servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True)
+        #Merges servers we already knew with newly found ones
+        for found_server in found_servers: 
+            try:
+                self.credentials.add_update_server(servers, found_server)
+            except KeyError:
+                continue
 
+        servers.sort(key=itemgetter('DateLastAccessed'), reverse=True)
         credentials['Servers'] = servers
-        self.credentials.get_credentials(credentials)
+        self.credentials.set(credentials)
 
         return servers
 
-    def login(self, server, username, password=None, clear=True, options={}):
+    def login(self, server_url, username, password=None):
 
         if not username:
             raise AttributeError("username cannot be empty")
 
-        if not server:
-            raise AttributeError("server cannot be empty")
+        if not server_url:
+            raise AttributeError("server url cannot be empty")
 
-        try:
-            request = {
-                'type': "POST",
-                'url': self.get_jellyfin_url(server, "Users/AuthenticateByName"),
-                'json': {
-                    'Username': username,
-                    'Pw': password or ""
-                }
-            }
+        data = self.API.login(server_url, username, password) # returns empty dict on failure
 
-            result = self._request_url(request, False)
-        except Exception as error:  # Failed to login
-            LOG.exception(error)
-            return False
+        if not data:
+            LOG.info("Failed to login as `"+username+"`")
+            return {}
+        
+        LOG.info("Succesfully logged in as %s" % (username))
+        ## TODO Change when moving to database storage of server details
+        credentials = self.credentials.get()
+
+        self.config.data['auth.user_id'] = data['User']['Id']
+        self.config.data['auth.token'] = data['AccessToken']
+
+        for server in credentials['Servers']:
+            if server['Id'] == data['ServerId']:
+                found_server = server
+                break
         else:
-            self._on_authenticated(result, options)
+            return  # No server found
+
+        found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
+        found_server['UserId'] = data['User']['Id']
+        found_server['AccessToken'] = data['AccessToken']
+
+        self.credentials.add_update_server(credentials['Servers'], found_server)
+
+        info = {
+            'Id': data['User']['Id'],
+            'IsSignedInOffline': True
+        }
+        self.credentials.add_update_user(server, info)
+
+        self.credentials.set_credentials(credentials)
+        
+        return data
 
-        return result
 
     def connect_to_address(self, address, options={}):
 
@@ -125,7 +133,7 @@ class ConnectionManager(object):
         address = self._normalize_address(address)
 
         try:
-            public_info = self._try_connect(address, options=options)
+            public_info = self.API.get_public_info(address)
             LOG.info("connectToAddress %s succeeded", address)
             server = {
                 'address': address,
@@ -146,26 +154,41 @@ class ConnectionManager(object):
     def connect_to_server(self, server, options={}):
 
         LOG.info("begin connectToServer")
-        timeout = self.timeout
 
         try:
-            result = self._try_connect(server['address'], timeout, options)
+            result = self.API.get_public_info(server.get('address'))
+
+            if not result:
+                LOG.error("Failed to connect to server: %s" % server.get('address'))
+                return { 'State': CONNECTION_STATE['Unavailable'] }
+
             LOG.info("calling onSuccessfulConnection with server %s", server.get('Name'))
-            credentials = self.credentials.get_credentials()
+
+            credentials = self.credentials.get()
             return self._after_connect_validated(server, credentials, result, True, options)
 
         except Exception as e:
-            LOG.info("Failing server connection. ERROR msg: {}".format(e))
+            LOG.error(traceback.format_exc())
+            LOG.error("Failing server connection. ERROR msg: {}".format(e))
             return { 'State': CONNECTION_STATE['Unavailable'] }
 
     def connect(self, options={}):
         LOG.info("Begin connect")
-        return self._connect_to_servers(self.get_available_servers(), options)
 
-    def jellyfin_user_id(self):
-        return self.get_server_info(self.server_id)['UserId']
+        servers = self.get_available_servers()
+        LOG.info("connect has %s servers", len(servers))
 
-    def jellyfin_token(self):
+        if not (len(servers)): #No servers provided
+            return {
+                'State': ['ServerSelection']
+            }
+
+        result = self.connect_to_server(servers[0], options)
+        LOG.debug("resolving connect with result: %s", result)
+
+        return result
+
+    def jellyfin_token(self): ## Called once monitor.py#163
         return self.get_server_info(self.server_id)['AccessToken']
 
     def get_server_info(self, server_id):
@@ -174,88 +197,15 @@ class ConnectionManager(object):
             LOG.info("server_id is empty")
             return {}
 
-        servers = self.credentials.get_credentials()['Servers']
+        servers = self.credentials.get()['Servers']
 
         for server in servers:
             if server['Id'] == server_id:
                 return server
 
-    def get_public_users(self):
+    def get_public_users(self): ## Required in connect.py#L213
         return self.client.jellyfin.get_public_users()
 
-    def get_jellyfin_url(self, base, handler):
-        return "%s/%s" % (base, handler)
-
-    def _request_url(self, request, headers=True):
-
-        request['timeout'] = request.get('timeout') or self.timeout
-        if headers:
-            self._get_headers(request)
-
-        try:
-            return self.http.request(request)
-        except Exception as error:
-            LOG.exception(error)
-            raise
-
-    def _add_app_info(self):
-        return "%s/%s" % (self.config.data['app.name'], self.config.data['app.version'])
-
-    def _get_headers(self, request):
-
-        headers = request.setdefault('headers', {})
-
-        if request.get('dataType') == "json":
-            headers['Accept'] = "application/json"
-            request.pop('dataType')
-
-        headers['X-Application'] = self._add_app_info()
-        headers['Content-type'] = request.get(
-            'contentType',
-            'application/x-www-form-urlencoded; charset=UTF-8'
-        )
-
-    def _connect_to_servers(self, servers, options):
-
-        LOG.info("Begin connectToServers, with %s servers", len(servers))
-        result = {}
-
-        if len(servers) == 1:
-            result = self.connect_to_server(servers[0], options)
-            LOG.debug("resolving connectToServers with result['State']: %s", result)
-
-            return result
-
-        first_server = self._get_last_used_server()
-        # See if we have any saved credentials and can auto sign in
-        if first_server is not None and first_server['DateLastAccessed'] != "2001-01-01T00:00:00Z":
-            result = self.connect_to_server(first_server, options)
-
-            if result['State'] in (CONNECTION_STATE['SignedIn'], CONNECTION_STATE['Unavailable']):
-                return result
-
-        # Return loaded credentials if exists
-        credentials = self.credentials.get_credentials()
-
-        return {
-            'Servers': servers,
-            'State': result.get('State') or CONNECTION_STATE['ServerSelection'],
-        }
-
-    def _try_connect(self, url, timeout=None, options={}):
-
-        url = self.get_jellyfin_url(url, "system/info/public")
-        LOG.info("tryConnect url: %s", url)
-
-        return self._request_url({
-            'type': "GET",
-            'url': url,
-            'dataType': "json",
-            'timeout': timeout,
-            'verify': options.get('ssl'),
-            'retry': False
-        })
-
     def _server_discovery(self):
 
         MULTI_GROUP = ("<broadcast>", 7359)
@@ -277,6 +227,7 @@ class ConnectionManager(object):
         try:
             sock.sendto(MESSAGE, MULTI_GROUP)
         except Exception as error:
+            LOG.exception(traceback.format_exc())
             LOG.exception(error)
             return servers
 
@@ -290,33 +241,10 @@ class ConnectionManager(object):
                 return servers
 
             except Exception as e:
+                LOG.error(traceback.format_exc())
                 LOG.exception("Error trying to find servers: %s", e)
                 return servers
 
-    def _get_last_used_server(self):
-
-        servers = self.credentials.get_credentials()['Servers']
-
-        if not len(servers):
-            return
-
-        try:
-            servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True)
-        except TypeError:
-            servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True)
-
-        return servers[0]
-
-    def _merge_servers(self, list1, list2):
-
-        for i in range(0, len(list2), 1):
-            try:
-                self.credentials.add_update_server(list1, list2[i])
-            except KeyError:
-                continue
-
-        return list1
-
     def _find_servers(self, found_servers):
 
         servers = []
@@ -336,7 +264,7 @@ class ConnectionManager(object):
             return servers
 
     # TODO: Make IPv6 compatable
-    def _convert_endpoint_address_to_manual_address(self, info):
+    def _convert_endpoint_address_to_manual_address(self, info): # Called once ^^ right there 
 
         if info.get('Address') and info.get('EndpointAddress'):
             address = info['EndpointAddress'].split(':')[0]
@@ -373,14 +301,6 @@ class ConnectionManager(object):
 
         return url.url
 
-    def _save_user_info_into_credentials(self, server, user):
-
-        info = {
-            'Id': user['Id'],
-            'IsSignedInOffline': True
-        }
-        self.credentials.add_update_user(server, info)
-
     def _after_connect_validated(self, server, credentials, system_info, verify_authentication, options):
         if options.get('enableAutoLogin') is False:
 
@@ -388,22 +308,24 @@ class ConnectionManager(object):
             self.config.data['auth.token'] = server.pop('AccessToken', None)
 
         elif verify_authentication and server.get('AccessToken'):
-            if self._validate_authentication(server, options) is not False:
-
+            system_info = self.API.validate_authentication_token(server)
+            if system_info:
+                
+                self._update_server_info(server, system_info)
                 self.config.data['auth.user_id'] = server['UserId']
                 self.config.data['auth.token'] = server['AccessToken']
+                
                 return self._after_connect_validated(server, credentials, system_info, False, options)
 
+            server['UserId'] = None
+            server['AccessToken'] = None
             return { 'State': CONNECTION_STATE['Unavailable'] }
 
         self._update_server_info(server, system_info)
-        self.server_version = system_info['Version']
-
-        if options.get('updateDateLastAccessed') is not False:
-            server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
 
+        server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
         self.credentials.add_update_server(credentials['Servers'], server)
-        self.credentials.get_credentials(credentials)
+        self.credentials.set(credentials)
         self.server_id = server['Id']
 
         # Update configs
@@ -419,28 +341,7 @@ class ConnectionManager(object):
         result['State'] = CONNECTION_STATE['SignedIn'] if server.get('AccessToken') else CONNECTION_STATE['ServerSignIn']
         # Connected
         return result
-
-    def _validate_authentication(self, server, options={}):
-
-        try:
-            system_info = self._request_url({
-                'type': "GET",
-                'url': self.get_jellyfin_url(server['address'], "System/Info"),
-                'verify': options.get('ssl'),
-                'dataType': "json",
-                'headers': {
-                    'X-MediaBrowser-Token': server['AccessToken']
-                }
-            })
-            self._update_server_info(server, system_info)
-        except Exception as error:
-            LOG.exception(error)
-
-            server['UserId'] = None
-            server['AccessToken'] = None
-
-            return False
-
+        
     def _update_server_info(self, server, system_info):
 
         if server is None or system_info is None:
@@ -450,30 +351,4 @@ class ConnectionManager(object):
         server['Id'] = system_info['Id']
 
         if system_info.get('address'):
-            server['address'] = system_info['address']
-
-        ## Finish updating server info
-
-    def _on_authenticated(self, result, options={}):
-
-        credentials = self.credentials.get_credentials()
-
-        self.config.data['auth.user_id'] = result['User']['Id']
-        self.config.data['auth.token'] = result['AccessToken']
-
-        for server in credentials['Servers']:
-            if server['Id'] == result['ServerId']:
-                found_server = server
-                break
-        else:
-            return  # No server found
-
-        if options.get('updateDateLastAccessed') is not False:
-            found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
-
-        found_server['UserId'] = result['User']['Id']
-        found_server['AccessToken'] = result['AccessToken']
-
-        self.credentials.add_update_server(credentials['Servers'], found_server)
-        self._save_user_info_into_credentials(found_server, result['User'])
-        self.credentials.get_credentials(credentials)
+            server['address'] = system_info['address']
\ No newline at end of file

From e5cde15ad2ef1b3e1d9287d2e77b6918a0bc08fc Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Fri, 21 Feb 2020 00:50:32 +0000
Subject: [PATCH 2/8] Added a missing file

---
 jellyfin_kodi/jellyfin/credentials.py | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/jellyfin_kodi/jellyfin/credentials.py b/jellyfin_kodi/jellyfin/credentials.py
index 2eeacb98..a9cca5d5 100644
--- a/jellyfin_kodi/jellyfin/credentials.py
+++ b/jellyfin_kodi/jellyfin/credentials.py
@@ -24,12 +24,8 @@ class Credentials(object):
     def set_credentials(self, credentials):
         self.credentials = credentials
 
-    def get_credentials(self, data=None):
-
-        if data is not None:
-            self._set(data)
-
-        return self._get()
+    def get_credentials(self):
+        return self.get()
 
     def _ensure(self):
 
@@ -46,12 +42,12 @@ class Credentials(object):
             LOG.debug("credentials initialized with: %s", self.credentials)
             self.credentials['Servers'] = self.credentials.setdefault('Servers', [])
 
-    def _get(self):
+    def get(self):
         self._ensure()
 
         return self.credentials
 
-    def _set(self, data):
+    def set(self, data):
 
         if data:
             self.credentials.update(data)
@@ -128,4 +124,4 @@ class Credentials(object):
             # Known Kodi/python error
             date_obj = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
 
-        return date_obj
+        return date_obj
\ No newline at end of file

From 298c8b6f25551824654d1e527e3cf690f4347874 Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Fri, 6 Mar 2020 22:57:13 +0000
Subject: [PATCH 3/8] Renamed function for better clarity

---
 jellyfin_kodi/jellyfin/api.py                | 2 +-
 jellyfin_kodi/jellyfin/connection_manager.py | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index 049e6035..fbae8fc1 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -422,7 +422,7 @@ class API(object):
                 return response.json()
             else:
                 LOG.error("Failed to login to server with status code: "+str(response.status_code))
-                LOG.error(response.text)
+                LOG.error("Server Response:\n"+str(response.content))
                 LOG.debug(headers)
 
                 return {}
diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py
index e66a763f..afed1717 100644
--- a/jellyfin_kodi/jellyfin/connection_manager.py
+++ b/jellyfin_kodi/jellyfin/connection_manager.py
@@ -59,7 +59,7 @@ class ConnectionManager(object):
 
         # Clone the credentials
         credentials = self.credentials.get()
-        found_servers = self._find_servers(self._server_discovery())
+        found_servers = self.process_found_servers(self._server_discovery())
 
         if not found_servers and not credentials['Servers']:  # back out right away, no point in continuing
             LOG.info("Found no servers")
@@ -245,7 +245,7 @@ class ConnectionManager(object):
                 LOG.exception("Error trying to find servers: %s", e)
                 return servers
 
-    def _find_servers(self, found_servers):
+    def process_found_servers(self, found_servers):
 
         servers = []
 

From 36cd765c215e49a2c7cc26f63621e065fa47832c Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Sun, 8 Mar 2020 16:29:15 +0000
Subject: [PATCH 4/8] Updated debug logging for sanitising user data

---
 jellyfin_kodi/jellyfin/api.py                |  4 ++--
 jellyfin_kodi/jellyfin/connection_manager.py |  2 +-
 jellyfin_kodi/library.py                     | 25 +++++++++++++++++++-
 resources/settings.xml                       |  2 +-
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index fbae8fc1..66442514 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -398,8 +398,8 @@ class API(object):
         if not settings('sslverify'):
             request_settings["verify"] = False
 
-        LOG.info("Sending %s request to %s" % (method, url))
-        LOG.debug(request_settings)
+        LOG.info("Sending %s request to %s" % (method, path))
+        LOG.debug(request_settings['timeout'], request_settings['headers'])
 
         return request_method(url, **request_settings)
 
diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py
index afed1717..b1d76eee 100644
--- a/jellyfin_kodi/jellyfin/connection_manager.py
+++ b/jellyfin_kodi/jellyfin/connection_manager.py
@@ -173,6 +173,7 @@ class ConnectionManager(object):
             return { 'State': CONNECTION_STATE['Unavailable'] }
 
     def connect(self, options={}):
+
         LOG.info("Begin connect")
 
         servers = self.get_available_servers()
@@ -207,7 +208,6 @@ class ConnectionManager(object):
         return self.client.jellyfin.get_public_users()
 
     def _server_discovery(self):
-
         MULTI_GROUP = ("<broadcast>", 7359)
         MESSAGE = b"who is JellyfinServer?"
 
diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py
index 5ca1bdd9..790def45 100644
--- a/jellyfin_kodi/library.py
+++ b/jellyfin_kodi/library.py
@@ -127,6 +127,13 @@ class Library(threading.Thread):
     @stop()
     def service(self):
 
+        from kodi_six import xbmc, xbmcaddon
+        from datetime import datetime
+        path = xbmcaddon.Addon(id='plugin.video.jellyfin').getAddonInfo('path')
+        from pyinstrument import Profiler
+
+        profiler = Profiler()
+        profiler.start()
         ''' If error is encountered, it will rerun this function.
             Start new "daemon threads" to process library updates.
             (actual daemon thread is not supported in Kodi)
@@ -146,8 +153,17 @@ class Library(threading.Thread):
             self.worker_userdata()
             self.worker_remove()
             self.worker_notify()
-
+        profiler.stop()
+        with open(str(path)+'/output-'+str(datetime.now())+'.html', 'w+') as output:
+            output.write(profiler.output_html().encode('utf-8'))
         if self.pending_refresh:
+            from kodi_six import xbmc, xbmcaddon
+            from datetime import datetime
+            path = xbmcaddon.Addon(id='plugin.video.jellyfin').getAddonInfo('path')
+            from pyinstrument import Profiler
+
+            profiler = Profiler()
+            profiler.start()
             window('jellyfin_sync.bool', True)
 
             if self.total_updates > self.progress_display:
@@ -169,6 +185,11 @@ class Library(threading.Thread):
                 self.screensaver = get_screensaver()
                 set_screensaver(value="")
 
+            profiler.stop()
+            with open(str(path)+'/output-'+str(datetime.now())+'.html', 'w+') as output:
+                output.write(profiler.output_html().encode('utf-8'))
+
+
         if (self.pending_refresh and not self.download_threads and not self.writer_threads['updated'] and not self.writer_threads['userdata'] and not self.writer_threads['removed']):
             self.pending_refresh = False
             self.save_last_sync()
@@ -194,6 +215,8 @@ class Library(threading.Thread):
                 if xbmc.getCondVisibility('Window.IsMedia'):
                     xbmc.executebuiltin('Container.Refresh')
 
+            
+
     def stop_client(self):
         self.stop_thread = True
 
diff --git a/resources/settings.xml b/resources/settings.xml
index 661da59b..7f1ff7f6 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -85,7 +85,7 @@
 	</category>
 	
 	<category label="30022"><!-- Advanced -->
-		<setting label="30004" id="logLevel" type="enum" values="Disabled|Info|Debug" default="1" />
+		<setting label="30004" id="logLevel" type="enum" values="Disabled|Info|Debug" default="2" />
 		<setting label="33164" id="maskInfo" type="bool" default="true" />
 		<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.jellyfin?mode=reset)" option="close" />
 		<setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.jellyfin?mode=deviceid)" option="close" />

From 068a842184473e342d7d4278a68a1eb7042e081a Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Sun, 8 Mar 2020 18:07:43 +0000
Subject: [PATCH 5/8] Removed profilling code accidentally commited

---
 jellyfin_kodi/jellyfin/api.py |  3 ++-
 jellyfin_kodi/library.py      | 24 ++----------------------
 resources/settings.xml        |  2 +-
 3 files changed, 5 insertions(+), 24 deletions(-)

diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index 66442514..8c704d27 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -399,7 +399,8 @@ class API(object):
             request_settings["verify"] = False
 
         LOG.info("Sending %s request to %s" % (method, path))
-        LOG.debug(request_settings['timeout'], request_settings['headers'])
+        LOG.debug(request_settings['timeout'])
+        LOG.debug(request_settings['headers'])
 
         return request_method(url, **request_settings)
 
diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py
index 790def45..a1ca26a0 100644
--- a/jellyfin_kodi/library.py
+++ b/jellyfin_kodi/library.py
@@ -127,13 +127,6 @@ class Library(threading.Thread):
     @stop()
     def service(self):
 
-        from kodi_six import xbmc, xbmcaddon
-        from datetime import datetime
-        path = xbmcaddon.Addon(id='plugin.video.jellyfin').getAddonInfo('path')
-        from pyinstrument import Profiler
-
-        profiler = Profiler()
-        profiler.start()
         ''' If error is encountered, it will rerun this function.
             Start new "daemon threads" to process library updates.
             (actual daemon thread is not supported in Kodi)
@@ -153,17 +146,9 @@ class Library(threading.Thread):
             self.worker_userdata()
             self.worker_remove()
             self.worker_notify()
-        profiler.stop()
-        with open(str(path)+'/output-'+str(datetime.now())+'.html', 'w+') as output:
-            output.write(profiler.output_html().encode('utf-8'))
-        if self.pending_refresh:
-            from kodi_six import xbmc, xbmcaddon
-            from datetime import datetime
-            path = xbmcaddon.Addon(id='plugin.video.jellyfin').getAddonInfo('path')
-            from pyinstrument import Profiler
 
-            profiler = Profiler()
-            profiler.start()
+        if self.pending_refresh:
+
             window('jellyfin_sync.bool', True)
 
             if self.total_updates > self.progress_display:
@@ -185,11 +170,6 @@ class Library(threading.Thread):
                 self.screensaver = get_screensaver()
                 set_screensaver(value="")
 
-            profiler.stop()
-            with open(str(path)+'/output-'+str(datetime.now())+'.html', 'w+') as output:
-                output.write(profiler.output_html().encode('utf-8'))
-
-
         if (self.pending_refresh and not self.download_threads and not self.writer_threads['updated'] and not self.writer_threads['userdata'] and not self.writer_threads['removed']):
             self.pending_refresh = False
             self.save_last_sync()
diff --git a/resources/settings.xml b/resources/settings.xml
index 7f1ff7f6..661da59b 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -85,7 +85,7 @@
 	</category>
 	
 	<category label="30022"><!-- Advanced -->
-		<setting label="30004" id="logLevel" type="enum" values="Disabled|Info|Debug" default="2" />
+		<setting label="30004" id="logLevel" type="enum" values="Disabled|Info|Debug" default="1" />
 		<setting label="33164" id="maskInfo" type="bool" default="true" />
 		<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.jellyfin?mode=reset)" option="close" />
 		<setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.jellyfin?mode=deviceid)" option="close" />

From 48ed8ad74cefade24a6940844bfa13a738acdf53 Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Thu, 12 Mar 2020 22:12:06 +0000
Subject: [PATCH 6/8] Implemented Suggestions

---
 jellyfin_kodi/jellyfin/api.py                | 30 +++++---------------
 jellyfin_kodi/jellyfin/connection_manager.py | 14 ++++-----
 jellyfin_kodi/jellyfin/credentials.py        |  2 +-
 jellyfin_kodi/library.py                     |  3 --
 4 files changed, 15 insertions(+), 34 deletions(-)

diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index 8c704d27..f3042cbc 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 from __future__ import division, absolute_import, print_function, unicode_literals
 import requests
 import json
@@ -43,7 +44,6 @@ class API(object):
         self.config = client.config
         self.default_timeout = 5
 
-
     def _http(self, action, url, request={}):
         request.update({'type': action, 'handler': url})
 
@@ -353,12 +353,6 @@ class API(object):
             'DeviceId': device_id
         })
 
-
-    #################################################################################################
-
-    # New API calls 
-
-    #################################################################################################
     def get_default_headers(self):
         auth = "MediaBrowser "
         auth += "Client=%s, " % self.config.data['app.name'].encode('utf-8')
@@ -376,17 +370,7 @@ class API(object):
             "x-emby-authorization": auth
         }
 
-    def send_request(self, url, path, method=None, timeout=None, headers=None, data=None):
-        if not timeout:
-            LOG.debug("No timeout set, using default")
-            timeout = self.default_timeout
-        if not headers:
-            LOG.debug("No headers set, using default")
-            headers = self.get_default_headers()
-        if not method:
-            LOG.debug("No method set, using default")
-            method = "get"  #Defaults to get request if none specified
-
+    def send_request(self, url, path, method="get", timeout=self.default_timeout, headers=self.get_default_headers(), data=None):
         request_method = getattr(requests, method.lower())
         url = "%s/%s" % (url, path)
         request_settings = {
@@ -405,11 +389,11 @@ class API(object):
         return request_method(url, **request_settings)
 
 
-    def login(self, server_url, username, password):
+    def login(self, server_url, username, password=""):
         path = "Users/AuthenticateByName"
         authData = {
                     "username": username,
-                    "Pw": password or ""
+                    "Pw": password
                 }
 
         headers = self.get_default_headers()
@@ -422,12 +406,12 @@ class API(object):
             if response.status_code == 200:
                 return response.json()
             else:
-                LOG.error("Failed to login to server with status code: "+str(response.status_code))
-                LOG.error("Server Response:\n"+str(response.content))
+                LOG.error("Failed to login to server with status code: " + str(response.status_code))
+                LOG.error("Server Response:\n" + str(response.content))
                 LOG.debug(headers)
 
                 return {}
-        except Exception as e: #Find exceptions for likely cases i.e, server timeout, etc
+        except Exception as e: # Find exceptions for likely cases i.e, server timeout, etc
             LOG.error(e) 
 
         return {}
diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py
index b1d76eee..aa9c0f57 100644
--- a/jellyfin_kodi/jellyfin/connection_manager.py
+++ b/jellyfin_kodi/jellyfin/connection_manager.py
@@ -44,7 +44,7 @@ class ConnectionManager(object):
 
         self.API = API(client)
 
-    def revoke_token(self): #Called once in http#L130
+    def revoke_token(self):
 
         LOG.info("revoking token")
 
@@ -67,7 +67,7 @@ class ConnectionManager(object):
 
         servers = list(credentials['Servers'])
 
-        #Merges servers we already knew with newly found ones
+        # Merges servers we already knew with newly found ones
         for found_server in found_servers: 
             try:
                 self.credentials.add_update_server(servers, found_server)
@@ -95,7 +95,7 @@ class ConnectionManager(object):
             return {}
         
         LOG.info("Succesfully logged in as %s" % (username))
-        ## TODO Change when moving to database storage of server details
+        # TODO Change when moving to database storage of server details
         credentials = self.credentials.get()
 
         self.config.data['auth.user_id'] = data['User']['Id']
@@ -179,7 +179,7 @@ class ConnectionManager(object):
         servers = self.get_available_servers()
         LOG.info("connect has %s servers", len(servers))
 
-        if not (len(servers)): #No servers provided
+        if not (len(servers)): # No servers provided
             return {
                 'State': ['ServerSelection']
             }
@@ -189,7 +189,7 @@ class ConnectionManager(object):
 
         return result
 
-    def jellyfin_token(self): ## Called once monitor.py#163
+    def jellyfin_token(self): # Called once monitor.py#163
         return self.get_server_info(self.server_id)['AccessToken']
 
     def get_server_info(self, server_id):
@@ -204,7 +204,7 @@ class ConnectionManager(object):
             if server['Id'] == server_id:
                 return server
 
-    def get_public_users(self): ## Required in connect.py#L213
+    def get_public_users(self):
         return self.client.jellyfin.get_public_users()
 
     def _server_discovery(self):
@@ -264,7 +264,7 @@ class ConnectionManager(object):
             return servers
 
     # TODO: Make IPv6 compatable
-    def _convert_endpoint_address_to_manual_address(self, info): # Called once ^^ right there 
+    def _convert_endpoint_address_to_manual_address(self, info):
 
         if info.get('Address') and info.get('EndpointAddress'):
             address = info['EndpointAddress'].split(':')[0]
diff --git a/jellyfin_kodi/jellyfin/credentials.py b/jellyfin_kodi/jellyfin/credentials.py
index a9cca5d5..4e28e2c4 100644
--- a/jellyfin_kodi/jellyfin/credentials.py
+++ b/jellyfin_kodi/jellyfin/credentials.py
@@ -124,4 +124,4 @@ class Credentials(object):
             # Known Kodi/python error
             date_obj = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
 
-        return date_obj
\ No newline at end of file
+        return date_obj
diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py
index a1ca26a0..5ca1bdd9 100644
--- a/jellyfin_kodi/library.py
+++ b/jellyfin_kodi/library.py
@@ -148,7 +148,6 @@ class Library(threading.Thread):
             self.worker_notify()
 
         if self.pending_refresh:
-
             window('jellyfin_sync.bool', True)
 
             if self.total_updates > self.progress_display:
@@ -195,8 +194,6 @@ class Library(threading.Thread):
                 if xbmc.getCondVisibility('Window.IsMedia'):
                     xbmc.executebuiltin('Container.Refresh')
 
-            
-
     def stop_client(self):
         self.stop_thread = True
 

From a044c1fd291f2ec369ba2d536bcd481da4c1bd6d Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Sat, 14 Mar 2020 21:53:15 +0000
Subject: [PATCH 7/8] Fixed API parameter bad optimisation

---
 jellyfin_kodi/jellyfin/api.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index f3042cbc..db074e28 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -370,12 +370,12 @@ class API(object):
             "x-emby-authorization": auth
         }
 
-    def send_request(self, url, path, method="get", timeout=self.default_timeout, headers=self.get_default_headers(), data=None):
+    def send_request(self, url, path, method="get", timeout=None, headers=None, data=None):
         request_method = getattr(requests, method.lower())
         url = "%s/%s" % (url, path)
         request_settings = {
-            "timeout": timeout,
-            "headers": headers,
+            "timeout": timeout or self.default_timeout,
+            "headers": headers or self.get_default_headers(),
             "data": data
         }
 

From 37a836dd575681e952e8c1ec6912ac1635647614 Mon Sep 17 00:00:00 2001
From: Abby Gourlay <abbygourlay98@gmail.com>
Date: Mon, 16 Mar 2020 12:36:06 +0000
Subject: [PATCH 8/8] Changed function return type

---
 jellyfin_kodi/jellyfin/connection_manager.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py
index aa9c0f57..72501a8d 100644
--- a/jellyfin_kodi/jellyfin/connection_manager.py
+++ b/jellyfin_kodi/jellyfin/connection_manager.py
@@ -106,7 +106,7 @@ class ConnectionManager(object):
                 found_server = server
                 break
         else:
-            return  # No server found
+            return {} # No server found
 
         found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
         found_server['UserId'] = data['User']['Id']