use crate::index_table::btree::BTreeMapIndexTable;
use crate::index_table::hash_map::HashMapIndexTable;
use crate::index_table::IndexTable;
use anyhow::{bail, Result};
use fs2::FileExt;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::PathBuf;
const TYPE_EXTENSION: &str = "type";
const DEFAULT_INDEX_NAME: &str = ".rdb.index";
#[derive(PartialEq)]
pub enum IndexType {
HashMap,
BTreeMap,
Auto,
}
pub struct IndexFactory {
index_type: IndexType,
}
impl IndexFactory {
pub fn new(index_type: IndexType) -> Self {
IndexFactory { index_type }
}
fn path2path(path: PathBuf) -> (PathBuf, PathBuf) {
let path = if path.is_dir() {
path.join(DEFAULT_INDEX_NAME)
} else {
path
};
let type_path = path.with_extension(TYPE_EXTENSION);
(path, type_path)
}
pub fn create(&self, path: PathBuf) -> Result<Box<dyn IndexTable>> {
let (path, type_path) = IndexFactory::path2path(path);
let file = File::create(type_path)?;
file.lock_exclusive()?;
let mut writer = BufWriter::new(&file);
match self.index_type {
IndexType::HashMap => writer.write_all(b"HashMap")?,
IndexType::BTreeMap => writer.write_all(b"BTreeMap")?,
IndexType::Auto => bail!("Cannot create index with type Auto"),
}
let index_table: Box<dyn IndexTable> = match self.index_type {
IndexType::HashMap => Box::new(HashMapIndexTable::new_default(path)?),
IndexType::BTreeMap => Box::new(BTreeMapIndexTable::new_default(path)?),
IndexType::Auto => unreachable!(),
};
file.unlock()?;
Ok(index_table)
}
pub fn load(&mut self, path: PathBuf) -> Result<Box<dyn IndexTable>> {
let (path, type_path) = IndexFactory::path2path(path);
let file = File::open(type_path)?;
file.lock_exclusive()?;
let mut reader = BufReader::new(&file);
let mut first_line = String::new();
reader.read_line(&mut first_line)?;
let file_type = match first_line.trim() {
"HashMap" => IndexType::HashMap,
"BTreeMap" => IndexType::BTreeMap,
_ => bail!("Unknown index type {} in file", first_line),
};
if file_type != self.index_type {
if self.index_type == IndexType::Auto {
self.index_type = file_type;
file.unlock()?;
return self.load(path);
}
bail!("Index type in file does not match specified type");
}
let mut index_table: Box<dyn IndexTable> = match self.index_type {
IndexType::HashMap => Box::new(HashMapIndexTable::new(path)?),
IndexType::BTreeMap => Box::new(BTreeMapIndexTable::new(path)?),
IndexType::Auto => unreachable!(),
};
index_table.load()?;
file.unlock()?;
Ok(index_table)
}
pub fn load_or_create(&mut self, path: PathBuf) -> Result<Box<dyn IndexTable>> {
let (_, type_path) = IndexFactory::path2path(path.clone());
if type_path.exists() {
return self.load(path);
}
self.create(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::prelude::*;
#[test]
fn test_create() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let index_path = temp_dir.path().join("index.bin");
let index_factory = IndexFactory::new(IndexType::HashMap);
index_factory.create(index_path.clone())?;
let type_path = index_path.with_extension(TYPE_EXTENSION);
let mut file = File::open(&type_path)?;
let mut type_string = String::new();
file.read_to_string(&mut type_string)?;
assert_eq!(type_string, "HashMap");
temp_dir.close()?;
Ok(())
}
#[test]
fn test_load() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let index_path = temp_dir.path().join("index.bin");
let mut index_factory = IndexFactory::new(IndexType::HashMap);
index_factory.create(index_path.clone())?;
let loaded_table = index_factory.load(index_path)?;
assert_eq!(loaded_table.index_type(), "hash_map");
temp_dir.close()?;
Ok(())
}
}