renamed short to room, fixed persistent room, argparse for client

This commit is contained in:
Christoph Stahl 2022-11-14 23:58:28 +01:00
parent 593ea380e3
commit 9685fe76a8
3 changed files with 106 additions and 75 deletions

View file

@ -1,6 +1,8 @@
import asyncio import asyncio
from traceback import print_exc from traceback import print_exc
from json import load from json import load
import logging
from argparse import ArgumentParser
import socketio import socketio
@ -9,21 +11,21 @@ from .entry import Entry
sio = socketio.AsyncClient() sio = socketio.AsyncClient()
logger = logging.getLogger(__name__)
sources: dict[str, Source] = {}
with open("./syng-client.json", encoding="utf8") as f:
source_config = load(f)
sources: dict[str, Source] = configure_sources(source_config)
currentLock = asyncio.Semaphore(0) currentLock = asyncio.Semaphore(0)
state = { state = {
"current": None, "current": None,
"queue": [], "queue": [],
"room": None,
} }
@sio.on("skip") @sio.on("skip")
async def handle_skip(): async def handle_skip():
print("Skipping current") logger.info("Skipping current")
await state["current"].skip_current() await state["current"].skip_current()
@ -34,12 +36,13 @@ async def handle_state(data):
@sio.on("connect") @sio.on("connect")
async def handle_connect(): async def handle_connect():
print("Connected to server") logging.info("Connected to server")
await sio.emit( await sio.emit(
"register-client", "register-client",
{ {
"secret": "test", "secret": "test",
"queue": [entry.to_dict() for entry in state["queue"]], "queue": [entry.to_dict() for entry in state["queue"]],
"room": state["room"],
}, },
) )
@ -54,7 +57,7 @@ async def handle_buffer(data):
@sio.on("play") @sio.on("play")
async def handle_play(data): async def handle_play(data):
entry = Entry(**data) entry = Entry(**data)
print(f"Playing {entry}") logging.info("Playing %s", entry)
try: try:
meta_info = await sources[entry.source].buffer(entry) meta_info = await sources[entry.source].buffer(entry)
await sio.emit("meta-info", {"uuid": data["uuid"], "meta": meta_info}) await sio.emit("meta-info", {"uuid": data["uuid"], "meta": meta_info})
@ -62,19 +65,21 @@ async def handle_play(data):
await sources[entry.source].play(entry) await sources[entry.source].play(entry)
except Exception: except Exception:
print_exc() print_exc()
print("Finished, waiting for next") logging.info("Finished, waiting for next")
await sio.emit("pop-then-get-next") await sio.emit("pop-then-get-next")
@sio.on("client-registered") @sio.on("client-registered")
async def handle_register(data): async def handle_register(data):
if data["success"]: if data["success"]:
print("Registered") logging.info("Registered")
await sio.emit("sources", {"sources": list(source_config.keys())}) print(f"Join using code: {data['room']}")
state["room"] = data["room"]
await sio.emit("sources", {"sources": list(sources.keys())})
if state["current"] is None: if state["current"] is None:
await sio.emit("get-first") await sio.emit("get-first")
else: else:
print("Registration failed") logging.warning("Registration failed")
await sio.disconnect() await sio.disconnect()
@ -99,6 +104,19 @@ async def handle_request_config(data):
async def main(): async def main():
parser = ArgumentParser()
parser.add_argument("--room", "-r")
parser.add_argument("config")
args = parser.parse_args()
with open(args.config, encoding="utf8") as file:
source_config = load(file)
sources.update(configure_sources(source_config))
if args.room:
state["room"] = args.room
await sio.connect("http://127.0.0.1:8080") await sio.connect("http://127.0.0.1:8080")
await sio.wait() await sio.wait()

View file

