use anyhow::Result;
use ghee_lang::{Key, Xattr};
use thiserror::Error;
use std::{collections::BTreeSet, fs::remove_file, path::PathBuf};
use crate::{containing_table_info, walk::walk_paths, xattr_values};
#[derive(Error, Debug)]
pub enum RmErr {
#[error("IO error removing xattrs: {0}")]
IoError(std::io::Error),
}
pub fn rm(
paths: &Vec<PathBuf>,
fields: &Vec<Xattr>,
recursive: bool,
force: bool,
verbose: bool,
) -> Result<()> {
if verbose {
let fields: Vec<String> = fields.iter().map(|f| f.to_string()).collect();
eprintln!("Removing {} from:", fields.join(", "));
}
for path in paths {
let table_info = containing_table_info(path)?;
for entry in walk_paths(path, &vec![], recursive, true)? {
let entry = entry?;
let xattrs = xattr_values(entry.path())?;
let fields: Vec<Xattr> = if fields.is_empty() {
xattrs.keys().cloned().collect()
} else {
fields.clone()
};
let mut exited_indices: BTreeSet<Key> = BTreeSet::new();
for field in fields.iter() {
let field_osstring = field.to_osstring();
let result = xattr::remove(entry.path(), field_osstring).map_err(RmErr::IoError);
if force {
result.unwrap_or_default();
} else {
result?;
}
if let Some(table_info) = table_info.as_ref() {
for key in table_info.indices_abs().keys() {
if key.subkeys.contains(field) && xattrs.contains_key(field) {
exited_indices.insert(key.clone());
}
}
}
}
if let Some(table_info) = table_info.as_ref() {
for key in exited_indices {
let mut path = table_info.index_path_abs(&key).clone();
for subkey_value in key.value_for_record(&xattrs)? {
path.push(subkey_value.to_string());
}
remove_file(path).map_err(RmErr::IoError)?;
}
}
if verbose {
eprintln!("\t{}", entry.path().display());
}
}
}
Ok(())
}
#[cfg(test)]
mod test {
use crate::{cmd::rm, test_support::Scenario, xattr_values};
#[test]
fn test_rm() {
let s = Scenario::new("rm");
let path1 = &s.dir1path1;
let path2 = &s.dir2path1;
assert!(path1.exists());
assert!(path2.exists());
{
let values1 = xattr_values(path1).unwrap();
assert!(values1.contains_key(&s.xattr1));
assert!(values1.contains_key(&s.xattr2));
assert!(values1.contains_key(&s.xattr3));
}
{
let values2 = xattr_values(path2).unwrap();
assert!(values2.contains_key(&s.xattr1));
assert!(values2.contains_key(&s.xattr2));
assert!(values2.contains_key(&s.xattr3));
}
rm(
&vec![path1.clone()],
&vec![s.xattr1.clone()],
false,
false,
false,
)
.unwrap();
assert!(
!path1.exists(),
"Record not removed from index even though it no longer has relevant xattr"
);
assert!(path2.exists());
{
let values2 = xattr_values(path2).unwrap();
assert!(!values2.contains_key(&s.xattr1));
assert!(values2.contains_key(&s.xattr2));
assert!(values2.contains_key(&s.xattr3));
}
}
#[test]
fn test_rm_recursive() {
let s = Scenario::new("rm-recursive");
let path1 = {
let mut path = s.dir1.clone();
path.push("0");
path
};
let path2 = {
let mut path = s.dir2.clone();
path.push("1");
path.push("2");
path
};
let path3 = {
let mut path = s.dir1.clone();
path.push("10");
path
};
let path4 = {
let mut path = s.dir2.clone();
path.push("11");
path.push("12");
path
};
assert!(path1.exists());
assert!(path2.exists());
assert!(path3.exists());
assert!(path4.exists());
for path in vec![&path1, &path2, &path3, &path4] {
let values = xattr_values(&path).unwrap();
assert!(values.contains_key(&s.xattr1));
assert!(values.contains_key(&s.xattr2));
assert!(values.contains_key(&s.xattr3));
}
rm(
&vec![s.dir1.clone()],
&vec![s.xattr1.clone()],
true,
true,
false,
)
.unwrap();
assert!(
!path1.exists(),
"Record not removed from index even though it no longer has relevant xattr"
);
assert!(path2.exists());
assert!(
!path3.exists(),
"Record not removed from index even though it no longer has relevant xattr"
);
assert!(path4.exists());
}
}