// This file is part of bsv. // // bsv is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // bsv is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for // more details. // // You should have received a copy of the Affero GNU General Public License // along with bsv. If not, see . use std::{fs::create_dir_all, io::BufReader}; use camino::{Utf8Path, Utf8PathBuf}; use cas_simple::utils::config_path; use toml::{Value, Table}; use cas_core::{err, Error, ObjectId, Result, ObjectType, CasWithRef}; use cas_simple::{SimpleCas, SimpleCasConfig}; use crate::PathMap; pub use crate::permissions::Permissions; use crate::tree_item::Deserialize; pub use crate::tree_item::{Serialize, TreeItem}; #[derive(Clone, Debug)] pub struct Config { pub device_name: String, pub cas: SimpleCasConfig, pub path_map: PathMap, } impl Config { pub fn from_toml_value(value: &Value) -> Result { let bsv_value = value.get("bsv") .ok_or_else(|| Error::unknown("bsv section missing from config"))?; if !bsv_value.is_table() { return err!("bsv section must be a table"); } Ok(Self { device_name: bsv_value.get("device_name") .ok_or_else(|| Error::unknown("bsv.device_name missing from config"))? .as_str() .ok_or_else(|| Error::unknown("bsv.device_name must be a string"))? .to_string(), cas: SimpleCasConfig::from_toml_value( value.get("cas").ok_or_else(|| Error::unknown("config must have a cas section"))? )?, path_map: PathMap::from_toml_value( value.get("mapping").ok_or_else(|| Error::unknown("config must have a mapping section"))? )?, }) } pub fn from_toml_file(config_path: &Utf8Path) -> Result { use std::io::Read; let mut file = std::fs::File::open(config_path).or_else(|err| err!("invalid repository: failed to read config file: {}", err) )?; let mut config_str = String::new(); file.read_to_string(&mut config_str).or_else(|err| err!("failed to read config file: {}", err) )?; let value = config_str.parse::().or_else(|err| err!("parse error while reading config file: {}", err) )?; Self::from_toml_value(&value) } pub fn to_toml_value(&self) -> Result { let mut bsv = Table::new(); bsv["device_name"] = Value::String(self.device_name.clone()); let mut table = Table::new(); table["bsv"] = Value::Table(bsv); table["cas"] = self.cas.to_toml_value()?; table["mapping"] = self.path_map.to_toml_value()?; Ok(Value::Table(table)) } pub fn write_toml_file(&self, config_path: &Utf8Path) -> Result<()> { use std::io::Write; let value = self.to_toml_value()?; let config_str = toml::to_string_pretty(&value).or_else(|err| err!("failed to serialize config: {}", err) )?; let mut file = tempfile::NamedTempFile::new_in(config_path.parent().unwrap()).or_else(|err| err!("cannot create temp config file: {}", err) )?; file.write_all(config_str.as_bytes()).or_else(|err| err!("failed to write to temp config: {}", err) )?; file.persist(config_path).or_else(|err| err!("failed to (over)write config: {}", err) )?; Ok(()) } } pub struct Repository { config: Config, cas: Box, } impl Repository { pub fn create(path: Utf8PathBuf, config: Config) -> Result { if path.exists() { return err!("cannot create bsv repository, path {} already exists", path); } create_dir_all(&path)?; config.write_toml_file(&config_path(&path))?; let cas = SimpleCas::create(path.to_path_buf(), config.cas.clone())?; Ok(Self { config, cas: Box::new(cas), }) } pub fn open(path: Utf8PathBuf) -> Result { if !path.is_dir() { return err!("failed to open repository, path {} is not a directory", path); } let config_file = cas_simple::utils::config_path(&path); let config = Config::from_toml_file(&config_file)?; let cas = SimpleCas::open(path, config.cas.clone())?; Ok(Self { config, cas: Box::new(cas), }) } pub fn cas(&self) -> &dyn CasWithRef { self.cas.as_ref() } pub fn physic_from_logic_path>(&self, logic_path: P) -> Option { self.config.path_map.physic_from_logic(&logic_path) } pub fn path_pair_from_physic_path>(&self, physic_path: P) -> Option { self.config.path_map.logic_from_physic(&physic_path) } pub fn oid_from_logic_path>(&self, _logic_path: P) -> Result { // let snapshot_oid = self.cas.get_ref(Utf8Path::new("latest"))?; // let snapshot = self.read_snapshot(snapshot_oid)?; err!("not implemented") } pub fn oid_from_physic_path>(&self, physic_path: P) -> Result { let logic_path = self.config.path_map.logic_from_physic(physic_path) .ok_or_else(|| Error::unknown("physic path do not map to a logic path"))?; self.oid_from_logic_path(logic_path) } pub fn read_tree(&self, oid: &ObjectId) -> Result>> { match self.cas.open_object(oid) { Ok((metadata, mut reader)) => { if metadata.otype() != &ObjectType::new(b"tree")? { err!("object is not a tree") } else { let mut buf_read = BufReader::new(reader.as_mut()); Ok(Some( Vec::::deserialize(&mut buf_read)? .unwrap_or_else(|| Vec::new()) )) } }, Err(Error::ObjectDoesNotExists(_)) => { Ok(None) } Err(err) => { Err(err) } } } // pub fn add(&mut self, path: P, visitor: &mut V) -> Result // where // P: AsRef, // V: FsVisitor, // { // let path_ref = path.as_ref(); // let metadata = std::fs::symlink_metadata(path_ref) // .or_else(|err| err!("failed to read file {}: {}", path_ref, err))?; // if visitor.accept(path_ref, &metadata) { // self.add_impl(path_ref, &metadata, visitor) // } // else { // err!("cannot add {}: path is excluded", path_ref) // } // } // pub fn add_impl(&mut self, path: &Utf8Path, metadata: &Metadata, visitor: &mut V) -> Result // where // V: FsVisitor, // { // let file_type = metadata.file_type(); // if file_type.is_file() { // self.add_file(path, metadata) // } // else if file_type.is_dir() { // self.add_tree(path, metadata, visitor) // } // else if file_type.is_symlink() { // err!("symlink are not supported yet") // } // else { // err!("not implemented") // } // } // pub fn add_tree_item(&mut self, path: &Utf8Path, metadata: &Metadata, visitor: &mut V) -> Result // where // V: FsVisitor, // { // let item_name = path // .file_name() // .ok_or_else(|| Error::unknown("dir entry with no name?"))? // .to_str() // .ok_or_else(|| Error::unknown(format!("file name contains non-unicode characters: {}", path)))?; // let oid = self.add(&path, visitor)?; // TreeItem::from_metadata( // item_name, // &metadata, // oid, // ) // } // fn add_file(&mut self, path: &Utf8Path, metadata: &Metadata) -> Result { // if !metadata.is_file() { // return err!("expected file"); // } // let file_size = metadata.len(); // let mut reader = std::fs::File::open(path) // .or_else(|err| err!("failed to open file {}: {}", path, err))?; // let mut writer = self.cas.new_writer( // &ObjectType::new(b"blob")?, // file_size // )?; // let mut buffer = [0; BUFFER_SIZE]; // let mut byte_count = 0u64; // loop { // use std::io::{Read, Write}; // let count = reader.read(&mut buffer) // .or_else(|err| err!("error while reading {}: {}", path, err))?; // if count == 0 { // break; // } // byte_count += count as u64; // writer.write_all(&buffer[..count]) // .or_else(|err| err!("error while writing blob for {}: {}", path, err))?; // } // if byte_count == file_size { // writer.finalize() // } // else { // err!("file size changed during processing for {}", path) // } // } // fn add_tree(&mut self, path: &Utf8Path, metadata: &Metadata, visitor: &mut V) -> Result // where // V: FsVisitor, // { // if !metadata.is_dir() { // return err!("expected directory"); // } // let mut tree = std::fs::read_dir(path) // .or_else(|err| err!("error while reading dir {}: {}", path, err))? // .filter_map(|entry| self.add_dir_entry(path, entry, visitor)) // .collect::>>()?; // tree.sort_unstable_by(|item0, item1| item0.name.cmp(&item1.name)); // let mut out = Vec::new(); // tree.serialize(&mut out)?; // self.cas.write_object( // &ObjectType::new(b"tree")?, // &out, // ) // } // fn add_dir_entry(&mut self, dir_path: &Utf8Path, entry: std::io::Result, visitor: &mut V) -> Option> // where // V: FsVisitor // { // let (entry_path, metadata) = match visitor.handle_result(Self::dir_entry_path_metadata(dir_path, entry)) { // Ok(path_metadata) => { path_metadata }, // Err(error) => { return Some(Err(error)) } // }; // if visitor.accept(&entry_path, &metadata) { // let result = self.add_tree_item(&entry_path, &metadata, visitor); // Some(visitor.handle_result(result)) // } // else { // None // } // } // fn dir_entry_path_metadata(dir_path: &Utf8Path, entry: std::io::Result) -> Result<(Utf8PathBuf, Metadata)> { // let dir_entry = entry // .or_else(|err| err!("error while reading dir {}: {}", dir_path, err))?; // let entry_path = dir_entry.path(); // let metadata = dir_entry.metadata() // .or_else(|err| err!("failed to read metadata for dir entry {}: {}", entry_path, err))?; // Ok((entry_path, metadata)) // } }