diff --git a/resources/lib/DownloadUtils.py b/resources/lib/DownloadUtils.py
index caccd109..e8361e1c 100644
--- a/resources/lib/DownloadUtils.py
+++ b/resources/lib/DownloadUtils.py
@@ -1,341 +1,225 @@
 import xbmc
 import xbmcgui
 import xbmcaddon
-import urllib
-import urllib2
-import httplib
-import hashlib
-import StringIO
-import gzip
-import sys
-import inspect
-import json as json
-from random import randrange
-from uuid import uuid4 as uuid4
+
+import requests
+import json
+import logging
+
+import Utils as utils
 from ClientInformation import ClientInformation
-import encodings
-import time
-import traceback
+
+# Disable requests logging
+logging.getLogger("requests").setLevel(logging.WARNING)
 
 class DownloadUtils():
-
-    WINDOW = xbmcgui.Window(10000)
-    logLevel = 0
-    addonSettings = None
-    getString = None
-    LogCalls = False
-    TrackLog = ""
-    TotalUrlCalls = 0
-
-    def __init__(self, *args):
-        addonId = ClientInformation().getAddonId()
-        self.addonSettings = xbmcaddon.Addon(id=addonId)
-        self.addon = xbmcaddon.Addon(id=addonId)
-        self.getString = self.addonSettings.getLocalizedString
-        level = self.addonSettings.getSetting('logLevel')        
-        self.logLevel = 0
-        if(level != None and level != ""):
-            self.logLevel = int(level)
-        if(self.logLevel == 2):
-            self.LogCalls = True
-
-    def logMsg(self, msg, level = 1):
-        if(self.logLevel >= level):
-            try:
-                xbmc.log("emby DownloadUtils -> " + str(msg))
-            except UnicodeEncodeError:
-                try:
-                    xbmc.log("emby DownloadUtils -> " + str(msg.encode('utf-8')))
-                except: pass
-
-    def getServer(self, prefix=True):
-        
-        WINDOW = self.WINDOW
-        username = WINDOW.getProperty("currUser")
-        
-        if prefix:
-            server = WINDOW.getProperty("server%s" % username)
-        else:
-            server = WINDOW.getProperty("server_%s" % username)
-
-        return server
-
-    def getUserId(self, suppress=True):
-
-        WINDOW = xbmcgui.Window( 10000 )
-        self.addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
-        port = self.addonSettings.getSetting('port')
-        host = self.addonSettings.getSetting('ipaddress')
-        userName = self.addonSettings.getSetting('username')
-        
-        userid = WINDOW.getProperty("userid" + userName)
-
-        if(userid != None and userid != ""):
-            self.logMsg("DownloadUtils -> Returning saved (WINDOW) UserID : " + userid + "UserName: " + userName,2)
-            return userid
-            
-        userid = self.addonSettings.getSetting("userid" + userName)
-        if(userid != None and userid != ""):
-            WINDOW.setProperty("userid" + userName, userid)
-            self.logMsg("DownloadUtils -> Returning saved (SETTING) UserID : " + userid + "UserName: " + userName,2)
-            return userid
     
-        self.logMsg("Looking for user name: " + userName)
+    # Borg - multiple instances, shared state
+    _shared_state = {}
+    clientInfo = ClientInformation()
 
-        authOk = self.authenticate()
-        if(authOk == ""):
-            if(suppress == False):
-                xbmcgui.Dialog().ok(self.getString(30044), self.getString(30044))
-            return ""
+    addonName = clientInfo.getAddonName()
+    addonId = clientInfo.getAddonId()
+    addon = xbmcaddon.Addon(id=addonId)
+    WINDOW = xbmcgui.Window(10000)
 
-        userid = WINDOW.getProperty("userid" + userName)
-        if(userid == "" and suppress == False):
-            xbmcgui.Dialog().ok(self.getString(30045),self.getString(30045))
+    # Requests session
+    s = None
+    timeout = 30
 
