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()
}
}
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())
}
}
}
pub fn close(mut self) -> std::result::Result<(), (Self, Error)> {
match self.close_mut() {
Ok(()) => Ok(()),
Err(e) => Err((self, e)),
}
}
fn discard_mut(&mut self) {
if !self.handle.is_null() {
unsafe {
ffi::zip_discard(self.handle);
}
}
}
pub fn discard(mut self) {
self.discard_mut()
}
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(())
}
}
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(())
}
}
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)
}
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)
}
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(())
}
}
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,
})
}
}
}
impl Drop for Archive<'_> {
fn drop(&mut self) {
if self.close_mut().is_err() {
self.discard_mut()
}
}
}