cranpose 0.1.149

Cranpose runtime and UI facade
Documentation
use std::{
    fs::File,
    path::{Component, Path, PathBuf},
    sync::Arc,
};

use cranpose_services::{
    BundledAssetError, BundledAssetReader, BundledAssets, StreamingAssetReader,
    set_platform_bundled_assets,
};
use objc2_foundation::NSBundle;

pub(crate) fn register() {
    set_platform_bundled_assets(Arc::new(IosBundledAssets));
}

struct IosBundledAssets;

impl IosBundledAssets {
    fn resolve(&self, path: &str) -> Result<PathBuf, BundledAssetError> {
        let relative = Path::new(path);
        let is_plain_relative = !relative.as_os_str().is_empty()
            && relative
                .components()
                .all(|component| matches!(component, Component::Normal(_)));
        if !is_plain_relative {
            return Err(BundledAssetError::InvalidPath(path.to_string()));
        }
        let root =
            NSBundle::mainBundle()
                .resourcePath()
                .ok_or_else(|| BundledAssetError::ReadFailed {
                    path: path.to_string(),
                    message: "the application bundle has no resource directory".to_string(),
                })?;
        Ok(PathBuf::from(root.to_string()).join(relative))
    }
}

impl BundledAssets for IosBundledAssets {
    fn read(&self, path: &str) -> Result<Vec<u8>, BundledAssetError> {
        let full_path = self.resolve(path)?;
        std::fs::read(&full_path).map_err(|error| read_error(path, &full_path, error))
    }

    fn open(&self, path: &str) -> Result<Box<dyn BundledAssetReader>, BundledAssetError> {
        let full_path = self.resolve(path)?;
        let file = File::open(&full_path).map_err(|error| read_error(path, &full_path, error))?;
        Ok(Box::new(StreamingAssetReader::new(path, file)))
    }

    fn len(&self, path: &str) -> Option<u64> {
        let full_path = self.resolve(path).ok()?;
        std::fs::metadata(full_path)
            .ok()
            .map(|metadata| metadata.len())
    }
}

fn read_error(path: &str, full_path: &Path, error: std::io::Error) -> BundledAssetError {
    if error.kind() == std::io::ErrorKind::NotFound {
        BundledAssetError::NotFound(path.to_string())
    } else {
        BundledAssetError::ReadFailed {
            path: path.to_string(),
            message: format!("{}: {error}", full_path.display()),
        }
    }
}