@ -5,6 +5,7 @@ import asyncio
from dataclasses import dataclass from dataclasses import dataclass
import string import string
import random import random
import logging
from aiohttp import web from aiohttp import web
import socketio import socketio
@ -12,42 +13,48 @@ import socketio
from .entry import Entry from .entry import Entry
from .sources import Source, available_sources from .sources import Source, available_sources
sio = socketio.AsyncServer(cors_allowed_origins="*", logger=True, engineio_logger=True) sio = socketio.AsyncServer(cors_allowed_origins="*", logger=True, engineio_logger=False)
app = web.Application() app = web.Application()
sio.attach(app) sio.attach(app)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
clients = {} clients = {}
class Queue(deque): class Queue:
def __init__(self, iterable=[]): def __init__(self, *args, **kwargs):
super().__init__(iterable) self._queue = deque(*args, **kwargs)
self.num_of_entries_sem = asyncio.Semaphore(len(iterable)) self.num_of_entries_sem = asyncio.Semaphore(len(self._queue))
self.readlock = asyncio.Lock() self.readlock = asyncio.Lock()
async def append(self, item: Entry) -> None: def append(self, x: Entry) -> None:
super().append(item) self._queue.append(x)
self.num_of_entries_sem.release() self.num_of_entries_sem.release()
async def peek(self) -> Entry: async def peek(self) -> Entry:
async with self.readlock: async with self.readlock:
await self.num_of_entries_sem.acquire() await self.num_of_entries_sem.acquire()
item = super().popleft() item = self._queue[0]
super().appendleft(item)
self.num_of_entries_sem.release() self.num_of_entries_sem.release()
return item return item
async def popleft(self) -> Entry: async def popleft(self) -> Entry:
async with self.readlock: async with self.readlock:
await self.num_of_entries_sem.acquire() await self.num_of_entries_sem.acquire()
item = super().popleft() item = self._queue.popleft()
await sio.emit("state", self.to_dict())
return item return item
def to_dict(self) -> list[dict[str, Any]]: def to_dict(self) -> list[dict[str, Any]]:
return [item.to_dict() for item in self] return [item.to_dict() for item in self._queue]
def update(self, locator, updater):
for item in self._queue:
if locator(item):
updater(item)
@dataclass @dataclass
@ -62,8 +69,8 @@ class State:
@sio.on("get-state") @sio.on("get-state")
async def handle_state(sid, data: dict[str, Any] = {}): async def handle_state(sid, data: dict[str, Any] = {}):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
await sio.emit("state", state.queue.to_dict(), room=sid) await sio.emit("state", state.queue.to_dict(), room=sid)
@ -71,40 +78,43 @@ async def handle_state(sid, data: dict[str, Any] = {}):
@sio.on("append") @sio.on("append")
async def handle_append(sid, data: dict[str, Any]): async def handle_append(sid, data: dict[str, Any]):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
print(f"append: {data}")
source_obj = state.sources[data["source"]] source_obj = state.sources[data["source"]]
entry = await Entry.from_source(data["performer"], data["id"], source_obj) entry = await Entry.from_source(data["performer"], data["id"], source_obj)
await state.queue.append(entry) state.queue.append(entry)
await sio.emit("state", state.queue.to_dict(), room=short) await sio.emit("state", state.queue.to_dict(), room=room)
await sio.emit( await sio.emit(
"buffer", "buffer",
entry.to_dict(), entry.to_dict(),
room=clients[short].sid, room=clients[room].sid,
) )
@sio.on("meta-info") @sio.on("meta-info")
async def handle_meta_info(sid, data): async def handle_meta_info(sid, data):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
for item in state.queue: state.queue.update(
if str(item.uuid) == data["uuid"]: lambda item: str(item.uuid) == data["uuid"],
item.update(**data["meta"]) lambda item: item.update(**data["meta"]),
)
# for item in state.queue:
# if str(item.uuid) == data["uuid"]:
# item.update(**data["meta"])
await sio.emit("state", state.queue.to_dict(), room=short) await sio.emit("state", state.queue.to_dict(), room=room)
@sio.on("get-first") @sio.on("get-first")
async def handle_get_first(sid, data={}): async def handle_get_first(sid, data={}):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
current = await state.queue.peek() current = await state.queue.peek()
@ -114,13 +124,13 @@ async def handle_get_first(sid, data={}):
@sio.on("pop-then-get-next") @sio.on("pop-then-get-next")
async def handle_pop_then_get_next(sid, data={}): async def handle_pop_then_get_next(sid, data={}):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
await state.queue.popleft() await state.queue.popleft()
current = await state.queue.peek() current = await state.queue.peek()
await sio.emit("state", state.queue.to_dict(), room=short) await sio.emit("state", state.queue.to_dict(), room=room)
await sio.emit("play", current.to_dict(), room=sid) await sio.emit("play", current.to_dict(), room=sid)
@ -133,29 +143,30 @@ def gen_id(length=4) -> str:
@sio.on("register-client") @sio.on("register-client")
async def handle_register_client(sid, data: dict[str, Any]): async def handle_register_client(sid, data: dict[str, Any]):
print(f"Registerd new client {sid}") room = data["room"] if "room" in data and data["room"] else gen_id()
short = data["short"] if "short" in data else gen_id()
async with sio.session(sid) as session: async with sio.session(sid) as session:
session["short"] = short session["room"] = room
print(f"short id: {short}") if room in clients:
if data["short"] in clients: old_state = clients[room]
old_state = clients[short]
if data["secret"] == old_state.secret: if data["secret"] == old_state.secret:
logger.info("Got new client connection for %s", room)
old_state.sid = sid old_state.sid = sid
sio.enter_room(sid, short) sio.enter_room(sid, room)
await sio.emit( await sio.emit(
"client-registered", {"success": True, "short": short}, room=sid "client-registered", {"success": True, "room": room}, room=sid
) )
else: else:
logger.warning("Got wrong secret for %s", room)
await sio.emit( await sio.emit(
"client-registered", {"success": False, "short": short}, room=sid "client-registered", {"success": False, "room": room}, room=sid
) )
else: else:
logger.info("Registerd new client %s", room)
initial_entries = [Entry(**entry) for entry in data["queue"]] initial_entries = [Entry(**entry) for entry in data["queue"]]
clients[short] = State(data["secret"], {}, [], Queue(initial_entries), sid) clients[room] = State(data["secret"], {}, [], Queue(initial_entries), sid)
sio.enter_room(sid, short) sio.enter_room(sid, room)
await sio.emit("client-registered", {"success": True, "short": short}, room=sid) await sio.emit("client-registered", {"success": True, "room": room}, room=sid)
@sio.on("sources") @sio.on("sources")
@ -166,8 +177,8 @@ async def handle_sources(sid, data):
sources and query for a config for all uninitialized sources sources and query for a config for all uninitialized sources
""" """
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
unused_sources = state.sources.keys() - data["sources"] unused_sources = state.sources.keys() - data["sources"]
new_sources = data["sources"] - state.sources.keys() new_sources = data["sources"] - state.sources.keys()
@ -184,10 +195,11 @@ async def handle_sources(sid, data):
@sio.on("config-chunk") @sio.on("config-chunk")
async def handle_config_chung(sid, data): async def handle_config_chung(sid, data):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
if not data["source"] in state.sources: if not data["source"] in state.sources:
logger.info("Added source %s", data["source"])
state.sources[data["source"]] = available_sources[data["source"]]( state.sources[data["source"]] = available_sources[data["source"]](
data["config"] data["config"]
) )
@ -198,19 +210,19 @@ async def handle_config_chung(sid, data):
@sio.on("config") @sio.on("config")
async def handle_config(sid, data): async def handle_config(sid, data):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
state.sources[data["source"]] = available_sources[data["source"]](data["config"]) state.sources[data["source"]] = available_sources[data["source"]](data["config"])
print(f"Added source {data['source']}") logger.info("Added source %s", data["source"])
@sio.on("register-web") @sio.on("register-web")
async def handle_register_web(sid, data): async def handle_register_web(sid, data):
async with sio.session(sid) as session: async with sio.session(sid) as session:
session["short"] = data["short"] session["room"] = data["room"]
sio.enter_room(sid, session["short"]) sio.enter_room(sid, session["room"])
state = clients[session["short"]] state = clients[session["room"]]
await sio.emit("state", state.queue.to_dict(), room=sid) await sio.emit("state", state.queue.to_dict(), room=sid)
@ -218,8 +230,8 @@ async def handle_register_web(sid, data):
@sio.on("register-admin") @sio.on("register-admin")
async def handle_register_admin(sid, data: dict[str, str]): async def handle_register_admin(sid, data: dict[str, str]):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
is_admin = data["secret"] == state.secret is_admin = data["secret"] == state.secret
async with sio.session(sid) as session: async with sio.session(sid) as session:
@ -230,9 +242,9 @@ async def handle_register_admin(sid, data: dict[str, str]):
@sio.on("get-config") @sio.on("get-config")
async def handle_get_config(sid, data): async def handle_get_config(sid, data):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
is_admin = session["admin"] is_admin = session["admin"]
state = clients[short] state = clients[room]
if is_admin: if is_admin:
await sio.emit( await sio.emit(
@ -244,24 +256,24 @@ async def handle_get_config(sid, data):
@sio.on("skip") @sio.on("skip")
async def handle_skip(sid, data={}): async def handle_skip(sid, data={}):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
is_admin = session["admin"] is_admin = session["admin"]
if is_admin: if is_admin:
await sio.emit("skip", room=clients[short].sid) await sio.emit("skip", room=clients[room].sid)
@sio.on("disconnect") @sio.on("disconnect")
async def handle_disconnect(sid, data={}): async def handle_disconnect(sid, data={}):
async with sio.session(sid) as session: async with sio.session(sid) as session:
sio.leave_room(sid, session["short"]) sio.leave_room(sid, session["room"])
@sio.on("search") @sio.on("search")
async def handle_search(sid, data: dict[str, str]): async def handle_search(sid, data: dict[str, str]):
async with sio.session(sid) as session: async with sio.session(sid) as session:
short = session["short"] room = session["room"]
state = clients[short] state = clients[room]
query = data["query"] query = data["query"]
result_futures = [] result_futures = []
@ -276,7 +288,6 @@ async def handle_search(sid, data: dict[str, str]):
for result_future in result_futures for result_future in result_futures
for search_result in await result_future for search_result in await result_future
] ]
print(f"Found {len(results)} results")
await sio.emit("search-results", [result.to_dict() for result in results], room=sid) await sio.emit("search-results", [result.to_dict() for result in results], room=sid)

View file

@ -5,6 +5,7 @@ from .entry import Entry
from aiocmd import aiocmd from aiocmd import aiocmd
sio = socketio.AsyncClient() sio = socketio.AsyncClient()
state = {}
@sio.on("search-results") @sio.on("search-results")
@ -26,6 +27,7 @@ async def handle_state(data):
@sio.on("connect") @sio.on("connect")
async def handle_connect(): async def handle_connect():
print("Connected") print("Connected")
await sio.emit("register-web", {"room": state["room"]})
@sio.on("register-admin") @sio.on("register-admin")
@ -61,9 +63,9 @@ class SyngShell(aiocmd.PromptToolkitCmd):
async def do_admin(self, data): async def do_admin(self, data):
await sio.emit("register-admin", {"secret": data}) await sio.emit("register-admin", {"secret": data})
async def do_connect(self, short): async def do_connect(self, room):
state["room"] = room
await sio.connect("http://127.0.0.1:8080") await sio.connect("http://127.0.0.1:8080")
await sio.emit("register-web", {"short": short})
async def do_skip(self): async def do_skip(self):
await sio.emit("skip") await sio.emit("skip")