mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-07 22:41:21 +00:00
expose main-loop file descriptor hooks
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Python descriptor hooks
|
||||
|
||||
`hook_fd(fd, callback, userdata=None, flags=FD_READ)` returns an ordinary hook
|
||||
handle accepted by `unhook()`. Callback arguments are `(fd, ready_flags, userdata)`;
|
||||
return True to retain the watch and False/None to remove it. Exceptions print a
|
||||
traceback and remove the watch. Unloading the script removes its watches.
|
||||
The callback runs in the context captured during registration, and that context
|
||||
is restored afterwards. A closed context removes the watch.
|
||||
|
||||
Use nonblocking IO and bounded reads/writes. The descriptor belongs to the script;
|
||||
unhook before closing it. On Windows socket descriptors use the default flags;
|
||||
CRT descriptors need FD_NOTSOCKET. The existing C API uses int descriptors: values
|
||||
outside its range are rejected rather than truncated. This does not add threads.
|
||||
|
||||
Example:
|
||||
```python
|
||||
watch = zoitechat.hook_fd(sock.fileno(), on_readable, flags=zoitechat.FD_READ)
|
||||
# def on_readable(fd, flags, userdata): ...; return True
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
"""File descriptor readiness hooks; callbacks execute on the main thread."""
|
||||
import operator
|
||||
import _zoitechat as api
|
||||
from _zoitechat_embedded import ffi, lib
|
||||
|
||||
__all__ = ['FD_READ', 'FD_WRITE', 'FD_EXCEPTION', 'FD_NOTSOCKET', 'hook_fd']
|
||||
FD_READ, FD_WRITE, FD_EXCEPTION, FD_NOTSOCKET = 1, 2, 4, 8
|
||||
|
||||
|
||||
def hook_fd(fd, callback, userdata=None, flags=FD_READ):
|
||||
"""callback(fd, ready_flags, userdata): True keeps the watch, False removes it."""
|
||||
fd = operator.index(fd)
|
||||
flags = operator.index(flags)
|
||||
if fd < 0 or fd > 2147483647:
|
||||
raise ValueError('fd must fit the C API nonnegative int descriptor')
|
||||
if flags & ~15 or not flags & 7:
|
||||
raise ValueError('flags must select READ, WRITE or EXCEPTION')
|
||||
if not callable(callback):
|
||||
raise TypeError('callback must be callable')
|
||||
plugin = api.__get_current_plugin()
|
||||
context = api.get_context()
|
||||
|
||||
def dispatch(descriptor, ready, data):
|
||||
previous = api.get_context()
|
||||
if not context.set():
|
||||
return False
|
||||
try:
|
||||
return callback(descriptor, ready, data)
|
||||
finally:
|
||||
previous.set()
|
||||
|
||||
hook = plugin.add_hook(dispatch, userdata)
|
||||
handle = lib.zoitechat_hook_fd(lib.ph, fd, flags, lib._on_fd_hook, hook.handle)
|
||||
if handle == ffi.NULL:
|
||||
hook.is_unload = True
|
||||
plugin.remove_hook(id(hook))
|
||||
raise RuntimeError('unable to watch descriptor')
|
||||
hook.zoitechat_hook = handle
|
||||
return id(hook)
|
||||
@@ -38,6 +38,7 @@ extern "Python" int _on_print_attrs_hook(char **, zoitechat_event_attrs *, void
|
||||
extern "Python" int _on_server_hook(char **, char **, void *);
|
||||
extern "Python" int _on_server_attrs_hook(char **, char **, zoitechat_event_attrs *, void *);
|
||||
extern "Python" int _on_timer_hook(void *);
|
||||
extern "Python" int _on_fd_hook(int, int, void *);
|
||||
|
||||
extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *);
|
||||
extern "Python" int _on_plugin_deinit(void);
|
||||
|
||||
@@ -304,6 +304,28 @@ def _on_timer_hook(userdata):
|
||||
return 0
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _on_fd_hook(fd, flags, userdata):
|
||||
hook = ffi.from_handle(userdata)
|
||||
keep = False
|
||||
try:
|
||||
keep = bool(hook.callback(fd, flags, hook.userdata))
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
if not keep:
|
||||
# The C watch removes itself on return; do not unhook it a second time.
|
||||
try:
|
||||
hook.is_unload = True
|
||||
# from_handle() yields a weak proxy: id(proxy) is not id(Hook).
|
||||
for existing in hook.plugin.hooks:
|
||||
if existing == hook:
|
||||
hook.plugin.remove_hook(id(existing))
|
||||
break
|
||||
except ReferenceError:
|
||||
pass # the callback already unhooked itself
|
||||
return int(keep)
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _on_say_command(word, word_eol, userdata):
|
||||
"""Handle input in the special >>python<< tab.
|
||||
|
||||
Reference in New Issue
Block a user