1use std::env::consts::{ARCH, OS};
2use std::env::temp_dir;
3use std::path::{Path, PathBuf};
4use std::str;
5
6use anyhow::{anyhow, Context, Result};
7use provider_archive::ProviderArchive;
8use tokio::fs::{self, File, OpenOptions};
9use tokio::io::AsyncWriteExt;
10use wascap::jwt;
11
12fn normalize_for_filename(input: &str) -> String {
13 input
14 .to_lowercase()
15 .replace(|c: char| !c.is_ascii_alphanumeric(), "_")
16}
17
18#[derive(Default, Clone, PartialEq, Eq)]
20pub enum UseParFileCache {
21 Ignore,
23 #[default]
25 Use,
26}
27
28fn native_target() -> String {
29 format!("{ARCH}-{OS}")
30}
31
32pub fn cache_path(host_id: impl AsRef<str>, provider_ref: impl AsRef<str>) -> PathBuf {
39 let provider_ref = normalize_for_filename(provider_ref.as_ref());
40
41 let mut cache = temp_dir();
42 cache.push("wasmcloudcache");
43 cache.push(host_id.as_ref());
44 cache.push(&provider_ref);
45 #[cfg(windows)]
46 cache.set_extension("exe");
47 cache
48}
49
50pub(super) async fn create(path: impl AsRef<Path>) -> Result<Option<File>> {
51 let path = path.as_ref();
52 if fs::metadata(path).await.is_ok() {
54 return Ok(None);
55 }
56 let dir = path.parent().context("failed to determine parent path")?;
57 fs::create_dir_all(dir)
58 .await
59 .context("failed to create parent directory")?;
60
61 open_file(path).await
62}
63
64async fn open_file(path: impl AsRef<Path>) -> Result<Option<File>> {
65 let path = path.as_ref();
66 let mut open_opts = OpenOptions::new();
67 open_opts.create(true).truncate(true).write(true);
68 #[cfg(unix)]
69 open_opts.mode(0o755);
70 open_opts
71 .open(path)
72 .await
73 .map(Some)
74 .with_context(|| format!("failed to open path [{}]", path.display()))
75}
76
77pub async fn read(
85 path: impl AsRef<Path>,
86 host_id: impl AsRef<str>,
87 provider_ref: impl AsRef<str>,
88 cache: UseParFileCache,
89) -> Result<(PathBuf, Option<jwt::Token<jwt::CapabilityProvider>>)> {
90 let par = ProviderArchive::try_load_target_from_file(path, &native_target())
91 .await
92 .map_err(|e| anyhow!(e).context("failed to load provider archive"))?;
93 let claims = par.claims_token();
94 let exe = cache_path(host_id, provider_ref);
95
96 let new_file = create(&exe).await?;
97 let mut file = match (cache, new_file) {
98 (UseParFileCache::Use, None) => {
99 return Ok((exe, claims));
100 }
101 (UseParFileCache::Ignore, None) => open_file(&exe)
102 .await?
103 .with_context(|| format!("failed to open file [{}]", exe.display()))?,
104 (UseParFileCache::Use, Some(file)) | (UseParFileCache::Ignore, Some(file)) => file,
105 };
106
107 let target = native_target();
108 let buf = par
109 .target_bytes(&target)
110 .with_context(|| format!("target `{target}` not found"))?;
111 file.write_all(&buf).await.context("failed to write")?;
112 file.flush().await.context("failed to flush")?;
113
114 Ok((exe, claims))
115}