use super::*;
pub struct DirSizing {
pub path: PathBuf,
pub found: u64,
pub(super) cancel: Arc<AtomicBool>,
updates: Receiver<SizeUpdate>,
}
enum SizeUpdate {
Found(u64),
Finished(Result<Option<u64>, String>),
}
impl AppState {
pub fn search_add_char(&mut self, c: char) {
self.search_input.push(c);
self.jump_to_first_match();
}
pub fn search_backspace(&mut self) {
self.search_input.pop();
if !self.search_input.is_empty() {
self.jump_to_first_match();
}
}
pub fn search_clear(&mut self) {
self.search_input.clear();
}
pub fn jump_to_first_match(&mut self) {
if self.search_input.is_empty() {
return;
}
let search_lower = self.search_input.to_lowercase();
let (children, state) = self.active_panel_mut();
if let Some(index) = children.iter().position(|item| item.name_full.to_lowercase().starts_with(&search_lower)) {
state.select(Some(index));
}
}
pub fn jump_to_next_match(&mut self) {
if self.search_input.is_empty() {
return;
}
let search_lower = self.search_input.to_lowercase();
let (children, state) = self.active_panel_mut();
let current = state.selected().unwrap_or(0);
let len = children.len();
for offset in 1..=len {
let i = (current + offset) % len;
if children[i].name_full.to_lowercase().starts_with(&search_lower) {
state.select(Some(i));
return;
}
}
}
pub fn jump_to_prev_match(&mut self) {
if self.search_input.is_empty() {
return;
}
let search_lower = self.search_input.to_lowercase();
let (children, state) = self.active_panel_mut();
let current = state.selected().unwrap_or(0);
let len = children.len();
for offset in 1..=len {
let i = (current + len - offset) % len;
if children[i].name_full.to_lowercase().starts_with(&search_lower) {
state.select(Some(i));
return;
}
}
}
fn active_panel_mut(&mut self) -> (&[Item], &mut TableState) {
if self.is_left_active { (&self.children_left, &mut self.state_left) } else { (&self.children_right, &mut self.state_right) }
}
pub fn open_select_prompt(&mut self, select: bool) {
let mut input = TextInput::new();
input.set(self.select_pattern.clone());
self.dialog = Some(Dialog::Prompt(if select { PromptKind::Select } else { PromptKind::Deselect }, input));
}
pub fn select_by(&mut self, glob: &crate::glob::Glob, select: bool) {
let (children, selected) = if self.is_left_active {
(&self.children_left, &mut self.selected_left)
} else {
(&self.children_right, &mut self.selected_right)
};
for item in children.iter().filter(|item| !item.is_parent() && glob.matches(&item.name_full, item.is_dir)) {
if select {
selected.insert(item.name_os.clone());
} else {
selected.remove(&item.name_os);
}
}
}
pub fn invert_selection(&mut self, with_dirs: bool) {
let (children, selected) = if self.is_left_active {
(&self.children_left, &mut self.selected_left)
} else {
(&self.children_right, &mut self.selected_right)
};
for item in children.iter().filter(|item| (with_dirs || !item.is_dir) && !item.is_parent()) {
if !selected.remove(&item.name_os) {
selected.insert(item.name_os.clone());
}
}
}
pub fn toggle_selection(&mut self) {
self.toggle_selection_inner(true);
}
pub fn toggle_selection_no_size(&mut self) {
self.toggle_selection_inner(false);
}
fn toggle_selection_inner(&mut self, calculate_size: bool) {
let mut to_size: Option<PathBuf> = None;
{
let (state, children, selected_set, current_dir) = if self.is_left_active {
(&mut self.state_left, &self.children_left, &mut self.selected_left, &self.dir_left)
} else {
(&mut self.state_right, &self.children_right, &mut self.selected_right, &self.dir_right)
};
if let Some(index) = state.selected() {
if index < children.len() && !children[index].is_parent() {
let item = &children[index];
if !selected_set.remove(&item.name_os) {
selected_set.insert(item.name_os.clone());
if calculate_size && item.is_dir && !item.is_symlink {
to_size = Some(item.path_in(current_dir));
}
}
}
let len = children.len();
if len > 0 {
state.select(Some((index + 1).min(len - 1)));
}
}
}
if let Some(path) = to_size {
self.start_dir_size(path);
}
}
pub fn start_dir_size(&mut self, path: PathBuf) {
if self.dir_sizing.iter().any(|sizing| sizing.path == path) {
return;
}
self.dir_size_queue.retain(|queued| *queued != path);
self.dir_size_queue.push_front(path);
self.start_queued_sizes();
}
pub fn size_all_dirs(&mut self) {
let (children, dir) = if self.is_left_active { (&self.children_left, &self.dir_left) } else { (&self.children_right, &self.dir_right) };
let paths: Vec<PathBuf> = children.iter().filter(|item| item.is_dir && !item.is_symlink && !item.is_parent()).map(|item| item.path_in(dir)).collect();
for path in paths {
if !self.dir_sizing.iter().any(|sizing| sizing.path == path) && !self.dir_size_queue.contains(&path) {
self.dir_size_queue.push_back(path);
}
}
self.start_queued_sizes();
}
fn start_queued_sizes(&mut self) {
while self.dir_sizing.len() < crate::constants::SIZE_WORKERS
&& let Some(path) = self.dir_size_queue.pop_front()
{
self.spawn_dir_size(path);
}
}
fn spawn_dir_size(&mut self, path: PathBuf) {
let cancel = Arc::new(AtomicBool::new(false));
let (sender, updates) = mpsc::channel();
let (worker_path, worker_cancel) = (path.clone(), Arc::clone(&cancel));
std::thread::spawn(move || {
let (mut found, mut reported) = (0, Instant::now());
let result = crate::fs_ops::walk_dir_size(&worker_path, &worker_cancel, &mut |bytes| {
found += bytes;
if reported.elapsed() >= crate::constants::SIZE_PROGRESS_INTERVAL {
reported = Instant::now();
let _ = sender.send(SizeUpdate::Found(found));
}
});
let _ = sender.send(SizeUpdate::Finished(result.map_err(|e| e.to_string())));
});
self.dir_sizing.push(DirSizing { path, found: 0, cancel, updates });
}
pub fn poll_dir_sizes(&mut self) {
let mut finished = Vec::new();
self.dir_sizing.retain_mut(|sizing| {
loop {
match sizing.updates.try_recv() {
Ok(SizeUpdate::Found(found)) => sizing.found = found,
Ok(SizeUpdate::Finished(result)) => {
finished.push((sizing.path.clone(), result));
break false;
}
Err(TryRecvError::Empty) => break true,
Err(TryRecvError::Disconnected) => break false,
}
}
});
for (path, result) in finished {
match result {
Ok(Some(size)) => {
self.dir_sizes.insert(path, size);
}
Ok(None) => {}
Err(e) => self.display_error(t!("Cannot calculate size: {e}", e)),
}
}
self.start_queued_sizes();
}
pub fn cancel_dir_sizes(&mut self) {
self.dir_size_queue.clear();
for sizing in self.dir_sizing.drain(..) {
sizing.cancel.store(true, Ordering::Relaxed);
}
}
pub(super) fn forget_sizes_in(&mut self, dir: &Path) {
if self.dir_left == dir || self.dir_right == dir {
return;
}
let inside = |path: &Path| path.parent() == Some(dir);
self.dir_size_queue.retain(|path| !inside(path));
self.dir_sizing.retain(|sizing| {
let stop = inside(&sizing.path);
if stop {
sizing.cancel.store(true, Ordering::Relaxed);
}
!stop
});
self.start_queued_sizes();
}
pub fn dir_size_so_far(&self, path: &Path) -> Option<u64> {
let waiting = || self.dir_size_queue.iter().any(|queued| queued == path).then_some(0);
self.dir_sizing.iter().find(|sizing| sizing.path == path).map(|sizing| sizing.found).or_else(waiting)
}
pub fn clear_active_selections(&mut self) {
if self.is_left_active {
self.selected_left.clear();
} else {
self.selected_right.clear();
}
}
}