146 lines
3.8 KiB
Rust
146 lines
3.8 KiB
Rust
// 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/>.
|
|
|
|
|
|
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<SimpleDb> {
|
|
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<R: Read + Seek>(&self, otype: &ObjectType, mut reader: R)
|
|
-> Result<ObjectId> {
|
|
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<ObjectId> {
|
|
let file = OpenOptions::new().read(true).open(file_path)?;
|
|
self.store_object(OTYPE_BLOB, file)
|
|
}
|
|
|
|
pub fn read_object(&self, oid: &ObjectId) -> Result<ObjectReader<File>> {
|
|
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<NamedTempFile> {
|
|
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"));
|
|
}
|
|
}
|