use bytes::Bytes;
use futures_util::SinkExt;
use tokio::{io::AsyncWrite, sync::mpsc};
use tokio_util::codec::FramedWrite;
use crate::{
bridge::protocol::{length_codec, Frame},
error::{codes, Error, Result},
};
#[derive(Clone)]
pub struct Writer {
tx: mpsc::UnboundedSender<Bytes>,
max_frame_size: usize,
}
impl Writer {
pub fn channel(max_frame_size: usize) -> (Self, mpsc::UnboundedReceiver<Bytes>) {
let (tx, rx) = mpsc::unbounded_channel();
(Self { tx, max_frame_size }, rx)
}
pub fn spawn<W: AsyncWrite + Unpin + Send + 'static>(stream: W, max_frame_size: usize) -> Self {
let (writer, mut rx) = Self::channel(max_frame_size);
tauri::async_runtime::spawn(async move {
let mut sink = FramedWrite::new(stream, length_codec(max_frame_size));
while let Some(frame) = rx.recv().await {
if let Err(e) = sink.send(frame).await {
log::error!(target: "py-runtime", "failed to write frame: {e}");
break;
}
}
});
writer
}
pub fn send(&self, frame: Frame) -> Result<()> {
let data = frame.encode();
if data.len() > self.max_frame_size {
return Err(Error::new(
codes::FRAME_TOO_LARGE,
format!("frame of {} bytes exceeds limit of {}", data.len(), self.max_frame_size),
));
}
self
.tx
.send(data)
.map_err(|_| Error::new(codes::RUNTIME_UNAVAILABLE, "python runtime is not running"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bridge::protocol::{FrameCodec, Message, DEFAULT_MAX_FRAME_SIZE};
use futures_util::StreamExt;
use tokio_util::codec::FramedRead;
fn close(channel: u64) -> Frame {
Frame::new(Message::ChannelClose { channel })
}
#[tokio::test]
async fn spawned_writer_writes_frames_in_order() {
let (a, b) = tokio::io::duplex(1024);
let writer = Writer::spawn(a, DEFAULT_MAX_FRAME_SIZE);
let mut frames = FramedRead::new(b, FrameCodec::new(DEFAULT_MAX_FRAME_SIZE));
for i in 0..3 {
writer.send(close(i)).unwrap();
}
for i in 0..3 {
assert_eq!(frames.next().await.unwrap().unwrap().unwrap(), close(i));
}
}
#[test]
fn rejects_oversized_frames() {
let (writer, _rx) = Writer::channel(16);
let mut frame = close(1);
frame.body = vec![0; 100];
assert_eq!(writer.send(frame).unwrap_err().code, "frame_too_large");
}
#[test]
fn send_after_close_is_unavailable() {
let (writer, rx) = Writer::channel(1024);
drop(rx);
assert_eq!(writer.send(close(1)).unwrap_err().code, "runtime_unavailable");
}
}