use crate::fsentry::{FilesystemEntry, FsEntryError};
use log::warn;
use std::path::{Path, PathBuf};
use users::UsersCache;
use walkdir::{DirEntry, IntoIter, WalkDir};
pub struct AnnotatedFsEntry {
pub inner: FilesystemEntry,
pub is_cachedir_tag: bool,
}
pub struct FsIterator {
iter: SkipCachedirs,
}
#[derive(Debug, thiserror::Error)]
pub enum FsIterError {
#[error("walkdir failed: {0}")]
WalkDir(walkdir::Error),
#[error("failed to get file system metadata for {0}: {1}")]
Metadata(PathBuf, std::io::Error),
#[error(transparent)]
FsEntryError(#[from] FsEntryError),
}
impl FsIterator {
pub fn new(root: &Path, exclude_cache_tag_directories: bool) -> Self {
Self {
iter: SkipCachedirs::new(
WalkDir::new(root).into_iter(),
exclude_cache_tag_directories,
),
}
}
}
impl Iterator for FsIterator {
type Item = Result<AnnotatedFsEntry, FsIterError>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
struct SkipCachedirs {
cache: UsersCache,
iter: IntoIter,
exclude_cache_tag_directories: bool,
cachedir_tag: Option<Result<AnnotatedFsEntry, FsIterError>>,
}
impl SkipCachedirs {
fn new(iter: IntoIter, exclude_cache_tag_directories: bool) -> Self {
Self {
cache: UsersCache::new(),
iter,
exclude_cache_tag_directories,
cachedir_tag: None,
}
}
fn try_enqueue_cachedir_tag(&mut self, entry: &DirEntry) {
if !self.exclude_cache_tag_directories {
return;
}
if !entry.file_type().is_dir() {
return;
}
let mut tag_path = entry.path().to_owned();
tag_path.push("CACHEDIR.TAG");
if !tag_path.is_file() {
return;
};
const CACHEDIR_TAG: &[u8] = b"Signature: 8a477f597d28d172789f06886806bc55";
let mut content = [0u8; CACHEDIR_TAG.len()];
let mut file = if let Ok(file) = std::fs::File::open(&tag_path) {
file
} else {
return;
};
use std::io::Read;
match file.read_exact(&mut content) {
Ok(_) => (),
Err(_) => return,
}
if content == CACHEDIR_TAG {
self.iter.skip_current_dir();
self.cachedir_tag = Some(new_entry(&tag_path, true, &mut self.cache));
}
}
}
impl Iterator for SkipCachedirs {
type Item = Result<AnnotatedFsEntry, FsIterError>;
fn next(&mut self) -> Option<Self::Item> {
self.cachedir_tag.take().or_else(|| {
let next = self.iter.next();
match next {
None => None,
Some(Err(err)) => Some(Err(FsIterError::WalkDir(err))),
Some(Ok(entry)) => {
self.try_enqueue_cachedir_tag(&entry);
Some(new_entry(entry.path(), false, &mut self.cache))
}
}
})
}
}
fn new_entry(
path: &Path,
is_cachedir_tag: bool,
cache: &mut UsersCache,
) -> Result<AnnotatedFsEntry, FsIterError> {
let meta = std::fs::symlink_metadata(path);
let meta = match meta {
Ok(meta) => meta,
Err(err) => {
warn!("failed to get metadata for {}: {}", path.display(), err);
return Err(FsIterError::Metadata(path.to_path_buf(), err));
}
};
let entry = FilesystemEntry::from_metadata(path, &meta, cache)?;
let annotated = AnnotatedFsEntry {
inner: entry,
is_cachedir_tag,
};
Ok(annotated)
}