compression-rs 0.2.0

Safe Rust bindings for Apple's Compression and AppleArchive APIs on macOS
Documentation
use crate::{ffi, util, CompressionError, Result};
use std::ffi::{c_void, CStr};
use std::fmt;
use std::ptr::NonNull;

#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FieldKey(u32);

fn parse_field_key(value: &str) -> Result<FieldKey> {
    if value.len() != 3 || !value.bytes().all(|byte| byte.is_ascii() && byte != 0) {
        return Err(CompressionError::InvalidFieldKey {
            key: value.to_string(),
        });
    }

    let upper = value.to_ascii_uppercase();
    let bytes = upper.as_bytes();
    Ok(FieldKey::from_bytes([bytes[0], bytes[1], bytes[2]]))
}

impl FieldKey {
    pub const ACL: Self = Self::from_bytes(*b"ACL");
    pub const BTM: Self = Self::from_bytes(*b"BTM");
    pub const CKS: Self = Self::from_bytes(*b"CKS");
    pub const CLC: Self = Self::from_bytes(*b"CLC");
    pub const CTM: Self = Self::from_bytes(*b"CTM");
    pub const DAT: Self = Self::from_bytes(*b"DAT");
    pub const DEV: Self = Self::from_bytes(*b"DEV");
    pub const DE2: Self = Self::from_bytes(*b"DE2");
    pub const DUZ: Self = Self::from_bytes(*b"DUZ");
    pub const FLG: Self = Self::from_bytes(*b"FLG");
    pub const GID: Self = Self::from_bytes(*b"GID");
    pub const GIN: Self = Self::from_bytes(*b"GIN");
    pub const HLC: Self = Self::from_bytes(*b"HLC");
    pub const IDX: Self = Self::from_bytes(*b"IDX");
    pub const IDZ: Self = Self::from_bytes(*b"IDZ");
    pub const INO: Self = Self::from_bytes(*b"INO");
    pub const LNK: Self = Self::from_bytes(*b"LNK");
    pub const MOD: Self = Self::from_bytes(*b"MOD");
    pub const MTM: Self = Self::from_bytes(*b"MTM");
    pub const NLK: Self = Self::from_bytes(*b"NLK");
    pub const PAT: Self = Self::from_bytes(*b"PAT");
    pub const SH1: Self = Self::from_bytes(*b"SH1");
    pub const SH2: Self = Self::from_bytes(*b"SH2");
    pub const SH3: Self = Self::from_bytes(*b"SH3");
    pub const SH5: Self = Self::from_bytes(*b"SH5");
    pub const SIZ: Self = Self::from_bytes(*b"SIZ");
    pub const SLC: Self = Self::from_bytes(*b"SLC");
    pub const TYP: Self = Self::from_bytes(*b"TYP");
    pub const UID: Self = Self::from_bytes(*b"UID");
    pub const UIN: Self = Self::from_bytes(*b"UIN");
    pub const XAT: Self = Self::from_bytes(*b"XAT");
    pub const YAF: Self = Self::from_bytes(*b"YAF");

    pub const fn from_bytes(bytes: [u8; 3]) -> Self {
        Self(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]))
    }

    pub fn parse(value: &str) -> Result<Self> {
        parse_field_key(value)
    }

    pub(crate) const fn from_raw(raw: u32) -> Self {
        Self(raw)
    }

    pub const fn raw(self) -> u32 {
        self.0
    }

    pub const fn as_bytes(self) -> [u8; 3] {
        let [a, b, c, _] = self.0.to_le_bytes();
        [a, b, c]
    }

    pub fn as_string(self) -> String {
        String::from_utf8_lossy(&self.as_bytes()).into_owned()
    }
}

impl fmt::Debug for FieldKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "FieldKey({self})")
    }
}

impl std::str::FromStr for FieldKey {
    type Err = CompressionError;

    fn from_str(value: &str) -> Result<Self> {
        parse_field_key(value)
    }
}

impl fmt::Display for FieldKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.as_string())
    }
}

impl TryFrom<&str> for FieldKey {
    type Error = CompressionError;

    fn try_from(value: &str) -> Result<Self> {
        parse_field_key(value)
    }
}

#[derive(Debug)]
pub struct FieldKeySet {
    handle: NonNull<c_void>,
}

