feat: it is now possible to cancel find command; show find progress (#284)

This commit is contained in:
Christian Visintin
2024-10-03 11:31:16 +02:00
committed by GitHub
parent b2a8a3041c
commit fc68e2621b
13 changed files with 229 additions and 137 deletions

View File

@@ -9,20 +9,6 @@ use super::super::browser::FileExplorerTab;
use super::{File, FileTransferActivity, LogLevel, SelectedFile, TransferOpts, TransferPayload};
impl FileTransferActivity {
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, String> {
match self.host.find("*") {
Ok(entries) => Ok(entries),
Err(err) => Err(format!("Could not search for files: {err}")),
}
}
pub(crate) fn action_walkdir_remote(&mut self) -> Result<Vec<File>, String> {
match self.client.as_mut().find("*") {
Ok(entries) => Ok(entries),
Err(err) => Err(format!("Could not search for files: {err}")),
}
}
pub(crate) fn action_find_changedir(&mut self) {
// Match entry
if let SelectedFile::One(entry) = self.get_found_selected_entries() {

View File

@@ -29,6 +29,7 @@ pub(crate) mod rename;
pub(crate) mod save;
pub(crate) mod submit;
pub(crate) mod symlink;
pub(crate) mod walkdir;
pub(crate) mod watcher;
#[derive(Debug)]

View File

@@ -0,0 +1,97 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
// locals
use std::path::{Path, PathBuf};
use super::{File, FileTransferActivity};
use crate::ui::activities::filetransfer::lib::walkdir::WalkdirStates;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WalkdirError {
Aborted,
Error(String),
}
impl FileTransferActivity {
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, WalkdirError> {
let mut acc = Vec::with_capacity(32_768);
self.walkdir(&mut acc, &self.host.pwd(), |activity, path| {
activity.host.list_dir(path).map_err(|e| e.to_string())
})?;
Ok(acc)
}
pub(crate) fn action_walkdir_remote(&mut self) -> Result<Vec<File>, WalkdirError> {
let mut acc = Vec::with_capacity(32_768);
let pwd = self
.client
.pwd()
.map_err(|e| WalkdirError::Error(e.to_string()))?;
self.walkdir(&mut acc, &pwd, |activity, path| {
activity.client.list_dir(path).map_err(|e| e.to_string())
})?;
Ok(acc)
}
fn walkdir<F>(
&mut self,
acc: &mut Vec<File>,
path: &Path,
list_dir_fn: F,
) -> Result<(), WalkdirError>
where
F: Fn(&mut Self, &Path) -> Result<Vec<File>, String> + Copy,
{
// init acc if empty
if acc.is_empty() {
self.init_walkdir();
}
// list current directory
let dir_entries = list_dir_fn(self, path).map_err(WalkdirError::Error)?;
// get dirs to scan later
let dirs = dir_entries
.iter()
.filter(|entry| entry.is_dir())
.map(|entry| entry.path.clone())
.collect::<Vec<PathBuf>>();
// extend acc
acc.extend(dir_entries.clone());
// update view
self.update_walkdir_entries(acc.len());
// check aborted
self.check_aborted()?;
for dir in dirs {
self.walkdir(acc, &dir, list_dir_fn)?;
}
Ok(())
}
fn check_aborted(&mut self) -> Result<(), WalkdirError> {
// read events
self.tick();
// check if the user wants to abort
if self.walkdir.aborted {
return Err(WalkdirError::Aborted);
}
Ok(())
}
fn init_walkdir(&mut self) {
self.walkdir = WalkdirStates::default();
}
}

View File

@@ -20,7 +20,8 @@ pub use popups::{
FileInfoPopup, FilterPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup,
OpenWithPopup, ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup,
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote,
SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WatchedPathsList, WatcherPopup,
SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WalkdirWaitPopup, WatchedPathsList,
WatcherPopup,
};
pub use transfer::{ExplorerFind, ExplorerFuzzy, ExplorerLocal, ExplorerRemote};

View File

@@ -1897,6 +1897,47 @@ impl Component<Msg, NoUserEvent> for WaitPopup {
}
}
#[derive(MockComponent)]
pub struct WalkdirWaitPopup {
component: Paragraph,
}
impl WalkdirWaitPopup {
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
Self {
component: Paragraph::default()
.alignment(Alignment::Center)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.text(&[
TextSpan::from(text.as_ref()),
TextSpan::from("Press 'CTRL+C' to abort"),
])
.wrap(true),
}
}
}
impl Component<Msg, NoUserEvent> for WalkdirWaitPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
if matches!(
ev,
Event::Keyboard(KeyEvent {
code: Key::Char('c'),
modifiers: KeyModifiers::CONTROL
})
) {
Some(Msg::Transfer(TransferMsg::AbortWalkdir))
} else {
None
}
}
}
#[derive(MockComponent)]
pub struct WatchedPathsList {
component: List,

View File

@@ -4,3 +4,4 @@
pub(crate) mod browser;
pub(crate) mod transfer;
pub(crate) mod walkdir;

View File

@@ -0,0 +1,4 @@
#[derive(Debug, Default)]
pub struct WalkdirStates {
pub aborted: bool,
}

View File

@@ -21,6 +21,7 @@ use chrono::{DateTime, Local};
use lib::browser;
use lib::browser::Browser;
use lib::transfer::{TransferOpts, TransferStates};
use lib::walkdir::WalkdirStates;
use remotefs::RemoteFs;
use session::TransferPayload;
use tempfile::TempDir;
@@ -93,6 +94,7 @@ enum PendingActionMsg {
#[derive(Debug, PartialEq)]
enum TransferMsg {
AbortWalkdir,
AbortTransfer,
Chmod(remotefs::fs::UnixPex),
CopyFileTo(String),
@@ -217,6 +219,9 @@ pub struct FileTransferActivity {
browser: Browser,
/// Current log lines
log_records: VecDeque<LogRecord>,
/// Fuzzy search states
walkdir: WalkdirStates,
/// Transfer states
transfer: TransferStates,
/// Temporary directory where to store temporary stuff
cache: Option<TempDir>,
@@ -244,6 +249,7 @@ impl FileTransferActivity {
client: Builder::build(params.protocol, params.params.clone(), &config_client),
browser: Browser::new(&config_client),
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
walkdir: WalkdirStates::default(),
transfer: TransferStates::default(),
cache: match TempDir::new() {
Ok(d) => Some(d),

View File

@@ -174,7 +174,7 @@ impl FileTransferActivity {
/// Scan current local directory
fn local_scan(&mut self, path: &Path) -> Result<(), HostError> {
match self.host.scan_dir(path) {
match self.host.list_dir(path) {
Ok(files) => {
// Set files and sort (sorting is implicit)
self.local_mut().set_files(files);
@@ -358,7 +358,7 @@ impl FileTransferActivity {
}
}
// Get files in dir
match self.host.scan_dir(entry.path()) {
match self.host.list_dir(entry.path()) {
Ok(entries) => {
// Iterate over files
for entry in entries.iter() {
@@ -1156,7 +1156,7 @@ impl FileTransferActivity {
fn get_total_transfer_size_local(&mut self, entry: &File) -> usize {
if entry.is_dir() {
// List dir
match self.host.scan_dir(entry.path()) {
match self.host.list_dir(entry.path()) {
Ok(files) => files
.iter()
.map(|x| self.get_total_transfer_size_local(x))

View File

@@ -8,6 +8,7 @@ use remotefs::fs::File;
use tuirealm::props::{AttrValue, Attribute};
use tuirealm::{State, StateValue, Update};
use super::actions::walkdir::WalkdirError;
use super::actions::SelectedFile;
use super::browser::{FileExplorerTab, FoundExplorerTab};
use super::{ExitReason, FileTransferActivity, Id, Msg, TransferMsg, TransferOpts, UiMsg};
@@ -32,6 +33,9 @@ impl FileTransferActivity {
TransferMsg::AbortTransfer => {
self.transfer.abort();
}
TransferMsg::AbortWalkdir => {
self.walkdir.aborted = true;
}
TransferMsg::Chmod(mode) => {
self.umount_chmod();
self.mount_blocking_wait("Applying new file mode…");
@@ -213,9 +217,9 @@ impl FileTransferActivity {
}
TransferMsg::InitFuzzySearch => {
// Mount wait
self.mount_blocking_wait("Scanning current directory…");
self.mount_walkdir_wait();
// Find
let res: Result<Vec<File>, String> = match self.browser.tab() {
let res: Result<Vec<File>, WalkdirError> = match self.browser.tab() {
FileExplorerTab::Local => self.action_walkdir_local(),
FileExplorerTab::Remote => self.action_walkdir_remote(),
_ => panic!("Trying to search for files, while already in a find result"),
@@ -224,10 +228,13 @@ impl FileTransferActivity {
self.umount_wait();
// Match result
match res {
Err(err) => {
Err(WalkdirError::Error(err)) => {
// Mount error
self.mount_error(err.as_str());
}
Err(WalkdirError::Aborted) => {
self.mount_info("Search aborted");
}
Ok(files) if files.is_empty() => {
// If no file has been found notify user
self.mount_info("There are no files in the current directory");

View File

@@ -6,9 +6,10 @@
// Ext
use remotefs::fs::{File, UnixPex};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{PropPayload, PropValue, TextSpan};
use tuirealm::tui::layout::{Constraint, Direction, Layout};
use tuirealm::tui::widgets::Clear;
use tuirealm::{Sub, SubClause, SubEventClause};
use tuirealm::{AttrValue, Attribute, Sub, SubClause, SubEventClause};
use unicode_width::UnicodeWidthStr;
use super::browser::{FileExplorerTab, FoundExplorerTab};
@@ -302,7 +303,15 @@ impl FileTransferActivity {
// make popup
self.app.view(&Id::ErrorPopup, f, popup);
} else if self.app.mounted(&Id::WaitPopup) {
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.size());
let wait_popup_lines = self
.app
.query(&Id::WaitPopup, Attribute::Text)
.map(|x| x.map(|x| x.unwrap_payload().unwrap_vec().len()))
.unwrap_or_default()
.unwrap_or(1) as u16;
let popup =
Popup(Size::Percentage(50), Size::Unit(2 + wait_popup_lines)).draw_in(f.size());
f.render_widget(Clear, popup);
// make popup
self.app.view(&Id::WaitPopup, f, popup);
@@ -392,6 +401,38 @@ impl FileTransferActivity {
assert!(self.app.active(&Id::WaitPopup).is_ok());
}
pub(super) fn mount_walkdir_wait(&mut self) {
let color = self.theme().misc_info_dialog;
assert!(self
.app
.remount(
Id::WaitPopup,
Box::new(components::WalkdirWaitPopup::new(
"Scanning current directory…",
color
)),
vec![],
)
.is_ok());
assert!(self.app.active(&Id::WaitPopup).is_ok());
self.view();
}
pub(super) fn update_walkdir_entries(&mut self, entries: usize) {
let text = format!("Scanning current directory… ({entries} items found)",);
let _ = self.app.attr(
&Id::WaitPopup,
Attribute::Text,
AttrValue::Payload(PropPayload::Vec(vec![
PropValue::TextSpan(TextSpan::from(text)),
PropValue::TextSpan(TextSpan::from("Press 'CTRL+C' to abort")),
])),
);
self.view();
}
pub(super) fn mount_blocking_wait<S: AsRef<str>>(&mut self, text: S) {
self.mount_wait(text);
self.view();