use std::{fs, path::PathBuf};
use entity_iter::{EntityIdIter, EntityIter};
use gix::{Repository, ThreadSafeRepository};
use redb::Database;
use self::entity::{
Entity, EntityRead,
id::{Id, entity_id::EntityId},
snapshot::Snapshot,
};
use crate::query::{Query, queryable::Queryable};
pub mod cache;
pub mod entity;
mod entity_iter;
#[derive(Debug)]
pub struct Replica {
db: Database,
repo: Repository,
}
impl Replica {
pub fn from_path(path: impl Into<PathBuf>) -> Result<Self, open::Error> {
let path = path.into();
let repo = ThreadSafeRepository::open(path.clone())
.map_err(|err| open::Error::RepoOpen {
path,
error: Box::new(err),
})?
.to_thread_local();
let db = {
let db_dir = repo.path().join("git-bug-rs");
let db_path = db_dir.join("cache");
fs::create_dir_all(&db_dir)
.map_err(|err| open::Error::CacheDirCreate { err, path: db_dir })?;
Database::create(&db_path)
.map_err(|err| open::Error::CacheDbOpen { err, path: db_path })?
};
Ok(Self { db, repo })
}
pub fn repo(&self) -> &Repository {
&self.repo
}
pub fn db(&self) -> &Database {
&self.db
}
pub fn get_all<E: Entity + EntityRead>(
&self,
) -> Result<
impl Iterator<Item = Result<Result<E, entity::read::Error<E>>, get::Error>>,
get::Error,
> {
EntityIter::new(self)
}
pub fn get_all_with_query<E>(
&self,
query: &Query<Snapshot<E>>,
) -> Result<
impl Iterator<Item = Result<Result<E, entity::read::Error<E>>, get::Error>>,
get::Error,
>
where
Snapshot<E>: Queryable,
E: Entity + EntityRead,
{
Ok(self.get_all::<E>()?.filter(move |maybe_entity| {
if let Ok(Ok(entity)) = maybe_entity {
return query.matches(&entity.snapshot());
}
true
}))
}
pub fn get_all_ids<E: Entity + EntityRead>(
&self,
) -> Result<impl Iterator<Item = Result<EntityId<E>, get::Error>>, get::Error> {
EntityIdIter::new(self.repo())
}
pub fn get<E: Entity + EntityRead>(
&self,
id: EntityId<E>,
) -> Result<E, entity::read::Error<E>> {
E::read(self, id)
}
pub fn get_by_id<E: Entity + EntityRead>(
&self,
id: Id,
) -> Result<Result<E, entity::read::Error<E>>, get_by_id::Error> {
let Some(entity_id) = self
.get_all_ids()
.map_err(get_by_id::Error::GetError)?
.flat_map(IntoIterator::into_iter)
.find(|found_id| found_id.as_id() == id)
else {
return Err(get_by_id::Error::IdNotFound(id));
};
Ok(E::read(self, entity_id))
}
pub fn contains<E: Entity + EntityRead>(&self) -> Result<bool, get::Error> {
Ok(self.get_all_ids::<E>()?.count() > 0)
}
}
#[allow(missing_docs)]
pub mod get_by_id {
use super::get;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Id not found for Entity: {0}")]
IdNotFound(super::entity::id::Id),
#[error("Constructing the underyling get iterator failed: {0}")]
GetError(get::Error),
}
}
#[allow(missing_docs)]
pub mod get {
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to open the packed buffer: {0}")]
PackedBufferOpen(#[from] gix::refs::packed::buffer::open::Error),
#[error("Failed to get an refererenc from the refs iter for replica: {0}")]
RefGet(String),
#[error("Failed to iterate over refs for namespace {nasp}: {error}")]
RefsIterPrefixed {
nasp: &'static str,
error: gix::reference::iter::init::Error,
},
#[error("Failed to read reference: {0}")]
InvalidRef(#[from] gix::refs::file::iter::loose_then_packed::Error),
#[error("Could not parse this Entity id ('{id}') as hex string")]
ParseAsHex {
id: String,
error: super::entity::id::decode::Error,
},
}
}
#[allow(missing_docs)]
pub mod open {
use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to open the replica at {path}, because: {error}")]
RepoOpen {
path: PathBuf,
error: Box<gix::open::Error>,
},
#[error("Failed to open the cache database at {path}, because: {err} ")]
CacheDbOpen {
err: redb::DatabaseError,
path: PathBuf,
},
#[error("Failed to create the cache directory at {path}, because: {err} ")]
CacheDirCreate { err: std::io::Error, path: PathBuf },
}
}