zccache 1.12.9

Local-first compiler cache for C/C++/Rust/Emscripten
use std::fs;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::download::stable_download_id;

use super::archive::detect_archive_format;
use super::hashing::ArtifactFingerprint;
use super::resolve::ResolvedFetchRequest;
use super::{ArchiveFormat, DownloadSource};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct ExpandedMarker {
    source: DownloadSource,
    cache_path: String,
    artifact_sha256: String,
    archive_format: ArchiveFormat,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct ArtifactMarker {
    source: DownloadSource,
    cache_path: String,
    sha256: String,
    bytes: u64,
}

pub(super) fn artifact_marker_path(cache_path: &Path) -> PathBuf {
    let hash = stable_download_id(cache_path);
    crate::core::config::default_cache_dir()
        .join("downloads")
        .join("artifact-state")
        .join(format!("{hash}.json"))
        .into_path_buf()
}

pub(super) fn expanded_marker_path(expanded_path: &Path) -> PathBuf {
    let hash = stable_download_id(expanded_path);
    crate::core::config::default_cache_dir()
        .join("downloads")
        .join("expanded-state")
        .join(format!("{hash}.json"))
        .into_path_buf()
}

pub(super) fn read_or_compute_artifact_fingerprint(
    request: &ResolvedFetchRequest,
) -> Result<ArtifactFingerprint, String> {
    let fingerprint = super::hashing::compute_artifact_fingerprint(&request.cache_path)
        .map_err(|e| e.to_string())?;
    if let Ok(content) = fs::read_to_string(artifact_marker_path(&request.cache_path)) {
        let marker: ArtifactMarker = serde_json::from_str(&content).map_err(|e| e.to_string())?;
        if marker.source != request.source
            || marker.cache_path != request.cache_path.to_string_lossy()
            || marker.sha256 != fingerprint.sha256
            || marker.bytes != fingerprint.bytes
        {
            return Err(format!(
                "artifact marker for {} does not match the on-disk payload",
                request.cache_path.display()
            ));
        }
    }
    Ok(fingerprint)
}

pub(super) fn write_artifact_marker(
    request: &ResolvedFetchRequest,
    fingerprint: &ArtifactFingerprint,
) -> Result<(), String> {
    let marker_path = artifact_marker_path(&request.cache_path);
    if let Some(parent) = marker_path.parent() {
        fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    }
    let marker = ArtifactMarker {
        source: request.source.clone(),
        cache_path: request.cache_path.to_string_lossy().into_owned(),
        sha256: fingerprint.sha256.clone(),
        bytes: fingerprint.bytes,
    };
    let json = serde_json::to_string(&marker).map_err(|e| e.to_string())?;
    fs::write(marker_path, json).map_err(|e| e.to_string())
}

pub(super) fn expanded_marker_matches(
    request: &ResolvedFetchRequest,
    fingerprint: &ArtifactFingerprint,
) -> Result<bool, String> {
    let Some(expanded_path) = &request.expanded_path else {
        return Ok(false);
    };
    let marker_path = expanded_marker_path(expanded_path);
    let marker: ExpandedMarker = match fs::read_to_string(&marker_path) {
        Ok(content) => serde_json::from_str(&content).map_err(|e| e.to_string())?,
        Err(_) => return Ok(false),
    };
    if marker.source != request.source {
        return Ok(false);
    }
    if marker.cache_path != request.cache_path.to_string_lossy() {
        return Ok(false);
    }
    if marker.artifact_sha256 != fingerprint.sha256 {
        return Ok(false);
    }
    if marker.archive_format != detect_archive_format(request)? {
        return Ok(false);
    }
    Ok(expanded_path.exists())
}

pub(super) fn write_expanded_marker(
    request: &ResolvedFetchRequest,
    fingerprint: &ArtifactFingerprint,
) -> Result<(), String> {
    let Some(expanded_path) = &request.expanded_path else {
        return Ok(());
    };
    let marker_path = expanded_marker_path(expanded_path);
    if let Some(parent) = marker_path.parent() {
        fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    }
    let marker = ExpandedMarker {
        source: request.source.clone(),
        cache_path: request.cache_path.to_string_lossy().into_owned(),
        artifact_sha256: fingerprint.sha256.clone(),
        archive_format: detect_archive_format(request)?,
    };
    let json = serde_json::to_string(&marker).map_err(|e| e.to_string())?;
    fs::write(marker_path, json).map_err(|e| e.to_string())
}