macintosh-utils 0.1.0

A collection of utilities to simplify working with classic macintosh files and programs
Documentation
#![doc = include_str!("../README.md")]

use binrw::{binread, BinRead, BinReaderExt};
use bitflags::bitflags;

pub use chrono;
pub use fourcc_rs::FourCC;

pub mod type_code {
    use fourcc_rs::{fourcc, FourCC};
    pub const APPLICATION: FourCC = fourcc!("APPL");
}

#[derive(BinRead, Debug, Copy, Clone)]
#[br(big)]
/// Point in 2D space, serializable
pub struct Point {
    pub x: i16,
    pub y: i16,
}

#[binread]
/// Common string variant where one-byte denotes the length of the string followed by that many mac-roman encoded characters
pub struct PascalString {
    #[br(temp)]
    len: u8,

    #[br(count(len), map(decode_string))]
    contents: String,
}

impl PascalString {
    pub fn contents(&self) -> String {
        self.contents.to_string()
    }
}

impl From<PascalString> for String {
    fn from(val: PascalString) -> Self {
        val.contents
    }
}

/// Helper to a allow binrw to read pscal strings more easily
pub fn string(text: PascalString) -> String {
    text.contents
}

/// Helper to a allow binrw to read mac-roman strings more easily
pub fn decode_string(bytes: Vec<u8>) -> String {
    encoding_rs::MACINTOSH.decode(&bytes).0.to_string()
}

/// Helper to a allow binrw to read mac-roman strings more easily
pub fn decode_string_from_slice(bytes: &[u8]) -> String {
    encoding_rs::MACINTOSH.decode(bytes).0.to_string()
}

/// Helper to a allow binrw to read macintosh dates more easily
///
/// Macintosh uses the first of January 1904 as its reference date instead of 1970
pub fn date(seconds_since_1904: u32) -> chrono::DateTime<chrono::Utc> {
    const SECONDS_FROM_1904_TO_1970: i64 = 2082844800;
    chrono::DateTime::from_timestamp(seconds_since_1904 as i64 - SECONDS_FROM_1904_TO_1970, 0)
        .unwrap()
}

#[derive(Debug, Copy, Clone)]
/// Identifies either of the two forks that can be used to store data on classic Macintosh
/// file systems
pub enum Fork {
    /// Identifies the _resource fork_ that stores data in a system defined structure
    Resource,
    /// Identifies the _data fork_ whose format is entirely up to the application creating it
    Data,
}

impl Fork {
    pub fn is_resource(&self) -> bool {
        matches!(self, Fork::Resource)
    }

    pub fn is_data(&self) -> bool {
        matches!(self, Fork::Data)
    }
}

bitflags! {
    #[derive(Debug, Default, Clone, Copy)]
    /// Flags from the file information record used by _Finder_ to manage files.
    ///
    /// Adopted from [Macintosh Toolbox Essentials, 7-47](https://developer.apple.com/library/archive/documentation/mac/pdf/MacintoshToolboxEssentials.pdf#I25.1.275332)
    ///
    ///
    /// ```
    /// use macintosh_utils::FinderFlags;
    ///
    /// let flags = FinderFlags::from_bits(0x500).unwrap();
    ///
    /// assert!(flags.contains(FinderFlags::CUSTOMICON));
    /// assert!(flags.contains(FinderFlags::INITED));
    /// assert!(!flags.contains(FinderFlags::INVISIBLE));
    /// ```

    pub struct FinderFlags: u16 {
        /// For a file, this bit indicates that the file is an alias file. For directories, this bit is reserved—in which case, set to 0.
        const ALIAS = (1 << 15);
        /// The file or directory is invisible from the Finder and from the Standard File Package dialog boxes.
        const INVISIBLE = (1 << 14);
        /// For a file, this bit indicates that the file contains a bundle resource. For directories, this bit is reserved – in which case, set to 0.
        const BUNDLE = (1 << 13);
        /// The file or directory can't be renamed from the Finder, and the icon cannot be changed.
        const NAMELOCKED = (1 << 12);
        /// This flag specifies that a file is a stationery pad and should be treated as a document
        /// template rather than a document itself
        const STATIONERY = (1 << 11);
        /// The file or directory contains a customized icon
        const CUSTOMICON = (1 << 10);
        /// Reserved; set to 0.
        const RESERVED = (1 << 9);
        /// The Finder has recorded information from the file’s bundle resource into the desktop database and given the file or folder a position on the desktop.
        const INITED = (1 << 8);
        /// The file contains no 'INIT' resources; set to 0. Reserved for directories; set to 0.
        const NOINIT = (1 << 7);
        /// The file is an application that can be executed by multiple users simultaneously. Defined only for applications; otherwise, set to 0.
        const SHARED = (1 << 6);
        /// Unused and reserved in System 7; set to 0.
        const SWITCH_LAUNCH = (1<<5);
        /// Unused and reserved in System 7; set to 0.
        const COLOR_RESERVED = (1<<4);
        /// Color coding bit 2
        const COLORBIT2 = (1 << 3);
        /// Color coding bit 1
        const COLORBIT1 = (1 << 2);
        /// Color coding bit 0
        const COLORBIT0 = (1 << 1);
        /// Unused and reserved in System 7; set to 0.
        const ON_DESKTOP = (1 << 0);
    }
}

impl BinRead for FinderFlags {
    type Args<'a> = ();

    fn read_options<R: std::io::Read + std::io::Seek>(
        reader: &mut R,
        _endian: binrw::Endian,
        _args: Self::Args<'_>,
    ) -> binrw::BinResult<Self> {
        let flags: u16 = reader.read_be()?;
        if let Some(flags) = FinderFlags::from_bits(flags) {
            return Ok(flags);
        }

        // TODO: consider printing a warning that flags had unknown bits set

        Ok(Default::default())
    }
}