client: add return annotations and cache users/channels properties

This commit is contained in:
Username
2026-02-24 16:45:45 +01:00
parent bbd28e2840
commit aa17159f7e

View File

@@ -74,6 +74,10 @@ class MumbleClient:
self._mumble = None
self._connected = False
self._dispatcher: Callable | None = None
self._users_cache: dict[int, User] = {}
self._channels_cache: dict[int, Channel] = {}
self._users_dirty: bool = True
self._channels_dirty: bool = True
# Application callbacks (fired via dispatcher)
self.on_connected = None
@@ -83,14 +87,14 @@ class MumbleClient:
self.on_channel_update = None # ()
self.on_sound_received = None # (user, pcm_data)
def set_dispatcher(self, fn: Callable):
def set_dispatcher(self, fn: Callable) -> None:
"""Set a function to marshal callbacks into the host event loop.
Typically Textual's ``call_from_thread``.
"""
self._dispatcher = fn
def _dispatch(self, callback, *args):
def _dispatch(self, callback, *args) -> None:
"""Call *callback* via the dispatcher, or directly if none is set."""
if callback is None:
return
@@ -113,6 +117,8 @@ class MumbleClient:
def users(self) -> dict[int, User]:
if not self._mumble:
return {}
if not self._users_dirty:
return self._users_cache
result = {}
for sid, u in self._mumble.users.items():
result[sid] = User(
@@ -124,12 +130,16 @@ class MumbleClient:
self_mute=u.get("self_mute", False),
self_deaf=u.get("self_deaf", False),
)
self._users_cache = result
self._users_dirty = False
return result
@property
def channels(self) -> dict[int, Channel]:
if not self._mumble:
return {}
if not self._channels_dirty:
return self._channels_cache
result = {}
for cid, ch in self._mumble.channels.items():
result[cid] = Channel(
@@ -138,6 +148,8 @@ class MumbleClient:
parent_id=ch.get("parent", 0),
description=ch.get("description", ""),
)
self._channels_cache = result
self._channels_dirty = False
return result
@property
@@ -151,7 +163,7 @@ class MumbleClient:
# -- connection ----------------------------------------------------------
def connect(self):
def connect(self) -> None:
"""Connect to the Mumble server (blocking).
Raises:
@@ -204,7 +216,7 @@ class MumbleClient:
self._host, self._port, self._username,
)
def disconnect(self):
def disconnect(self) -> None:
"""Disconnect from the server."""
if self._mumble:
try:
@@ -212,9 +224,11 @@ class MumbleClient:
except Exception:
pass
self._connected = False
self._users_dirty = True
self._channels_dirty = True
log.info("disconnected")
def reconnect(self):
def reconnect(self) -> None:
"""Disconnect and reconnect to the same server.
Raises:
@@ -226,7 +240,7 @@ class MumbleClient:
# -- actions -------------------------------------------------------------
def send_text(self, message: str):
def send_text(self, message: str) -> None:
"""Send a text message to the current channel."""
if self._mumble and self._connected:
try:
@@ -237,12 +251,12 @@ class MumbleClient:
return
ch.send_text_message(message)
def send_audio(self, pcm_data: bytes):
def send_audio(self, pcm_data: bytes) -> None:
"""Send PCM audio to the server (pymumble encodes to Opus)."""
if self._mumble and self._connected:
self._mumble.sound_output.add_sound(pcm_data)
def join_channel(self, channel_id: int):
def join_channel(self, channel_id: int) -> None:
"""Move to a different channel.
Raises:
@@ -254,7 +268,7 @@ class MumbleClient:
raise ValueError(f"channel {channel_id} not found")
ch.move_in()
def set_self_deaf(self, deaf: bool):
def set_self_deaf(self, deaf: bool) -> None:
"""Toggle self-deafen on the server."""
if self._mumble and self._connected:
if deaf:
@@ -264,7 +278,7 @@ class MumbleClient:
# -- pymumble callbacks (run on pymumble thread) -------------------------
def _register_callbacks(self):
def _register_callbacks(self) -> None:
import pymumble_py3.constants as const
cb = self._mumble.callbacks
@@ -279,25 +293,31 @@ class MumbleClient:
cb.set_callback(const.PYMUMBLE_CLBK_CHANNELUPDATED, self._on_channel_event)
cb.set_callback(const.PYMUMBLE_CLBK_CHANNELREMOVED, self._on_channel_event)
def _on_connected(self):
def _on_connected(self) -> None:
self._connected = True
self._users_dirty = True
self._channels_dirty = True
self._dispatch(self.on_connected)
def _on_disconnected(self):
def _on_disconnected(self) -> None:
self._connected = False
self._users_dirty = True
self._channels_dirty = True
self._dispatch(self.on_disconnected)
def _on_text_message(self, message):
def _on_text_message(self, message) -> None:
users = self._mumble.users
actor = message.actor
name = users[actor]["name"] if actor in users else "?"
self._dispatch(self.on_text_message, name, message.message)
def _on_sound_received(self, user, sound_chunk):
def _on_sound_received(self, user, sound_chunk) -> None:
self._dispatch(self.on_sound_received, user, sound_chunk.pcm)
def _on_user_event(self, *_args):
def _on_user_event(self, *_args) -> None:
self._users_dirty = True
self._dispatch(self.on_user_update)
def _on_channel_event(self, *_args):
def _on_channel_event(self, *_args) -> None:
self._channels_dirty = True
self._dispatch(self.on_channel_update)