From 0e7c98cfd90168477c99b0ad713fb2fb1ccf672b Mon Sep 17 00:00:00 2001 From: quodrumglas Date: Fri, 23 Feb 2024 16:23:38 +0000 Subject: [PATCH] Login workflow improvements --- mopidy_tidal/auth_http_server.py | 8 +++-- mopidy_tidal/backend.py | 53 ++++++++++++++++++++------------ mopidy_tidal/session.py | 5 +-- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/mopidy_tidal/auth_http_server.py b/mopidy_tidal/auth_http_server.py index 0fafea7..6afc8ac 100755 --- a/mopidy_tidal/auth_http_server.py +++ b/mopidy_tidal/auth_http_server.py @@ -5,7 +5,7 @@ from mopidy_tidal.session import PersistentSession from http.server import HTTPServer, BaseHTTPRequestHandler -from urllib.parse import unquote +from urllib.parse import unquote, urlparse HTML_BODY = """ @@ -51,10 +51,12 @@ def __init__(self, session: PersistentSession, login_result_holder, *args, **kwa super().__init__(*args, **kwargs) def do_GET(self): + url = urlparse(self.path) self.send_response(200) self.end_headers() - interactive = INTERACTIVE_HTML_BODY if self.login_handler.is_pkce else '' - self.wfile.write(HTML_BODY(authurl=self.login_handler.get_login_url(), interactive=interactive).encode()) + if url.path == '/' and url.query == '' and url.params == '': + interactive = INTERACTIVE_HTML_BODY if self.login_handler.is_pkce else '' + self.wfile.write(HTML_BODY(authurl=self.login_handler.get_login_url(), interactive=interactive).encode()) def do_POST(self): content_length = int(self.headers.get("Content-Length"), 0) diff --git a/mopidy_tidal/backend.py b/mopidy_tidal/backend.py index c0c55fb..f21913f 100755 --- a/mopidy_tidal/backend.py +++ b/mopidy_tidal/backend.py @@ -16,6 +16,7 @@ logger = logging.getLogger(__name__) OAUTH_JSON = "tidal.oauth.{}.json".format +MAX_LOGIN_WAIT_MINS = 5 class TidalBackend(ThreadingActor, backend.Backend): @@ -44,44 +45,58 @@ def get_dir(self, folder): return method(self._config) def on_start(self): - login_pkce = self.get_config("login_pkce") or True client_id = self.get_config("client_id") client_secret = self.get_config("client_secret") - quality = self.get_config("quality") - config = Config(quality=Quality(quality)) + config = Config(quality=Quality(self.get_config("quality"))) + is_hires_quality = config.quality in [Quality.hi_res.value, Quality.hi_res_lossless.value] + login_pkce = is_hires_quality or self.get_config("login_pkce") if client_id: - config.client_id = client_id - config.client_secret = client_secret if login_pkce: config.client_id_pkce = client_id config.client_secret_pkce = client_secret - oauth_file_location = os.path.join(self.get_dir("data"), OAUTH_JSON( - client_id or config.client_id_pkce if login_pkce else config.client_id)) + else: + config.client_id = client_id + config.client_secret = client_secret + client_id_in_use = config.client_id_pkce if login_pkce else config.client_id + oauth_file_location = os.path.join(self.get_dir("data"), OAUTH_JSON(client_id_in_use)) self.session = PersistentSession(config, login_pkce=login_pkce, authentication_local_storage=oauth_file_location) - logger.info("Connecting to TIDAL... Requested Quality = %s" % quality) + logger.info(f"{client_id_in_use} connecting to TIDAL. Requested Quality: {config.quality}") + if is_hires_quality: + logger.info("Enabling TIDAL HI-RES") + self.session.client_enable_hires() + self.connect() + + def connect(self): + success = False try: self.session.load_oauth_session_from_file() - logger.info(f"Session loaded from {oauth_file_location}") - except FileNotFoundError: + if self.session.check_login(): + logger.info("TIDAL Login OK") + success = True + else: + logger.info("TIDAL Login KO") + raise PermissionError("Saved session failed to authenticate") + except (FileNotFoundError, PermissionError, ) as e: + logger.info(e) try: self.new_login() + success = True + except TimeoutError as e: + logger.error(e) except Exception as e: logger.exception(e) - if self.session.check_login(): - logger.info("TIDAL Login OK") - subscription = self.session.request.basic_request('GET', f'users/{self.session.user.id}/subscription').json() - logger.info("HighestSoundQuality: {highestSoundQuality}".format(**subscription)) - else: - logger.info("TIDAL Login KO") - + if not success: + raise RuntimeError("Failed connection to TIDAL Service") + subscription = self.session.request.basic_request('GET', f'users/{self.session.user.id}/subscription').json() + logger.info("Connected to TIDAL. HighestSoundQuality: {highestSoundQuality}".format(**subscription)) def new_login(self): login_web_port = self.get_config("login_web_port") login_result_holder = Queue(maxsize=1) terminate = start_oauth_deamon(self.session, login_web_port, login_result_holder) - logger.info(f"No saved session found. Please visit http://{get_ip()}:{login_web_port} to authenticate") + logger.info(f"Please visit http://{get_ip()}:{login_web_port} to authenticate") try: - exception = login_result_holder.get(timeout=300) + exception = login_result_holder.get(timeout=MAX_LOGIN_WAIT_MINS * 60) if exception: raise exception except Empty: diff --git a/mopidy_tidal/session.py b/mopidy_tidal/session.py index 026eaee..3298a38 100644 --- a/mopidy_tidal/session.py +++ b/mopidy_tidal/session.py @@ -1,6 +1,7 @@ import datetime import json import logging +from os.path import basename from tidalapi import Session @@ -20,7 +21,7 @@ def load_oauth_session_from_file(self): data = json.load(f) data["expiry_time"] = datetime.datetime.fromisoformat(data["expiry_time"]) self.load_oauth_session(**data) - logger.info(f"Session Loaded. Expires at {self.expiry_time.isoformat()}") + logger.info(f"Session loaded from {basename(self._authentication_local_storage)} - Expires at {self.expiry_time.isoformat()}") def save_oauth_session_to_file(self): with open(self._authentication_local_storage, 'w') as f: @@ -30,7 +31,7 @@ def save_oauth_session_to_file(self): "refresh_token": self.refresh_token, "expiry_time": self.expiry_time.isoformat(), }, f) - logger.info(f"Session Saved. Expires at {self.expiry_time.isoformat()}") + logger.info(f"Session saved to {basename(self._authentication_local_storage)} - Expires at {self.expiry_time.isoformat()}") def token_refresh(self, *args, **kwargs): logger.info(f"Authentication expired at {self.expiry_time.isoformat()} ...Refreshing")