use std::collections::HashMap;
use anyhow::{Context, Result, ensure};
use log::warn;
use sux::bits::BitFieldVec;
use sux::traits::bit_field_slice::BitFieldSlice;
use swh_graph::NodeType;
use swh_graph::graph::*;
use swh_graph::labels::{EdgeLabel, LabelNameId, Permission};
use swh_graph::properties;
fn msg_no_label_name_id(name: impl AsRef<[u8]>) -> String {
format!(
"no label_name id found for entry \"{}\"",
String::from_utf8_lossy(name.as_ref())
)
}
pub trait IdPath {
fn iter(&self) -> impl Iterator<Item = LabelNameId>;
}
impl IdPath for Vec<LabelNameId> {
fn iter(&self) -> impl Iterator<Item = LabelNameId> {
#[allow(clippy::into_iter_on_ref)] self.into_iter().copied()
}
}
impl IdPath for Box<[LabelNameId]> {
fn iter(&self) -> impl Iterator<Item = LabelNameId> {
self.into_iter().copied()
}
}
impl IdPath for &[LabelNameId] {
fn iter(&self) -> impl Iterator<Item = LabelNameId> {
#[allow(clippy::into_iter_on_ref)] self.into_iter().copied()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BitFieldIdPath<B: BitFieldSlice<u64>>(B);
impl<B: BitFieldSlice<u64>> From<B> for BitFieldIdPath<B> {
fn from(v: B) -> Self {
Self(v)
}
}
#[inline]
fn bitfieldvec_from_slice(b: &[LabelNameId]) -> BitFieldVec<u64, Vec<u64>> {
let bit_width = u64::BITS
- b.iter()
.map(|LabelNameId(item)| item)
.max()
.unwrap_or(&1)
.leading_zeros();
let mut v = BitFieldVec::with_capacity(
bit_width
.try_into()
.expect("overflowed BitFieldVec item length"),
b.len(),
);
for &LabelNameId(item) in b {
v.push(item);
}
v
}
impl From<Box<[LabelNameId]>> for BitFieldIdPath<BitFieldVec<u64, Box<[u64]>>> {
#[inline(always)]
fn from(b: Box<[LabelNameId]>) -> Self {
BitFieldIdPath(bitfieldvec_from_slice(&b).into())
}
}
impl From<Vec<LabelNameId>> for BitFieldIdPath<BitFieldVec<u64, Box<[u64]>>> {
#[inline(always)]
fn from(v: Vec<LabelNameId>) -> Self {
BitFieldIdPath(bitfieldvec_from_slice(&v).into())
}
}
impl From<&[LabelNameId]> for BitFieldIdPath<BitFieldVec<u64, Box<[u64]>>> {
#[inline(always)]
fn from(s: &[LabelNameId]) -> Self {
BitFieldIdPath(bitfieldvec_from_slice(s).into())
}
}
impl<B: BitFieldSlice<u64>> From<BitFieldIdPath<B>> for Box<[LabelNameId]> {
#[inline(always)]
fn from(v: BitFieldIdPath<B>) -> Self {
v.iter().collect()
}
}
impl<B: BitFieldSlice<u64>> IdPath for BitFieldIdPath<B> {
#[inline(always)]
fn iter(&self) -> impl Iterator<Item = LabelNameId> {
(0..self.0.len()).map(|i| LabelNameId(self.0.get_value(i).unwrap()))
}
}
pub fn resolve_name<G>(graph: &G, dir: NodeId, name: impl AsRef<[u8]>) -> Result<Option<NodeId>>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::LabelNames: properties::LabelNames,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
{
let props = graph.properties();
let name_id = props
.label_name_id(name.as_ref())
.with_context(|| msg_no_label_name_id(name))?;
resolve_name_by_id(&graph, dir, name_id)
}
pub fn resolve_name_by_id<G>(graph: &G, dir: NodeId, name: LabelNameId) -> Result<Option<NodeId>>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
{
let node_type = graph.properties().node_type(dir);
ensure!(
node_type == NodeType::Directory,
"Type of {dir} should be dir, but is {node_type} instead"
);
for (succ, label) in graph.labeled_successors(dir).flatten_labels() {
#[allow(clippy::collapsible_if)]
if let EdgeLabel::DirEntry(dentry) = label {
if dentry.label_name_id() == name {
return Ok(Some(succ));
}
}
}
Ok(None)
}
pub fn resolve_path<G>(graph: &G, dir: NodeId, path: impl AsRef<[u8]>) -> Result<Option<NodeId>>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::LabelNames: properties::LabelNames,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
{
let props = graph.properties();
let path = path
.as_ref()
.split(|byte| *byte == b'/')
.map(|name| {
props
.label_name_id(name)
.with_context(|| msg_no_label_name_id(name))
})
.collect::<Result<Vec<LabelNameId>, _>>()?;
resolve_path_by_id(&graph, dir, path)
}
pub fn resolve_path_by_id<G, P: IdPath>(graph: &G, dir: NodeId, path: P) -> Result<Option<NodeId>>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
{
let mut cur_entry = dir;
for name in path.iter() {
match resolve_name_by_id(graph, cur_entry, name)? {
None => return Ok(None),
Some(entry) => cur_entry = entry,
}
}
Ok(Some(cur_entry))
}
#[derive(Debug, Default, PartialEq)]
pub enum FsTree {
#[default]
Content,
Directory(HashMap<Vec<u8>, (FsTree, Option<Permission>)>),
Revision(NodeId),
}
pub fn ls_tree<G>(graph: &G, dir: NodeId) -> Result<FsTree>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::LabelNames: properties::LabelNames,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
{
let props = graph.properties();
let node_type = props.node_type(dir);
ensure!(
node_type == NodeType::Directory,
"Type of {dir} should be dir, but is {node_type} instead"
);
let mut dir_entries = HashMap::new();
for (succ, labels) in graph.labeled_successors(dir) {
let node_type = props.node_type(succ);
for label in labels {
if let EdgeLabel::DirEntry(dentry) = label {
let file_name = props.label_name(dentry.label_name_id());
let perm = dentry.permission();
match node_type {
NodeType::Content => {
dir_entries.insert(file_name, (FsTree::Content, perm));
}
NodeType::Directory => {
if let Ok(subdir) = ls_tree(graph, succ) {
dir_entries.insert(file_name, (subdir, perm));
} else {
warn!("Cannot list (sub-)directory {succ}, skipping it");
}
}
NodeType::Revision | NodeType::Release => {
dir_entries.insert(file_name, (FsTree::Revision(succ), perm));
}
NodeType::Origin | NodeType::Snapshot => {
warn!("Ignoring dir entry with unexpected type {node_type}");
}
}
}
}
}
Ok(FsTree::Directory(dir_entries))
}