axfive_libzip/
archive.rs

1use crate::error::ZipErrorT;
2use crate::ffi;
3use crate::file::{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 {
22    handle: *mut ffi::zip_t,
23}
24
25impl Archive {
26    pub fn open<S, F>(mut source: Source<S>, flags: F) -> Result<Archive>
27    where
28        F: AsRef<[OpenFlag]>,
29    {
30        let mut flags_value = 0;
31        for flag in flags.as_ref() {
32            match flag {
33                OpenFlag::CheckConsistency => flags_value |= ffi::ZIP_CHECKCONS,
34                OpenFlag::Create => flags_value |= ffi::ZIP_CREATE,
35                OpenFlag::Exclusive => flags_value |= ffi::ZIP_EXCL,
36                OpenFlag::Truncate => flags_value |= ffi::ZIP_TRUNCATE,
37                OpenFlag::ReadOnly => flags_value |= ffi::ZIP_RDONLY,
38            }
39        }
40
41        unsafe {
42            let mut error = ZipErrorT::default();
43            let handle =
44                ffi::zip_open_from_source(source.handle_mut(), flags_value as _, &mut *error);
45
46            if handle.is_null() {
47                Err(error.into())
48            } else {
49                source.taken();
50                Ok(Archive { handle })
51            }
52        }
53    }
54
55    fn error(&mut self) -> ZipErrorT<&mut ffi::zip_error_t> {
56        unsafe {
57            let error = ffi::zip_get_error(self.handle);
58            (&mut *error).into()
59        }
60    }
61
62    /// Closes and consumes a zip file.  If this fails, an error and the failed-to-close zipfile
63    /// will be returned
64    fn close_mut(&mut self) -> Result<()> {
65        if self.handle.is_null() {
66            Ok(())
67        } else {
68            let result = unsafe { ffi::zip_close(self.handle) };
69            if result == 0 {
70                self.handle = null_mut();
71                Ok(())
72            } else {
73                Err(self.error().into())
74            }
75        }
76    }
77
78    /// Closes and consumes a zip file.
79    /// If this fails, the failed-to-close zipfile and an error will be returned.
80    pub fn close(mut self) -> std::result::Result<(), (Self, Error)> {
81        match self.close_mut() {
82            Ok(()) => Ok(()),
83            Err(e) => Err((self, e)),
84        }
85    }
86
87    /// Internal non-consuming discard, to facilitate drop
88    fn discard_mut(&mut self) {
89        if !self.handle.is_null() {
90            unsafe {
91                ffi::zip_discard(self.handle);
92            }
93        }
94    }
95
96    /// Discard and consume the zipfile.  This will never fail.
97    pub fn discard(mut self) {
98        self.discard_mut()
99    }
100
101    /// Add a file to the zip archive.
102    /// Returns the index of the new file.
103    pub fn add<N, S>(
104        &mut self,
105        name: N,
106        mut source: Source<S>,
107        encoding: Encoding,
108        overwrite: bool,
109    ) -> Result<u64>
110    where
111        N: AsRef<CStr>,
112    {
113        let mut flags = match encoding {
114            Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
115            Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
116            Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
117        };
118        if overwrite {
119            flags |= ffi::ZIP_FL_OVERWRITE;
120        }
121
122        let response = unsafe {
123            ffi::zip_file_add(
124                self.handle,
125                name.as_ref().as_ptr(),
126                source.handle_mut(),
127                flags as _,
128            )
129        };
130        if response == -1 {
131            Err(self.error().into())
132        } else {
133            source.taken();
134            Ok(response as _)
135        }
136    }
137
138    /// Replace a file in the zip archive.
139    pub fn replace<S>(&mut self, index: u64, mut source: Source<S>) -> Result<()> {
140        let response =
141            unsafe { ffi::zip_file_replace(self.handle, index as _, source.handle_mut(), 0) };
142        if response == -1 {
143            Err(self.error().into())
144        } else {
145            source.taken();
146            Ok(())
147        }
148    }
149
150    /// Add a file to the zip archive.
151    /// Returns the index of the new file.
152    pub fn open_file<N, O, L>(
153        &mut self,
154        name: N,
155        open_flags: O,
156        locate_flags: L,
157    ) -> Result<File<'_>>
158    where
159        N: AsRef<CStr>,
160        O: AsRef<[FileOpenFlag]>,
161        L: AsRef<[LocateFlag]>,
162    {
163        let mut flags_value = 0;
164        for flag in open_flags.as_ref() {
165            match flag {
166                FileOpenFlag::Compressed => flags_value |= ffi::ZIP_FL_COMPRESSED,
167                FileOpenFlag::Unchanged => flags_value |= ffi::ZIP_FL_UNCHANGED,
168            }
169        }
170        for flag in locate_flags.as_ref() {
171            match flag {
172                LocateFlag::NoCase => flags_value |= ffi::ZIP_FL_NOCASE,
173                LocateFlag::NoDir => flags_value |= ffi::ZIP_FL_NODIR,
174                LocateFlag::EncodingRaw => flags_value |= ffi::ZIP_FL_ENC_RAW,
175                LocateFlag::EncodingGuess => flags_value |= ffi::ZIP_FL_ENC_GUESS,
176                LocateFlag::EncodingStrict => flags_value |= ffi::ZIP_FL_ENC_STRICT,
177            }
178        }
179        let handle =
180            unsafe { ffi::zip_fopen(self.handle, name.as_ref().as_ptr(), flags_value as _) };
181        if handle.is_null() {
182            Err(self.error().into())
183        } else {
184            Ok(File {
185                handle,
186                phantom: PhantomData,
187            })
188        }
189    }
190}
191
192/// Closes the archive, silently discarding on error.
193/// It's strongly recommended to use the [Archive::close] method instead and validate that no
194/// errors have occurred.
195impl Drop for Archive {
196    fn drop(&mut self) {
197        if let Err(_) = self.close_mut() {
198            self.discard_mut()
199        }
200    }
201}