use crate::error::ZipErrorT;
use crate::ffi;
use crate::Error;
use crate::Result;
use std::ffi::{c_void, CStr, CString};
use std::marker::PhantomData;
use std::path::Path;
use std::ptr::null_mut;
#[derive(Debug)]
pub struct Source<'a> {
handle: *mut ffi::zip_source_t,
phantom: PhantomData<&'a ()>,
}
impl<'a> Source<'a> {
pub(crate) fn taken(mut self) {
self.handle = null_mut();
}
pub(crate) fn handle_mut(&mut self) -> *mut ffi::zip_source_t {
self.handle
}
pub(crate) fn from_reader(reader: Box<dyn std::io::Read + 'a>, size: Option<usize>) -> Result<Self> {
let mut error = ZipErrorT::default();
let reader_source = ReaderSource {
reader,
error: ZipErrorT::default(),
size,
};
let handle = unsafe {
ffi::zip_source_function_create(
Some(zip_source_callback_fn),
Box::into_raw(Box::new(reader_source)) as *mut c_void,
&mut *error,
)
};
if handle.is_null() {
Err(error.into())
} else {
Ok(Source {
handle,
phantom: PhantomData,
})
}
}
pub fn from_reader_with_size(reader: Box<dyn std::io::Read + 'a>, size: usize) -> Result<Self> {
Self::from_reader(reader, Some(size))
}
}
impl<'a> TryFrom<&'a [u8]> for Source<'a> {
type Error = Error;
fn try_from(buffer: &[u8]) -> Result<Source<'_>> {
let mut error = ZipErrorT::default();
let handle = unsafe {
ffi::zip_source_buffer_create(buffer.as_ptr() as _, buffer.len() as _, 0, &mut *error)
};
if handle.is_null() {
Err(error.into())
} else {
Ok(Source {
handle,
phantom: PhantomData,
})
}
}
}
impl TryFrom<&CStr> for Source<'static> {
type Error = Error;
fn try_from(filename: &CStr) -> Result<Source<'static>> {
let mut error = ZipErrorT::default();
let handle = unsafe { ffi::zip_source_file_create(filename.as_ptr(), 0, 0, &mut *error) };
if handle.is_null() {
Err(error.into())
} else {
Ok(Source {
handle,
phantom: PhantomData,
})
}
}
}
impl TryFrom<&Path> for Source<'static> {
type Error = Error;
fn try_from(filename: &Path) -> Result<Source<'static>> {
let filename = CString::new(filename.to_string_lossy().into_owned())
.expect("The path could not be converted into a CString");
filename.as_ref().try_into()
}
}
struct ReaderSource<'a> {
size: Option<usize>,
reader: Box<dyn std::io::Read + 'a>,
error: ZipErrorT<ffi::zip_error_t>,
}
unsafe extern "C" fn zip_source_callback_fn(
userdata: *mut c_void,
buf: *mut c_void,
len: ffi::zip_uint64_t,
cmd: ffi::zip_source_cmd_t,
) -> ffi::zip_int64_t {
let data = unsafe { &mut *(userdata as *mut ReaderSource) };
match cmd {
ffi::zip_source_cmd_ZIP_SOURCE_OPEN => 0,
ffi::zip_source_cmd_ZIP_SOURCE_READ => {
let buf = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, len as usize) };
match data.reader.read(buf) {
Ok(n) => n as i64,
Err(e) => {
unsafe {
ffi::zip_error_set(
&mut *data.error,
ffi::ZIP_ER_READ as i32,
e.raw_os_error().unwrap_or(0),
);
}
-1
}
}
}
ffi::zip_source_cmd_ZIP_SOURCE_CLOSE => 0,
ffi::zip_source_cmd_ZIP_SOURCE_STAT => {
let buf = buf as *mut ffi::zip_stat_t;
unsafe {
ffi::zip_stat_init(buf);
if let Some(size) = data.size {
(*buf).size = size as u64;
(*buf).valid = ffi::ZIP_STAT_SIZE as u64;
}
}
std::mem::size_of::<ffi::zip_stat_t>() as i64
}
ffi::zip_source_cmd_ZIP_SOURCE_ERROR => {
let n = unsafe { ffi::zip_error_to_data(&*data.error, buf, len) };
unsafe { ffi::zip_error_init(&mut *data.error) };
n
}
ffi::zip_source_cmd_ZIP_SOURCE_FREE => {
drop(unsafe { Box::from_raw(data) });
0
}
_ => {
unsafe {
ffi::zip_error_set(&mut *data.error, ffi::ZIP_ER_OPNOTSUPP as i32, 0);
}
-1
}
}
}
impl TryFrom<Box<dyn std::io::Read>> for Source<'static> {
type Error = Error;
fn try_from(reader: Box<dyn std::io::Read>) -> Result<Source<'static>> {
Self::from_reader(reader, None)
}
}
impl TryFrom<Box<[u8]>> for Source<'static> {
type Error = Error;
fn try_from(reader: Box<[u8]>) -> Result<Source<'static>> {
let size = reader.len();
let reader = Box::new(std::io::Cursor::new(reader));
Self::from_reader_with_size(reader, size)
}
}
impl Drop for Source<'_> {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe {
ffi::zip_source_free(self.handle);
}
}
}
}