// 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 . extern crate tempfile; use std::path::{Path, PathBuf}; use std::io::{Read, Seek}; use std::fs::{File, OpenOptions, create_dir, create_dir_all}; use tempfile::{NamedTempFile}; use crate::core::error::*; use crate::core::ObjectId; use crate::core::{ObjectType, OTYPE_BLOB}; use super::object::{ ObjectReader, WriteAsObject, }; const OBJECTS_DIR: &str = "objects"; const TMP_DIR: &str = "tmp"; #[derive(Debug)] pub struct SimpleDb { path: PathBuf, } impl SimpleDb { pub fn new(path: &Path) -> Result { Ok(SimpleDb { path: path.into(), }) } pub fn setup(&self) -> Result<()> { create_dir(self.objects_dir())?; create_dir(self.tmp_dir())?; Ok(()) } pub fn has_object(&self, oid: &ObjectId) -> bool { let obj_path = self.path_from_id(oid); if let Ok(metadata) = std::fs::metadata(obj_path) { metadata.is_file() } else { false } } pub fn store_object(&self, otype: &ObjectType, mut reader: R) -> Result { let mut tmp_file = self.create_tmp_file()?; let oid = reader.write_as_object(&mut tmp_file, otype)?; let dst_file_path = self.path_from_id(&oid); // TODO: Check if dst_file_path exists create_dir_all(dst_file_path.parent().unwrap())?; match tmp_file.persist(dst_file_path) { Ok(file) => { file.sync_data()?; Ok(oid) }, Err(err) => Err(err.error.into()), } } pub fn store_file_as_blob(&self, file_path: &Path) -> Result { let file = OpenOptions::new().read(true).open(file_path)?; self.store_object(OTYPE_BLOB, file) } pub fn read_object(&self, oid: &ObjectId) -> Result> { let path = self.path_from_id(oid); let file = OpenOptions::new().read(true).open(path)?; ObjectReader::new(file) } fn objects_dir(&self) -> PathBuf { let mut path = self.path.clone(); path.push(OBJECTS_DIR); path } fn tmp_dir(&self) -> PathBuf { let mut path = self.path.clone(); path.push(TMP_DIR); path } fn path_from_id(&self, oid: &ObjectId) -> PathBuf { let oid_str = oid.to_string(); let mut path = self.path.clone(); path.push(OBJECTS_DIR); path.push(oid_str.get(..4).unwrap()); // unwrap is ok here because we know oid_str is a path.push(oid_str.get(4..).unwrap()); // hexadecimal number (i.e. ascii only). path } fn create_tmp_file(&self) -> Result { let tmp_dir = self.tmp_dir(); Ok(tempfile::Builder::new().tempfile_in(tmp_dir)?) } } #[cfg(test)] mod tests { use std::str::{FromStr}; use super::ObjectId; use super::SimpleDb; #[test] fn simple_db_path_from_id() { let db = SimpleDb::new(std::path::Path::new(".bsv")).unwrap(); let oid = ObjectId::from_str("0001020304050607").unwrap(); let path = db.path_from_id(&oid); assert_eq!(path, std::path::PathBuf::from(".bsv/objects/0001/020304050607")); } }