1use anyhow::{ensure, Context, Result};
4
5use std::{
6 path::PathBuf,
7 sync::atomic::{AtomicBool, Ordering},
8};
9
10use crate::fs::{create_dir_all, write_file};
11
12const CONFIG_DIR: &str = "spin";
13const REGISTRY_CACHE_DIR: &str = "registry";
14const MANIFESTS_DIR: &str = "manifests";
15const WASM_DIR: &str = "wasm";
16const DATA_DIR: &str = "data";
17
18#[derive(Debug)]
20pub struct Cache {
21 root: PathBuf,
23 dirs_ensured_once: AtomicBool,
26}
27
28impl Cache {
29 pub async fn new(root: Option<PathBuf>) -> Result<Self> {
31 let root = match root {
32 Some(root) => root,
33 None => dirs::cache_dir()
34 .context("cannot get cache directory")?
35 .join(CONFIG_DIR),
36 };
37 let root = root.join(REGISTRY_CACHE_DIR);
38
39 Ok(Self {
40 root,
41 dirs_ensured_once: AtomicBool::new(false),
42 })
43 }
44
45 pub fn manifests_dir(&self) -> PathBuf {
47 self.root.join(MANIFESTS_DIR)
48 }
49
50 fn wasm_dir(&self) -> PathBuf {
52 self.root.join(WASM_DIR)
53 }
54
55 fn data_dir(&self) -> PathBuf {
57 self.root.join(DATA_DIR)
58 }
59
60 pub fn wasm_file(&self, digest: impl AsRef<str>) -> Result<PathBuf> {
62 let mut path = self.wasm_path(&digest);
67 if !path.exists() {
68 path = self.data_path(&digest);
69 }
70 ensure!(
71 path.exists(),
72 "cannot find wasm file for digest {}",
73 digest.as_ref()
74 );
75 Ok(path)
76 }
77
78 pub fn data_file(&self, digest: impl AsRef<str>) -> Result<PathBuf> {
80 let path = self.data_path(&digest);
81 ensure!(
82 path.exists(),
83 "cannot find data file for digest {}",
84 digest.as_ref()
85 );
86 Ok(path)
87 }
88
89 pub async fn write_wasm(&self, bytes: impl AsRef<[u8]>, digest: impl AsRef<str>) -> Result<()> {
91 self.ensure_dirs().await?;
92 write_file(&self.wasm_path(digest), bytes.as_ref()).await?;
93 Ok(())
94 }
95
96 pub async fn write_data(&self, bytes: impl AsRef<[u8]>, digest: impl AsRef<str>) -> Result<()> {
98 self.ensure_dirs().await?;
99 write_file(&self.data_path(digest), bytes.as_ref()).await?;
100 Ok(())
101 }
102
103 pub fn wasm_path(&self, digest: impl AsRef<str>) -> PathBuf {
105 self.wasm_dir().join(safe_name(digest).as_ref())
106 }
107
108 pub fn data_path(&self, digest: impl AsRef<str>) -> PathBuf {
110 self.data_dir().join(safe_name(digest).as_ref())
111 }
112
113 pub async fn ensure_dirs(&self) -> Result<()> {
123 tracing::debug!("using cache root directory {}", self.root.display());
124
125 if self.dirs_ensured_once.load(Ordering::Relaxed) {
128 return Ok(());
129 }
130
131 let root = &self.root;
132
133 let p = root.join(MANIFESTS_DIR);
134 if !p.is_dir() {
135 create_dir_all(&p).await.with_context(|| {
136 format!("failed to create manifests directory `{}`", p.display())
137 })?;
138 }
139
140 let p = root.join(WASM_DIR);
141 if !p.is_dir() {
142 create_dir_all(&p)
143 .await
144 .with_context(|| format!("failed to create wasm directory `{}`", p.display()))?;
145 }
146
147 let p = root.join(DATA_DIR);
148 if !p.is_dir() {
149 create_dir_all(&p)
150 .await
151 .with_context(|| format!("failed to create assets directory `{}`", p.display()))?;
152 }
153
154 self.dirs_ensured_once.store(true, Ordering::Relaxed);
155
156 Ok(())
157 }
158}
159
160#[cfg(windows)]
161fn safe_name(digest: impl AsRef<str>) -> impl AsRef<std::path::Path> {
162 digest.as_ref().replace(':', "_")
163}
164
165#[cfg(not(windows))]
166fn safe_name(digest: impl AsRef<str>) -> impl AsRef<str> {
167 digest
168}
169
170#[cfg(test)]
171mod test {
172 use spin_common::sha256::hex_digest_from_bytes;
173
174 use super::*;
175
176 #[tokio::test]
177 async fn accepts_prefixed_digests() -> anyhow::Result<()> {
178 let temp_dir = tempfile::tempdir()?;
179 let cache = Cache::new(Some(temp_dir.path().to_owned())).await?;
180
181 let wasm = "Wasm".as_bytes();
182 let digest = format!("sha256:{}", hex_digest_from_bytes(wasm));
183 cache.write_wasm(wasm, &digest).await?;
184 assert_eq!(wasm, std::fs::read(cache.wasm_path(&digest))?);
185
186 let data = "hello".as_bytes();
187 let digest = format!("sha256:{}", hex_digest_from_bytes(data));
188 cache.write_data(data, &digest).await?;
189 assert_eq!(data, std::fs::read(cache.data_path(&digest))?);
190
191 Ok(())
192 }
193}