add session listener
This commit is contained in:
parent
e5d6db0b24
commit
33f40520da
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in New Issue