use std::{ffi::OsStr, fs::read_dir, io::Write, path::Path, sync::Arc};
use indicatif::ProgressBar;
use rayon::{prelude::*, Scope};
use skim::prelude::*;
use crate::deleted_lookup::get_unique_deleted;
use crate::display::display_exec;
use crate::interactive::SelectionCandidate;
use crate::utility::httm_is_dir;
use crate::versions_lookup::get_versions_set;
use crate::{
BasicDirEntryInfo, Config, DeletedMode, ExecMode, HttmError, PathData,
BTRFS_SNAPPER_HIDDEN_DIRECTORY, ZFS_HIDDEN_DIRECTORY,
};
pub fn display_recursive_wrapper(
config: &Config,
) -> Result<[Vec<PathData>; 2], Box<dyn std::error::Error + Send + Sync + 'static>> {
let (dummy_tx_item, _): (SkimItemSender, SkimItemReceiver) = unbounded();
let config_clone = Arc::new(config.clone());
match &config.requested_dir {
Some(requested_dir) => {
recursive_exec(config_clone, &dummy_tx_item, &requested_dir.path_buf)?;
}
None => {
return Err(HttmError::new(
"requested_dir should never be None in Display Recursive mode",
)
.into())
}
}
if config.opt_recursive {
let mut out = std::io::stdout();
out.flush()?;
}
std::process::exit(0)
}
pub fn recursive_exec(
config: Arc<Config>,
tx_item: &SkimItemSender,
requested_dir: &Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
const DEFAULT_STACK_SIZE: usize = 8388608;
let thread_pool = rayon::ThreadPoolBuilder::new()
.stack_size(DEFAULT_STACK_SIZE)
.build()
.unwrap();
thread_pool.in_place_scope(|scope| {
let _ = enumerate_live_versions(config, tx_item, requested_dir, scope);
});
Ok(())
}
fn enumerate_live_versions(
config: Arc<Config>,
tx_item: &SkimItemSender,
requested_dir: &Path,
scope: &Scope,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let (vec_dirs, vec_files): (Vec<BasicDirEntryInfo>, Vec<BasicDirEntryInfo>) =
get_entries_partitioned(config.clone(), requested_dir)?;
match config.exec_mode {
ExecMode::Display => unreachable!(),
ExecMode::DisplayRecursive => {
match config.deleted_mode {
DeletedMode::Disabled => unreachable!(),
DeletedMode::DepthOfOne | DeletedMode::Enabled | DeletedMode::Only => {
spawn_enumerate_deleted(config.clone(), requested_dir, tx_item, scope);
}
}
}
ExecMode::Interactive => {
let combined_vec = || {
let mut combined = vec_files;
combined.extend(vec_dirs.clone());
combined
};
let entries: Vec<BasicDirEntryInfo> = match config.deleted_mode {
DeletedMode::Only => {
spawn_enumerate_deleted(config.clone(), requested_dir, tx_item, scope);
Vec::new()
}
DeletedMode::DepthOfOne | DeletedMode::Enabled => {
spawn_enumerate_deleted(config.clone(), requested_dir, tx_item, scope);
combined_vec()
}
DeletedMode::Disabled => combined_vec(),
};
process_entries(config.clone(), entries, false, tx_item)?;
}
}
if config.opt_recursive {
vec_dirs.into_iter().for_each(move |requested_dir| {
let _ = enumerate_live_versions(config.clone(), tx_item, &requested_dir.path, scope);
});
}
Ok(())
}
fn get_entries_partitioned(
_config: Arc<Config>,
requested_dir: &Path,
) -> Result<
(Vec<BasicDirEntryInfo>, Vec<BasicDirEntryInfo>),
Box<dyn std::error::Error + Send + Sync + 'static>,
> {
let (vec_dirs, vec_files) = read_dir(&requested_dir)?
.flatten()
.par_bridge()
.filter(|dir_entry| {
dir_entry.file_name().as_os_str() != OsStr::new(ZFS_HIDDEN_DIRECTORY)
&& dir_entry.file_name().as_os_str() != OsStr::new(BTRFS_SNAPPER_HIDDEN_DIRECTORY)
})
.map(|dir_entry| BasicDirEntryInfo {
file_name: dir_entry.file_name(),
path: dir_entry.path(),
file_type: dir_entry.file_type().ok(),
})
.partition(|entry| httm_is_dir(entry));
Ok((vec_dirs, vec_files))
}
fn spawn_enumerate_deleted(
config: Arc<Config>,
requested_dir: &Path,
tx_item: &SkimItemSender,
scope: &Scope,
) {
let requested_dir_clone = requested_dir.to_path_buf();
let tx_item_clone = tx_item.clone();
scope.spawn(move |_| {
let _ = enumerate_deleted(config, &requested_dir_clone, &tx_item_clone);
});
}
fn enumerate_deleted(
config: Arc<Config>,
requested_dir: &Path,
tx_item: &SkimItemSender,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let deleted = get_unique_deleted(&config, requested_dir)?;
let (vec_dirs, vec_files): (Vec<BasicDirEntryInfo>, Vec<BasicDirEntryInfo>) = deleted
.into_iter()
.partition(|basic_dir_entry_info| httm_is_dir(basic_dir_entry_info));
if config.deleted_mode != DeletedMode::DepthOfOne && config.opt_recursive {
let _ = &vec_dirs
.clone()
.into_iter()
.map(|basic_dir_entry_info| basic_dir_entry_info.path)
.for_each(|deleted_dir| {
let config_clone = config.clone();
let requested_dir_clone = requested_dir.to_path_buf();
let tx_item_clone = tx_item.clone();
let _ = get_entries_behind_deleted_dir(
config_clone,
&tx_item_clone,
&deleted_dir,
&requested_dir_clone,
);
});
}
let mut entries = vec_files;
entries.extend(vec_dirs);
let pseudo_live_versions: Vec<BasicDirEntryInfo> =
get_pseudo_live_versions(entries, requested_dir);
process_entries(config, pseudo_live_versions, true, tx_item)?;
Ok(())
}
fn get_entries_behind_deleted_dir(
config: Arc<Config>,
tx_item: &SkimItemSender,
deleted_dir: &Path,
requested_dir: &Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
fn recurse_behind_deleted_dir(
config: Arc<Config>,
tx_item: &SkimItemSender,
dir_name: &Path,
from_deleted_dir: &Path,
from_requested_dir: &Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let deleted_dir_on_snap = &from_deleted_dir.to_path_buf().join(&dir_name);
let pseudo_live_dir = &from_requested_dir.to_path_buf().join(&dir_name);
let (vec_dirs, vec_files): (Vec<BasicDirEntryInfo>, Vec<BasicDirEntryInfo>) =
get_entries_partitioned(config.clone(), deleted_dir_on_snap)?;
let mut entries = vec_files;
entries.extend(vec_dirs.clone());
let pseudo_live_versions: Vec<BasicDirEntryInfo> =
get_pseudo_live_versions(entries, pseudo_live_dir);
process_entries(config.clone(), pseudo_live_versions, true, tx_item)?;
vec_dirs.into_iter().for_each(|basic_dir_entry_info| {
let _ = recurse_behind_deleted_dir(
config.clone(),
tx_item,
Path::new(&basic_dir_entry_info.file_name),
deleted_dir_on_snap,
pseudo_live_dir,
);
});
Ok(())
}
match &deleted_dir.file_name() {
Some(dir_name) => recurse_behind_deleted_dir(
config,
tx_item,
Path::new(dir_name),
deleted_dir.parent().unwrap_or_else(|| Path::new("/")),
requested_dir,
)?,
None => return Err(HttmError::new("Not a valid file!").into()),
}
Ok(())
}
fn get_pseudo_live_versions(
entries: Vec<BasicDirEntryInfo>,
pseudo_live_dir: &Path,
) -> Vec<BasicDirEntryInfo> {
entries
.into_iter()
.map(|basic_dir_entry_info| BasicDirEntryInfo {
path: pseudo_live_dir.join(&basic_dir_entry_info.file_name),
file_name: basic_dir_entry_info.file_name,
file_type: basic_dir_entry_info.file_type,
})
.collect()
}
fn process_entries(
config: Arc<Config>,
entries: Vec<BasicDirEntryInfo>,
is_phantom: bool,
tx_item: &SkimItemSender,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
match config.exec_mode {
ExecMode::Interactive => send_entries(config, entries, is_phantom, tx_item)?,
ExecMode::DisplayRecursive => {
lazy_static! {
static ref PROGRESS_BAR: ProgressBar = indicatif::ProgressBar::new_spinner();
}
if !entries.is_empty() {
print_deleted_recursive(config.clone(), entries)?
} else if config.opt_recursive {
PROGRESS_BAR.tick();
}
}
_ => unreachable!(),
}
Ok(())
}
fn send_entries(
config: Arc<Config>,
entries: Vec<BasicDirEntryInfo>,
is_phantom: bool,
tx_item: &SkimItemSender,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
entries.into_iter().for_each(|basic_dir_entry_info| {
let _ = tx_item.send(Arc::new(SelectionCandidate::new(
config.clone(),
basic_dir_entry_info.file_name,
basic_dir_entry_info.path,
basic_dir_entry_info.file_type,
is_phantom,
)));
});
Ok(())
}
fn print_deleted_recursive(
config: Arc<Config>,
entries: Vec<BasicDirEntryInfo>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let pseudo_live_set: Vec<PathData> = entries
.iter()
.map(|basic_dir_entry_info| PathData::from(basic_dir_entry_info.path.as_path()))
.collect();
let snaps_and_live_set = get_versions_set(&config, &pseudo_live_set)?;
let output_buf = display_exec(&config, snaps_and_live_set)?;
println!("{}", output_buf);
Ok(())
}