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
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))
|
|
// }
|
|
}
|
|
|