use super::{InMemory, TreeHeightsCache};
use crate::{
Result,
backend::{VerificationStatus, errors::BackendError},
entry::{Entry, ID},
};
pub(crate) fn get(backend: &InMemory, id: &ID) -> Result<Entry> {
let entries = backend.entries.read().unwrap();
entries
.get(id)
.cloned()
.ok_or_else(|| BackendError::EntryNotFound { id: id.clone() }.into())
}
pub(crate) fn put(
backend: &InMemory,
verification_status: VerificationStatus,
entry: Entry,
) -> Result<()> {
entry.validate()?;
let entry_id = entry.id();
let tree_id = entry.root();
let additional_tree_id = if tree_id.is_empty() {
Some(entry_id.clone())
} else {
None
};
{
let mut entries = backend.entries.write().unwrap();
entries.insert(entry_id.clone(), entry.clone());
}
{
let mut verification_status_map = backend.verification_status.write().unwrap();
verification_status_map.insert(entry_id.clone(), verification_status);
}
{
let mut heights_cache = backend.heights.write().unwrap();
if let Some(cache) = heights_cache.get_mut(&tree_id) {
update_cached_heights(cache, &entry, &entry_id);
}
}
let update_tips_for_tree = |tips_cache: &mut std::collections::HashMap<
ID,
super::TreeTipsCache,
>,
target_tree_id: &ID| {
let cache = tips_cache.entry(target_tree_id.clone()).or_default();
cache.tree_tips.clear();
let entries = backend.entries.read().unwrap();
let tree_entries: Vec<&Entry> = entries
.values()
.filter(|e| e.root() == target_tree_id || (e.is_root() && e.id() == *target_tree_id))
.collect();
for entry in &tree_entries {
let entry_id = entry.id();
let mut is_tip = true;
for other_entry in &tree_entries {
if let Ok(parents) = other_entry.parents()
&& parents.contains(&entry_id)
{
is_tip = false;
break;
}
}
if is_tip {
cache.tree_tips.insert(entry_id);
}
}
drop(entries);
};
{
let mut tips_cache = backend.tips.write().unwrap();
update_tips_for_tree(&mut tips_cache, &tree_id);
if let Some(ref additional_tree) = additional_tree_id {
update_tips_for_tree(&mut tips_cache, additional_tree);
}
let cache = tips_cache.entry(tree_id.clone()).or_default();
for subtree_name in entry.subtrees() {
let subtree_tips = cache.subtree_tips.entry(subtree_name.clone()).or_default();
if let Ok(store_parents) = entry.subtree_parents(&subtree_name) {
if store_parents.is_empty() {
subtree_tips.insert(entry_id.clone());
} else {
for parent in &store_parents {
subtree_tips.remove(parent);
}
subtree_tips.insert(entry_id.clone());
}
}
}
}
Ok(())
}
pub(crate) fn is_tip(backend: &InMemory, tree: &ID, entry_id: &ID) -> bool {
let entries = backend.entries.read().unwrap();
for other_entry in entries.values() {
if other_entry.root() == tree
&& other_entry.parents().unwrap_or_default().contains(entry_id)
{
return false;
}
}
true
}
pub(crate) fn is_subtree_tip(backend: &InMemory, tree: &ID, subtree: &str, entry_id: &ID) -> bool {
let entries = backend.entries.read().unwrap();
for other_entry in entries.values() {
if other_entry.root() == tree
&& other_entry.subtrees().contains(&subtree.to_string())
&& let Ok(store_parents) = other_entry.subtree_parents(subtree)
&& store_parents.contains(entry_id)
{
return false;
}
}
true
}
fn update_cached_heights(cache: &mut TreeHeightsCache, entry: &Entry, entry_id: &ID) {
let tree_height = if let Ok(parents) = entry.parents() {
if parents.is_empty() {
0 } else {
parents
.iter()
.filter_map(|parent_id| cache.get(parent_id).map(|(h, _)| h))
.max()
.unwrap_or(&0)
+ 1
}
} else {
0 };
let mut subtree_heights = std::collections::HashMap::new();
for subtree_name in entry.subtrees() {
let subtree_height = if let Ok(store_parents) = entry.subtree_parents(&subtree_name) {
if store_parents.is_empty() {
0 } else {
store_parents
.iter()
.filter_map(|parent_id| {
cache
.get(parent_id)
.and_then(|(_, subtree_map)| subtree_map.get(&subtree_name))
})
.max()
.unwrap_or(&0)
+ 1
}
} else {
0 };
subtree_heights.insert(subtree_name, subtree_height);
}
cache.insert(entry_id.clone(), (tree_height, subtree_heights));
}
pub(crate) fn get_tree(backend: &InMemory, tree: &ID) -> Result<Vec<Entry>> {
let entries = backend.entries.read().unwrap();
let mut tree_entries: Vec<Entry> = entries
.values()
.filter(|entry| entry.in_tree(tree))
.cloned()
.collect();
drop(entries);
super::cache::sort_entries_by_height(backend, tree, &mut tree_entries)?;
Ok(tree_entries)
}
pub(crate) fn get_store(backend: &InMemory, tree: &ID, subtree: &str) -> Result<Vec<Entry>> {
let entries = backend.entries.read().unwrap();
let mut subtree_entries: Vec<Entry> = entries
.values()
.filter(|entry| entry.in_tree(tree) && entry.in_subtree(subtree))
.cloned()
.collect();
drop(entries);
super::cache::sort_entries_by_subtree_height(backend, tree, subtree, &mut subtree_entries)?;
Ok(subtree_entries)
}
pub(crate) fn get_tree_from_tips(backend: &InMemory, tree: &ID, tips: &[ID]) -> Result<Vec<Entry>> {
if tips.is_empty() {
return Ok(vec![]);
}
let mut result = Vec::new();
let mut to_process = std::collections::VecDeque::new();
let mut processed = std::collections::HashSet::new();
let entries = backend.entries.read().unwrap();
for tip in tips {
if let Some(entry) = entries.get(tip) {
if entry.in_tree(tree) {
to_process.push_back(tip.clone());
}
}
}
while let Some(current_id) = to_process.pop_front() {
if processed.contains(¤t_id) {
continue;
}
if let Some(entry) = entries.get(¤t_id) {
if entry.in_tree(tree) {
if let Ok(parents) = entry.parents() {
for parent in parents {
if !processed.contains(&parent) {
to_process.push_back(parent);
}
}
}
result.push(entry.clone());
processed.insert(current_id);
}
}
}
drop(entries);
if !result.is_empty() {
super::cache::sort_entries_by_height(backend, tree, &mut result)?;
}
Ok(result)
}
pub(crate) fn get_store_from_tips(
backend: &InMemory,
tree: &ID,
subtree: &str,
tips: &[ID],
) -> Result<Vec<Entry>> {
if tips.is_empty() {
return Ok(vec![]);
}
let mut result = Vec::new();
let mut to_process = std::collections::VecDeque::new();
let mut processed = std::collections::HashSet::new();
let entries = backend.entries.read().unwrap();
for tip in tips {
if let Some(entry) = entries.get(tip) {
if entry.in_tree(tree) && entry.in_subtree(subtree) {
to_process.push_back(tip.clone());
}
}
}
while let Some(current_id) = to_process.pop_front() {
if processed.contains(¤t_id) {
continue;
}
if let Some(entry) = entries.get(¤t_id) {
if entry.in_tree(tree) && entry.in_subtree(subtree) {
if let Ok(store_parents) = entry.subtree_parents(subtree) {
for parent in store_parents {
if !processed.contains(&parent) {
to_process.push_back(parent);
}
}
}
result.push(entry.clone());
processed.insert(current_id);
}
}
}
drop(entries);
if !result.is_empty() {
super::cache::sort_entries_by_subtree_height(backend, tree, subtree, &mut result)?;
}
Ok(result)
}