mirror of
https://github.com/veeso/termscp.git
synced 2025-12-06 17:15:35 -08:00
feat: it is now possible to cancel find command; show find progress (#284)
This commit is contained in:
committed by
GitHub
parent
b2a8a3041c
commit
fc68e2621b
@@ -42,6 +42,7 @@
|
||||
Released on
|
||||
|
||||
- [Issue 249](https://github.com/veeso/termscp/issues/249): The old *find* command has been replaced with a brand new explorer with support to 🪄 **Fuzzy search** 🪄. The command is still `<F>`.
|
||||
- [Issue 283](https://github.com/veeso/termscp/issues/283): **Find command can now be cancelled** by pressing `<CTRL+C>`. While scanning the directory it will also display the current progress.
|
||||
- [Issue 268](https://github.com/veeso/termscp/issues/268): 📦 **Pods and container explorer** 🐳 for Kube protocol.
|
||||
- BREAKING ‼️ Kube address argument has changed to `namespace[@<cluster_url>][$<path>]`
|
||||
- Pod and container argumets have been removed; from now on you will connect with the following syntax to the provided namespace: `/pod-name/container-name/path/to/file`
|
||||
|
||||
134
src/host/mod.rs
134
src/host/mod.rs
@@ -16,7 +16,6 @@ use filetime::{self, FileTime};
|
||||
use remotefs::fs::UnixPex;
|
||||
use remotefs::fs::{File, FileType, Metadata};
|
||||
use thiserror::Error;
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
// Locals
|
||||
use crate::utils::path;
|
||||
@@ -112,7 +111,7 @@ impl Localhost {
|
||||
));
|
||||
}
|
||||
// Retrieve files for provided path
|
||||
host.files = match host.scan_dir(host.wrkdir.as_path()) {
|
||||
host.files = match host.list_dir(host.wrkdir.as_path()) {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -131,12 +130,6 @@ impl Localhost {
|
||||
self.wrkdir.clone()
|
||||
}
|
||||
|
||||
/// List files in current directory
|
||||
#[allow(dead_code)]
|
||||
pub fn list_dir(&self) -> Vec<File> {
|
||||
self.files.clone()
|
||||
}
|
||||
|
||||
/// Change working directory with the new provided directory
|
||||
pub fn change_wrkdir(&mut self, new_dir: &Path) -> Result<PathBuf, HostError> {
|
||||
let new_dir: PathBuf = self.to_path(new_dir);
|
||||
@@ -164,7 +157,7 @@ impl Localhost {
|
||||
// Change dir
|
||||
self.wrkdir = new_dir;
|
||||
// Scan new directory
|
||||
self.files = match self.scan_dir(self.wrkdir.as_path()) {
|
||||
self.files = match self.list_dir(self.wrkdir.as_path()) {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!("Could not scan new directory: {}", err);
|
||||
@@ -204,7 +197,7 @@ impl Localhost {
|
||||
Ok(_) => {
|
||||
// Update dir
|
||||
if dir_name.is_relative() {
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
}
|
||||
info!("Created directory {}", dir_path.display());
|
||||
Ok(())
|
||||
@@ -237,7 +230,7 @@ impl Localhost {
|
||||
match std::fs::remove_dir_all(entry.path()) {
|
||||
Ok(_) => {
|
||||
// Update dir
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
info!("Removed directory {}", entry.path().display());
|
||||
Ok(())
|
||||
}
|
||||
@@ -265,7 +258,7 @@ impl Localhost {
|
||||
match std::fs::remove_file(entry.path()) {
|
||||
Ok(_) => {
|
||||
// Update dir
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
info!("Removed file {}", entry.path().display());
|
||||
Ok(())
|
||||
}
|
||||
@@ -286,7 +279,7 @@ impl Localhost {
|
||||
match std::fs::rename(entry.path(), dst_path) {
|
||||
Ok(_) => {
|
||||
// Scan dir
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
debug!(
|
||||
"Moved file {} to {}",
|
||||
entry.path().display(),
|
||||
@@ -327,7 +320,7 @@ impl Localhost {
|
||||
self.mkdir(dst.as_path())?;
|
||||
}
|
||||
// Scan dir
|
||||
let dir_files: Vec<File> = self.scan_dir(entry.path())?;
|
||||
let dir_files: Vec<File> = self.list_dir(entry.path())?;
|
||||
// Iterate files
|
||||
for dir_entry in dir_files.iter() {
|
||||
// Calculate dst
|
||||
@@ -362,11 +355,11 @@ impl Localhost {
|
||||
match dst.is_dir() {
|
||||
true => {
|
||||
if dst == self.pwd().as_path() {
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
} else if let Some(parent) = dst.parent() {
|
||||
// If parent is pwd, scan directory
|
||||
if parent == self.pwd().as_path() {
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,7 +367,7 @@ impl Localhost {
|
||||
if let Some(parent) = dst.parent() {
|
||||
// If parent is pwd, scan directory
|
||||
if parent == self.pwd().as_path() {
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
self.files = self.list_dir(self.wrkdir.as_path())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,7 +550,7 @@ impl Localhost {
|
||||
}
|
||||
|
||||
/// Get content of the current directory as a list of fs entry
|
||||
pub fn scan_dir(&self, dir: &Path) -> Result<Vec<File>, HostError> {
|
||||
pub fn list_dir(&self, dir: &Path) -> Result<Vec<File>, HostError> {
|
||||
info!("Reading directory {}", dir.display());
|
||||
match std::fs::read_dir(dir) {
|
||||
Ok(e) => {
|
||||
@@ -579,12 +572,6 @@ impl Localhost {
|
||||
}
|
||||
}
|
||||
|
||||
/// Find files matching `search` on localhost starting from current directory. Search supports recursive search of course.
|
||||
/// The `search` argument supports wilcards ('*', '?')
|
||||
pub fn find(&self, search: &str) -> Result<Vec<File>, HostError> {
|
||||
self.iter_search(self.wrkdir.as_path(), &WildMatch::new(search))
|
||||
}
|
||||
|
||||
/// Create a symlink at path pointing at target
|
||||
#[cfg(unix)]
|
||||
pub fn symlink(&self, path: &Path, target: &Path) -> Result<(), HostError> {
|
||||
@@ -600,41 +587,6 @@ impl Localhost {
|
||||
})
|
||||
}
|
||||
|
||||
// -- privates
|
||||
|
||||
/// Recursive call for `find` method.
|
||||
/// Search in current directory for files which match `filter`.
|
||||
/// If a directory is found in current directory, `iter_search` will be called using that dir as argument.
|
||||
fn iter_search(&self, dir: &Path, filter: &WildMatch) -> Result<Vec<File>, HostError> {
|
||||
// Scan directory
|
||||
let mut drained: Vec<File> = Vec::new();
|
||||
match self.scan_dir(dir) {
|
||||
Err(err) => Err(err),
|
||||
Ok(entries) => {
|
||||
// Iter entries
|
||||
/* For each entry:
|
||||
- if is dir: call iter_search with `dir`
|
||||
- push `iter_search` result to `drained`
|
||||
- if is file: check if it matches `filter`
|
||||
- if it matches `filter`: push to to filter
|
||||
*/
|
||||
for entry in entries.into_iter() {
|
||||
if entry.is_dir() {
|
||||
// If directory matches; push directory to drained
|
||||
let next_path = entry.path().to_path_buf();
|
||||
if filter.matches(entry.name().as_str()) {
|
||||
drained.push(entry);
|
||||
}
|
||||
drained.append(&mut self.iter_search(next_path.as_path(), filter)?);
|
||||
} else if filter.matches(entry.name().as_str()) {
|
||||
drained.push(entry);
|
||||
}
|
||||
}
|
||||
Ok(drained)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert path to absolute path
|
||||
fn to_path(&self, p: &Path) -> PathBuf {
|
||||
path::absolutize(self.wrkdir.as_path(), p)
|
||||
@@ -658,7 +610,7 @@ mod tests {
|
||||
use super::*;
|
||||
#[cfg(unix)]
|
||||
use crate::utils::test_helpers::make_fsentry;
|
||||
use crate::utils::test_helpers::{create_sample_file, make_dir_at, make_file_at};
|
||||
use crate::utils::test_helpers::{create_sample_file, make_file_at};
|
||||
|
||||
#[test]
|
||||
fn test_host_error_new() {
|
||||
@@ -711,19 +663,6 @@ mod tests {
|
||||
assert_eq!(host.pwd(), PathBuf::from("/dev"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_list_files() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Scan dir
|
||||
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
||||
let mut counter: usize = 0;
|
||||
for _ in entries {
|
||||
counter += 1;
|
||||
}
|
||||
assert_eq!(host.list_dir().len(), counter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_change_dir() {
|
||||
@@ -813,7 +752,7 @@ mod tests {
|
||||
.is_ok());
|
||||
// Get dir
|
||||
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
// Verify files
|
||||
let file_0: &File = files.get(0).unwrap();
|
||||
if file_0.name() == *"foo.txt" {
|
||||
@@ -841,10 +780,10 @@ mod tests {
|
||||
fn test_host_localhost_mkdir() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 0); // There should be 0 files now
|
||||
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 1); // There should be 1 file now
|
||||
// Try to re-create directory
|
||||
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err());
|
||||
@@ -868,17 +807,17 @@ mod tests {
|
||||
// Create sample file
|
||||
assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok());
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 1); // There should be 1 file now
|
||||
// Remove file
|
||||
assert!(host.remove(files.get(0).unwrap()).is_ok());
|
||||
// There should be 0 files now
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 0); // There should be 0 files now
|
||||
// Create directory
|
||||
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
|
||||
// Delete directory
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 1); // There should be 1 file now
|
||||
assert!(host.remove(files.get(0).unwrap()).is_ok());
|
||||
// Remove unexisting directory
|
||||
@@ -899,7 +838,7 @@ mod tests {
|
||||
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str());
|
||||
assert!(StdFile::create(src_path.as_path()).is_ok());
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 1); // There should be 1 file now
|
||||
assert_eq!(files.get(0).unwrap().name(), "foo.txt");
|
||||
// Rename file
|
||||
@@ -909,7 +848,7 @@ mod tests {
|
||||
.rename(files.get(0).unwrap(), dst_path.as_path())
|
||||
.is_ok());
|
||||
// There should be still 1 file now, but named bar.txt
|
||||
let files: Vec<File> = host.list_dir();
|
||||
let files: Vec<File> = host.files.clone();
|
||||
assert_eq!(files.len(), 1); // There should be 0 files now
|
||||
assert_eq!(files.get(0).unwrap().name(), "bar.txt");
|
||||
// Fail
|
||||
@@ -1084,39 +1023,6 @@ mod tests {
|
||||
assert!(host.exec("echo 5").ok().unwrap().as_str().contains("5"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_host_find() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let dir_path: &Path = tmpdir.path();
|
||||
// Make files
|
||||
assert!(make_file_at(dir_path, "pippo.txt").is_ok());
|
||||
assert!(make_file_at(dir_path, "foo.jpg").is_ok());
|
||||
// Make nested struct
|
||||
assert!(make_dir_at(dir_path, "examples").is_ok());
|
||||
let mut subdir: PathBuf = PathBuf::from(dir_path);
|
||||
subdir.push("examples/");
|
||||
assert!(make_file_at(subdir.as_path(), "omar.txt").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "errors.txt").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "screenshot.png").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok());
|
||||
let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap();
|
||||
// Find txt files
|
||||
let mut result: Vec<File> = host.find("*.txt").ok().unwrap();
|
||||
result.sort_by_key(|x: &File| x.name().to_lowercase());
|
||||
// There should be 3 entries
|
||||
assert_eq!(result.len(), 3);
|
||||
// Check names (they should be sorted alphabetically already; NOTE: examples/ comes before pippo.txt)
|
||||
assert_eq!(result[0].name(), "errors.txt");
|
||||
assert_eq!(result[1].name(), "omar.txt");
|
||||
assert_eq!(result[2].name(), "pippo.txt");
|
||||
// Search for directory
|
||||
let mut result: Vec<File> = host.find("examples*").ok().unwrap();
|
||||
result.sort_by_key(|x: &File| x.name().to_lowercase());
|
||||
assert_eq!(result.len(), 2);
|
||||
assert_eq!(result[0].name(), "examples");
|
||||
assert_eq!(result[1].name(), "examples.csv");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn should_create_symlink() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
97
src/ui/activities/filetransfer/actions/walkdir.rs
Normal file
97
src/ui/activities/filetransfer/actions/walkdir.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
|
||||
pub(crate) mod browser;
|
||||
pub(crate) mod transfer;
|
||||
pub(crate) mod walkdir;
|
||||
|
||||
4
src/ui/activities/filetransfer/lib/walkdir.rs
Normal file
4
src/ui/activities/filetransfer/lib/walkdir.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WalkdirStates {
|
||||
pub aborted: bool,
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user