From d2bca61ebd695ea34d32dcd79fe783362e424e2c Mon Sep 17 00:00:00 2001 From: Christoph Stahl Date: Tue, 17 Jun 2025 00:13:25 +0200 Subject: [PATCH] Allow client and server to import and export the queue/waitingroom/recent --- syng/client.py | 38 ++++++++++++++++++++++++++++++++++++++ syng/jsonencoder.py | 12 +++++++++++- syng/queue.py | 11 +++++++++++ syng/server.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/syng/client.py b/syng/client.py index e92a921..25e7ef9 100644 --- a/syng/client.py +++ b/syng/client.py @@ -45,6 +45,7 @@ from . import SYNG_VERSION, jsonencoder from .entry import Entry from .sources import configure_sources, Source from .log import logger +from . import jsonencoder class ConnectionState: @@ -617,6 +618,43 @@ class Client: logger.info("Removing room %s from server", self.state.config["room"]) await self.sio.emit("remove-room", {"room": self.state.config["room"]}) + def export_queue(self, filename: str) -> None: + """ + Export the current queue to a file. + + :param filename: The name of the file to export the queue to. + :type filename: str + :rtype: None + """ + with open(filename, "w", encoding="utf8") as file: + jsonencoder.dump( + { + "queue": self.state.queue, + "waiting_room": self.state.waiting_room, + "recent": self.state.recent, + }, + file, + indent=2, + ensure_ascii=False, + ) + + async def import_queue(self, filename: str) -> None: + """ + Import a queue from a file. + + :param filename: The name of the file to import the queue from. + :type filename: str + :rtype: None + """ + with open(filename, "r", encoding="utf8") as file: + data = jsonencoder.load(file) + queue = [Entry(**entry) for entry in data["queue"]] + waiting_room = [Entry(**entry) for entry in data["waiting_room"]] + recent = [Entry(**entry) for entry in data["recent"]] + await self.sio.emit( + "import-queue", {"queue": queue, "waiting_room": waiting_room, "recent": recent} + ) + async def handle_room_removed(self, data: dict[str, Any]) -> None: """ Handle the "room-removed" message. diff --git a/syng/jsonencoder.py b/syng/jsonencoder.py index 7f1bc2f..4e82417 100644 --- a/syng/jsonencoder.py +++ b/syng/jsonencoder.py @@ -34,10 +34,20 @@ class SyngEncoder(json.JSONEncoder): def dumps(obj: Any, **kw: Any) -> str: - """Wrap around ``json.dump`` with the :py:class:`SyngEncoder`.""" + """Wrap around ``json.dumps`` with the :py:class:`SyngEncoder`.""" return json.dumps(obj, cls=SyngEncoder, **kw) +def dump(obj: Any, fp: Any, **kw: Any) -> None: + """Forward everything to ``json.dump``.""" + json.dump(obj, fp, cls=SyngEncoder, **kw) + + def loads(string: str, **kw: Any) -> Any: """Forward everything to ``json.loads``.""" return json.loads(string, **kw) + + +def load(fp: Any, **kw: Any) -> Any: + """Forward everything to ``json.load``.""" + return json.load(fp, **kw) diff --git a/syng/queue.py b/syng/queue.py index d7e5566..9e272d6 100644 --- a/syng/queue.py +++ b/syng/queue.py @@ -31,6 +31,17 @@ class Queue: self.num_of_entries_sem = asyncio.Semaphore(len(self._queue)) self.readlock = asyncio.Lock() + def extend(self, entries: Iterable[Entry]) -> None: + """ + Extend the queue with a list of entries and increase the semaphore. + + :param entries: The entries to add + :type entries: Iterable[Entry] + :rtype: None + """ + for entry in entries: + self.append(entry) + def append(self, entry: Entry) -> None: """ Append an entry to the queue, increase the semaphore. diff --git a/syng/server.py b/syng/server.py index 9ba0ccb..51d0720 100644 --- a/syng/server.py +++ b/syng/server.py @@ -230,6 +230,7 @@ class Server: self.sio.on("connect", self.handle_connect) self.sio.on("search", self.handle_search) self.sio.on("search-results", self.handle_search_results) + self.sio.on("import-queue", self.handle_import_queue) async def is_admin(self, state: State, sid: str) -> bool: """ @@ -963,6 +964,35 @@ class Server: ) return True + @admin + @with_state + async def handle_import_queue(self, state: State, sid: str, data: dict[str, Any]) -> None: + """ + Handle the "import-queue" message. + + This will add entries to the queue and waiting room from the client. + + The data dictionary should have the following keys: + - `queue`, a list of entries to import into the queue + - `waiting_room`, a list of entries to import into the waiting room + + :param sid: The session id of the client sending this request + :type sid: str + :param data: A dictionary with the keys described above + :type data: dict[str, Any] + :rtype: None + """ + + queue_entries = [Entry(**entry) for entry in data.get("queue", [])] + waiting_room_entries = [Entry(**entry) for entry in data.get("waiting_room", [])] + recent_entries = [Entry(**entry) for entry in data.get("recent", [])] + + state.queue.extend(queue_entries) + state.waiting_room.extend(waiting_room_entries) + state.recent.extend(recent_entries) + + await self.broadcast_state(state, sid=sid) + @admin @with_state async def handle_remove_room(self, state: State, sid: str, data: dict[str, Any]) -> None: