Backup, Synchronize, Versionize
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
12 KiB

// 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 <https://www.gnu.org/licenses/>.
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<Self> {
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<Self> {
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::<Value>().or_else(|err|
err!("parse error while reading config file: {}", err)
)?;
Self::from_toml_value(&value)
}
pub fn to_toml_value(&self) -> Result<Value> {
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<dyn CasWithRef>,
}
impl Repository {
pub fn create(path: Utf8PathBuf, config: Config) -> Result<Self> {
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<Self> {
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<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<Utf8PathBuf> {
self.config.path_map.physic_from_logic(&logic_path)
}
pub fn path_pair_from_physic_path<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<Utf8PathBuf> {
self.config.path_map.logic_from_physic(&physic_path)
}
pub fn oid_from_logic_path<P: AsRef<Utf8Path>>(&self, _logic_path: P) -> Result<ObjectId> {
// 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<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Result<ObjectId> {
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<Option<Vec<TreeItem>>> {
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::<TreeItem>::deserialize(&mut buf_read)?
.unwrap_or_else(|| Vec::new())
))
}
},
Err(Error::ObjectDoesNotExists(_)) => {
Ok(None)
}
Err(err) => {
Err(err)
}
}
}
// pub fn add<P, V>(&mut self, path: P, visitor: &mut V) -> Result<ObjectId>
// where
// P: AsRef<Utf8Path>,
// 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<V>(&mut self, path: &Utf8Path, metadata: &Metadata, visitor: &mut V) -> Result<ObjectId>
// 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<V>(&mut self, path: &Utf8Path, metadata: &Metadata, visitor: &mut V) -> Result<TreeItem>
// 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<ObjectId> {
// 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<V>(&mut self, path: &Utf8Path, metadata: &Metadata, visitor: &mut V) -> Result<ObjectId>
// 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::<Result<Vec<_>>>()?;
// 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<V>(&mut self, dir_path: &Utf8Path, entry: std::io::Result<std::fs::DirEntry>, visitor: &mut V) -> Option<Result<TreeItem>>
// 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<std::fs::DirEntry>) -> 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))
// }
}