mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-11-08 17:05:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420adde173 |
No files matched your search
@@ -0,0 +1,24 @@
|
|||||||
|
# Connection and context navigation
|
||||||
|
|
||||||
|
`get_connections()` returns one Connection snapshot per live connection:
|
||||||
|
`id`, `network`, `server`, `context`. Connection IDs distinguish simultaneous
|
||||||
|
connections with identical names. Prefer its server-tab context where present.
|
||||||
|
`get_connection(context=None)` finds the owner of a live context.
|
||||||
|
|
||||||
|
`get_contexts(connection_id=None, types=None)` lists live contexts, optionally
|
||||||
|
filtered by connection ID (or a Connection object) and session types: server=1,
|
||||||
|
channel=2, query=3. `connection.contexts(types=None)` is a convenience method.
|
||||||
|
`find_context_by_id(connection_id, channel=None)` / `connection.find(channel)`
|
||||||
|
resolve using that connection's IRC comparator; no channel selects the server tab.
|
||||||
|
Missing/closed contexts return None. Existing find_context() remains unchanged.
|
||||||
|
|
||||||
|
These helpers use the existing channels list's ID/context fields, so no new C
|
||||||
|
context ABI is needed. Snapshots are not persistent IDs across client restarts;
|
||||||
|
refresh them after closing/recreating connections. Always check Context.set()
|
||||||
|
when explicitly selecting a context that might have closed.
|
||||||
|
|
||||||
|
```python
|
||||||
|
connection = zoitechat.get_connection()
|
||||||
|
for context in connection.contexts(types=(2,)):
|
||||||
|
context.prnt('A channel on this exact connection')
|
||||||
|
```
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Explicit IRC comparison keys
|
|
||||||
|
|
||||||
`IRCName(text, context=None, casemapping=None, connection_id=None)` is an
|
|
||||||
immutable helper, not a str subclass. Construct it explicitly for dictionary/set
|
|
||||||
keys; existing get_info/list/event values remain normal strings.
|
|
||||||
Equality and hashing include the connection ID and captured casemapping, so
|
|
||||||
identical-looking names on different connections are different keys. An IRCName
|
|
||||||
never compares equal to a plain str. `str(key)` / `key.text` recover the spelling.
|
|
||||||
|
|
||||||
`irc_casefold(text, casemapping='rfc1459')` supports ascii, strict-rfc1459 and
|
|
||||||
rfc1459. Only ASCII IRC characters fold; Unicode casefold would be incorrect.
|
|
||||||
The new `get_info('casemapping')` reports the comparator actually used by core.
|
|
||||||
Current master handles ASCII and RFC1459; this patch does not change its handling
|
|
||||||
of advertised strict-rfc1459. An explicit mapping is available for scripts that
|
|
||||||
need strict-rfc1459 independently. Keys capture their mapping so hashes stay
|
|
||||||
stable; rebuild a collection if the connection's mapping changes.
|
|
||||||
|
|
||||||
```python
|
|
||||||
seen = {zoitechat.IRCName('[SomeNick]')}
|
|
||||||
assert zoitechat.IRCName('{somenick}') in seen # RFC1459 connection
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Navigate live connections by ID instead of ambiguous server/network names."""
|
||||||
|
from collections import namedtuple
|
||||||
|
import operator
|
||||||
|
import _zoitechat as api
|
||||||
|
|
||||||
|
__all__ = ['Connection', 'get_connections', 'get_connection', 'get_contexts',
|
||||||
|
'find_context_by_id']
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(namedtuple('_Connection', 'id network server context')):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def contexts(self, types=None):
|
||||||
|
return get_contexts(self.id, types)
|
||||||
|
|
||||||
|
def find(self, channel=None):
|
||||||
|
return find_context_by_id(self.id, channel)
|
||||||
|
|
||||||
|
|
||||||
|
def _id(connection):
|
||||||
|
return operator.index(connection.id if isinstance(connection, Connection) else connection)
|
||||||
|
|
||||||
|
|
||||||
|
def get_connections():
|
||||||
|
"""Return one snapshot per live connection, including disconnected ones."""
|
||||||
|
entries = api.get_list('channels')
|
||||||
|
by_id = {}
|
||||||
|
order = []
|
||||||
|
for entry in entries:
|
||||||
|
if entry.id not in by_id:
|
||||||
|
order.append(entry.id)
|
||||||
|
if entry.id not in by_id or entry.type == 1:
|
||||||
|
by_id[entry.id] = Connection(entry.id, entry.network, entry.server,
|
||||||
|
entry.context)
|
||||||
|
return [by_id[connection_id] for connection_id in order]
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection(context=None):
|
||||||
|
"""Find the owning connection of a Context; return None when it has closed."""
|
||||||
|
context = context or api.get_context()
|
||||||
|
for entry in api.get_list('channels'):
|
||||||
|
if entry.context == context:
|
||||||
|
for connection in get_connections():
|
||||||
|
if connection.id == entry.id:
|
||||||
|
return connection
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_contexts(connection_id=None, types=None):
|
||||||
|
"""Return contexts, optionally restricted to a connection and session types."""
|
||||||
|
if connection_id is not None:
|
||||||
|
connection_id = _id(connection_id)
|
||||||
|
if types is not None:
|
||||||
|
types = frozenset(types)
|
||||||
|
return [entry.context for entry in api.get_list('channels')
|
||||||
|
if (connection_id is None or entry.id == connection_id)
|
||||||
|
and (types is None or entry.type in types)]
|
||||||
|
|
||||||
|
|
||||||
|
def find_context_by_id(connection_id, channel=None):
|
||||||
|
"""Resolve a live context on exactly one connection; None selects its server tab."""
|
||||||
|
connection_id = _id(connection_id)
|
||||||
|
previous = api.get_context()
|
||||||
|
try:
|
||||||
|
for entry in api.get_list('channels'):
|
||||||
|
if entry.id != connection_id:
|
||||||
|
continue
|
||||||
|
if channel is None:
|
||||||
|
if entry.type == 1:
|
||||||
|
return entry.context
|
||||||
|
elif entry.context.set() and api.nickcmp(entry.channel, channel) == 0:
|
||||||
|
return entry.context
|
||||||
|
finally:
|
||||||
|
previous.set()
|
||||||
|
return None
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
"""Explicit immutable IRC names; existing string-returning APIs are unchanged."""
|
|
||||||
import _zoitechat as api
|
|
||||||
|
|
||||||
__all__ = ['IRCName', 'irc_casefold']
|
|
||||||
|
|
||||||
|
|
||||||
def irc_casefold(text, casemapping='rfc1459'):
|
|
||||||
"""Fold ASCII IRC characters only; never apply Unicode lower()/casefold()."""
|
|
||||||
if casemapping not in ('ascii', 'strict-rfc1459', 'rfc1459'):
|
|
||||||
raise ValueError('unsupported IRC casemapping')
|
|
||||||
extra = {'[': '{', ']': '}', '\\': '|'} if casemapping != 'ascii' else {}
|
|
||||||
if casemapping == 'rfc1459':
|
|
||||||
extra['^'] = '~'
|
|
||||||
return ''.join(chr(ord(c) + 32) if 'A' <= c <= 'Z' else extra.get(c, c)
|
|
||||||
for c in text)
|
|
||||||
|
|
||||||
|
|
||||||
class IRCName:
|
|
||||||
"""Equality/hash include connection identity and the captured casemapping."""
|
|
||||||
__slots__ = ('_text', '_casemapping', '_connection_id', '_key')
|
|
||||||
|
|
||||||
def __init__(self, text, context=None, casemapping=None, connection_id=None):
|
|
||||||
context = context or api.get_context()
|
|
||||||
if casemapping is None:
|
|
||||||
casemapping = context.get_info('casemapping')
|
|
||||||
if casemapping is None:
|
|
||||||
raise RuntimeError('host does not expose its comparison casemapping')
|
|
||||||
if connection_id is None:
|
|
||||||
previous = api.get_context()
|
|
||||||
if not context.set():
|
|
||||||
raise ValueError('context has closed')
|
|
||||||
try:
|
|
||||||
connection_id = api.get_prefs('id')
|
|
||||||
finally:
|
|
||||||
previous.set()
|
|
||||||
object.__setattr__(self, '_text', text)
|
|
||||||
object.__setattr__(self, '_casemapping', casemapping)
|
|
||||||
object.__setattr__(self, '_connection_id', connection_id)
|
|
||||||
object.__setattr__(self, '_key', (connection_id, casemapping,
|
|
||||||
irc_casefold(text, casemapping)))
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
raise AttributeError('IRCName is immutable')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
@property
|
|
||||||
def casemapping(self):
|
|
||||||
return self._casemapping
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connection_id(self):
|
|
||||||
return self._connection_id
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'IRCName({!r}, casemapping={!r}, connection_id={!r})'.format(
|
|
||||||
self._text, self._casemapping, self._connection_id)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, IRCName):
|
|
||||||
return NotImplemented
|
|
||||||
return self._key == other._key
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
result = self.__eq__(other)
|
|
||||||
return NotImplemented if result is NotImplemented else not result
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self._key)
|
|
||||||
@@ -1187,16 +1187,6 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
|
|||||||
return text_find_format_string (e);
|
return text_find_format_string (e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Report the comparison actually used by this connection, not an
|
|
||||||
* advertised mapping that the current core does not implement. */
|
|
||||||
if (strcmp (id, "casemapping") == 0)
|
|
||||||
{
|
|
||||||
if (!is_session (ph->context))
|
|
||||||
return NULL;
|
|
||||||
return ph->context->server->p_cmp == g_ascii_strcasecmp
|
|
||||||
? "ascii" : "rfc1459";
|
|
||||||
}
|
|
||||||
|
|
||||||
hash = str_hash (id);
|
hash = str_hash (id);
|
||||||
/* do the session independant ones first */
|
/* do the session independant ones first */
|
||||||
switch (hash)
|
switch (hash)
|
||||||
|
|||||||
Reference in new issue
Block a user