1use serde_json::Value;
10
11use crate::error::{Error, Result};
12use crate::process::run;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum Compression {
17 #[default]
18 Gzip,
19 Zstd,
20 Estargz,
21 None,
22}
23
24impl Compression {
25 fn as_str(self) -> &'static str {
26 match self {
27 Compression::Gzip => "gzip",
28 Compression::Zstd => "zstd",
29 Compression::Estargz => "estargz",
30 Compression::None => "none",
31 }
32 }
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct CacheEntry {
38 pub key: String,
40 pub path: String,
42 pub compression: String,
43 pub size: u64,
45 pub created: u64,
47 pub digest: String,
49}
50
51impl CacheEntry {
52 fn from_value(v: &Value) -> CacheEntry {
53 CacheEntry {
54 key: str_at(v, "key"),
55 path: str_at(v, "path"),
56 compression: str_at(v, "compression"),
57 size: num_at(v, "size").unwrap_or(0),
58 created: num_at(v, "created").unwrap_or(0),
59 digest: str_at(v, "digest"),
60 }
61 }
62}
63
64#[derive(Debug, Clone, Default)]
66pub struct RestoreResult {
67 pub restored: bool,
68 pub requested_key: String,
70 pub key: Option<String>,
73 pub path: Option<String>,
75 pub size: Option<u64>,
76 pub compression: Option<String>,
77 pub created: Option<u64>,
78}
79
80impl RestoreResult {
81 fn from_value(v: &Value) -> RestoreResult {
82 RestoreResult {
83 restored: v.get("restored").and_then(Value::as_bool).unwrap_or(false),
84 requested_key: str_at(v, "requested_key"),
85 key: opt_str_at(v, "key"),
86 path: opt_str_at(v, "path"),
87 size: num_at(v, "size"),
88 compression: opt_str_at(v, "compression"),
89 created: num_at(v, "created"),
90 }
91 }
92}
93
94fn str_at(v: &Value, key: &str) -> String {
98 v.get(key)
99 .and_then(Value::as_str)
100 .unwrap_or_default()
101 .to_string()
102}
103
104fn opt_str_at(v: &Value, key: &str) -> Option<String> {
105 v.get(key).and_then(Value::as_str).map(str::to_string)
106}
107
108fn num_at(v: &Value, key: &str) -> Option<u64> {
109 v.get(key).and_then(Value::as_u64)
110}
111
112pub struct Cache {
127 id: String,
128}
129
130impl Cache {
131 pub(crate) fn new(id: impl Into<String>) -> Self {
132 Cache { id: id.into() }
133 }
134
135 pub fn save(
137 &self,
138 path: &str,
139 key: &str,
140 compression: Compression,
141 force: bool,
142 ) -> Result<CacheEntry> {
143 let mut args = vec![
144 "cache".to_string(),
145 "save".to_string(),
146 format!("{}:{}", self.id, path),
147 "--key".to_string(),
148 key.to_string(),
149 "--json".to_string(),
150 ];
151 if compression != Compression::Gzip {
152 args.push("--compression".to_string());
153 args.push(compression.as_str().to_string());
154 }
155 if force {
156 args.push("--force".to_string());
157 }
158 Ok(CacheEntry::from_value(&json(&args, "bsdkrun cache save")?))
159 }
160
161 pub fn restore(
167 &self,
168 key: &str,
169 path: Option<&str>,
170 restore_keys: &[String],
171 ) -> Result<RestoreResult> {
172 let target = match path {
173 Some(p) => format!("{}:{}", self.id, p),
174 None => self.id.clone(),
175 };
176 let mut args = vec![
177 "cache".to_string(),
178 "restore".to_string(),
179 target,
180 "--key".to_string(),
181 key.to_string(),
182 "--json".to_string(),
183 ];
184 if !restore_keys.is_empty() {
185 args.push("--restore-keys".to_string());
186 args.extend(restore_keys.iter().cloned());
187 }
188 Ok(RestoreResult::from_value(&json(
189 &args,
190 "bsdkrun cache restore",
191 )?))
192 }
193}
194
195pub fn list() -> Result<Vec<CacheEntry>> {
197 let v = json(
198 &["cache".to_string(), "ls".to_string(), "--json".to_string()],
199 "bsdkrun cache ls",
200 )?;
201 Ok(v.as_array()
202 .map(|rows| rows.iter().map(CacheEntry::from_value).collect())
203 .unwrap_or_default())
204}
205
206pub fn remove(keys: &[String], all: bool) -> Result<()> {
208 let mut args = vec!["cache".to_string(), "rm".to_string()];
209 if all {
210 args.push("--all".to_string());
211 } else {
212 args.extend(keys.iter().cloned());
213 }
214 let res = run(args)?;
215 if res.exit_code != 0 {
216 return Err(Error::CommandFailed {
217 exit_code: res.exit_code,
218 stdout: res.stdout,
219 stderr: res.stderr,
220 command: "bsdkrun cache rm".to_string(),
221 });
222 }
223 Ok(())
224}
225
226fn json(args: &[String], label: &str) -> Result<Value> {
227 let res = run(args.to_vec())?;
228 if res.exit_code != 0 {
229 return Err(Error::CommandFailed {
230 exit_code: res.exit_code,
231 stdout: res.stdout,
232 stderr: res.stderr,
233 command: label.to_string(),
234 });
235 }
236 serde_json::from_str(res.stdout.trim()).map_err(|e| Error::CommandFailed {
237 exit_code: 0,
238 stdout: res.stdout.clone(),
239 stderr: format!("could not decode {label} output: {e}"),
240 command: label.to_string(),
241 })
242}