impl FieldKeySet {
    pub fn new() -> Result<Self> {
        let handle = unsafe { ffi::aa_field_key::compression_rs_aa_field_key_set_create() };
        Ok(Self {
            handle: util::nonnull_handle(handle, "AAFieldKeySetCreate")?,
        })
    }

    pub fn from_csv(value: &str) -> Result<Self> {
        let value = util::cstring("value", value)?;
        let handle = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_create_with_string(value.as_ptr())
        };
        Ok(Self {
            handle: util::nonnull_handle(handle, "AAFieldKeySetCreateWithString")?,
        })
    }

    pub(crate) fn as_ptr(&self) -> *mut c_void {
        self.handle.as_ptr()
    }

    pub fn clear(&mut self) -> Result<()> {
        let status =
            unsafe { ffi::aa_field_key::compression_rs_aa_field_key_set_clear(self.as_ptr()) };
        util::status_result("AAFieldKeySetClear", status)
    }

    pub fn contains(&self, key: FieldKey) -> Result<bool> {
        match unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_contains_key(
                self.as_ptr(),
                key.raw(),
            )
        } {
            value if value < 0 => Err(CompressionError::OperationFailed {
                operation: "AAFieldKeySetContainsKey",
                code: value,
            }),
            0 => Ok(false),
            _ => Ok(true),
        }
    }

    pub fn insert(&mut self, key: FieldKey) -> Result<()> {
        let status = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_insert_key(self.as_ptr(), key.raw())
        };
        util::status_result("AAFieldKeySetInsertKey", status)
    }

    pub fn remove(&mut self, key: FieldKey) -> Result<()> {
        let status = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_remove_key(self.as_ptr(), key.raw())
        };
        util::status_result("AAFieldKeySetRemoveKey", status)
    }

    pub fn insert_set(&mut self, other: &Self) -> Result<()> {
        let status = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_insert_key_set(
                self.as_ptr(),
                other.as_ptr(),
            )
        };
        util::status_result("AAFieldKeySetInsertKeySet", status)
    }

    pub fn remove_set(&mut self, other: &Self) -> Result<()> {
        let status = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_remove_key_set(
                self.as_ptr(),
                other.as_ptr(),
            )
        };
        util::status_result("AAFieldKeySetRemoveKeySet", status)
    }

    pub fn select_set(&mut self, other: &Self) -> Result<()> {
        let status = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_select_key_set(
                self.as_ptr(),
                other.as_ptr(),
            )
        };
        util::status_result("AAFieldKeySetSelectKeySet", status)
    }

    pub fn len(&self) -> u32 {
        unsafe { ffi::aa_field_key::compression_rs_aa_field_key_set_get_key_count(self.as_ptr()) }
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    pub fn key(&self, index: u32) -> Result<FieldKey> {
        if index >= self.len() {
            return Err(CompressionError::OperationFailed {
                operation: "AAFieldKeySetGetKey",
                code: -1,
            });
        }

        Ok(FieldKey::from_raw(unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_get_key(self.as_ptr(), index)
        }))
    }

    pub fn serialize(&self) -> Result<String> {
        let capacity = (self.len() as usize)
            .saturating_mul(4)
            .saturating_add(1)
            .max(1);
        let mut buffer = vec![0_i8; capacity];
        let status = unsafe {
            ffi::aa_field_key::compression_rs_aa_field_key_set_serialize(
                self.as_ptr(),
                buffer.len(),
                buffer.as_mut_ptr(),
            )
        };
        util::status_result("AAFieldKeySetSerialize", status)?;

        let value = unsafe { CStr::from_ptr(buffer.as_ptr()) }
            .to_str()
            .map_err(|_| CompressionError::Utf8Error {
                operation: "AAFieldKeySetSerialize",
            })?;
        Ok(value.to_string())
    }
}

impl Clone for FieldKeySet {
    fn clone(&self) -> Self {
        let handle =
            unsafe { ffi::aa_field_key::compression_rs_aa_field_key_set_clone(self.as_ptr()) };
        Self {
            handle: util::nonnull_handle(handle, "AAFieldKeySetClone")
                .expect("AAFieldKeySetClone returned null"),
        }
    }
}

impl Drop for FieldKeySet {
    fn drop(&mut self) {
        unsafe { ffi::aa_field_key::compression_rs_aa_field_key_set_release(self.as_ptr()) };
    }
}