-        self.logMsg("userid : " + userid)
-        self.postcapabilities()
-        
-        return userid
-        
-    def postcapabilities(self):
-        self.logMsg("postcapabilities called")
-        
-        # Set Capabilities
-        server = self.getServer()
-        clientInfo = ClientInformation()
-        machineId = clientInfo.getMachineId()
-        
-        # get session id
-        url = "%s/mediabrowser/Sessions?DeviceId=%s&format=json" % (server, machineId)
-        self.logMsg("Session URL : " + url);
-        jsonData = self.downloadUrl(url)
-        self.logMsg("Session JsonData : " + jsonData)
-        result = json.loads(jsonData)
-        self.logMsg("Session JsonData : " + str(result))
-        sessionId = result[0].get("Id")
-        self.logMsg("Session Id : " + str(sessionId))
-        
-        # post capability data
-        #playableMediaTypes = "Audio,Video,Photo"
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+        self.className = self.__class__.__name__
+
+    def logMsg(self, msg, lvl=1):
+
+        utils.logMsg("%s %s" % (self.addonName, self.className), str(msg), int(lvl))
+
+    def setUsername(self, username):
+        # Reserved for UserClient only
+        self.username = username
+        self.logMsg("Set username: %s" % username, 1)
+
+    def setUserId(self, userId):
+        # Reserved for UserClient only
+        self.userId = userId
+        self.logMsg("Set userId: %s" % userId, 2)
+
+    def setServer(self, server):
+        # Reserved for UserClient only
+        self.server = server
+        self.logMsg("Set server: %s" % server, 2)
+
+    def setToken(self, token):
+        # Reserved for UserClient only
+        self.token = token
+        self.logMsg("Set token: %s" % token, 2)
+
+    def postCapabilities(self, deviceId):
+
+        # Get sessionId
+        url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
+        result = self.downloadUrl(url)
+        # sessionId result
+        self.logMsg("Session result: %s" % result, 1)
+        self.sessionId = result[0][u'Id']
+
+        # Settings for capabilities
         playableMediaTypes = "Audio,Video"
-        #supportedCommands = "Play,Playstate,DisplayContent,GoHome,SendString,GoToSettings,DisplayMessage,PlayNext"
         supportedCommands = "Play,Playstate,SendString,DisplayMessage,PlayNext"
+
+        # Post settings to sessionId
+        url = "{server}/mediabrowser/Sessions/Capabilities?Id=%s&PlayableMediaTypes=%s&SupportedCommands=%s&SupportsMediaControl=True" % (self.sessionId, playableMediaTypes, supportedCommands)
+        data = {}
+        self.logMsg("Capabilities URL: %s" % url, 2)
+        self.logMsg("PostData: %s" % data, 2)
+
+        self.downloadUrl(url, postBody=data, type="POST")
+        self.logMsg("Posted capabilities to sessionId: %s" % self.sessionId, 1)
+
+    def startSession(self):
+
+        self.deviceId = self.clientInfo.getMachineId()
+
+        # User is identified from this point
+        # Attach authenticated header to the session
+        cert = None
+        header = self.getHeader()
+
+        if self.addon.getSetting('sslcert') != "None":
+            # If user uses HTTPS and has a custom client certificate
+            cert = self.addon.getSetting('sslcert')
         
-        url = "%s/mediabrowser/Sessions/Capabilities?Id=%s&PlayableMediaTypes=%s&SupportedCommands=%s&SupportsMediaControl=True" % (server, sessionId, playableMediaTypes, supportedCommands)
-        postData = {}
-        #postData["Id"] = sessionId;
-        #postData["PlayableMediaTypes"] = "Video";
-        #postData["SupportedCommands"] = "MoveUp";
-        stringdata = json.dumps(postData)
-        self.logMsg("Capabilities URL : " + url);
-        self.logMsg("Capabilities Data : " + stringdata)
-        
-        self.downloadUrl(url, postBody=stringdata, type="POST")
+        # Start session
+        self.s = requests.Session()
+        self.s.headers.update(header)
+        self.s.cert = cert
+        # Retry connections to the server
+        self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
+        self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
+
+        self.logMsg("Requests session started on: %s" % self.server)
+        self.postCapabilities(self.deviceId)
 
     def imageUrl(self, id, type, index, width, height):
-    
-        server = self.getServer()
-        
-        return "%s/mediabrowser/Items/%s/Images/%s?MaxWidth=%s&MaxHeight=%s&Index=%s" % (server, id, type, width, height, index)
-    
-    def getAuthHeader(self, authenticate=True):
-        clientInfo = ClientInformation()
-        txt_mac = clientInfo.getMachineId()
+        # To move to API.py
+        return "%s/mediabrowser/Items/%s/Images/%s?MaxWidth=%s&MaxHeight=%s&Index=%s" % (self.server, id, type, width, height, index)
+
+    def getHeader(self, authenticate=True):
+
+        clientInfo = self.clientInfo
+
+        deviceName = clientInfo.getDeviceName()
+        deviceId = clientInfo.getMachineId()
         version = clientInfo.getVersion()
-        
-        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 + "\""
-            headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString}        
-            return headers
+        if not authenticate:
+            # If user is not authenticated
+            auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version)
+            header = {"Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": auth}      
+            
+            self.logMsg("Header: %s" % header, 2)
+            return header
+        
         else:
