add session listener

This commit is contained in:
kokarare1212 2021-09-14 19:48:08 +09:00
parent e5d6db0b24
commit 33f40520da
No known key found for this signature in database
GPG Key ID: 9FB32C7C7D874F7A
2 changed files with 51 additions and 26 deletions

View File

@ -4,7 +4,7 @@ import typing
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from librespot.audio import AbsChunkedInputStream from librespot.audio import AbsChunkedInputStream
from librespot.audio.format import SuperAudioFormat from librespot.audio.format import SuperAudioFormat
from librespot.core import DealerClient from librespot.core import DealerClient, Session
from librespot.crypto import Packet from librespot.crypto import Packet
from librespot.mercury import MercuryClient from librespot.mercury import MercuryClient
from librespot.proto import Metadata_pb2 as Metadata from librespot.proto import Metadata_pb2 as Metadata
@ -86,6 +86,14 @@ class Runnable:
raise NotImplementedError raise NotImplementedError
class SessionListener:
def session_closing(self, session: Session) -> None:
raise NotImplementedError
def session_changed(self, session: Session) -> None:
raise NotImplementedError
class SubListener: class SubListener:
def event(self, resp: MercuryClient.Response) -> None: def event(self, resp: MercuryClient.Response) -> None:
raise NotImplementedError raise NotImplementedError

View File

