use once_cell::sync::OnceCell;
use std::{
borrow::Cow,
fs::FileType,
path::{Path, PathBuf},
sync::Arc,
time::SystemTime,
};
use crate::{description::DescriptionData, Error, RResult, Resolver};
#[derive(Debug, Default, Clone, Copy)]
pub struct EntryStat {
file_type: Option<FileType>,
modified: Option<SystemTime>,
}
impl EntryStat {
fn new(file_type: Option<FileType>, modified: Option<SystemTime>) -> Self {
Self {
file_type,
modified,
}
}
pub fn file_type(&self) -> Option<FileType> {
self.file_type
}
pub fn modified(&self) -> Option<SystemTime> {
self.modified
}
fn stat(path: &Path) -> Self {
if let Ok(meta) = path.metadata() {
let modified = meta.modified().ok();
Self::new(Some(meta.file_type()), modified)
} else {
Self::new(None, None)
}
}
}
#[derive(Debug)]
pub struct Entry {
parent: Option<Arc<Entry>>,
path: Box<Path>,
pkg_info: OnceCell<Option<Arc<DescriptionData>>>,
stat: OnceCell<EntryStat>,
symlink: OnceCell<Option<Box<Path>>>,
real: OnceCell<Box<Path>>,
}
impl Entry {
pub fn path(&self) -> &Path {
&self.path
}
pub fn parent(&self) -> Option<&Arc<Entry>> {
self.parent.as_ref()
}
pub fn pkg_info(&self, resolver: &Resolver) -> RResult<&Option<Arc<DescriptionData>>> {
self.pkg_info.get_or_try_init(|| {
let pkg_name = &resolver.options.description_file;
let path = self.path();
let is_pkg_suffix = path.ends_with(pkg_name);
if self.is_dir() || is_pkg_suffix {
let pkg_path = if is_pkg_suffix {
Cow::Borrowed(path)
} else {
Cow::Owned(path.join(pkg_name))
};
match resolver
.cache
.fs
.read_description_file(&pkg_path, EntryStat::default())
{
Ok(info) => {
return Ok(Some(info));
}
Err(error @ (Error::UnexpectedJson(_) | Error::UnexpectedValue(_))) => {
return Err(error);
}
Err(Error::Io(_)) => {
}
_ => unreachable!(),
};
}
if let Some(parent) = &self.parent() {
return parent.pkg_info(resolver).cloned();
}
Ok(None)
})
}
pub fn is_file(&self) -> bool {
self.cached_stat()
.file_type()
.map_or(false, |ft| ft.is_file())
}
pub fn is_dir(&self) -> bool {
self.cached_stat()
.file_type()
.map_or(false, |ft| ft.is_dir())
}
pub fn exists(&self) -> bool {
self.cached_stat().file_type().is_some()
}
pub fn cached_stat(&self) -> EntryStat {
*self.stat.get_or_init(|| EntryStat::stat(&self.path))
}
pub fn real(&self) -> Option<&Path> {
self.real.get().map(|p| &**p)
}
pub fn init_real(&self, path: Box<Path>) {
self.real.get_or_init(|| path);
}
pub fn symlink(&self) -> &Option<Box<Path>> {
self.symlink.get_or_init(|| {
debug_assert!(self.path.is_absolute());
if self.path.read_link().is_err() {
return None;
}
match dunce::canonicalize(&self.path) {
Ok(symlink_path) => Some(Box::from(symlink_path)),
Err(_) => None,
}
})
}
}
impl Resolver {
pub(super) fn load_entry(&self, path: &Path) -> Arc<Entry> {
if let Some(cached) = self.cache.entries.get(path) {
cached.clone()
} else {
let entry = Arc::new(self.load_entry_uncached(path));
self.cache
.entries
.entry(path.into())
.or_insert(entry.clone());
entry
}
}
fn load_entry_uncached(&self, path: &Path) -> Entry {
let parent = if let Some(parent) = path.parent() {
let entry = self.load_entry(parent);
Some(entry)
} else {
None
};
Entry {
parent,
path: path.into(),
pkg_info: OnceCell::default(),
stat: OnceCell::default(),
symlink: OnceCell::default(),
real: OnceCell::default(),
}
}
pub fn clear_entries(&self) {
self.cache.entries.clear();
}
#[must_use]
pub fn get_dependency_from_entry(&self) -> (Vec<PathBuf>, Vec<PathBuf>) {
todo!("get_dependency_from_entry")
}
}
#[test]
#[ignore]
fn dependency_test() {
let case_path = super::test_helper::p(vec!["full", "a"]);
let request = "package2";
let resolver = Resolver::new(Default::default());
resolver.resolve(&case_path, request).unwrap();
let (file, missing) = resolver.get_dependency_from_entry();
assert_eq!(file.len(), 3);
assert_eq!(missing.len(), 1);
}