axfive-libzip 0.2.5

Bindings for system libzip
Documentation
use crate::error::ZipErrorT;
use crate::ffi;
use crate::file::{Compression, Encoding, File, LocateFlag, OpenFlag as FileOpenFlag};
use crate::source::Source;
use crate::Error;
use crate::Result;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::ptr::null_mut;

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum OpenFlag {
    CheckConsistency,
    Create,
    Exclusive,
    Truncate,
    ReadOnly,
}

#[derive(Debug)]
pub struct Archive<'a> {
    handle: *mut ffi::zip_t,
    phantom: PhantomData<&'a ()>,
}

impl<'a> Archive<'a> {
    pub fn open<F>(mut source: Source<'a>, flags: F) -> Result<Archive<'a>>
    where
        F: AsRef<[OpenFlag]>,
    {
        let mut flags_value = 0;
        for flag in flags.as_ref() {
            match flag {
                OpenFlag::CheckConsistency => flags_value |= ffi::ZIP_CHECKCONS,
                OpenFlag::Create => flags_value |= ffi::ZIP_CREATE,
                OpenFlag::Exclusive => flags_value |= ffi::ZIP_EXCL,
                OpenFlag::Truncate => flags_value |= ffi::ZIP_TRUNCATE,
                OpenFlag::ReadOnly => flags_value |= ffi::ZIP_RDONLY,
            }
        }

        unsafe {
            let mut error = ZipErrorT::default();
            let handle =
                ffi::zip_open_from_source(source.handle_mut(), flags_value as _, &mut *error);

            if handle.is_null() {
                Err(error.into())
            } else {
                source.taken();
                Ok(Archive {
                    handle,
                    phantom: PhantomData,
                })
            }
        }
    }

    fn error(&mut self) -> ZipErrorT<&mut ffi::zip_error_t> {
        unsafe {
            let error = ffi::zip_get_error(self.handle);
            (&mut *error).into()
        }
    }

    /// Closes and consumes a zip file.  If this fails, an error and the failed-to-close zipfile
    /// will be returned
    fn close_mut(&mut self) -> Result<()> {
        if self.handle.is_null() {
            Ok(())
        } else {
            let result = unsafe { ffi::zip_close(self.handle) };
            if result == 0 {
                self.handle = null_mut();
                Ok(())
            } else {
                Err(self.error().into())
            }
        }
    }

    /// Closes and consumes a zip file.
    /// If this fails, the failed-to-close zipfile and an error will be returned.
    pub fn close(mut self) -> std::result::Result<(), (Self, Error)> {
        match self.close_mut() {
            Ok(()) => Ok(()),
            Err(e) => Err((self, e)),
        }
    }

    /// Internal non-consuming discard, to facilitate drop
    fn discard_mut(&mut self) {
        if !self.handle.is_null() {
            unsafe {
                ffi::zip_discard(self.handle);
            }
        }
    }

    /// Discard and consume the zipfile.  This will never fail.
    pub fn discard(mut self) {
        self.discard_mut()
    }

    /// Set the permissions on the entry
    /// The mode parameter should include the entry type: 0o100000 for regular file, 0o40000 for directory
    fn set_mode(&mut self, index: u64, mode: u32) -> Result<()> {
        let result = unsafe {
            ffi::zip_file_set_external_attributes(
                self.handle,
                index,
                0,
                ffi::ZIP_OPSYS_UNIX as u8,
                mode << 16,
            )
        };
        if result == -1 {
            Err(self.error().into())
        } else {
            Ok(())
        }
    }

    /// Set the last modified time on the entry
    /// The mtime parameter is given as a big-endian u32 DOS timestamp
    fn set_mtime(&mut self, index: u64, mtime: u32) -> Result<()> {
        let result = unsafe {
            ffi::zip_file_set_dostime(
                self.handle,
                index,
                (mtime & 0xffff) as u16,
                (mtime >> 16) as u16,
                0,
            )
        };
        if result == -1 {
            Err(self.error().into())
        } else {
            Ok(())
        }
    }

    /// Add a file to the zip archive.
    /// Returns the index of the new file.
    /// The mtime parameter is given as a big-endian u32 DOS timestamp
    pub fn add<'b: 'a, N>(
        &mut self,
        name: N,
        mut source: Source<'b>,
        encoding: Encoding,
        compression: Compression,
        mode: Option<u16>,
        mtime: Option<u32>,
        overwrite: bool,
    ) -> Result<u64>
    where
        N: AsRef<CStr>,
    {
        let mut flags = match encoding {
            Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
            Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
            Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
        };
        if overwrite {
            flags |= ffi::ZIP_FL_OVERWRITE;
        }

        let response = unsafe {
            ffi::zip_file_add(
                self.handle,
                name.as_ref().as_ptr(),
                source.handle_mut(),
                flags as _,
            )
        };
        let index = if response == -1 {
            return Err(self.error().into());
        } else {
            source.taken();
            response as u64
        };

        let (comp, flags) = match compression {
            Compression::Default => (ffi::ZIP_CM_DEFAULT as u32, 0),
            Compression::Store => (ffi::ZIP_CM_STORE, 0),
            Compression::Bzip2(i) => (ffi::ZIP_CM_BZIP2, i),
            Compression::Deflate(i) => (ffi::ZIP_CM_DEFLATE, i),
            Compression::Xz(i) => (ffi::ZIP_CM_XZ, i),
            Compression::Zstd(i) => (ffi::ZIP_CM_ZSTD, i),
        };
        let result =
            unsafe { ffi::zip_set_file_compression(self.handle, index, comp as i32, flags) };
        if result == -1 {
            return Err(self.error().into());
        }

        if let Some(mode) = mode {
            self.set_mode(index, mode as u32 | 0o100000)?;
        }
        if let Some(mtime) = mtime {
            self.set_mtime(index, mtime)?;
        }

        Ok(index)
    }

    /// Add a directory entry to the zip archive.
    /// Returns the index of the new entry.
    /// The mtime parameter is given as a big-endian u32 DOS timestamp
    /// The source must outlive the archive, because according to the libzip
    /// interface, changes are only written when the archive is closed, and
    /// the source may be used until then.
    /// If you need to work with a short-lived source, consider closing and
    /// re-opening the archive for each operation.
    pub fn add_dir_entry<N>(
        &mut self,
        name: N,
        encoding: Encoding,
        mode: Option<u16>,
        mtime: Option<u32>,
    ) -> Result<u64>
    where
        N: AsRef<CStr>,
    {
        let flags = match encoding {
            Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
            Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
            Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
        };

        let response = unsafe {
            ffi::zip_dir_add(
                self.handle,
                name.as_ref().as_ptr(),
                flags as _,
            )
        };
        let index = if response == -1 {
            return Err(self.error().into());
        } else {
            response as u64
        };

        if let Some(mode) = mode {
            self.set_mode(index, mode as u32 | 0o40000)?;
        }
        if let Some(mtime) = mtime {
            self.set_mtime(index, mtime)?;
        }

        Ok(index)
    }

    /// Replace a file in the zip archive.
    /// The source must outlive the archive, because according to the libzip
    /// interface, changes are only written when the archive is closed, and
    /// the source may be used until then.
    /// If you need to work with a short-lived source, consider closing and
    /// re-opening the archive for each operation.
    pub fn replace<'b: 'a>(&mut self, index: u64, mut source: Source<'b>) -> Result<()> {
        let response =
            unsafe { ffi::zip_file_replace(self.handle, index as _, source.handle_mut(), 0) };
        if response == -1 {
            Err(self.error().into())
        } else {
            source.taken();
            Ok(())
        }
    }

    /// Add a file to the zip archive.
    /// Returns the index of the new file.
    pub fn open_file<N, O, L>(
        &mut self,
        name: N,
        open_flags: O,
        locate_flags: L,
    ) -> Result<File<'_>>
    where
        N: AsRef<CStr>,
        O: AsRef<[FileOpenFlag]>,
        L: AsRef<[LocateFlag]>,
    {
        let mut flags_value = 0;
        for flag in open_flags.as_ref() {
            match flag {
                FileOpenFlag::Compressed => flags_value |= ffi::ZIP_FL_COMPRESSED,
                FileOpenFlag::Unchanged => flags_value |= ffi::ZIP_FL_UNCHANGED,
            }
        }
        for flag in locate_flags.as_ref() {
            match flag {
                LocateFlag::NoCase => flags_value |= ffi::ZIP_FL_NOCASE,
                LocateFlag::NoDir => flags_value |= ffi::ZIP_FL_NODIR,
                LocateFlag::EncodingRaw => flags_value |= ffi::ZIP_FL_ENC_RAW,
                LocateFlag::EncodingGuess => flags_value |= ffi::ZIP_FL_ENC_GUESS,
                LocateFlag::EncodingStrict => flags_value |= ffi::ZIP_FL_ENC_STRICT,
            }
        }
        let handle =
            unsafe { ffi::zip_fopen(self.handle, name.as_ref().as_ptr(), flags_value as _) };
        if handle.is_null() {
            Err(self.error().into())
        } else {
            Ok(File {
                handle,
                phantom: PhantomData,
            })
        }
    }
}

/// Closes the archive, silently discarding on error.
/// It's strongly recommended to use the [Archive::close] method instead and validate that no
/// errors have occurred.
impl Drop for Archive<'_> {
    fn drop(&mut self) {
        if self.close_mut().is_err() {
            self.discard_mut()
        }
    }
}