use super::inventory::Inventory;
use super::layout_error::ProtoLayoutError;
use crate::id::Id;
use crate::layout::ShimRegistry;
use crate::tool_manifest::ToolManifest;
use once_cell::sync::OnceCell;
use proto_pdk_api::ToolInventoryOptions;
use proto_shim::{create_shim, locate_proto_exe};
use serde::Serialize;
use starbase_styles::color;
use starbase_utils::{envx, fs, path};
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tracing::{debug, instrument};
#[derive(Clone, Default, Serialize)]
pub struct Store {
pub dir: PathBuf,
pub backends_dir: PathBuf,
pub bin_dir: PathBuf,
pub builders_dir: PathBuf,
pub cache_dir: PathBuf,
pub inventory_dir: PathBuf,
pub plugins_dir: PathBuf,
pub shims_dir: PathBuf,
pub temp_dir: PathBuf,
#[serde(skip)]
shim_binary: Arc<OnceCell<Vec<u8>>>,
}
impl Store {
#[instrument(name = "create_store")]
pub fn new(dir: &Path) -> Self {
let temp_dir = match envx::path_var("PROTO_TEMP_DIR") {
Some(custom) => {
debug!(
temp_dir = ?custom,
"Using custom temp directory from {}",
color::symbol("PROTO_TEMP_DIR")
);
custom
}
None => dir.join("temp"),
};
Self {
dir: dir.to_path_buf(),
backends_dir: dir.join("backends"),
bin_dir: dir.join("bin"),
builders_dir: dir.join("builders"),
cache_dir: dir.join("cache"),
inventory_dir: dir.join("tools"),
plugins_dir: dir.join("plugins"),
shims_dir: dir.join("shims"),
temp_dir,
shim_binary: Arc::new(OnceCell::new()),
}
}
pub fn create_inventory(
&self,
id: &Id,
config: &ToolInventoryOptions,
) -> Result<Inventory, ProtoLayoutError> {
let dir = self.inventory_dir.join(path::encode_component(id));
Ok(Inventory {
manifest: ToolManifest::load_from(&dir)?,
dir,
dir_original: None,
temp_dir: self.temp_dir.join(path::encode_component(id)),
config: config.to_owned(),
})
}
pub fn load_uuid(&self) -> Result<String, ProtoLayoutError> {
let id_path = self.dir.join("id");
if id_path.exists() {
return Ok(fs::read_file(id_path)?);
}
let id = uuid::Uuid::new_v4().to_string();
fs::write_file(id_path, &id)?;
Ok(id)
}
#[instrument(skip(self))]
pub fn load_preferred_profile(&self) -> Result<Option<PathBuf>, ProtoLayoutError> {
let profile_path = self.dir.join("profile");
if profile_path.exists() {
return Ok(Some(fs::read_file(profile_path)?.into()));
}
Ok(None)
}
#[instrument(skip(self))]
pub fn load_shim_binary(&self) -> Result<&Vec<u8>, ProtoLayoutError> {
self.shim_binary.get_or_try_init(|| {
Ok(fs::read_file_bytes(
locate_proto_exe("proto-shim").ok_or_else(|| {
ProtoLayoutError::MissingShimBinary {
bin_dir: self.bin_dir.clone(),
}
})?,
)?)
})
}
#[instrument(skip(self))]
pub fn load_shims_registry(&self) -> Result<ShimRegistry, ProtoLayoutError> {
ShimRegistry::load(&self.shims_dir)
}
#[instrument(skip(self))]
pub fn save_preferred_profile(&self, path: &Path) -> Result<(), ProtoLayoutError> {
fs::write_file(
self.dir.join("profile"),
path.as_os_str().as_encoded_bytes(),
)?;
Ok(())
}
#[instrument(skip(self))]
pub fn link_bin(&self, bin_path: &Path, src_path: &Path) -> Result<(), ProtoLayoutError> {
#[cfg(windows)]
{
fs::copy_file(src_path, bin_path)?;
}
#[cfg(unix)]
{
use starbase_utils::fs::FsError;
std::os::unix::fs::symlink(src_path, bin_path).map_err(|error| {
ProtoLayoutError::Fs(Box::new(FsError::Create {
path: src_path.to_path_buf(),
error: Box::new(error),
}))
})?;
}
Ok(())
}
#[instrument(skip(self))]
pub fn unlink_bin(&self, bin_path: &Path) -> Result<(), ProtoLayoutError> {
if bin_path.is_symlink() {
fs::remove_link(bin_path)?;
} else if bin_path.is_file() {
fs::remove_file(bin_path)?;
}
Ok(())
}
#[instrument(skip(self))]
pub fn create_shim(&self, shim_path: &Path) -> Result<(), ProtoLayoutError> {
create_shim(self.load_shim_binary()?, shim_path).map_err(|error| {
ProtoLayoutError::FailedCreateShim {
path: shim_path.to_owned(),
error: Box::new(error),
}
})?;
Ok(())
}
#[instrument(skip(self))]
pub fn remove_shim(&self, shim_path: &Path) -> Result<(), ProtoLayoutError> {
fs::remove(shim_path)?;
Ok(())
}
}
impl fmt::Debug for Store {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Store")
.field("dir", &self.dir)
.field("bin_dir", &self.bin_dir)
.field("cache_dir", &self.cache_dir)
.field("inventory_dir", &self.inventory_dir)
.field("plugins_dir", &self.plugins_dir)
.field("shims_dir", &self.shims_dir)
.field("temp_dir", &self.temp_dir)
.finish()
}
}