diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 6aa3f7c..e2eda45 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -130,9 +130,28 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - let entries = entries.iter().map(|x| x.get_realfile()).collect(); + let entries: Vec = entries.iter().map(|x| x.get_realfile()).collect(); match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { + if opts.check_replace && self.config().get_prompt_on_file_replace() { + // Check which file would be replaced + let existing_files: Vec<&FsEntry> = entries + .iter() + .filter(|x| { + self.remote_file_exists( + Self::file_to_check_many(x, dest_path.as_path()).as_path(), + ) + }) + .collect(); + // Save pending transfer + if !existing_files.is_empty() { + self.set_pending_transfer_many( + existing_files, + &dest_path.to_string_lossy().to_owned(), + ); + return; + } + } if let Err(err) = self.filetransfer_send( TransferPayload::Many(entries), dest_path.as_path(), @@ -148,6 +167,26 @@ impl FileTransferActivity { } } FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + if opts.check_replace && self.config().get_prompt_on_file_replace() { + // Check which file would be replaced + let existing_files: Vec<&FsEntry> = entries + .iter() + .filter(|x| { + self.local_file_exists( + Self::file_to_check_many(x, dest_path.as_path()).as_path(), + ) + }) + .collect(); + // Save pending transfer + // Save pending transfer + if !existing_files.is_empty() { + self.set_pending_transfer_many( + existing_files, + &dest_path.to_string_lossy().to_owned(), + ); + return; + } + } if let Err(err) = self.filetransfer_recv( TransferPayload::Many(entries), dest_path.as_path(), diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index f13437b..495c927 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -30,15 +30,15 @@ use super::{ super::STORAGE_PENDING_TRANSFER, FileExplorerTab, FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferOpts, TransferPayload, }; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; impl FileTransferActivity { pub(crate) fn action_local_saveas(&mut self, input: String) { - self.local_send_file(TransferOpts::default().save_as(input)); + self.local_send_file(TransferOpts::default().save_as(Some(input))); } pub(crate) fn action_remote_saveas(&mut self, input: String) { - self.remote_recv_file(TransferOpts::default().save_as(input)); + self.remote_recv_file(TransferOpts::default().save_as(Some(input))); } pub(crate) fn action_local_send(&mut self) { @@ -57,11 +57,10 @@ impl FileTransferActivity { /// NOTE: Panics if `STORAGE_PENDING_TRANSFER` is undefined pub(crate) fn action_finalize_pending_transfer(&mut self) { // Retrieve pending transfer - let file_name: String = self + let file_name = self .context_mut() .store_mut() - .take_string(STORAGE_PENDING_TRANSFER) - .unwrap(); + .take_string(STORAGE_PENDING_TRANSFER); // Send file match self.browser.tab() { FileExplorerTab::Local => self.local_send_file( @@ -82,9 +81,12 @@ impl FileTransferActivity { } // Reload browsers match self.browser.tab() { - FileExplorerTab::Local => self.reload_remote_dir(), - FileExplorerTab::Remote => self.reload_local_dir(), - FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {} + FileExplorerTab::Local | FileExplorerTab::FindLocal => { + self.update_remote_filelist(); + } + FileExplorerTab::Remote | FileExplorerTab::FindRemote => { + self.update_local_filelist(); + } } } @@ -122,7 +124,26 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - let entries = entries.iter().map(|x| x.get_realfile()).collect(); + let entries: Vec = entries.iter().map(|x| x.get_realfile()).collect(); + if opts.check_replace && self.config().get_prompt_on_file_replace() { + // Check which file would be replaced + let existing_files: Vec<&FsEntry> = entries + .iter() + .filter(|x| { + self.remote_file_exists( + Self::file_to_check_many(x, dest_path.as_path()).as_path(), + ) + }) + .collect(); + // Save pending transfer + if !existing_files.is_empty() { + self.set_pending_transfer_many( + existing_files, + &dest_path.to_string_lossy().to_owned(), + ); + return; + } + } if let Err(err) = self.filetransfer_send( TransferPayload::Many(entries), dest_path.as_path(), @@ -175,7 +196,26 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - let entries = entries.iter().map(|x| x.get_realfile()).collect(); + let entries: Vec = entries.iter().map(|x| x.get_realfile()).collect(); + if opts.check_replace && self.config().get_prompt_on_file_replace() { + // Check which file would be replaced + let existing_files: Vec<&FsEntry> = entries + .iter() + .filter(|x| { + self.local_file_exists( + Self::file_to_check_many(x, dest_path.as_path()).as_path(), + ) + }) + .collect(); + // Save pending transfer + if !existing_files.is_empty() { + self.set_pending_transfer_many( + existing_files, + &dest_path.to_string_lossy().to_owned(), + ); + return; + } + } if let Err(err) = self.filetransfer_recv( TransferPayload::Many(entries), dest_path.as_path(), @@ -205,6 +245,17 @@ impl FileTransferActivity { .set_string(STORAGE_PENDING_TRANSFER, file_name.to_string()); } + /// ### set_pending_transfer_many + /// + /// Set pending transfer for many files into storage and mount radio + pub(crate) fn set_pending_transfer_many(&mut self, files: Vec<&FsEntry>, dest_path: &str) { + let file_names: Vec<&str> = files.iter().map(|x| x.get_name()).collect(); + self.mount_radio_replace_many(file_names.as_slice()); + self.context_mut() + .store_mut() + .set_string(STORAGE_PENDING_TRANSFER, dest_path.to_string()); + } + /// ### file_to_check /// /// Get file to check for path @@ -214,4 +265,10 @@ impl FileTransferActivity { None => PathBuf::from(e.get_name()), } } + + pub(crate) fn file_to_check_many(e: &FsEntry, wrkdir: &Path) -> PathBuf { + let mut p = wrkdir.to_path_buf(); + p.push(e.get_name()); + p + } } diff --git a/src/ui/activities/filetransfer/lib/transfer.rs b/src/ui/activities/filetransfer/lib/transfer.rs index be8b75e..263596a 100644 --- a/src/ui/activities/filetransfer/lib/transfer.rs +++ b/src/ui/activities/filetransfer/lib/transfer.rs @@ -222,8 +222,8 @@ impl TransferOpts { /// ### save_as /// /// Define the name of the file to be saved - pub fn save_as>(mut self, n: S) -> Self { - self.save_as = Some(n.as_ref().to_string()); + pub fn save_as>(mut self, n: Option) -> Self { + self.save_as = n.map(|x| x.as_ref().to_string()); self } @@ -314,7 +314,7 @@ mod test { assert!(opts.save_as.is_none()); let opts = TransferOpts::default() .check_replace(false) - .save_as("omar.txt"); + .save_as(Some("omar.txt")); assert_eq!(opts.save_as.as_deref().unwrap(), "omar.txt"); assert_eq!(opts.check_replace, false); } diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 74065ef..74ad64f 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -88,6 +88,7 @@ const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING"; const COMPONENT_SPAN_STATUS_BAR_LOCAL: &str = "STATUS_BAR_LOCAL"; const COMPONENT_SPAN_STATUS_BAR_REMOTE: &str = "STATUS_BAR_REMOTE"; const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO"; +const COMPONENT_LIST_REPLACING_FILES: &str = "LIST_REPLACING_FILES"; // NOTE: used for file transfers, to list files which are going to be replaced /// ## LogLevel /// diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index c18d143..77169e3 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -31,10 +31,11 @@ use super::{ COMPONENT_EXPLORER_FIND, COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE, COMPONENT_INPUT_COPY, COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO, COMPONENT_INPUT_MKDIR, COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_OPEN_WITH, - COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, COMPONENT_LIST_FILEINFO, COMPONENT_LOG_BOX, - COMPONENT_PROGRESS_BAR_FULL, COMPONENT_PROGRESS_BAR_PARTIAL, COMPONENT_RADIO_DELETE, - COMPONENT_RADIO_DISCONNECT, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_REPLACE, - COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP, + COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, COMPONENT_LIST_FILEINFO, + COMPONENT_LIST_REPLACING_FILES, COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR_FULL, + COMPONENT_PROGRESS_BAR_PARTIAL, COMPONENT_RADIO_DELETE, COMPONENT_RADIO_DISCONNECT, + COMPONENT_RADIO_QUIT, COMPONENT_RADIO_REPLACE, COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR, + COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP, }; use crate::fs::explorer::FileSorting; use crate::fs::FsEntry; @@ -583,7 +584,7 @@ impl Update for FileTransferActivity { FileExplorerTab::Remote => self.action_remote_saveas(input.to_string()), FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { // Get entry - self.action_find_transfer(TransferOpts::default().save_as(input)); + self.action_find_transfer(TransferOpts::default().save_as(Some(input))); } } self.umount_saveas(); @@ -661,6 +662,12 @@ impl Update for FileTransferActivity { self.umount_radio_replace(); None } + (COMPONENT_RADIO_REPLACE, key) if key == &MSG_KEY_TAB => { + if self.is_radio_replace_extended() { + self.view.active(COMPONENT_LIST_REPLACING_FILES); + } + None + } (COMPONENT_RADIO_REPLACE, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => { // Choice is 'YES' self.umount_radio_replace(); @@ -668,6 +675,15 @@ impl Update for FileTransferActivity { None } (COMPONENT_RADIO_REPLACE, _) => None, + (COMPONENT_LIST_REPLACING_FILES, key) if key == &MSG_KEY_TAB => { + self.view.active(COMPONENT_RADIO_REPLACE); + None + } + (COMPONENT_LIST_REPLACING_FILES, key) if key == &MSG_KEY_ESC => { + self.umount_radio_replace(); + None + } + (COMPONENT_LIST_REPLACING_FILES, _) => None, // -- disconnect (COMPONENT_RADIO_DISCONNECT, key) if key == &MSG_KEY_ESC diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index cbe2655..c277067 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -310,10 +310,30 @@ impl FileTransferActivity { } if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_REPLACE) { if props.visible { - let popup = draw_area_in(f.size(), 50, 10); - f.render_widget(Clear, popup); - // make popup - self.view.render(super::COMPONENT_RADIO_REPLACE, f, popup); + // NOTE: handle extended / normal modes + if self.is_radio_replace_extended() { + let popup = draw_area_in(f.size(), 50, 50); + f.render_widget(Clear, popup); + let popup_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(85), // List + Constraint::Percentage(15), // Radio + ] + .as_ref(), + ) + .split(popup); + self.view + .render(super::COMPONENT_LIST_REPLACING_FILES, f, popup_chunks[0]); + self.view + .render(super::COMPONENT_RADIO_REPLACE, f, popup_chunks[1]); + } else { + let popup = draw_area_in(f.size(), 50, 10); + f.render_widget(Clear, popup); + // make popup + self.view.render(super::COMPONENT_RADIO_REPLACE, f, popup); + } } } if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_DISCONNECT) { @@ -708,28 +728,57 @@ impl FileTransferActivity { pub(super) fn mount_radio_replace(&mut self, file_name: &str) { let warn_color = self.theme().misc_warn_dialog; - self.view.mount( + self.mount_radio_dialog( super::COMPONENT_RADIO_REPLACE, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(warn_color) - .with_inverted_color(Color::Black) - .with_borders(Borders::ALL, BorderType::Plain, warn_color) + format!("File '{}' already exists. Overwrite file?", file_name), + &["Yes", "No"], + 0, + warn_color, + ); + } + + pub(super) fn mount_radio_replace_many(&mut self, files: &[&str]) { + let warn_color = self.theme().misc_warn_dialog; + // Make rows + let rows = files.iter().map(|x| vec![TextSpan::new(x)]).collect(); + self.view.mount( + super::COMPONENT_LIST_REPLACING_FILES, + Box::new(List::new( + ListPropsBuilder::default() + .with_borders(Borders::ALL, BorderType::Rounded, warn_color) + .scrollable(true) + .with_highlighted_color(warn_color) + .with_highlighted_str(Some("> ")) .with_title( - format!("File '{}' already exists. Overwrite file?", file_name), + "The following files are going to be replaced", Alignment::Center, ) - .with_options(&[String::from("Yes"), String::from("No")]) - .with_value(0) - .rewind(true) + .with_foreground(warn_color) + .with_rows(rows) .build(), )), ); - self.view.active(super::COMPONENT_RADIO_REPLACE); + self.mount_radio_dialog( + super::COMPONENT_RADIO_REPLACE, + "Overwrite files?", + &["Yes", "No"], + 0, + warn_color, + ); + } + + /// ### is_radio_replace_extended + /// + /// Returns whether radio replace is in "extended" mode (for many files) + pub(super) fn is_radio_replace_extended(&self) -> bool { + self.view + .get_state(super::COMPONENT_LIST_REPLACING_FILES) + .is_some() } pub(super) fn umount_radio_replace(&mut self) { self.view.umount(super::COMPONENT_RADIO_REPLACE); + self.view.umount(super::COMPONENT_LIST_REPLACING_FILES); // NOTE: replace anyway } pub(super) fn mount_file_info(&mut self, file: &FsEntry) { @@ -1056,10 +1105,10 @@ impl FileTransferActivity { self.view.active(id); } - fn mount_radio_dialog( + fn mount_radio_dialog>( &mut self, id: &str, - text: &str, + text: S, opts: &[&str], default: usize, color: Color, @@ -1071,7 +1120,7 @@ impl FileTransferActivity { .with_color(color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, color) - .with_title(text, Alignment::Center) + .with_title(text.as_ref(), Alignment::Center) .with_options(opts) .with_value(default) .rewind(true)