runner_core/packs/
cache.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use anyhow::{Context, Result};
6
7use super::{PackDigest, PackEntry, PackRef, PackVersion};
8pub struct PackCache {
9    root: PathBuf,
10}
11
12impl PackCache {
13    pub fn new(root: PathBuf) -> Self {
14        let root = root.canonicalize().unwrap_or(root);
15        Self { root }
16    }
17
18    pub fn root(&self) -> &Path {
19        &self.root
20    }
21
22    pub fn store(&self, entry: &PackEntry, source: &Path, digest: &PackDigest) -> Result<PathBuf> {
23        let dest_dir = self.dir_for(&entry.reference);
24        fs::create_dir_all(&dest_dir)
25            .with_context(|| format!("failed to create cache dir {}", dest_dir.display()))?;
26        let dest_path = dest_dir.join("pack.gtpack");
27        if dest_path.exists() {
28            if digest.matches_file(&dest_path)? {
29                return Ok(dest_path);
30            }
31            fs::remove_file(&dest_path)
32                .with_context(|| format!("failed to remove stale cache {}", dest_path.display()))?;
33        }
34
35        if same_path(source, &dest_path) {
36            return Ok(dest_path);
37        }
38
39        copy_atomic(source, &dest_path)?;
40        Ok(dest_path)
41    }
42
43    fn dir_for(&self, reference: &PackRef) -> PathBuf {
44        match &reference.version {
45            PackVersion::Semver(version) => self
46                .root
47                .join(super::sanitize_segment(&reference.name))
48                .join(version.to_string()),
49            PackVersion::Digest(digest) => self
50                .root
51                .join(super::sanitize_segment(&reference.name))
52                .join(digest.cache_label()),
53        }
54    }
55}
56
57fn copy_atomic(source: &Path, dest: &Path) -> Result<()> {
58    let tmp = dest.with_extension(format!(
59        "tmp-{}",
60        SystemTime::now()
61            .duration_since(UNIX_EPOCH)
62            .map(|dur| dur.as_nanos())
63            .unwrap_or(0)
64    ));
65    fs::copy(source, &tmp)
66        .with_context(|| format!("failed to copy {} -> {}", source.display(), dest.display()))?;
67    fs::rename(&tmp, dest)
68        .with_context(|| format!("failed to place cached pack at {}", dest.display()))?;
69    Ok(())
70}
71
72fn same_path(a: &Path, b: &Path) -> bool {
73    if let (Ok(a), Ok(b)) = (a.canonicalize(), b.canonicalize()) {
74        a == b
75    } else {
76        a == b
77    }
78}