use std::{ops::Deref, path::Path, sync::Arc}; use data_encoding::BASE64; use nix_compat::store_path::{self, StorePath}; use tracing::{debug, instrument}; use tvix_castore::{ blobservice::{self, BlobService}, directoryservice::{self, DirectoryService}, proto::node::Node, }; use crate::{ pathinfoservice::{self, PathInfoService}, proto::{nar_info, NarInfo, PathInfo}, }; /// Construct the three store handles from their addrs. pub async fn construct_services( blob_service_addr: impl AsRef, directory_service_addr: impl AsRef, path_info_service_addr: impl AsRef, ) -> std::io::Result<( Arc, Arc, Box, )> { let blob_service: Arc = blobservice::from_addr(blob_service_addr.as_ref()) .await? .into(); let directory_service: Arc = directoryservice::from_addr(directory_service_addr.as_ref()) .await? .into(); let path_info_service = pathinfoservice::from_addr( path_info_service_addr.as_ref(), blob_service.clone(), directory_service.clone(), ) .await?; Ok((blob_service, directory_service, path_info_service)) } /// Imports a given path on the filesystem into the store, and returns the /// [PathInfo] describing the path, that was sent to /// [PathInfoService]. #[instrument(skip_all, fields(path=?path), err)] pub async fn import_path( path: P, blob_service: BS, directory_service: DS, path_info_service: PS, ) -> Result where P: AsRef + std::fmt::Debug, BS: Deref + Clone, DS: Deref, PS: Deref, { // calculate the name // TODO: make a path_to_name helper function? let name = path .as_ref() .file_name() .and_then(|file_name| file_name.to_str()) .ok_or_else(|| { std::io::Error::new( std::io::ErrorKind::InvalidInput, "path must not be .. and the basename valid unicode", ) })?; // Ingest the path into blob and directory service. let root_node = tvix_castore::import::ingest_path(blob_service, &directory_service.deref(), &path) .await .expect("failed to ingest path"); debug!(root_node =?root_node, "import successful"); // Ask the PathInfoService for the NAR size and sha256 let (nar_size, nar_sha256) = path_info_service.calculate_nar(&root_node).await?; // Calculate the output path. This might still fail, as some names are illegal. let output_path = store_path::build_nar_based_store_path(&nar_sha256, name).map_err(|_| { std::io::Error::new( std::io::ErrorKind::InvalidData, format!("invalid name: {}", name), ) })?; // assemble a new root_node with a name that is derived from the nar hash. let root_node = root_node.rename(output_path.to_string().into_bytes().into()); log_node(&root_node, path.as_ref()); // assemble the [crate::proto::PathInfo] object. let path_info = PathInfo { node: Some(tvix_castore::proto::Node { node: Some(root_node), }), // There's no reference scanning on path contents ingested like this. references: vec![], narinfo: Some(NarInfo { nar_size, nar_sha256: nar_sha256.to_vec().into(), signatures: vec![], reference_names: vec![], deriver: None, ca: Some(nar_info::Ca { r#type: nar_info::ca::Hash::NarSha256.into(), digest: nar_sha256.to_vec().into(), }), }), }; // put into [PathInfoService], and return the PathInfo that we get back // from there (it might contain additional signatures). let _path_info = path_info_service.put(path_info).await?; Ok(output_path.to_owned()) } fn log_node(node: &Node, path: &Path) { match node { Node::Directory(directory_node) => { debug!( path = ?path, name = ?directory_node.name, digest = BASE64.encode(&directory_node.digest), "import successful", ) } Node::File(file_node) => { debug!( path = ?path, name = ?file_node.name, digest = BASE64.encode(&file_node.digest), "import successful" ) } Node::Symlink(symlink_node) => { debug!( path = ?path, name = ?symlink_node.name, target = ?symlink_node.target, "import successful" ) } } }