From: Christian Heller Date: Mon, 25 Aug 2025 17:06:05 +0000 (+0200) Subject: Simplify ChannelDb.user_ids structure, minor bugfixes. X-Git-Url: https://plomlompom.com/repos/test?a=commitdiff_plain;h=8a1bc2053aea9ba75b9827519e42252c8827d4e4;p=ircplom Simplify ChannelDb.user_ids structure, minor bugfixes. --- diff --git a/ircplom/client.py b/ircplom/client.py index 78243f1..8b02880 100644 --- a/ircplom/client.py +++ b/ircplom/client.py @@ -6,7 +6,7 @@ from dataclasses import dataclass, asdict as dc_asdict, InitVar from enum import Enum, auto from getpass import getuser from threading import Thread -from typing import Any, Callable, NamedTuple, Optional, Self +from typing import Any, Callable, Generic, NamedTuple, Optional, Self, TypeVar from uuid import uuid4 # ourselves from ircplom.events import ( @@ -110,6 +110,11 @@ _EXPECTATIONS += [ _MsgTok.ANY, _MsgTok.ANY, _MsgTok.ANY)), + _MsgParseExpectation(_MsgTok.SERVER, + '366', # RPL_ENDOFNAMES + ((_MsgTok.NICKNAME, 'set_me_attr:nick'), + _MsgTok.CHANNEL, + _MsgTok.ANY)), # comment _MsgParseExpectation(_MsgTok.SERVER, '375', # RPL_MOTDSTART already implied by 1st 372 ((_MsgTok.NICKNAME, 'set_me_attr:nick'), @@ -269,11 +274,6 @@ _EXPECTATIONS += [ '=', (_MsgTok.CHANNEL, ':channel'), (_MsgTok.LIST, ':names'))), - _MsgParseExpectation(_MsgTok.SERVER, - '366', # RPL_ENDOFNAMES - ((_MsgTok.NICKNAME, 'set_me_attr:nick'), - (_MsgTok.CHANNEL, ':channel'), - _MsgTok.ANY)), # comment _MsgParseExpectation((_MsgTok.NICK_USER_HOST, ':joiner'), 'JOIN', ((_MsgTok.CHANNEL, ':channel'),)), @@ -654,32 +654,42 @@ class _Db(Db): self._on_update(key) -class _ChannelDb(_Db): - _completable_user_ids: _CompletableStringsList +class SharedChannelDbFields: + 'API for fields shared directly in name and type with TUI.' + user_ids: tuple[str, ...] # topic: str # channel_modes: str -class SharedClientDbFields(IrcConnSetup): +_ChannelDbFields = TypeVar('_ChannelDbFields', bound=SharedChannelDbFields) + + +class _ChannelDb(_Db, SharedChannelDbFields): + + def __init__(self, purge_users: Callable, **kwargs) -> None: + self._purge_users = purge_users + super().__init__(**kwargs) + + def add_user(self, user_id: str) -> None: + 'Add user_id to .user_ids.' + self.user_ids = tuple(list(self.user_ids) + [user_id]) + self._on_update('user_ids') + + def remove_user(self, user_id: str) -> None: + 'Remove user_id from .user_ids.' + self.user_ids = tuple(id_ for id_ in self.user_ids if id_ != user_id) + self._on_update('user_ids') + self._purge_users() + + +class SharedClientDbFields(IrcConnSetup, Generic[_ChannelDbFields]): 'API for fields shared directly in name and type with TUI.' connection_state: str sasl_account: str sasl_auth_state: str user_modes: str users: Any - _channels: dict[str, Any] - - def _purge_users(self) -> None: - to_keep = {'me'} - for chan in self._channels.values(): - to_keep |= set(chan.user_ids) - for user_id in [id_ for id_ in self.users.keys if id_ not in to_keep]: - del self.users[user_id] - - def chans_of_user(self, user_id: str) -> tuple[str, ...]: - 'Return names of channels user of user_id currently participates in.' - return tuple(k for k, v in self._channels.items() - if user_id in v.user_ids) + _channels: dict[str, _ChannelDbFields] @dataclass @@ -744,19 +754,22 @@ class _ClientDb(_Db, SharedClientDbFields): self.users[id_] = _NickUserHost(query) return id_ - def remove_user_from_channel(self, user_id: str, chan_name: str) -> None: - 'Remove user from channel, check that user deleted if that was last.' - self.chan(chan_name).remove_completable('user_ids', user_id, True) - if user_id == 'me': - self.del_chan(chan_name) - self._purge_users() + def _purge_users(self) -> None: + to_keep = {'me'} + for chan in self._channels.values(): + to_keep |= set(chan.user_ids) + for user_id in [id_ for id_ in self.users.keys + if id_ not in to_keep]: + del self.users[user_id] def remove_user(self, user_id: str) -> tuple[str, ...]: 'Run remove_user_from_channel on all channels user is in.' - affected_chans = self.chans_of_user(user_id) - for chan_name in affected_chans: - self.remove_user_from_channel(user_id, chan_name) - return affected_chans + affected_chans = [] + for id_, chan in [(k, v) for k, v in self._channels.items() + if user_id in v.user_ids]: + chan.remove_user(user_id) + affected_chans += [id_] + return tuple(affected_chans) def needs_arg(self, key: str) -> bool: 'Reply if attribute of key may reasonably be addressed without an arg.' @@ -777,7 +790,8 @@ class _ClientDb(_Db, SharedClientDbFields): 'Produce DB for channel of name – pre-existing, or newly created.' if name not in self._channels: self._channels[name] = _ChannelDb( - on_update=lambda k: self._on_update(name, k)) + on_update=lambda k: self._on_update(name, k), + purge_users=self._purge_users) return self._channels[name] @@ -951,7 +965,7 @@ class Client(ABC, ClientQueueMixin): setattr(self._db, arg, ret[arg]) if task == 'set_me_attr': setattr(self._db.users['me'], arg, ret[arg]) - if task == 'set_user': + if task == 'set_user' and ret[arg] != self._db.users['me']: self._db.user_id(ret[arg]) if ret['verb'] == '005': # RPL_ISUPPORT for item in ret['isupports']: @@ -962,11 +976,10 @@ class Client(ABC, ClientQueueMixin): self._db.isupports[toks[0]] = (toks[1] if len(toks) > 1 else '') elif ret['verb'] == '353': # RPL_NAMREPLY - for id_ in [self._db.user_id(name.lstrip(_ILLEGAL_NICK_FIRSTCHARS)) - for name in ret['names']]: - ret['channel']['db'].append_completable('user_ids', id_) - elif ret['verb'] == '366': # RPL_ENDOFNAMES - ret['channel']['db'].declare_complete('user_ids') + for user_id in [ + self._db.user_id(name.lstrip(_ILLEGAL_NICK_FIRSTCHARS)) + for name in ret['names']]: + ret['channel']['db'].add_user(user_id) elif ret['verb'] == '372': # RPL_MOTD self._db.append_completable('motd', ret['line']) elif ret['verb'] == '376': # RPL_ENDOFMOTD @@ -1004,8 +1017,7 @@ class Client(ABC, ClientQueueMixin): elif ret['verb'] == 'ERROR': self.close() elif ret['verb'] == 'JOIN' and ret['joiner'] != self._db.users['me']: - ret['channel']['db'].append_completable( - 'user_ids', self._db.user_id(ret['joiner']), True) + ret['channel']['db'].add_user(self._db.user_id(ret['joiner'])) elif ret['verb'] == 'NICK': user_id = self._db.user_id(ret['named']) self._db.users[user_id].nick = ret['nick'] @@ -1020,8 +1032,11 @@ class Client(ABC, ClientQueueMixin): else ret['channel']['id'])} self._log(ret['message'], out=False, **kw) elif ret['verb'] == 'PART': - self._db.remove_user_from_channel(self._db.user_id(ret['parter']), - ret['channel']['id']) + if ret['parter'] == self._db.users['me']: + self._db.del_chan(ret['channel']['id']) + else: + ret['channel']['db'].remove_user( + self._db.user_id(ret['parter'])) elif ret['verb'] == 'PING': self.send(IrcMessage(verb='PONG', params=(ret['reply'],))) elif ret['verb'] == 'QUIT': diff --git a/ircplom/client_tui.py b/ircplom/client_tui.py index 920dc35..7528cb2 100644 --- a/ircplom/client_tui.py +++ b/ircplom/client_tui.py @@ -9,7 +9,8 @@ from ircplom.tui_base import (BaseTui, PromptWidget, TuiEvent, Window, from ircplom.irc_conn import IrcMessage from ircplom.client import ( Client, ClientQueueMixin, Db, IrcConnSetup, LogScope, NewClientEvent, - NickUserHost, ServerCapability, SharedClientDbFields) + NickUserHost, ServerCapability, SharedChannelDbFields, + SharedClientDbFields) CMD_SHORTCUTS['disconnect'] = 'window.disconnect' CMD_SHORTCUTS['join'] = 'window.join' @@ -142,7 +143,7 @@ class _Db(Db): if update.value is None: if update.arg == '': d.clear() - else: + elif update.arg in d: del d[update.arg] return True old_value = d.get(update.arg, None) @@ -158,8 +159,7 @@ class _Db(Db): return update.value != old_value -class _ChannelDb(_Db): - user_ids: tuple[str, ...] +class _ChannelDb(_Db, SharedChannelDbFields): def set_and_check_for_change(self, update: _Update ) -> bool | dict[str, tuple[str, ...]]: