cueward-adapter-macos 0.3.0

macOS adapter for Cueward with Safari, Notes, Messages, Reminders, Calendar, Screenshot, Clipboard, and OCR integrations.
use super::super::{AttachmentOcrBlock, MediaAttachment};
use super::super::db::compute_sha256;
use super::ocr_support::load_or_run_file_ocr;

pub(super) fn collect_attachment_ocr_blocks(
    attachments: &[MediaAttachment],
    placeholder_count: usize,
) -> Vec<AttachmentOcrBlock> {
    attachments
        .iter()
        .take(placeholder_count)
        .enumerate()
        .filter_map(|(idx, attachment)| {
            let path = attachment.path.as_path();
            let text = load_or_run_file_ocr(path, attachment.sha256.as_deref(), &attachment.filename)
                .ok()
                .flatten()?;
            let trimmed = text.trim();
            if trimmed.is_empty() {
                return None;
            }

            Some(AttachmentOcrBlock {
                index: idx + 1,
                filename: attachment.filename.clone(),
                sha256: attachment.sha256.clone(),
                text: trimmed.to_string(),
            })
        })
        .collect()
}

pub(super) fn materialize_attachments(
    attachments: &[MediaAttachment],
    placeholder_count: usize,
) -> Vec<MediaAttachment> {
    attachments
        .iter()
        .take(placeholder_count)
        .cloned()
        .map(|mut attachment| {
            if attachment.sha256.is_none() {
                attachment.sha256 = compute_sha256(&attachment.path);
            }
            attachment
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use std::fs;

    use super::super::ocr_support::{cached_ocr_text, ocr_cache_file_path};
    use crate::notes::db::compute_sha256;
    use crate::notes::OCR_EMPTY_SENTINEL;

    #[test]
    fn ocr_cache_file_path_uses_hash_name() {
        let path = ocr_cache_file_path("abc123").expect("cache path");

        assert!(path.ends_with(".cueward/cache/ocr/abc123.txt"));
    }

    #[test]
    fn compute_sha256_returns_stable_digest() {
        let temp = tempfile::tempdir().expect("tempdir");
        let target = temp.path().join("image.png");
        fs::write(&target, b"hello world").expect("write media file");

        let digest = compute_sha256(&target);

        assert_eq!(
            digest.as_deref(),
            Some("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
        );
    }

    #[test]
    fn cached_ocr_text_treats_empty_files_as_cache_miss() {
        assert_eq!(cached_ocr_text(""), None);
        assert_eq!(cached_ocr_text(OCR_EMPTY_SENTINEL), Some(String::new()));
    }
}