-            userid = self.getUserId()
-            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.WINDOW.getProperty("accessToken%s" % username)
-            if(authToken != ""):
-                headers["X-MediaBrowser-Token"] = authToken
+            userId = self.userId
+            token = self.token
+            # Attached to the requests session
+            auth = 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (userId, deviceName, deviceId, version)
+            header = {"Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": auth, "X-MediaBrowser-Token": token}        
                     
-            self.logMsg("Authentication Header : " + str(headers),2)
-            return headers
-        
-    def downloadUrl(self, url, suppress=False, postBody=None, type="GET", popup=0, authenticate=True ):
-        self.logMsg("== ENTER: getURL ==",2)
+            self.logMsg("Header: %s" % header, 2)
+            return header
 
-        if(authenticate == True and suppress == True):
-            token = self.authenticate(retreive=False)
-            if(token == ""):
-                self.logMsg("No auth info set and suppress is true so returning no data!")
-                return ""
+    def downloadUrl(self, url, postBody=None, type="GET", authenticate=True):
         
-        self.TotalUrlCalls = self.TotalUrlCalls + 1
-        if(self.LogCalls):
-            stackString = ""
-            for f in inspect.stack():
-                stackString = stackString + "\r - " + str(f)
-            self.TrackLog = self.TrackLog + "HTTP_API_CALL : " + url + stackString + "\r"
+        self.logMsg("=== ENTER downloadUrl ===", 2)
+
+        WINDOW = self.WINDOW
+        timeout = self.timeout
+        default_link = ""
+
+        # If user is authenticated
+        if (authenticate):
+            # Get requests session
+            s = self.s
+            # Replace for the real values and append api_key
+            url = url.replace("{server}", self.server, 1)
+            url = url.replace("{UserId}", self.userId, 1)
+            url = "%s&api_key=%s" % (url, self.token)
             
-        link = ""
-        https = None
+            self.logMsg("URL: %s" % url, 1)
+            # Prepare request
+            if type == "GET":
+                r = s.get(url, params=postBody, timeout=timeout)
+            elif type == "POST":
+                r = s.post(url, params=postBody, timeout=timeout)
+            elif type == "DELETE":
+                r = s.delete(url, params=postBody, timeout=timeout)
+
+        # If user is not authenticated
+        elif not authenticate:
+            
+            self.logMsg("URL: %s" % url, 1)
+            header = self.getHeader(authenticate=False)
+            
+            # Prepare request
+            if type == "GET":
+                r = requests.get(url, params=postBody, headers=header, timeout=timeout, verify=False)
+            elif type == "POST":
+                r = requests.post(url, params=postBody, headers=header, timeout=timeout)
+        
+        # Process the response
         try:
-            if url[0:5] == "https":
-                serversplit = 2
-                urlsplit = 3
-            elif url[0:4] == "http":
-                serversplit = 2
-                urlsplit = 3
-            else:
-                serversplit = 0
-                urlsplit = 1
+            r.raise_for_status()
 
-            https = self.addonSettings.getSetting('https')
-
-            server = url.split('/')[serversplit]
-            urlPath = "/"+"/".join(url.split('/')[urlsplit:])
-
-            self.logMsg("DOWNLOAD_URL = " + url,2)
-            self.logMsg("server = " + str(server),2)
-            self.logMsg("urlPath = " + str(urlPath),2)
-            
-            if(server[0:1] == ":" or server[-1:] == ":"):
-                self.logMsg("No server host or port set in url")
-                return ""
-            
-            head = self.getAuthHeader(authenticate)
-            self.logMsg("HEADERS : " + str(head), level=2)
-            
-            if (https == 'false'):
-                #xbmc.log("Https disabled.")
-                conn = httplib.HTTPConnection(server, timeout=30)
-            elif (https == 'true'):
-                #xbmc.log("Https enabled.")
-                conn = httplib.HTTPSConnection(server, timeout=30)
-
-            # make the connection and send the request
-            if(postBody != None):
-                head["Content-Type"] = "application/x-www-form-urlencoded"
-                head["Content-Length"] = str(len(postBody))
-                self.logMsg("POST DATA : " + postBody,2)
-                conn.request(method=type, url=urlPath, body=postBody, headers=head)
-            else:
-                conn.request(method=type, url=urlPath, headers=head)
-
-            # get the response
-            tries = 0
-            while tries <= 4:
-                try:
-                    data = conn.getresponse()
-                    break
-                except:
-                    # TODO: we need to work out which errors we can just quit trying immediately
-                    if(xbmc.abortRequested == True):
-                        return ""
-                    xbmc.sleep(100)
-                    if(xbmc.abortRequested == True):
-                        return ""                    
-                    tries += 1
-            if tries == 5:
-                data = conn.getresponse()
-            
-            self.logMsg("GET URL HEADERS : " + str(data.getheaders()), level=2)
-
-            # process the response
-            contentType = "none"
-            if int(data.status) == 200:
-                retData = data.read()
-                contentType = data.getheader('content-encoding')
-                self.logMsg("Data Len Before : " + str(len(retData)), level=2)
-                if(contentType == "gzip"):
-                    retData = StringIO.StringIO(retData)
-                    gzipper = gzip.GzipFile(fileobj=retData)
-                    link = gzipper.read()
-                else:
-                    link = retData
-                self.logMsg("Data Len After : " + str(len(link)), level=2)
-                self.logMsg("====== 200 returned =======", level=2)
-                self.logMsg("Content-Type : " + str(contentType), level=2)
-                self.logMsg(link, level=2)
-                self.logMsg("====== 200 finished ======", level=2)
-
-            elif ( int(data.status) == 301 ) or ( int(data.status) == 302 ):
+            if r.status_code == 204:
+                # No response in body
+                self.logMsg("====== 204 Success ======", 1)
+                return default_link
+            # Response code 200
+            elif r.status_code == requests.codes.ok:
                 try: 
