From 33f40520dac4e4a266de55df7a004f2f80cced5a Mon Sep 17 00:00:00 2001 From: kokarare1212 Date: Tue, 14 Sep 2021 19:48:08 +0900 Subject: [PATCH] add session listener --- librespot/structure.py | 10 ++++++- librespot/zeroconf.py | 67 ++++++++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/librespot/structure.py b/librespot/structure.py index 0868c19..5d9c670 100644 --- a/librespot/structure.py +++ b/librespot/structure.py @@ -4,7 +4,7 @@ import typing if typing.TYPE_CHECKING: from librespot.audio import AbsChunkedInputStream from librespot.audio.format import SuperAudioFormat - from librespot.core import DealerClient + from librespot.core import DealerClient, Session from librespot.crypto import Packet from librespot.mercury import MercuryClient from librespot.proto import Metadata_pb2 as Metadata @@ -86,6 +86,14 @@ class Runnable: raise NotImplementedError +class SessionListener: + def session_closing(self, session: Session) -> None: + raise NotImplementedError + + def session_changed(self, session: Session) -> None: + raise NotImplementedError + + class SubListener: def event(self, resp: MercuryClient.Response) -> None: raise NotImplementedError diff --git a/librespot/zeroconf.py b/librespot/zeroconf.py index fc6708d..89991e4 100644 --- a/librespot/zeroconf.py +++ b/librespot/zeroconf.py @@ -6,7 +6,7 @@ from librespot import util, Version from librespot.core import Session from librespot.crypto import DiffieHellman from librespot.proto import Connect_pb2 as Connect -from librespot.structure import Closeable, Runnable +from librespot.structure import Closeable, Runnable, SessionListener import base64 import concurrent.futures import copy @@ -54,7 +54,7 @@ class ZeroconfServer(Closeable): __runner: HttpRunner __service_info: zeroconf.ServiceInfo __session: typing.Union[Session, None] = None - __session_listeners = [] + __session_listeners: typing.List[SessionListener] = [] __zeroconf: zeroconf.Zeroconf def __init__(self, inner: Inner, listen_port): @@ -85,10 +85,21 @@ class ZeroconfServer(Closeable): threading.Thread(target=self.__zeroconf.start, name="zeroconf-multicast-dns-server").start() + def add_session_listener(self, listener: ZeroconfServer): + self.__session_listeners.append(listener) + def close(self) -> None: self.__zeroconf.close() self.__runner.close() + def close_session(self) -> None: + if self.__session is None: + return + for session_listener in self.__session_listeners: + session_listener.session_closing(self.__session) + self.__session.close() + self.__session = None + def get_useful_hostname(self) -> str: host = socket.gethostname() if host == "localhost": @@ -100,18 +111,18 @@ class ZeroconfServer(Closeable): http_version: str) -> None: username = params.get("userName") if not username: - logging.error("Missing userName!") + self.logger.error("Missing userName!") return blob_str = params.get("blob") if not blob_str: - logging.error("Missing blob!") + self.logger.error("Missing blob!") return client_key_str = params.get("clientKey") if not client_key_str: - logging.error("Missing clientKey!") + self.logger.error("Missing clientKey!") with self.__connection_lock: if username == self.__connecting_username: - logging.info( + self.logger.info( "{} is already trying to connect.".format(username)) __socket.send(http_version.encode()) __socket.send(b" 403 Forbidden") @@ -138,7 +149,7 @@ class ZeroconfServer(Closeable): hmac.update(encrypted) mac = hmac.digest() if mac != checksum: - logging.error("Mac and checksum don't match!") + self.logger.error("Mac and checksum don't match!") __socket.send(http_version.encode()) __socket.send(b" 400 Bad Request") __socket.send(self.__eol) @@ -146,9 +157,10 @@ class ZeroconfServer(Closeable): return aes = AES.new(encryption_key[:16], AES.MODE_CTR, counter=Counter.new(128, initial_value=int.from_bytes(iv, "big"))) decrypted = aes.decrypt(encrypted) + self.close_session() with self.__connection_lock: self.__connecting_username = username - logging.info("Accepted new user from {}. [deviceId: {}]".format( + self.logger.info("Accepted new user from {}. [deviceId: {}]".format( params.get("deviceName"), self.__inner.device_id)) response = json.dumps(self.__default_successful_add_user) __socket.send(http_version.encode()) @@ -168,6 +180,8 @@ class ZeroconfServer(Closeable): .create() with self.__connection_lock: self.__connecting_username = None + for session_listener in self.__session_listeners: + session_listener.session_changed(self.__session) def handle_get_info(self, __socket: socket.socket, http_version: str) -> None: @@ -204,6 +218,19 @@ class ZeroconfServer(Closeable): parsed[key] = value return parsed + def remove_session_listener(self, listener: SessionListener): + self.__session_listeners.remove(listener) + + class Builder(Session.Builder): + listen_port: int = -1 + + def set_listen_port(self, listen_port: int): + self.listen_port = listen_port + return self + + def create(self) -> ZeroconfServer: + return ZeroconfServer(ZeroconfServer.Inner(self.device_type, self.device_name, self.device_id, self.preferred_locale, self.conf), self.listen_port) + class HttpRunner(Closeable, Runnable): __should_stop = False __socket: socket.socket @@ -215,7 +242,7 @@ class ZeroconfServer(Closeable): self.__socket.bind((".".join(["0"] * 4), port)) self.__socket.listen(5) self.__zeroconf_server = zeroconf_server - logging.info("Zeroconf HTTP server started successfully on port {}!".format(port)) + self.__zeroconf_server.logger.info("Zeroconf HTTP server started successfully on port {}!".format(port)) def close(self) -> None: pass @@ -234,7 +261,7 @@ class ZeroconfServer(Closeable): request = io.BytesIO(__socket.recv(1024 * 1024)) request_line = request.readline().strip().split(b" ") if len(request_line) != 3: - logging.warning( + self.__zeroconf_server.logger.warning( "Unexpected request line: {}".format(request_line)) method = request_line[0].decode() path = request_line[1].decode() @@ -247,18 +274,18 @@ class ZeroconfServer(Closeable): split = header.split(b":") headers[split[0].decode()] = split[1].strip().decode() if not self.__zeroconf_server.has_valid_session(): - logging.debug( + self.__zeroconf_server.logger.debug( "Handling request: {}, {}, {}, headers: {}".format( method, path, http_version, headers)) params = {} if method == "POST": content_type = headers.get("Content-Type") if content_type != "application/x-www-form-urlencoded": - logging.error("Bad Content-Type: {}".format(content_type)) + self.__zeroconf_server.logger.error("Bad Content-Type: {}".format(content_type)) return content_length_str = headers.get("Content-Length") if content_length_str is None: - logging.error("Missing Content-Length header!") + self.__zeroconf_server.logger.error("Missing Content-Length header!") return content_length = int(content_length_str) body = request.read(content_length).decode() @@ -271,7 +298,7 @@ class ZeroconfServer(Closeable): params = self.__zeroconf_server.parse_path(path) action = params.get("action") if action is None: - logging.debug("Request is missing action.") + self.__zeroconf_server.logger.debug("Request is missing action.") return self.handle_request(__socket, http_version, action, params) @@ -284,17 +311,7 @@ class ZeroconfServer(Closeable): elif action == "getInfo": self.__zeroconf_server.handle_get_info(__socket, http_version) else: - logging.warning("Unknown action: {}".format(action)) - - class Builder(Session.Builder): - listen_port: int = -1 - - def set_listen_port(self, listen_port: int): - self.listen_port = listen_port - return self - - def create(self) -> ZeroconfServer: - return ZeroconfServer(ZeroconfServer.Inner(self.device_type, self.device_name, self.device_id, self.preferred_locale, self.conf), self.listen_port) + self.__zeroconf_server.logger.warning("Unknown action: {}".format(action)) class Inner: conf: typing.Final[Session.Configuration]