use crate::utils::{slice_from_external_parts, slice_from_external_parts_mut};
use clap_sys::stream::{clap_istream, clap_ostream};
use std::error::Error;
use std::ffi::c_void;
use std::fmt::{Display, Formatter};
use std::io::{ErrorKind, Read, Write};
use std::marker::PhantomData;
#[derive(Copy, Clone, Debug)]
pub struct StreamError {
code: i64,
}
impl StreamError {
pub fn code(&self) -> i64 {
self.code
}
}
impl Display for StreamError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "CLAP stream error (code: {})", self.code)
}
}
impl Error for StreamError {}
#[repr(C)]
pub struct InputStream<'a>(clap_istream, PhantomData<(&'a mut clap_istream, *const ())>);
impl<'a> InputStream<'a> {
pub fn from_reader<R: Read + Sized + 'a>(reader: &'a mut R) -> Self {
Self(
clap_istream {
ctx: reader as *mut R as *mut _,
read: Some(read::<R>),
},
PhantomData,
)
}
#[inline]
pub unsafe fn from_raw_mut(raw: &mut clap_istream) -> &mut Self {
&mut *(raw as *mut _ as *mut _)
}
#[inline]
pub fn as_raw_mut(&mut self) -> &mut clap_istream {
&mut self.0
}
}
impl Read for InputStream<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let ret = if let Some(read) = self.0.read {
unsafe { read(&self.0, buf.as_mut_ptr().cast(), buf.len() as u64) }
} else {
return Ok(0);
};
match ret {
i if i >= 0 => Ok(usize::try_from(i).map_err(std::io::Error::other)?),
code => Err(std::io::Error::other(StreamError { code })),
}
}
}
#[repr(C)]
pub struct OutputStream<'a>(clap_ostream, PhantomData<(&'a mut clap_ostream, *const ())>);
impl<'a> OutputStream<'a> {
pub fn from_writer<W: Write + Sized + 'a>(writer: &'a mut W) -> Self {
Self(
clap_ostream {
ctx: writer as *mut W as *mut _,
write: Some(write::<W>),
},
PhantomData,
)
}
#[inline]
pub unsafe fn from_raw_mut(raw: &mut clap_ostream) -> &mut Self {
&mut *(raw as *mut _ as *mut _)
}
#[inline]
pub fn as_raw_mut(&mut self) -> &mut clap_ostream {
&mut self.0
}
}
impl Write for OutputStream<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let ret = if let Some(write) = self.0.write {
unsafe { write(&self.0, buf.as_ptr().cast(), buf.len() as u64) }
} else {
return Ok(0);
};
match ret {
i if i >= 0 => Ok(usize::try_from(i).map_err(std::io::Error::other)?),
code => Err(std::io::Error::other(StreamError { code })),
}
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn saturating_from_u64(val: u64) -> usize {
let val = i64::try_from(val).unwrap_or(i64::MAX);
usize::try_from(val).unwrap_or(usize::MAX)
}
#[allow(clippy::missing_safety_doc)]
unsafe extern "C" fn read<R: Read + Sized>(
istream: *const clap_istream,
buffer: *mut c_void,
size: u64,
) -> i64 {
let reader = &mut *((*istream).ctx as *mut R);
let size = saturating_from_u64(size);
let buffer = slice_from_external_parts_mut(buffer as *mut u8, size);
match handle_interrupted(|| reader.read(buffer)) {
#[allow(clippy::cast_possible_wrap)]
Ok(read) => {
if read > size {
size as i64
} else {
read as i64
}
}
Err(_) => -1,
}
}
#[allow(clippy::missing_safety_doc)]
unsafe extern "C" fn write<W: Write + Sized>(
ostream: *const clap_ostream,
buffer: *const c_void,
size: u64,
) -> i64 {
let writer = &mut *((*ostream).ctx as *mut W);
let size = saturating_from_u64(size);
let buffer = slice_from_external_parts(buffer as *const u8, size);
match handle_interrupted(|| writer.write(buffer)) {
#[allow(clippy::cast_possible_wrap)]
Ok(written) => {
if written > size {
size as i64
} else {
written as i64
}
}
Err(_) => -1,
}
}
fn handle_interrupted<F: FnMut() -> std::io::Result<usize>>(
mut handler: F,
) -> std::io::Result<usize> {
const MAX_ATTEMPTS: u8 = 5;
let mut attempts = 0u8;
loop {
match handler() {
Err(e) if e.kind() == ErrorKind::Interrupted && attempts < MAX_ATTEMPTS => {
attempts += 1
}
res => return res,
}
}
}
#[cfg(test)]
mod test {
extern crate static_assertions as sa;
use super::*;
use std::io::Cursor;
sa::assert_not_impl_any!(InputStream: Send, Sync);
sa::assert_not_impl_any!(OutputStream: Send, Sync);
#[test]
fn input_streams_work() {
let src = b"Hello";
let mut buf = vec![0; 5];
let mut cursor = Cursor::new(src);
let mut stream = InputStream::from_reader(&mut cursor);
let res = stream.read(&mut buf).unwrap();
assert_eq!(res, 5);
assert_eq!(&buf, b"Hello");
}
#[test]
fn output_streams_work() {
let mut buf = vec![];
let mut stream = OutputStream::from_writer(&mut buf);
let res = stream.write(b"Hello").unwrap();
assert_eq!(res, 5);
assert_eq!(&buf, b"Hello");
}
}