-                    conn.close()
-                except: 
-                    pass
-                return data.getheader('Location')
+                    # UTF-8 - JSON object
+                    r = r.json()
+                    self.logMsg("====== 200 Success ======", 1)
+                    return r
+                except:
+                    self.logMsg("Unable to convert the response for: %s" % url, 1)
 
-            elif int(data.status) == 401:
-                WINDOW = xbmcgui.Window(10000)
+            return default_link
+        
+        # TO REVIEW EXCEPTIONS
+        except requests.exceptions.ConnectionError as e:
+            self.logMsg("Server unreachable at: %s" % url, 0)
+            self.logMsg(e, 1)
+
+        except requests.exceptions.ConnectTimeout as e:
+            self.logMsg("Server timeout at: %s" % url, 0)
+            self.logMsg(e, 1)
+
+        except requests.exceptions.HTTPError as e:
+
+            if r.status_code == 401:
+                # Unauthorized
                 status = WINDOW.getProperty("Server_status")
-                # Prevent multiple re-auth
                 if (status == "401") or (status == "Auth"):
                     pass
                 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)
-                xbmc.log(error)
-                stack = self.FormatException()
-                self.logMsg(stack)                
-                if suppress is False:
-                    if popup == 0:
-                        xbmc.executebuiltin("XBMC.Notification(URL error: "+ str(data.reason) +",)")
-                    else:
-                        xbmcgui.Dialog().ok(self.getString(30135),server)
-                try: 
-                    conn.close()
-                except: 
-                    pass
-                return ""
-            else:
-                link = ""
-        except Exception, msg:
-            error = "Unable to connect to " + str(server) + " : " + str(msg)
-            xbmc.log(error)
-            stack = self.FormatException()
-            self.logMsg(stack)
-            if suppress is False:
-                if popup == 0:
-                    xbmc.executebuiltin("XBMC.Notification(: Connection Error: Error connecting to server,)")
-                else:
-                    xbmcgui.Dialog().ok(self.getString(30204), str(msg))
-            pass
-        else:
-            try: 
-                conn.close()
-            except: 
+                    self.logMsg("HTTP Error: %s" % e, 0)
+
+            elif (r.status_code == 301) or (r.status_code == 302):
+                # Redirects
+                pass
+            elif r.status_code == 400:
+                # Bad requests
                 pass
 
-        return link
-        
-    def FormatException(self):
-        exception_list = traceback.format_stack()
-        exception_list = exception_list[:-2]
-        exception_list.extend(traceback.format_tb(sys.exc_info()[2]))
-        exception_list.extend(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1]))
+        except requests.exceptions.RequestException as e:
+            self.logMsg("Unknown error connecting to: %s" % url, 0)
+            self.logMsg(e, 1)
 
-        exception_str = "Traceback (most recent call last):\n"
-        exception_str += "".join(exception_list)
-        # Removing the last \n
-        exception_str = exception_str[:-1]
-
-        return exception_str          
-    
-    def __del__(self):
-        return
-        # xbmc.log("\rURL_REQUEST_REPORT : Total Calls : " + str(self.TotalUrlCalls) + "\r" + self.TrackLog)
+        return default_link