use crate::disk::AsarError;
use glob::glob;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct FileMetadata {
pub file_type: FileType,
pub mode: u32,
pub size: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileType {
File,
Directory,
Link,
}
fn links_in(metadata: &HashMap<PathBuf, FileMetadata>) -> Option<Vec<PathBuf>> {
let links: Vec<_> = metadata
.iter()
.filter_map(|(p, m)| (m.file_type == FileType::Link).then_some(p.clone()))
.collect();
if links.is_empty() {
None
} else {
Some(links)
}
}
pub fn crawl(pattern: &str) -> Result<(Vec<PathBuf>, HashMap<PathBuf, FileMetadata>), AsarError> {
let mut metadata: HashMap<PathBuf, FileMetadata> = HashMap::new();
let mut paths = Vec::new();
let entries = glob(pattern).map_err(|e| AsarError::Other(e.to_string()))?;
for entry in entries {
let path = entry.map_err(|e| AsarError::Other(e.to_string()))?;
if let Some(ft) = determine_file_type(&path) {
let meta = fs::symlink_metadata(&path).map_err(|e| AsarError::Other(e.to_string()))?;
let mode = {
#[cfg(unix)]
{
if let Ok(regular_meta) = fs::metadata(&path) {
use std::os::unix::fs::PermissionsExt;
regular_meta.permissions().mode()
} else {
0o644
}
}
#[cfg(not(unix))]
{
0o644
}
};
metadata.insert(
path.clone(),
FileMetadata {
file_type: ft,
mode,
size: meta.len(),
},
);
paths.push(path);
}
}
paths.sort_unstable();
let paths = if let Some(link_dirs) = links_in(&metadata) {
let link_set: std::collections::HashSet<_> = link_dirs.iter().cloned().collect();
paths
.into_iter()
.filter(|filename| {
if link_set.contains(filename) {
return true;
}
let mut ancestor = filename.as_path();
while let Some(parent) = ancestor.parent() {
if parent.as_os_str().is_empty() {
break;
}
if link_set.contains(parent) {
return false;
}
ancestor = parent;
}
true
})
.collect()
} else {
paths
};
Ok((paths, metadata))
}
fn determine_file_type(path: &Path) -> Option<FileType> {
let meta = fs::symlink_metadata(path).ok()?;
let file_type = meta.file_type();
if file_type.is_file() {
Some(FileType::File)
} else if file_type.is_dir() {
Some(FileType::Directory)
} else if file_type.is_symlink() {
Some(FileType::Link)
} else {
None
}
}