use std::ffi::c_void;
use std::io::{Seek, Write};
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::NonNull;
use crate::ffi;
use crate::ffi::mux::{WriterGetPosFn, WriterSetPosFn};
unsafe impl Send for OwnedWriterPtr {}
struct OwnedWriterPtr {
writer: ffi::mux::WriterNonNullPtr,
}
impl OwnedWriterPtr {
const unsafe fn new(writer: ffi::mux::WriterNonNullPtr) -> Self {
Self { writer }
}
const fn as_ptr(&self) -> ffi::mux::WriterMutPtr {
self.writer.as_ptr()
}
}
impl Drop for OwnedWriterPtr {
fn drop(&mut self) {
unsafe {
ffi::mux::delete_writer(self.writer.as_ptr());
}
}
}
pub struct Writer<W: Write> {
writer_data: Pin<Box<MuxWriterData<W>>>,
mkv_writer: OwnedWriterPtr,
}
struct MuxWriterData<T> {
dest: T,
bytes_written: u64,
_marker: PhantomPinned,
}
impl<T> Writer<T>
where
T: Write,
{
#[inline]
pub fn new_non_seek(dest: T) -> Self {
extern "C" fn get_pos_fn<T>(data: *mut c_void) -> u64 {
let data = unsafe { data.cast::<MuxWriterData<T>>().as_ref() };
data.map_or(0, |d| d.bytes_written)
}
Self::make_writer(dest, get_pos_fn::<T>, None)
}
#[must_use]
#[inline]
pub fn into_inner(self) -> T {
let Self { writer_data, .. } = self;
unsafe { Pin::into_inner_unchecked(writer_data).dest }
}
pub(crate) const fn mkv_writer(&self) -> ffi::mux::WriterMutPtr {
self.mkv_writer.as_ptr()
}
fn make_writer(dest: T, get_pos_fn: WriterGetPosFn, set_pos_fn: Option<WriterSetPosFn>) -> Self {
extern "C" fn write_fn<T>(data: *mut c_void, buf: *const c_void, len: usize) -> bool where T: Write {
let Some(data) = (unsafe { data.cast::<MuxWriterData<T>>().as_mut() }) else {
return false;
};
if buf.is_null() {
return false;
}
let buf = unsafe { std::slice::from_raw_parts(buf.cast::<u8>(), len) };
match data.dest.write_all(buf) {
Ok(()) => {
data.bytes_written += buf.len() as u64;
true
},
Err(_) => false,
}
}
let mut writer_data = Box::pin(MuxWriterData {
dest,
bytes_written: 0,
_marker: PhantomPinned,
});
let mkv_writer = unsafe {
NonNull::new(ffi::mux::new_writer(
Some(write_fn::<T>),
Some(get_pos_fn),
set_pos_fn,
None,
std::ptr::from_mut(writer_data.as_mut().get_unchecked_mut()).cast(),
)).expect("mux::new_writer")
};
Self {
writer_data,
mkv_writer: unsafe { OwnedWriterPtr::new(mkv_writer) },
}
}
}
impl<T> Writer<T>
where
T: Write + Seek,
{
#[inline]
pub fn new(dest: T) -> Self {
use std::io::SeekFrom;
extern "C" fn get_pos_fn<T>(data: *mut c_void) -> u64 where T: Write + Seek {
let data = unsafe { data.cast::<MuxWriterData<T>>().as_mut() };
data.and_then(|d| d.dest.stream_position().ok()).unwrap_or(!0)
}
extern "C" fn set_pos_fn<T>(data: *mut c_void, pos: u64) -> bool where T: Write + Seek {
let Some(data) = (unsafe { data.cast::<MuxWriterData<T>>().as_mut() }) else {
return false;
};
data.dest.seek(SeekFrom::Start(pos)).is_ok()
}
Self::make_writer(dest, get_pos_fn::<T>, Some(set_pos_fn::<T>))
}
}
#[test]
fn sendable() {
fn is_send<T: Send>(_: &T) {}
let w = Writer::new(std::io::Cursor::new(vec![1, 2, 3]));
is_send(&w);
assert_eq!([1, 2, 3], *w.into_inner().into_inner());
let w = Writer::new_non_seek(vec![3, 4, 5]);
is_send(&w);
assert_eq!([3, 4, 5], *w.into_inner());
}