2018-09-06 08:36:32 +00:00
# -*- coding: utf-8 -*-
2020-01-04 02:32:30 +00:00
from __future__ import division , absolute_import , print_function , unicode_literals
2018-09-06 08:36:32 +00:00
#################################################################################################
import json
import logging
import time
2019-01-30 12:42:06 +00:00
import requests
2020-01-04 04:17:51 +00:00
from six import string_types
2020-01-04 02:32:30 +00:00
from . exceptions import HTTPException
2020-02-16 22:07:56 +00:00
from helper import debug
2018-09-06 08:36:32 +00:00
#################################################################################################
2019-10-03 02:14:54 +00:00
LOG = logging . getLogger ( ' Jellyfin. ' + __name__ )
2018-09-06 08:36:32 +00:00
#################################################################################################
2019-07-09 20:05:28 +00:00
2018-09-06 08:36:32 +00:00
class HTTP ( object ) :
session = None
keep_alive = False
def __init__ ( self , client ) :
self . client = client
2019-09-09 00:20:58 +00:00
self . config = client . config
2018-09-06 08:36:32 +00:00
def start_session ( self ) :
2019-07-09 20:05:28 +00:00
2018-09-06 08:36:32 +00:00
self . session = requests . Session ( )
2019-09-09 00:20:58 +00:00
max_retries = self . config . data [ ' http.max_retries ' ]
2018-09-06 08:36:32 +00:00
self . session . mount ( " http:// " , requests . adapters . HTTPAdapter ( max_retries = max_retries ) )
self . session . mount ( " https:// " , requests . adapters . HTTPAdapter ( max_retries = max_retries ) )
def stop_session ( self ) :
2019-07-09 20:05:28 +00:00
2018-09-06 08:36:32 +00:00
if self . session is None :
return
try :
2019-10-02 00:59:25 +00:00
LOG . info ( " --<[ session/ %s ] " , id ( self . session ) )
2018-09-06 08:36:32 +00:00
self . session . close ( )
except Exception as error :
2019-10-02 00:59:25 +00:00
LOG . warning ( " The requests session could not be terminated: %s " , error )
2018-09-06 08:36:32 +00:00
def _replace_user_info ( self , string ) :
2018-09-15 08:16:37 +00:00
if ' {server} ' in string :
2020-01-05 23:41:26 +00:00
if self . config . data . get ( ' auth.server ' , None ) :
2020-01-04 02:32:30 +00:00
string = string . replace ( " {server} " , self . config . data [ ' auth.server ' ] )
2018-09-15 08:16:37 +00:00
else :
2020-01-05 23:41:26 +00:00
LOG . debug ( " Server address not set " )
2018-09-15 08:16:37 +00:00
if ' {UserId} ' in string :
2020-01-05 23:41:26 +00:00
if self . config . data . get ( ' auth.user_id ' , None ) :
2020-01-04 02:32:30 +00:00
string = string . replace ( " {UserId} " , self . config . data [ ' auth.user_id ' ] )
2018-09-15 08:16:37 +00:00
else :
2020-01-05 23:41:26 +00:00
LOG . debug ( " UserId is not set. " )
2018-09-06 08:36:32 +00:00
return string
def request ( self , data , session = None ) :
2019-02-02 13:10:33 +00:00
''' Give a chance to retry the connection. Jellyfin sometimes can be slow to answer back
2018-09-06 08:36:32 +00:00
data dictionary can contain :
type : GET , POST , etc .
url : ( optional )
handler : not considered when url is provided ( optional )
params : request parameters ( optional )
json : request body ( optional )
headers : ( optional ) ,
verify : ssl certificate , True ( verify using device built - in library ) or False
'''
if not data :
raise AttributeError ( " Request cannot be empty " )
data = self . _request ( data )
2020-02-16 22:07:56 +00:00
LOG . debug ( " --->[ http ] %s " , debug . JsonDebugPrinter ( data ) )
2018-09-06 08:36:32 +00:00
retry = data . pop ( ' retry ' , 5 )
while True :
try :
r = self . _requests ( session or self . session or requests , data . pop ( ' type ' , " GET " ) , * * data )
2019-07-09 20:05:28 +00:00
r . content # release the connection
2018-09-06 08:36:32 +00:00
if not self . keep_alive and self . session is not None :
self . stop_session ( )
r . raise_for_status ( )
except requests . exceptions . ConnectionError as error :
if retry :
retry - = 1
time . sleep ( 1 )
continue
LOG . error ( error )
2019-09-09 00:20:58 +00:00
self . client . callback ( " ServerUnreachable " , { ' ServerId ' : self . config . data [ ' auth.server-id ' ] } )
2018-09-06 08:36:32 +00:00
raise HTTPException ( " ServerUnreachable " , error )
except requests . exceptions . ReadTimeout as error :
if retry :
retry - = 1
time . sleep ( 1 )
continue
LOG . error ( error )
raise HTTPException ( " ReadTimeout " , error )
except requests . exceptions . HTTPError as error :
LOG . error ( error )
if r . status_code == 401 :
if ' X-Application-Error-Code ' in r . headers :
2019-09-09 00:20:58 +00:00
self . client . callback ( " AccessRestricted " , { ' ServerId ' : self . config . data [ ' auth.server-id ' ] } )
2018-09-06 08:36:32 +00:00
raise HTTPException ( " AccessRestricted " , error )
else :
2019-09-09 00:20:58 +00:00
self . client . callback ( " Unauthorized " , { ' ServerId ' : self . config . data [ ' auth.server-id ' ] } )
2019-10-03 02:14:54 +00:00
self . client . auth . revoke_token ( )
2018-09-06 08:36:32 +00:00
raise HTTPException ( " Unauthorized " , error )
2019-07-09 20:05:28 +00:00
elif r . status_code == 500 : # log and ignore.
2018-09-06 08:36:32 +00:00
LOG . error ( " --[ 500 response ] %s " , error )
return
elif r . status_code == 502 :
if retry :
retry - = 1
time . sleep ( 1 )
continue
raise HTTPException ( r . status_code , error )
except requests . exceptions . MissingSchema as error :
2020-01-05 23:41:26 +00:00
LOG . error ( " Request missing Schema. " + str ( error ) )
raise HTTPException ( " MissingSchema " , { ' Id ' : self . config . data . get ( ' auth.server ' , " None " ) } )
2018-09-06 08:36:32 +00:00
except Exception as error :
raise
else :
try :
2019-09-09 00:20:58 +00:00
self . config . data [ ' server-time ' ] = r . headers [ ' Date ' ]
2018-09-06 08:36:32 +00:00
elapsed = int ( r . elapsed . total_seconds ( ) * 1000 )
response = r . json ( )
LOG . debug ( " ---<[ http ][ %s ms] " , elapsed )
2020-02-16 22:07:56 +00:00
LOG . debug ( debug . JsonDebugPrinter ( response ) )
2018-09-06 08:36:32 +00:00
return response
except ValueError :
return
def _request ( self , data ) :
if ' url ' not in data :
2020-01-05 23:41:26 +00:00
data [ ' url ' ] = " %s / %s " % ( self . config . data . get ( " auth.server " , " " ) , data . pop ( ' handler ' , " " ) )
2018-09-06 08:36:32 +00:00
self . _get_header ( data )
2019-09-09 00:20:58 +00:00
data [ ' timeout ' ] = data . get ( ' timeout ' ) or self . config . data [ ' http.timeout ' ]
2020-01-05 23:41:26 +00:00
data [ ' verify ' ] = data . get ( ' verify ' ) or self . config . data . get ( ' auth.ssl ' , False )
2018-09-06 08:36:32 +00:00
data [ ' url ' ] = self . _replace_user_info ( data [ ' url ' ] )
self . _process_params ( data . get ( ' params ' ) or { } )
self . _process_params ( data . get ( ' json ' ) or { } )
return data
def _process_params ( self , params ) :
for key in params :
value = params [ key ]
if isinstance ( value , dict ) :
self . _process_params ( value )
2020-01-04 04:17:51 +00:00
if isinstance ( value , string_types ) :
2018-09-06 08:36:32 +00:00
params [ key ] = self . _replace_user_info ( value )
def _get_header ( self , data ) :
data [ ' headers ' ] = data . setdefault ( ' headers ' , { } )
if not data [ ' headers ' ] :
data [ ' headers ' ] . update ( {
' Content-type ' : " application/json " ,
' Accept-Charset ' : " UTF-8,* " ,
' Accept-encoding ' : " gzip " ,
2020-01-05 23:41:26 +00:00
' User-Agent ' : self . config . data [ ' http.user_agent ' ] or " %s / %s " % ( self . config . data . get ( ' app.name ' , ' Jellyfin for Kodi ' ) , self . config . data . get ( ' app.version ' , " 0.0.0 " ) )
2018-09-06 08:36:32 +00:00
} )
2019-08-30 13:45:37 +00:00
if ' x-emby-authorization ' not in data [ ' headers ' ] :
2018-09-06 08:36:32 +00:00
self . _authorization ( data )
return data
def _authorization ( self , data ) :
2019-07-09 20:05:28 +00:00
auth = " MediaBrowser "
2020-01-05 23:41:26 +00:00
auth + = " Client= %s , " % self . config . data . get ( ' app.name ' , " Jellyfin for Kodi " )
auth + = " Device= %s , " % self . config . data . get ( ' app.device_name ' , ' Unknown Device ' )
auth + = " DeviceId= %s , " % self . config . data . get ( ' app.device_id ' , ' Unknown Device id ' )
auth + = " Version= %s " % self . config . data . get ( ' app.version ' , ' 0.0.0 ' )
2018-09-06 08:36:32 +00:00
2019-08-30 13:45:37 +00:00
data [ ' headers ' ] . update ( { ' x-emby-authorization ' : auth } )
2018-09-06 08:36:32 +00:00
2019-09-09 00:20:58 +00:00
if self . config . data . get ( ' auth.token ' ) and self . config . data . get ( ' auth.user_id ' ) :
2020-01-05 23:41:26 +00:00
auth + = ' , UserId= %s ' % self . config . data . get ( ' auth.user_id ' )
data [ ' headers ' ] . update ( { ' x-emby-authorization ' : auth , ' X-MediaBrowser-Token ' : self . config . data . get ( ' auth.token ' ) } )
2018-09-06 08:36:32 +00:00
return data
def _requests ( self , session , action , * * kwargs ) :
if action == " GET " :
return session . get ( * * kwargs )
elif action == " POST " :
return session . post ( * * kwargs )
elif action == " HEAD " :
return session . head ( * * kwargs )
elif action == " DELETE " :
return session . delete ( * * kwargs )