1use crate::error::ZipErrorT;
2use crate::ffi;
3use crate::file::{Compression, Encoding, File, LocateFlag, OpenFlag as FileOpenFlag};
4use crate::source::Source;
5use crate::Error;
6use crate::Result;
7use std::ffi::CStr;
8use std::marker::PhantomData;
9use std::ptr::null_mut;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
12pub enum OpenFlag {
13 CheckConsistency,
14 Create,
15 Exclusive,
16 Truncate,
17 ReadOnly,
18}
19
20#[derive(Debug)]
21pub struct Archive<'a> {
22 handle: *mut ffi::zip_t,
23 phantom: PhantomData<&'a ()>,
24}
25
26impl<'a> Archive<'a> {
27 pub fn open<F>(mut source: Source<'a>, flags: F) -> Result<Archive<'a>>
28 where
29 F: AsRef<[OpenFlag]>,
30 {
31 let mut flags_value = 0;
32 for flag in flags.as_ref() {
33 match flag {
34 OpenFlag::CheckConsistency => flags_value |= ffi::ZIP_CHECKCONS,
35 OpenFlag::Create => flags_value |= ffi::ZIP_CREATE,
36 OpenFlag::Exclusive => flags_value |= ffi::ZIP_EXCL,
37 OpenFlag::Truncate => flags_value |= ffi::ZIP_TRUNCATE,
38 OpenFlag::ReadOnly => flags_value |= ffi::ZIP_RDONLY,
39 }
40 }
41
42 unsafe {
43 let mut error = ZipErrorT::default();
44 let handle =
45 ffi::zip_open_from_source(source.handle_mut(), flags_value as _, &mut *error);
46
47 if handle.is_null() {
48 Err(error.into())
49 } else {
50 source.taken();
51 Ok(Archive {
52 handle,
53 phantom: PhantomData,
54 })
55 }
56 }
57 }
58
59 fn error(&mut self) -> ZipErrorT<&mut ffi::zip_error_t> {
60 unsafe {
61 let error = ffi::zip_get_error(self.handle);
62 (&mut *error).into()
63 }
64 }
65
66 fn close_mut(&mut self) -> Result<()> {
69 if self.handle.is_null() {
70 Ok(())
71 } else {
72 let result = unsafe { ffi::zip_close(self.handle) };
73 if result == 0 {
74 self.handle = null_mut();
75 Ok(())
76 } else {
77 Err(self.error().into())
78 }
79 }
80 }
81
82 pub fn close(mut self) -> std::result::Result<(), (Self, Error)> {
85 match self.close_mut() {
86 Ok(()) => Ok(()),
87 Err(e) => Err((self, e)),
88 }
89 }
90
91 fn discard_mut(&mut self) {
93 if !self.handle.is_null() {
94 unsafe {
95 ffi::zip_discard(self.handle);
96 }
97 }
98 }
99
100 pub fn discard(mut self) {
102 self.discard_mut()
103 }
104
105 fn set_mode(&mut self, index: u64, mode: u32) -> Result<()> {
108 let result = unsafe {
109 ffi::zip_file_set_external_attributes(
110 self.handle,
111 index,
112 0,
113 ffi::ZIP_OPSYS_UNIX as u8,
114 mode << 16,
115 )
116 };
117 if result == -1 {
118 Err(self.error().into())
119 } else {
120 Ok(())
121 }
122 }
123
124 fn set_mtime(&mut self, index: u64, mtime: u32) -> Result<()> {
127 let result = unsafe {
128 ffi::zip_file_set_dostime(
129 self.handle,
130 index,
131 (mtime & 0xffff) as u16,
132 (mtime >> 16) as u16,
133 0,
134 )
135 };
136 if result == -1 {
137 Err(self.error().into())
138 } else {
139 Ok(())
140 }
141 }
142
143 pub fn add<'b: 'a, N>(
147 &mut self,
148 name: N,
149 mut source: Source<'b>,
150 encoding: Encoding,
151 compression: Compression,
152 mode: Option<u16>,
153 mtime: Option<u32>,
154 overwrite: bool,
155 ) -> Result<u64>
156 where
157 N: AsRef<CStr>,
158 {
159 let mut flags = match encoding {
160 Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
161 Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
162 Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
163 };
164 if overwrite {
165 flags |= ffi::ZIP_FL_OVERWRITE;
166 }
167
168 let response = unsafe {
169 ffi::zip_file_add(
170 self.handle,
171 name.as_ref().as_ptr(),
172 source.handle_mut(),
173 flags as _,
174 )
175 };
176 let index = if response == -1 {
177 return Err(self.error().into());
178 } else {
179 source.taken();
180 response as u64
181 };
182
183 let (comp, flags) = match compression {
184 Compression::Default => (ffi::ZIP_CM_DEFAULT as u32, 0),
185 Compression::Store => (ffi::ZIP_CM_STORE, 0),
186 Compression::Bzip2(i) => (ffi::ZIP_CM_BZIP2, i),
187 Compression::Deflate(i) => (ffi::ZIP_CM_DEFLATE, i),
188 Compression::Xz(i) => (ffi::ZIP_CM_XZ, i),
189 Compression::Zstd(i) => (ffi::ZIP_CM_ZSTD, i),
190 };
191 let result =
192 unsafe { ffi::zip_set_file_compression(self.handle, index, comp as i32, flags) };
193 if result == -1 {
194 return Err(self.error().into());
195 }
196
197 if let Some(mode) = mode {
198 self.set_mode(index, mode as u32 | 0o100000)?;
199 }
200 if let Some(mtime) = mtime {
201 self.set_mtime(index, mtime)?;
202 }
203
204 Ok(index)
205 }
206
207 pub fn add_dir_entry<N>(
216 &mut self,
217 name: N,
218 encoding: Encoding,
219 mode: Option<u16>,
220 mtime: Option<u32>,
221 ) -> Result<u64>
222 where
223 N: AsRef<CStr>,
224 {
225 let flags = match encoding {
226 Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
227 Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
228 Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
229 };
230
231 let response = unsafe {
232 ffi::zip_dir_add(
233 self.handle,
234 name.as_ref().as_ptr(),
235 flags as _,
236 )
237 };
238 let index = if response == -1 {
239 return Err(self.error().into());
240 } else {
241 response as u64
242 };
243
244 if let Some(mode) = mode {
245 self.set_mode(index, mode as u32 | 0o40000)?;
246 }
247 if let Some(mtime) = mtime {
248 self.set_mtime(index, mtime)?;
249 }
250
251 Ok(index)
252 }
253
254 pub fn replace<'b: 'a>(&mut self, index: u64, mut source: Source<'b>) -> Result<()> {
261 let response =
262 unsafe { ffi::zip_file_replace(self.handle, index as _, source.handle_mut(), 0) };
263 if response == -1 {
264 Err(self.error().into())
265 } else {
266 source.taken();
267 Ok(())
268 }
269 }
270
271 pub fn open_file<N, O, L>(
274 &mut self,
275 name: N,
276 open_flags: O,
277 locate_flags: L,
278 ) -> Result<File<'_>>
279 where
280 N: AsRef<CStr>,
281 O: AsRef<[FileOpenFlag]>,
282 L: AsRef<[LocateFlag]>,
283 {
284 let mut flags_value = 0;
285 for flag in open_flags.as_ref() {
286 match flag {
287 FileOpenFlag::Compressed => flags_value |= ffi::ZIP_FL_COMPRESSED,
288 FileOpenFlag::Unchanged => flags_value |= ffi::ZIP_FL_UNCHANGED,
289 }
290 }
291 for flag in locate_flags.as_ref() {
292 match flag {
293 LocateFlag::NoCase => flags_value |= ffi::ZIP_FL_NOCASE,
294 LocateFlag::NoDir => flags_value |= ffi::ZIP_FL_NODIR,
295 LocateFlag::EncodingRaw => flags_value |= ffi::ZIP_FL_ENC_RAW,
296 LocateFlag::EncodingGuess => flags_value |= ffi::ZIP_FL_ENC_GUESS,
297 LocateFlag::EncodingStrict => flags_value |= ffi::ZIP_FL_ENC_STRICT,
298 }
299 }
300 let handle =
301 unsafe { ffi::zip_fopen(self.handle, name.as_ref().as_ptr(), flags_value as _) };
302 if handle.is_null() {
303 Err(self.error().into())
304 } else {
305 Ok(File {
306 handle,
307 phantom: PhantomData,
308 })
309 }
310 }
311}
312
313impl Drop for Archive<'_> {
317 fn drop(&mut self) {
318 if self.close_mut().is_err() {
319 self.discard_mut()
320 }
321 }
322}