1use crate::error::ZipErrorT;
2use crate::ffi;
3use crate::Error;
4use crate::Result;
5use std::ffi::{c_void, CStr, CString};
6use std::marker::PhantomData;
7use std::path::Path;
8use std::ptr::null_mut;
9
10#[derive(Debug)]
12pub struct Source<'a> {
13 handle: *mut ffi::zip_source_t,
14 phantom: PhantomData<&'a ()>,
15}
16
17impl<'a> Source<'a> {
18 pub(crate) fn taken(mut self) {
21 self.handle = null_mut();
22 }
23
24 pub(crate) fn handle_mut(&mut self) -> *mut ffi::zip_source_t {
25 self.handle
26 }
27
28 pub(crate) fn from_reader(reader: Box<dyn std::io::Read + 'a>, size: Option<usize>) -> Result<Self> {
29 let mut error = ZipErrorT::default();
30 let reader_source = ReaderSource {
31 reader,
32 error: ZipErrorT::default(),
33 size,
34 };
35 let handle = unsafe {
36 ffi::zip_source_function_create(
37 Some(zip_source_callback_fn),
38 Box::into_raw(Box::new(reader_source)) as *mut c_void,
39 &mut *error,
40 )
41 };
42 if handle.is_null() {
43 Err(error.into())
44 } else {
45 Ok(Source {
46 handle,
47 phantom: PhantomData,
48 })
49 }
50 }
51
52 pub fn from_reader_with_size(reader: Box<dyn std::io::Read + 'a>, size: usize) -> Result<Self> {
53 Self::from_reader(reader, Some(size))
54 }
55}
56
57impl<'a> TryFrom<&'a [u8]> for Source<'a> {
58 type Error = Error;
59
60 fn try_from(buffer: &[u8]) -> Result<Source<'_>> {
61 let mut error = ZipErrorT::default();
62 let handle = unsafe {
63 ffi::zip_source_buffer_create(buffer.as_ptr() as _, buffer.len() as _, 0, &mut *error)
64 };
65 if handle.is_null() {
66 Err(error.into())
67 } else {
68 Ok(Source {
69 handle,
70 phantom: PhantomData,
71 })
72 }
73 }
74}
75
76impl TryFrom<&CStr> for Source<'static> {
77 type Error = Error;
78
79 fn try_from(filename: &CStr) -> Result<Source<'static>> {
80 let mut error = ZipErrorT::default();
81 let handle = unsafe { ffi::zip_source_file_create(filename.as_ptr(), 0, 0, &mut *error) };
82 if handle.is_null() {
83 Err(error.into())
84 } else {
85 Ok(Source {
86 handle,
87 phantom: PhantomData,
88 })
89 }
90 }
91}
92
93impl TryFrom<&Path> for Source<'static> {
99 type Error = Error;
100
101 fn try_from(filename: &Path) -> Result<Source<'static>> {
102 let filename = CString::new(filename.to_string_lossy().into_owned())
103 .expect("The path could not be converted into a CString");
104 filename.as_ref().try_into()
105 }
106}
107
108struct ReaderSource<'a> {
109 size: Option<usize>,
110 reader: Box<dyn std::io::Read + 'a>,
111 error: ZipErrorT<ffi::zip_error_t>,
112}
113
114unsafe extern "C" fn zip_source_callback_fn(
115 userdata: *mut c_void,
116 buf: *mut c_void,
117 len: ffi::zip_uint64_t,
118 cmd: ffi::zip_source_cmd_t,
119) -> ffi::zip_int64_t {
120 let data = unsafe { &mut *(userdata as *mut ReaderSource) };
121 match cmd {
122 ffi::zip_source_cmd_ZIP_SOURCE_OPEN => 0,
123 ffi::zip_source_cmd_ZIP_SOURCE_READ => {
124 let buf = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, len as usize) };
125 match data.reader.read(buf) {
126 Ok(n) => n as i64,
127 Err(e) => {
128 unsafe {
129 ffi::zip_error_set(
130 &mut *data.error,
131 ffi::ZIP_ER_READ as i32,
132 e.raw_os_error().unwrap_or(0),
133 );
134 }
135 -1
136 }
137 }
138 }
139 ffi::zip_source_cmd_ZIP_SOURCE_CLOSE => 0,
140 ffi::zip_source_cmd_ZIP_SOURCE_STAT => {
141 let buf = buf as *mut ffi::zip_stat_t;
142 unsafe {
143 ffi::zip_stat_init(buf);
144 if let Some(size) = data.size {
145 (*buf).size = size as u64;
146 (*buf).valid = ffi::ZIP_STAT_SIZE as u64;
147 }
148 }
149 std::mem::size_of::<ffi::zip_stat_t>() as i64
150 }
151 ffi::zip_source_cmd_ZIP_SOURCE_ERROR => {
152 let n = unsafe { ffi::zip_error_to_data(&*data.error, buf, len) };
153 unsafe { ffi::zip_error_init(&mut *data.error) };
154 n
155 }
156 ffi::zip_source_cmd_ZIP_SOURCE_FREE => {
157 drop(unsafe { Box::from_raw(data) });
158 0
159 }
160 _ => {
161 unsafe {
162 ffi::zip_error_set(&mut *data.error, ffi::ZIP_ER_OPNOTSUPP as i32, 0);
163 }
164 -1
165 }
166 }
167}
168
169impl TryFrom<Box<dyn std::io::Read>> for Source<'static> {
170 type Error = Error;
171
172 fn try_from(reader: Box<dyn std::io::Read>) -> Result<Source<'static>> {
173 Self::from_reader(reader, None)
174 }
175}
176
177impl TryFrom<Box<[u8]>> for Source<'static> {
178 type Error = Error;
179
180 fn try_from(reader: Box<[u8]>) -> Result<Source<'static>> {
181 let size = reader.len();
182 let reader = Box::new(std::io::Cursor::new(reader));
183 Self::from_reader_with_size(reader, size)
184 }
185}
186
187impl Drop for Source<'_> {
188 fn drop(&mut self) {
189 if !self.handle.is_null() {
190 unsafe {
191 ffi::zip_source_free(self.handle);
192 }
193 }
194 }
195}