osmaczko 7838d43b30
feat(client): add threaded transport polling (#125)
The client, not the app, now drives the transport; events are delivered
asynchronously, per ADR 0001.

- ChatClient owns Arc<Mutex<Core>> + a worker thread.
- The worker select!s over the inbound and shutdown channels; Drop joins it.
  Outbound runs on the caller's thread.
- A single Transport (DeliveryService + inbound()) owns both directions of the
  boundary, so the client takes one transport rather than a (delivery, inbound)
  pair. InProcessDelivery::new, CDelivery, and chat-cli's transports implement it.
- FFI replaces client_receive with client_push_inbound + client_poll_events.
- chat-cli drains Receiver<Event>; inbound and event channels are both crossbeam.
- Corrects ADR 0001's inbound sequence to push — the worker parks on select!,
  it never polls.
2026-06-11 10:08:07 +02:00

57 lines
1.7 KiB
Rust

use crossbeam_channel::Receiver;
use libchat::AddressedEnvelope;
use logos_chat::{DeliveryService, Transport};
/// C callback invoked for each outbound envelope. Return 0 or positive on success, negative on
/// error. `addr_ptr/addr_len` is the delivery address; `data_ptr/data_len` is the encrypted
/// payload. Both pointers are borrowed for the duration of the call only; the callee must not
/// retain or free them.
pub type DeliverFn = Option<
unsafe extern "C" fn(
addr_ptr: *const u8,
addr_len: usize,
data_ptr: *const u8,
data_len: usize,
) -> i32,
>;
#[derive(Debug)]
pub struct CDelivery {
pub callback: DeliverFn,
inbound_rx: Option<Receiver<Vec<u8>>>,
}
impl CDelivery {
pub fn new(callback: DeliverFn, inbound: Receiver<Vec<u8>>) -> Self {
Self {
callback,
inbound_rx: Some(inbound),
}
}
}
impl DeliveryService for CDelivery {
type Error = i32;
fn publish(&mut self, envelope: AddressedEnvelope) -> Result<(), i32> {
let cb = self.callback.expect("callback must be non-null");
let addr = envelope.delivery_address.as_bytes();
let data = envelope.data.as_slice();
let rc = unsafe { cb(addr.as_ptr(), addr.len(), data.as_ptr(), data.len()) };
if rc < 0 { Err(rc) } else { Ok(()) }
}
fn subscribe(&mut self, _delivery_address: &str) -> Result<(), Self::Error> {
// TODO: (P1) CDelivery does not support delivery_address filtering
Ok(())
}
}
impl Transport for CDelivery {
fn inbound(&mut self) -> Receiver<Vec<u8>> {
self.inbound_rx
.take()
.expect("CDelivery::inbound called more than once")
}
}