xzip 0.4.0

ZIP CLI with explicit filename encoding for pack and unpack workflows.
Documentation
use std::fs::{self, File, OpenOptions};
use std::path::Path;

use crate::error::XzipError;

/// Open a zip archive for reading with platform-specific tuning.
pub fn open_zip_file(path: &Path) -> Result<File, XzipError> {
    let mut options = OpenOptions::new();
    options.read(true);

    #[cfg(windows)]
    {
        use std::os::windows::fs::OpenOptionsExt;
        const FILE_SHARE_READ: u32 = 0x0000_0001;
        const FILE_SHARE_DELETE: u32 = 0x0000_0004;
        const FILE_FLAG_SEQUENTIAL_SCAN: u32 = 0x0800_0000;
        options.share_mode(FILE_SHARE_READ | FILE_SHARE_DELETE);
        options.custom_flags(FILE_FLAG_SEQUENTIAL_SCAN);
    }

    options.open(path).map_err(XzipError::from)
}

/// Create a parent directory if needed and apply platform-specific directory hints.
pub fn create_dir(path: &Path) -> Result<(), XzipError> {
    if path.exists() {
        return Ok(());
    }
    fs::create_dir(path)?;
    apply_dir_hints(path)?;
    Ok(())
}

/// Create a directory tree if needed and apply platform-specific directory hints.
pub fn create_dir_all(path: &Path) -> Result<(), XzipError> {
    if path.exists() {
        return Ok(());
    }

    let mut to_create = Vec::new();
    let mut cursor = Some(path);
    while let Some(dir) = cursor {
        if dir.exists() {
            break;
        }
        to_create.push(dir.to_path_buf());
        cursor = dir.parent();
    }

    for dir in to_create.into_iter().rev() {
        fs::create_dir(&dir)?;
        apply_dir_hints(&dir)?;
    }

    Ok(())
}

/// Create an output file for extraction.
pub fn create_output_file(path: &Path) -> Result<File, XzipError> {
    if let Some(parent) = path.parent() {
        create_dir_all(parent)?;
    }
    File::create(path).map_err(XzipError::from)
}

#[cfg(windows)]
fn apply_dir_hints(path: &Path) -> Result<(), XzipError> {
    use std::os::windows::ffi::OsStrExt;

    const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x0000_2000;

    let wide: Vec<u16> = path
        .as_os_str()
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    let attrs =
        unsafe { windows_sys::Win32::Storage::FileSystem::GetFileAttributesW(wide.as_ptr()) };
    if attrs == u32::MAX {
        return Err(XzipError::Io(std::io::Error::last_os_error()));
    }

    let updated = attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
    if unsafe {
        windows_sys::Win32::Storage::FileSystem::SetFileAttributesW(wide.as_ptr(), updated)
    } == 0
    {
        return Err(XzipError::Io(std::io::Error::last_os_error()));
    }

    Ok(())
}

#[cfg(not(windows))]
fn apply_dir_hints(_path: &Path) -> Result<(), XzipError> {
    Ok(())
}