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