use std::collections::BTreeSet;
use std::future::Future;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use super::Graph;
use crate::document::{is_opaque_payload, require_whole_file};
use crate::error::{Error, Result};
use crate::fs::ReadStorage;
use crate::index::IdIndex;
use crate::link;
use crate::manifest::{Manifest, manifest_node_candidates};
impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix> {
pub async fn manifest_of(&self, node: &Path) -> Result<Option<(PathBuf, Manifest)>> {
let (_, doc) = self.load(node).await?;
let Some(raw) = doc.manifest_attr() else {
return Ok(None);
};
let path = link::resolve(node, raw);
let manifest = self.read_manifest(&path).await?;
Ok(Some((path, manifest)))
}
pub async fn read_manifest(&self, path: &Path) -> Result<Manifest> {
let (_, doc) = self.load(path).await?;
let carrier = doc
.carrier
.ok_or_else(|| Error::Structure(format!("{} carries no metadata", path.display())))?;
require_whole_file(path, carrier)?;
let manifest = Manifest::from_meta(&doc.meta)
.map_err(|e| Error::Structure(format!("{}: {e}", path.display())))?;
manifest
.checked_root(path)
.map_err(|e| Error::Structure(format!("{}: {e}", path.display())))?;
Ok(manifest)
}
pub async fn manifest_claims(&self, candidate: &Path, dir: &Path) -> bool {
match self.manifest_of(candidate).await {
Ok(Some((manifest_doc, manifest))) => {
manifest.covered_root(&manifest_doc) == link::normalize(dir)
}
_ => false,
}
}
pub async fn manifest_node_for(&self, dir: &Path) -> Result<Option<PathBuf>> {
let dir = link::normalize(dir);
for candidate in manifest_node_candidates(&dir) {
if self.exists(&candidate).await? && self.manifest_claims(&candidate, &dir).await {
return Ok(Some(candidate));
}
}
Ok(None)
}
pub async fn under_manifest(&self, path: &Path) -> Result<bool> {
let path = link::normalize(path);
let mut dir = path.parent().map(Path::to_path_buf);
while let Some(current) = dir {
if current.as_os_str().is_empty() {
break;
}
if self.manifest_node_for(¤t).await?.is_some() {
return Ok(true);
}
dir = current.parent().map(Path::to_path_buf);
}
Ok(false)
}
pub async fn scan_covered(&self, root: &Path) -> Result<Vec<PathBuf>> {
let mut found = Vec::new();
self.scan_covered_into(root, PathBuf::new(), &mut found)
.await?;
found.sort_by(|a, b| {
crate::manifest::path_sort_key(a).cmp(&crate::manifest::path_sort_key(b))
});
Ok(found)
}
fn scan_covered_into<'a>(
&'a self,
root: &'a Path,
rel: PathBuf,
out: &'a mut Vec<PathBuf>,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
Box::pin(async move {
let dir = link::normalize(root.join(&rel));
let Ok(entries) = self.listing(&dir).await else {
return Ok(());
};
let mut names: Vec<(String, bool)> = Vec::new();
for entry in entries {
let Some(name) = entry
.file_name()
.and_then(|n| n.to_str())
.map(str::to_owned)
else {
continue;
};
if name.starts_with('.') {
continue;
}
names.push((name, entry.file_type().is_dir()));
}
for (name, is_dir) in names {
let child = if rel.as_os_str().is_empty() {
PathBuf::from(&name)
} else {
rel.join(&name)
};
if is_dir {
if self
.manifest_node_for(&link::normalize(root.join(&child)))
.await?
.is_some()
{
continue;
}
self.scan_covered_into(root, child, out).await?;
} else if is_opaque_payload(&child) {
out.push(child);
}
}
Ok(())
})
}
pub async fn manifest_roots(&self, walk_docs: &BTreeSet<PathBuf>) -> BTreeSet<PathBuf> {
let mut roots = BTreeSet::new();
for doc in walk_docs {
if let Ok(Some((manifest_doc, manifest))) = self.manifest_of(doc).await {
roots.insert(manifest.covered_root(&manifest_doc));
}
}
roots
}
}