use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use crate::change::Change;
use crate::change::ChangeLog;
use crate::error::DatabaseError;
use crate::file::File;
use crate::file::FileId;
use crate::file::FileType;
use crate::file::line_starts;
mod utils;
pub mod change;
pub mod error;
pub mod exclusion;
pub mod file;
pub mod loader;
#[derive(Debug, Default)]
pub struct Database {
files: HashMap<Cow<'static, str>, Arc<File>>,
id_to_name: HashMap<FileId, Cow<'static, str>>,
}
#[derive(Debug)]
pub struct ReadDatabase {
files: Vec<Arc<File>>,
id_to_index: HashMap<FileId, usize>,
name_to_index: HashMap<Cow<'static, str>, usize>,
path_to_index: HashMap<PathBuf, usize>,
}
impl Database {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, file: File) {
let name = file.name.clone();
let id = file.id;
if let Some(old_file) = self.files.insert(name.clone(), Arc::new(file)) {
self.id_to_name.remove(&old_file.id);
}
self.id_to_name.insert(id, name);
}
pub fn update(&mut self, id: FileId, new_contents: Cow<'static, str>) -> bool {
if let Some(name) = self.id_to_name.get(&id)
&& let Some(file) = self.files.get_mut(name)
&& let Some(file) = Arc::get_mut(file)
{
file.contents = new_contents;
file.size = file.contents.len() as u32;
file.lines = line_starts(file.contents.as_ref()).collect();
return true;
}
false
}
pub fn delete(&mut self, id: FileId) -> bool {
if let Some(name) = self.id_to_name.remove(&id) { self.files.remove(&name).is_some() } else { false }
}
pub fn commit(&mut self, change_log: ChangeLog) -> Result<(), DatabaseError> {
for change in change_log.into_inner()? {
self.apply(change);
}
Ok(())
}
fn apply(&mut self, change: Change) {
match change {
Change::Add(file) => self.add(file),
Change::Update(id, contents) => {
self.update(id, contents);
}
Change::Delete(id) => {
self.delete(id);
}
}
}
pub fn read_only(&self) -> ReadDatabase {
let mut files_vec: Vec<Arc<File>> = self.files.values().cloned().collect();
files_vec.sort_unstable_by_key(|f| f.id);
let mut id_to_index = HashMap::with_capacity(files_vec.len());
let mut name_to_index = HashMap::with_capacity(files_vec.len());
let mut path_to_index = HashMap::with_capacity(files_vec.len());
for (index, file) in files_vec.iter().enumerate() {
id_to_index.insert(file.id, index);
name_to_index.insert(file.name.clone(), index);
if let Some(path) = &file.path {
path_to_index.insert(path.clone(), index);
}
}
ReadDatabase { files: files_vec, id_to_index, name_to_index, path_to_index }
}
}
impl ReadDatabase {
pub fn single(file: File) -> Self {
let mut id_to_index = HashMap::with_capacity(1);
let mut name_to_index = HashMap::with_capacity(1);
let mut path_to_index = HashMap::with_capacity(1);
id_to_index.insert(file.id, 0);
name_to_index.insert(file.name.clone(), 0);
if let Some(path) = &file.path {
path_to_index.insert(path.clone(), 0);
}
Self { files: vec![Arc::new(file)], id_to_index, name_to_index, path_to_index }
}
}
pub trait DatabaseReader {
fn get_id(&self, name: &str) -> Option<FileId>;
fn get_name(&self, id: &FileId) -> Option<&str> {
self.get_by_id(id).map(|file| file.name.as_ref()).ok()
}
fn get_by_id(&self, id: &FileId) -> Result<&File, DatabaseError>;
fn get_by_name(&self, name: &str) -> Result<&File, DatabaseError>;
fn get_by_path(&self, path: &Path) -> Result<&File, DatabaseError>;
fn files(&self) -> impl Iterator<Item = &File>;
fn files_with_type(&self, file_type: FileType) -> impl Iterator<Item = &File> {
self.files().filter(move |file| file.file_type == file_type)
}
fn files_without_type(&self, file_type: FileType) -> impl Iterator<Item = &File> {
self.files().filter(move |file| file.file_type != file_type)
}
fn file_ids(&self) -> impl Iterator<Item = FileId> {
self.files().map(|file| file.id)
}
fn file_ids_with_type(&self, file_type: FileType) -> impl Iterator<Item = FileId> {
self.files_with_type(file_type).map(|file| file.id)
}
fn file_ids_without_type(&self, file_type: FileType) -> impl Iterator<Item = FileId> {
self.files_without_type(file_type).map(|file| file.id)
}
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl DatabaseReader for Database {
fn get_id(&self, name: &str) -> Option<FileId> {
self.files.get(name).map(|f| f.id)
}
fn get_by_id(&self, id: &FileId) -> Result<&File, DatabaseError> {
let name = self.id_to_name.get(id).ok_or(DatabaseError::FileNotFound)?;
let file = self.files.get(name).ok_or(DatabaseError::FileNotFound)?;
Ok(file.as_ref())
}
fn get_by_name(&self, name: &str) -> Result<&File, DatabaseError> {
self.files.get(name).map(|file| file.as_ref()).ok_or(DatabaseError::FileNotFound)
}
fn get_by_path(&self, path: &Path) -> Result<&File, DatabaseError> {
self.files
.values()
.find(|file| file.path.as_deref() == Some(path))
.map(|file| file.as_ref())
.ok_or(DatabaseError::FileNotFound)
}
fn files(&self) -> impl Iterator<Item = &File> {
self.files.values().map(|file| file.as_ref())
}
fn len(&self) -> usize {
self.files.len()
}
}
impl DatabaseReader for ReadDatabase {
fn get_id(&self, name: &str) -> Option<FileId> {
self.name_to_index.get(name).and_then(|&i| self.files.get(i)).map(|f| f.id)
}
fn get_by_id(&self, id: &FileId) -> Result<&File, DatabaseError> {
let index = self.id_to_index.get(id).ok_or(DatabaseError::FileNotFound)?;
self.files.get(*index).map(|file| file.as_ref()).ok_or(DatabaseError::FileNotFound)
}
fn get_by_name(&self, name: &str) -> Result<&File, DatabaseError> {
self.name_to_index
.get(name)
.and_then(|&i| self.files.get(i))
.map(|file| file.as_ref())
.ok_or(DatabaseError::FileNotFound)
}
fn get_by_path(&self, path: &Path) -> Result<&File, DatabaseError> {
self.path_to_index
.get(path)
.and_then(|&i| self.files.get(i))
.map(|file| file.as_ref())
.ok_or(DatabaseError::FileNotFound)
}
fn files(&self) -> impl Iterator<Item = &File> {
self.files.iter().map(|file| file.as_ref())
}
fn len(&self) -> usize {
self.files.len()
}
}