Start implementing a cas in separate crates.
This commit is contained in:
15
cas-simple/Cargo.toml
Normal file
15
cas-simple/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "cas-simple"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
digest = { version = "0.9.0", features = ["alloc"] }
|
||||
sha2 = "0.9.5"
|
||||
toml = "0.5.8"
|
||||
cas-core = { path = "../cas-core" }
|
||||
tempfile = "3.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
245
cas-simple/src/lib.rs
Normal file
245
cas-simple/src/lib.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
// 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.
|
||||
//
|
||||
// cdb 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 cdb. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! # cas-simple
|
||||
//!
|
||||
//! `cas-simple` implements a simple content addressable storage using
|
||||
//! cas-core.
|
||||
|
||||
|
||||
extern crate digest;
|
||||
extern crate sha2;
|
||||
extern crate toml;
|
||||
extern crate cas_core;
|
||||
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use digest::DynDigest;
|
||||
use toml::Value;
|
||||
use cas_core::{Cas, Error, hex, ObjectId, ObjectMetadata, ObjectType, Result};
|
||||
|
||||
|
||||
fn new_digest(id: &str) -> Result<Box<dyn DynDigest>> {
|
||||
match id {
|
||||
"sha256" => { Ok(Box::new(sha2::Sha256::default())) },
|
||||
_ => { Err(Error::error(format!("unknown digest '{}'", id))) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct SimpleCas {
|
||||
db_path: PathBuf,
|
||||
digest: Box<dyn DynDigest>,
|
||||
config: Value,
|
||||
}
|
||||
|
||||
|
||||
impl SimpleCas {
|
||||
pub fn create(db_path: PathBuf, config: Value) -> Result<Self> {
|
||||
let digest_id = config["cas"]["digest"].as_str()
|
||||
.ok_or_else(|| Error::error(
|
||||
"mandatory cas.digest value is invalid or missing from config"
|
||||
))?;
|
||||
let digest = new_digest(digest_id)?;
|
||||
|
||||
if db_path.exists() {
|
||||
return Err(Error::error(format!(
|
||||
"failed to create SimpleCas: target directory already exists ({})",
|
||||
db_path.to_string_lossy(),
|
||||
)));
|
||||
}
|
||||
|
||||
for path in [
|
||||
&db_path,
|
||||
&obj_dir(&db_path),
|
||||
&ref_dir(&db_path),
|
||||
&tmp_dir(&db_path),
|
||||
] {
|
||||
std::fs::create_dir(path).or_else(|e|
|
||||
Err(Error::error(format!(
|
||||
"failed to create directory ({}): {}",
|
||||
path.to_string_lossy(), e,
|
||||
)))
|
||||
)?;
|
||||
}
|
||||
|
||||
write_config(&config, &db_path)?;
|
||||
|
||||
Ok(SimpleCas {
|
||||
db_path,
|
||||
digest,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open(db_path: PathBuf) -> Result<Self> {
|
||||
let config = read_config(&db_path)?;
|
||||
|
||||
let digest_id = config["cas"]["digest"].as_str()
|
||||
.ok_or_else(|| Error::error(
|
||||
"mandatory cas.digest value is invalid or missing from config"
|
||||
))?;
|
||||
let digest = new_digest(digest_id)?;
|
||||
|
||||
Ok(SimpleCas {
|
||||
db_path,
|
||||
digest,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save_config(&self) -> Result<()> {
|
||||
write_config(&self.config, &self.db_path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Cas for SimpleCas {
|
||||
fn object_id_from_string(&self, hex: &str) -> Result<ObjectId> {
|
||||
if hex.len() == self.digest.output_size() * 2 {
|
||||
ObjectId::from_str(hex)
|
||||
}
|
||||
else {
|
||||
Err(Error::error(format!(
|
||||
"invalid object id size: got {}, expected {}",
|
||||
hex.len(), self.digest.output_size() * 2,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn object_id_from_partial(&self, _hex: &str) -> Result<ObjectId> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
|
||||
fn has_object_id(&self, oid: &ObjectId) -> Result<bool> {
|
||||
let opath = obj_path(&self.db_path, oid);
|
||||
Ok(opath.is_file())
|
||||
}
|
||||
|
||||
fn read_object(&self, _oid: &ObjectId) -> Result<(ObjectMetadata, &dyn std::io::Read)> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
|
||||
fn write_object(&mut self, _otype: ObjectType, _data: &mut dyn std::io::Read) -> Result<ObjectId> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
|
||||
fn remove_object(&mut self, _oid: &ObjectId) -> Result<()> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
|
||||
fn read_ref(&self, _key: &str) -> Result<ObjectId> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
|
||||
fn write_ref(&mut self, _key: &str, _value: &ObjectId) -> Result<()> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
|
||||
fn remove_ref(&mut self, _key: &str) -> Result<()> {
|
||||
Err(Error::error("Not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn obj_dir(cas_path: &Path) -> PathBuf {
|
||||
cas_path.join("obj")
|
||||
}
|
||||
|
||||
fn obj_path(cas_path: &Path, oid: &ObjectId) -> PathBuf {
|
||||
let mut path = cas_path.to_path_buf();
|
||||
path.push(hex(&oid.id()[0..1]));
|
||||
path.push(hex(&oid.id()[1..2]));
|
||||
path.push(hex(&oid.id()[2..]));
|
||||
path
|
||||
}
|
||||
|
||||
fn ref_dir(cas_path: &Path) -> PathBuf {
|
||||
cas_path.join("ref")
|
||||
}
|
||||
|
||||
fn tmp_dir(cas_path: &Path) -> PathBuf {
|
||||
cas_path.join("tmp")
|
||||
}
|
||||
|
||||
fn config_path(cas_path: &Path) -> PathBuf {
|
||||
cas_path.join("config")
|
||||
}
|
||||
|
||||
fn read_config(db_path: &Path) -> Result<Value> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut file = std::fs::File::open(config_path(db_path)).or_else(|err|
|
||||
Err(Error::error(format!("invalid repository: no config file: {}", err)))
|
||||
)?;
|
||||
let mut config_str = String::new();
|
||||
file.read_to_string(&mut config_str).or_else(|err|
|
||||
Err(Error::error(format!("cannot read config file: {}", err)))
|
||||
)?;
|
||||
let config = toml::from_str(&config_str).or_else(|err|
|
||||
Err(Error::error(format!("error while reading config file: {}", err)))
|
||||
)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn write_config(config: &Value, db_path: &Path) -> Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
let config_str = toml::to_string_pretty(config).or_else(|err|
|
||||
Err(Error::error(format!("failed to serialize config: {}", err)))
|
||||
)?;
|
||||
let mut file = tempfile::NamedTempFile::new_in(tmp_dir(db_path)).or_else(|err|
|
||||
Err(Error::error(format!("cannot create temp config file: {}", err)))
|
||||
)?;
|
||||
file.write_all(config_str.as_bytes()).or_else(|err|
|
||||
Err(Error::error(format!("failed to write to temp config: {}", err)))
|
||||
)?;
|
||||
file.persist(config_path(db_path)).or_else(|err|
|
||||
Err(Error::error(format!("failed to (over)write config: {}", err)))
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate tempfile;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_simple_cas() {
|
||||
let dir = tempfile::tempdir()
|
||||
.expect("failed to create temp test dir");
|
||||
let cas_path = {
|
||||
let mut cas_path = dir.path().to_path_buf();
|
||||
cas_path.push(".bsv");
|
||||
cas_path
|
||||
};
|
||||
let config = toml::toml!(
|
||||
[cas]
|
||||
digest = "sha256"
|
||||
);
|
||||
|
||||
let cas = SimpleCas::create(cas_path, config)
|
||||
.expect("failed to create SimpleCas object");
|
||||
let oid = cas.object_id_from_string("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
.expect("failed to create object id");
|
||||
|
||||
assert!(!cas.has_object_id(&oid).expect("has_object_id failed"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user