use super::*;
const COLLECTIONS_DIR: &str = "collections";
#[derive(Serialize, Deserialize, Debug)]
pub struct CollectionRecord {
pub name: String,
pub path: String,
pub count: usize,
pub created_at: usize,
pub updated_at: usize,
}
#[cfg_attr(feature = "py", pyclass(module = "oasysdb.database"))]
pub struct Database {
collections: Db,
count: usize,
path: String,
}
#[cfg(feature = "py")]
#[pymethods]
impl Database {
#[staticmethod]
#[pyo3(name = "new")]
fn py_new(path: &str) -> PyResult<Self> {
Self::new(path).map_err(|e| e.into())
}
#[new]
fn py_open(path: &str) -> PyResult<Self> {
Self::open(path).map_err(|e| e.into())
}
fn __len__(&self) -> usize {
self.len()
}
}
#[cfg_attr(feature = "py", pymethods)]
impl Database {
pub fn get_collection(&self, name: &str) -> Result<Collection, Error> {
let record: CollectionRecord = match self.collections.get(name)? {
Some(value) => bincode::deserialize(&value)?,
None => return Err(Error::collection_not_found()),
};
self.read_from_file(&record.path)
}
pub fn save_collection(
&mut self,
name: &str,
collection: &Collection,
) -> Result<(), Error> {
let mut new = false;
let mut record: CollectionRecord;
let path: String;
if !self.collections.contains_key(name)? {
new = true;
path = self.create_new_collection_path(name)?;
let timestamp = self.get_timestamp();
record = CollectionRecord {
name: name.to_string(),
path: path.clone(),
count: collection.len(),
created_at: timestamp,
updated_at: timestamp,
};
} else {
let bytes = self.collections.get(name)?.unwrap().to_vec();
record = bincode::deserialize(&bytes)?;
path = record.path.clone();
record.count = collection.len();
record.updated_at = self.get_timestamp();
}
self.write_to_file(&path, collection)?;
let bytes = bincode::serialize(&record)?;
self.collections.insert(name, bytes)?;
if new {
self.count += 1;
}
Ok(())
}
pub fn delete_collection(&mut self, name: &str) -> Result<(), Error> {
let record: CollectionRecord = match self.collections.get(name)? {
Some(value) => bincode::deserialize(&value)?,
None => return Err(Error::collection_not_found()),
};
self.delete_file(&record.path)?;
self.collections.remove(name)?;
self.count -= 1;
Ok(())
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn flush(&self) -> Result<usize, Error> {
let bytes = self.collections.flush()?;
Ok(bytes)
}
pub async fn async_flush(&self) -> Result<usize, Error> {
let bytes = self.collections.flush_async().await?;
Ok(bytes)
}
}
impl Database {
pub fn new(path: &str) -> Result<Self, Error> {
if Path::new(path).exists() {
remove_dir_all(path)?;
}
Self::setup_collections_dir(path)?;
let config = sled::Config::new().path(path);
let collections = config.open()?;
Ok(Self { collections, count: 0, path: path.to_string() })
}
pub fn open(path: &str) -> Result<Self, Error> {
let collections = sled::open(path)?;
let count = collections.len();
Self::setup_collections_dir(path)?;
Ok(Self { collections, count, path: path.to_string() })
}
fn write_to_file(
&self,
path: &str,
collection: &Collection,
) -> Result<(), Error> {
let data = bincode::serialize(collection)?;
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)?;
let mut writer = BufWriter::new(file);
writer.write_all(&data)?;
Ok(())
}
fn read_from_file(&self, path: &str) -> Result<Collection, Error> {
let file = OpenOptions::new().read(true).open(path)?;
let mut reader = BufReader::new(file);
let mut data = Vec::new();
reader.read_to_end(&mut data)?;
let collection = bincode::deserialize(&data)?;
Ok(collection)
}
fn delete_file(&self, path: &str) -> Result<(), Error> {
remove_file(path)?;
Ok(())
}
fn create_new_collection_path(&self, name: &str) -> Result<String, Error> {
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
let filename = hasher.finish();
let path = Path::new(&self.path)
.join(COLLECTIONS_DIR)
.join(filename.to_string())
.to_str()
.unwrap()
.to_string();
Ok(path)
}
fn setup_collections_dir(path: &str) -> Result<(), Error> {
let collections_dir = Path::new(path).join(COLLECTIONS_DIR);
if !collections_dir.exists() {
create_dir_all(&collections_dir)?;
}
Ok(())
}
fn get_timestamp(&self) -> usize {
let now = SystemTime::now();
let timestamp = now.duration_since(UNIX_EPOCH).unwrap();
timestamp.as_millis() as usize
}
}