diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index f8eb6d94..d5a43064 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -375,7 +375,7 @@ class AbstractProvider(object): self.log.warning('Multiple busy dialogs active' ' - Rerouting workaround') return UriItem('command://{0}'.format(action)) - context.sleep(1) + context.sleep(0.1) else: context.execute( action, diff --git a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py index e9d29b14..2ad0a112 100644 --- a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py +++ b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py @@ -15,8 +15,6 @@ __all__ = ( 'available_cpu_count', 'byte_string_type', 'datetime_infolabel', - 'default_quote', - 'default_quote_plus', 'entity_escape', 'generate_hash', 'parse_qs', @@ -120,70 +118,6 @@ try: for ordinal in range(128, 256) }) - - def default_quote(string, - safe='', - encoding=None, - errors=None, - _encoding='utf-8', - _errors='strict', - _reserved=reserved, - _non_ascii=non_ascii, - _encode=str.encode, - _is_ascii=str.isascii, - _replace=str.replace, - _old='\\x', - _new='%', - _slice=slice(2, -1), - _str=str, - _translate=str.translate): - _string = _translate(string, _reserved) - if _is_ascii(_string): - return _string - _string = _str(_encode(_string, _encoding, _errors))[_slice] - if _string == string: - if _is_ascii(_string): - return _string - return _translate(_string, _non_ascii) - if _is_ascii(_string): - return _replace(_string, _old, _new) - return _translate(_replace(_string, _old, _new), _non_ascii) - - - def default_quote_plus(string, - safe='', - encoding=None, - errors=None, - _encoding='utf-8', - _errors='strict', - _reserved=reserved_plus, - _non_ascii=non_ascii, - _encode=str.encode, - _is_ascii=str.isascii, - _replace=str.replace, - _old='\\x', - _new='%', - _slice=slice(2, -1), - _str=str, - _translate=str.translate): - if (not safe and encoding is None and errors is None - and isinstance(string, str)): - _string = _translate(string, _reserved) - if _is_ascii(_string): - return _string - _string = _str(_encode(_string, _encoding, _errors))[_slice] - if _string == string: - if _is_ascii(_string): - return _string - return _translate(_string, _non_ascii) - if _is_ascii(_string): - return _replace(_string, _old, _new) - return _translate(_replace(_string, _old, _new), _non_ascii) - return quote_plus(string, safe, encoding, errors) - - - urlencode.__defaults__ = (False, '', None, None, default_quote_plus) - # Compatibility shims for Kodi v18 and Python v2.7 except ImportError: import cPickle as pickle @@ -220,16 +154,10 @@ except ImportError: return _quote(to_str(data), *args, **kwargs) - default_quote = quote - - def quote_plus(data, *args, **kwargs): return _quote_plus(to_str(data), *args, **kwargs) - default_quote_plus = quote_plus - - def unquote(data): return _unquote(to_str(data)) diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index 41d380ee..f23f2292 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -14,8 +14,8 @@ import os from .. import logging from ..compatibility import ( - default_quote, parse_qsl, + quote, string_type, to_str, unquote, @@ -387,7 +387,7 @@ class AbstractContext(object): params = urlencode([ ( ('%' + param, - ','.join([default_quote(item) for item in value])) + ','.join([quote(item) for item in value])) if len(value) > 1 else (param, value[0]) ) @@ -482,7 +482,7 @@ class AbstractContext(object): return ('/', parts) if include_parts else '/' if kwargs.get('is_uri'): - path = default_quote(path) + path = quote(path) return (path, parts) if include_parts else path def get_path(self): diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py index 7952e027..0713653f 100644 --- a/resources/lib/youtube_plugin/kodion/items/base_item.py +++ b/resources/lib/youtube_plugin/kodion/items/base_item.py @@ -348,12 +348,15 @@ class _Encoder(json.JSONEncoder): def encode(self, obj, nested=False): if isinstance(obj, (date, datetime)): class_name = obj.__class__.__name__ - if 'fromisoformat' in dir(obj): - obj = { - '__class__': class_name, - '__isoformat__': obj.isoformat(), - } - else: + try: + if obj.fromisoformat: + obj = { + '__class__': class_name, + '__isoformat__': obj.isoformat(), + } + else: + raise AttributeError + except AttributeError: if class_name == 'datetime': if obj.tzinfo: format_string = '%Y-%m-%dT%H:%M:%S%z' diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py index d248134e..73a694ce 100644 --- a/resources/lib/youtube_plugin/kodion/network/requests.py +++ b/resources/lib/youtube_plugin/kodion/network/requests.py @@ -11,11 +11,19 @@ from __future__ import absolute_import, division, unicode_literals import socket from atexit import register as atexit_register +from collections import OrderedDict -from requests import Request, Session from requests.adapters import HTTPAdapter, Retry from requests.exceptions import InvalidJSONError, RequestException, URLRequired -from requests.utils import DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths +from requests.hooks import default_hooks +from requests.models import DEFAULT_REDIRECT_LIMIT, Request +from requests.sessions import Session +from requests.utils import ( + DEFAULT_CA_BUNDLE_PATH, + cookiejar_from_dict, + default_headers, + extract_zipped_paths, +) from urllib3.util.ssl_ import create_urllib3_context from .. import logging @@ -64,21 +72,83 @@ class SSLHTTPAdapter(HTTPAdapter): return super(SSLHTTPAdapter, self).cert_verify(conn, url, verify, cert) +class CustomSession(Session): + def __init__(self): + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + #: CustomSession.trust_env is False + self.trust_env = False + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', SSLHTTPAdapter( + pool_maxsize=20, + pool_block=True, + max_retries=Retry( + total=3, + backoff_factor=0.1, + status_forcelist={500, 502, 503, 504}, + allowed_methods=None, + ) + )) + self.mount('http://', HTTPAdapter()) + + class BaseRequestsClass(object): log = logging.getLogger(__name__) - _session = Session() - _session.trust_env = False - _session.mount('https://', SSLHTTPAdapter( - pool_maxsize=10, - pool_block=True, - max_retries=Retry( - total=3, - backoff_factor=0.1, - status_forcelist={500, 502, 503, 504}, - allowed_methods=None, - ) - )) + _session = CustomSession() atexit_register(_session.close) _context = None diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py index 5ff6b445..fea926c7 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py @@ -439,7 +439,7 @@ class XbmcPlugin(AbstractPlugin): @staticmethod def post_run(context, ui, *actions, **kwargs): timeout = kwargs.get('timeout', 30) - interval = kwargs.get('interval', 0.1) + interval = kwargs.get('interval', 0.01) for action in actions: while not ui.get_container(container_type=None, check_ready=True): timeout -= interval diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py index 288992d0..9d15af7c 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py @@ -481,7 +481,7 @@ def process_items_for_playlist(context, command = playlist_player.play_playlist_item(position, defer=True) return UriItem(command) - context.sleep(1) + context.sleep(0.1) else: playlist_player.play_playlist_item(position) return items[position - 1]