wsi-rs 0.5.1

wsi-rs whole-slide image reader
Documentation
use super::storage::{hex_encode, is_fresh_svcache};
use super::*;
use std::io::ErrorKind;
use std::sync::Arc;

pub fn default_svcache_path(source_path: &Path) -> PathBuf {
    let name = source_path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("slide");
    source_path.with_file_name(format!("{name}.svcache"))
}

pub fn cache_dir_svcache_path(source_path: &Path) -> PathBuf {
    let mut hasher = Sha256::new();
    hasher.update(source_path.to_string_lossy().as_bytes());
    let hash = hex_encode(&hasher.finalize());
    let home = std::env::var_os("HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("."));
    home.join(".cache")
        .join("slideviewer")
        .join("svcache")
        .join(format!("{hash}.svcache"))
}

pub fn svcache_candidate_paths(source_path: &Path) -> [PathBuf; 2] {
    [
        default_svcache_path(source_path),
        cache_dir_svcache_path(source_path),
    ]
}

pub(crate) fn resolve_open_path_with_policy(
    path: &Path,
    policy: SvcachePolicy,
) -> Result<PathBuf, WsiError> {
    if path
        .extension()
        .and_then(|ext| ext.to_str())
        .is_some_and(|ext| ext.eq_ignore_ascii_case("svcache"))
    {
        return Ok(path.to_path_buf());
    }
    if matches!(policy, SvcachePolicy::Off) {
        return Ok(path.to_path_buf());
    }

    let mut stale_candidate = None;
    for candidate in svcache_candidate_paths(path) {
        match std::fs::metadata(&candidate) {
            Ok(metadata) if metadata.is_file() => match is_fresh_svcache(&candidate, path) {
                Ok(true) => return Ok(candidate),
                Ok(false) => stale_candidate = Some(candidate),
                Err(err) => return Err(err),
            },
            Ok(_) => continue,
            Err(source) if source.kind() == ErrorKind::NotFound => continue,
            Err(source) => {
                return Err(WsiError::IoWithPath {
                    source: Arc::new(source),
                    path: candidate,
                });
            }
        }
    }

    if matches!(policy, SvcachePolicy::RequireFresh) {
        let detail = stale_candidate
            .map(|candidate| format!("; stale candidate: {}", candidate.display()))
            .unwrap_or_default();
        return Err(WsiError::UnsupportedFormat(format!(
            "fresh .svcache required for {}{}",
            path.display(),
            detail
        )));
    }
    Ok(path.to_path_buf())
}