@ -6,7 +6,7 @@ from librespot import util, Version
from librespot.core import Session from librespot.core import Session
from librespot.crypto import DiffieHellman from librespot.crypto import DiffieHellman
from librespot.proto import Connect_pb2 as Connect from librespot.proto import Connect_pb2 as Connect
from librespot.structure import Closeable, Runnable from librespot.structure import Closeable, Runnable, SessionListener
import base64 import base64
import concurrent.futures import concurrent.futures
import copy import copy
@ -54,7 +54,7 @@ class ZeroconfServer(Closeable):
__runner: HttpRunner __runner: HttpRunner
__service_info: zeroconf.ServiceInfo __service_info: zeroconf.ServiceInfo
__session: typing.Union[Session, None] = None __session: typing.Union[Session, None] = None
__session_listeners = [] __session_listeners: typing.List[SessionListener] = []
__zeroconf: zeroconf.Zeroconf __zeroconf: zeroconf.Zeroconf
def __init__(self, inner: Inner, listen_port): def __init__(self, inner: Inner, listen_port):
@ -85,10 +85,21 @@ class ZeroconfServer(Closeable):
threading.Thread(target=self.__zeroconf.start, threading.Thread(target=self.__zeroconf.start,
name="zeroconf-multicast-dns-server").start() name="zeroconf-multicast-dns-server").start()
def add_session_listener(self, listener: ZeroconfServer):
self.__session_listeners.append(listener)
def close(self) -> None: def close(self) -> None:
self.__zeroconf.close() self.__zeroconf.close()
self.__runner.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: def get_useful_hostname(self) -> str:
host = socket.gethostname() host = socket.gethostname()
if host == "localhost": if host == "localhost":
@ -100,18 +111,18 @@ class ZeroconfServer(Closeable):
http_version: str) -> None: http_version: str) -> None:
username = params.get("userName") username = params.get("userName")
if not username: if not username:
logging.error("Missing userName!") self.logger.error("Missing userName!")
return return
blob_str = params.get("blob") blob_str = params.get("blob")
if not blob_str: if not blob_str:
logging.error("Missing blob!") self.logger.error("Missing blob!")
return return
client_key_str = params.get("clientKey") client_key_str = params.get("clientKey")
if not client_key_str: if not client_key_str:
logging.error("Missing clientKey!") self.logger.error("Missing clientKey!")
with self.__connection_lock: with self.__connection_lock:
if username == self.__connecting_username: if username == self.__connecting_username:
logging.info( self.logger.info(
"{} is already trying to connect.".format(username)) "{} is already trying to connect.".format(username))
__socket.send(http_version.encode()) __socket.send(http_version.encode())
__socket.send(b" 403 Forbidden") __socket.send(b" 403 Forbidden")
@ -138,7 +149,7 @@ class ZeroconfServer(Closeable):
hmac.update(encrypted) hmac.update(encrypted)
mac = hmac.digest() mac = hmac.digest()
if mac != checksum: 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(http_version.encode())
__socket.send(b" 400 Bad Request") __socket.send(b" 400 Bad Request")
__socket.send(self.__eol) __socket.send(self.__eol)
@ -146,9 +157,10 @@ class ZeroconfServer(Closeable):
return return
aes = AES.new(encryption_key[:16], AES.MODE_CTR, counter=Counter.new(128, initial_value=int.from_bytes(iv, "big"))) aes = AES.new(encryption_key[:16], AES.MODE_CTR, counter=Counter.new(128, initial_value=int.from_bytes(iv, "big")))
decrypted = aes.decrypt(encrypted) decrypted = aes.decrypt(encrypted)
self.close_session()
with self.__connection_lock: with self.__connection_lock:
self.__connecting_username = username 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)) params.get("deviceName"), self.__inner.device_id))
response = json.dumps(self.__default_successful_add_user) response = json.dumps(self.__default_successful_add_user)
__socket.send(http_version.encode()) __socket.send(http_version.encode())
@ -168,6 +180,8 @@ class ZeroconfServer(Closeable):
.create() .create()
with self.__connection_lock: with self.__connection_lock:
self.__connecting_username = None 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, def handle_get_info(self, __socket: socket.socket,
http_version: str) -> None: http_version: str) -> None:
@ -204,6 +218,19 @@ class ZeroconfServer(Closeable):
parsed[key] = value parsed[key] = value
return parsed 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): class HttpRunner(Closeable, Runnable):
__should_stop = False __should_stop = False
__socket: socket.socket __socket: socket.socket
@ -215,7 +242,7 @@ class ZeroconfServer(Closeable):
self.__socket.bind((".".join(["0"] * 4), port)) self.__socket.bind((".".join(["0"] * 4), port))
self.__socket.listen(5) self.__socket.listen(5)
self.__zeroconf_server = zeroconf_server 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: def close(self) -> None:
pass pass
@ -234,7 +261,7 @@ class ZeroconfServer(Closeable):
request = io.BytesIO(__socket.recv(1024 * 1024)) request = io.BytesIO(__socket.recv(1024 * 1024))
request_line = request.readline().strip().split(b" ") request_line = request.readline().strip().split(b" ")
if len(request_line) != 3: if len(request_line) != 3:
logging.warning( self.__zeroconf_server.logger.warning(
"Unexpected request line: {}".format(request_line)) "Unexpected request line: {}".format(request_line))
method = request_line[0].decode() method = request_line[0].decode()
path = request_line[1].decode() path = request_line[1].decode()
@ -247,18 +274,18 @@ class ZeroconfServer(Closeable):
split = header.split(b":") split = header.split(b":")
headers[split[0].decode()] = split[1].strip().decode() headers[split[0].decode()] = split[1].strip().decode()
if not self.__zeroconf_server.has_valid_session(): if not self.__zeroconf_server.has_valid_session():
logging.debug( self.__zeroconf_server.logger.debug(
"Handling request: {}, {}, {}, headers: {}".format( "Handling request: {}, {}, {}, headers: {}".format(
method, path, http_version, headers)) method, path, http_version, headers))
params = {} params = {}
if method == "POST": if method == "POST":
content_type = headers.get("Content-Type") content_type = headers.get("Content-Type")
if content_type != "application/x-www-form-urlencoded": 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 return
content_length_str = headers.get("Content-Length") content_length_str = headers.get("Content-Length")
if content_length_str is None: if content_length_str is None:
logging.error("Missing Content-Length header!") self.__zeroconf_server.logger.error("Missing Content-Length header!")
return return
content_length = int(content_length_str) content_length = int(content_length_str)
body = request.read(content_length).decode() body = request.read(content_length).decode()
@ -271,7 +298,7 @@ class ZeroconfServer(Closeable):
params = self.__zeroconf_server.parse_path(path) params = self.__zeroconf_server.parse_path(path)
action = params.get("action") action = params.get("action")
if action is None: if action is None:
logging.debug("Request is missing action.") self.__zeroconf_server.logger.debug("Request is missing action.")
return return
self.handle_request(__socket, http_version, action, params) self.handle_request(__socket, http_version, action, params)
@ -284,17 +311,7 @@ class ZeroconfServer(Closeable):
elif action == "getInfo": elif action == "getInfo":
self.__zeroconf_server.handle_get_info(__socket, http_version) self.__zeroconf_server.handle_get_info(__socket, http_version)
else: else:
logging.warning("Unknown action: {}".format(action)) self.__zeroconf_server.logger.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)
class Inner: class Inner:
conf: typing.Final[Session.Configuration] conf: typing.Final[Session.Configuration]