mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Option: prompt user when about to replace an existing file caused by a file transfer
This commit is contained in:
@@ -27,7 +27,9 @@
|
||||
*/
|
||||
// locals
|
||||
use super::super::browser::FileExplorerTab;
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
|
||||
use super::{
|
||||
FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferOpts, TransferPayload,
|
||||
};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -69,7 +71,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn action_find_transfer(&mut self, save_as: Option<String>) {
|
||||
pub(crate) fn action_find_transfer(&mut self, opts: TransferOpts) {
|
||||
let wrkdir: PathBuf = match self.browser.tab() {
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => self.remote().wrkdir.clone(),
|
||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => self.local().wrkdir.clone(),
|
||||
@@ -77,10 +79,19 @@ impl FileTransferActivity {
|
||||
match self.get_found_selected_entries() {
|
||||
SelectedEntry::One(entry) => match self.browser.tab() {
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||
if opts.check_replace
|
||||
&& self.config().get_prompt_on_file_replace()
|
||||
&& self.remote_file_exists(file_to_check.as_path())
|
||||
{
|
||||
// Save pending transfer
|
||||
self.set_pending_transfer(
|
||||
opts.save_as.as_deref().unwrap_or_else(|| entry.get_name()),
|
||||
);
|
||||
} else if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
opts.save_as,
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
@@ -90,10 +101,19 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||
if opts.check_replace
|
||||
&& self.config().get_prompt_on_file_replace()
|
||||
&& self.local_file_exists(file_to_check.as_path())
|
||||
{
|
||||
// Save pending transfer
|
||||
self.set_pending_transfer(
|
||||
opts.save_as.as_deref().unwrap_or_else(|| entry.get_name()),
|
||||
);
|
||||
} else if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
opts.save_as,
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
@@ -106,7 +126,7 @@ impl FileTransferActivity {
|
||||
SelectedEntry::Many(entries) => {
|
||||
// In case of selection: save multiple files in wrkdir/input
|
||||
let mut dest_path: PathBuf = wrkdir;
|
||||
if let Some(save_as) = save_as {
|
||||
if let Some(save_as) = opts.save_as {
|
||||
dest_path.push(save_as);
|
||||
}
|
||||
// Iter files
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel, TransferPayload};
|
||||
pub(self) use super::{
|
||||
browser::FileExplorerTab, FileTransferActivity, FsEntry, LogLevel, TransferOpts,
|
||||
TransferPayload,
|
||||
};
|
||||
use tuirealm::{Payload, Value};
|
||||
|
||||
// actions
|
||||
|
||||
@@ -26,34 +26,85 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// locals
|
||||
use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload};
|
||||
use super::{
|
||||
super::STORAGE_PENDING_TRANSFER, FileExplorerTab, FileTransferActivity, FsEntry, LogLevel,
|
||||
SelectedEntry, TransferOpts, TransferPayload,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_local_saveas(&mut self, input: String) {
|
||||
self.action_local_send_file(Some(input));
|
||||
self.local_send_file(TransferOpts::default().save_as(input));
|
||||
}
|
||||
|
||||
pub(crate) fn action_remote_saveas(&mut self, input: String) {
|
||||
self.action_remote_recv_file(Some(input));
|
||||
self.remote_recv_file(TransferOpts::default().save_as(input));
|
||||
}
|
||||
|
||||
pub(crate) fn action_local_send(&mut self) {
|
||||
self.action_local_send_file(None);
|
||||
self.local_send_file(TransferOpts::default());
|
||||
}
|
||||
|
||||
pub(crate) fn action_remote_recv(&mut self) {
|
||||
self.action_remote_recv_file(None);
|
||||
self.remote_recv_file(TransferOpts::default());
|
||||
}
|
||||
|
||||
fn action_local_send_file(&mut self, save_as: Option<String>) {
|
||||
/// ### action_finalize_pending_transfer
|
||||
///
|
||||
/// Finalize "pending" transfer.
|
||||
/// The pending transfer is created after a transfer which required a user action to be completed first.
|
||||
/// The name of the file to transfer, is contained in the storage at `STORAGE_PENDING_TRANSFER`.
|
||||
/// 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
|
||||
.context_mut()
|
||||
.store_mut()
|
||||
.take_string(STORAGE_PENDING_TRANSFER)
|
||||
.unwrap();
|
||||
// Send file
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.local_send_file(
|
||||
TransferOpts::default()
|
||||
.save_as(file_name)
|
||||
.check_replace(false),
|
||||
),
|
||||
FileExplorerTab::Remote => self.remote_recv_file(
|
||||
TransferOpts::default()
|
||||
.save_as(file_name)
|
||||
.check_replace(false),
|
||||
),
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => self.action_find_transfer(
|
||||
TransferOpts::default()
|
||||
.save_as(file_name)
|
||||
.check_replace(false),
|
||||
),
|
||||
}
|
||||
// Reload browsers
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.reload_remote_dir(),
|
||||
FileExplorerTab::Remote => self.reload_local_dir(),
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn local_send_file(&mut self, opts: TransferOpts) {
|
||||
let wrkdir: PathBuf = self.remote().wrkdir.clone();
|
||||
match self.get_local_selected_entries() {
|
||||
SelectedEntry::One(entry) => {
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||
if opts.check_replace
|
||||
&& self.config().get_prompt_on_file_replace()
|
||||
&& self.remote_file_exists(file_to_check.as_path())
|
||||
{
|
||||
// Save pending transfer
|
||||
self.set_pending_transfer(
|
||||
opts.save_as.as_deref().unwrap_or_else(|| entry.get_name()),
|
||||
);
|
||||
} else if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
opts.save_as,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
@@ -67,7 +118,7 @@ impl FileTransferActivity {
|
||||
SelectedEntry::Many(entries) => {
|
||||
// In case of selection: save multiple files in wrkdir/input
|
||||
let mut dest_path: PathBuf = wrkdir;
|
||||
if let Some(save_as) = save_as {
|
||||
if let Some(save_as) = opts.save_as {
|
||||
dest_path.push(save_as);
|
||||
}
|
||||
// Iter files
|
||||
@@ -90,14 +141,23 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
fn action_remote_recv_file(&mut self, save_as: Option<String>) {
|
||||
fn remote_recv_file(&mut self, opts: TransferOpts) {
|
||||
let wrkdir: PathBuf = self.local().wrkdir.clone();
|
||||
match self.get_remote_selected_entries() {
|
||||
SelectedEntry::One(entry) => {
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||
if opts.check_replace
|
||||
&& self.config().get_prompt_on_file_replace()
|
||||
&& self.local_file_exists(file_to_check.as_path())
|
||||
{
|
||||
// Save pending transfer
|
||||
self.set_pending_transfer(
|
||||
opts.save_as.as_deref().unwrap_or_else(|| entry.get_name()),
|
||||
);
|
||||
} else if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
opts.save_as,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
@@ -111,7 +171,7 @@ impl FileTransferActivity {
|
||||
SelectedEntry::Many(entries) => {
|
||||
// In case of selection: save multiple files in wrkdir/input
|
||||
let mut dest_path: PathBuf = wrkdir;
|
||||
if let Some(save_as) = save_as {
|
||||
if let Some(save_as) = opts.save_as {
|
||||
dest_path.push(save_as);
|
||||
}
|
||||
// Iter files
|
||||
@@ -133,4 +193,25 @@ impl FileTransferActivity {
|
||||
SelectedEntry::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### set_pending_transfer
|
||||
///
|
||||
/// Set pending transfer into storage
|
||||
pub(crate) fn set_pending_transfer(&mut self, file_name: &str) {
|
||||
self.mount_radio_replace(file_name);
|
||||
// Put pending transfer in store
|
||||
self.context_mut()
|
||||
.store_mut()
|
||||
.set_string(STORAGE_PENDING_TRANSFER, file_name.to_string());
|
||||
}
|
||||
|
||||
/// ### file_to_check
|
||||
///
|
||||
/// Get file to check for path
|
||||
pub(crate) fn file_to_check(e: &FsEntry, alt: Option<&String>) -> PathBuf {
|
||||
match alt {
|
||||
Some(s) => PathBuf::from(s),
|
||||
None => PathBuf::from(e.get_name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ use bytesize::ByteSize;
|
||||
use std::fmt;
|
||||
use std::time::Instant;
|
||||
|
||||
// -- States and progress
|
||||
|
||||
/// ### TransferStates
|
||||
///
|
||||
/// TransferStates contains the states related to the transfer process
|
||||
@@ -195,6 +197,45 @@ impl ProgressStates {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Options
|
||||
|
||||
/// ## TransferOpts
|
||||
///
|
||||
/// Defines the transfer options for transfer actions
|
||||
pub struct TransferOpts {
|
||||
/// Save file as
|
||||
pub save_as: Option<String>,
|
||||
/// Whether to check if file is being replaced
|
||||
pub check_replace: bool,
|
||||
}
|
||||
|
||||
impl Default for TransferOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
save_as: None,
|
||||
check_replace: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransferOpts {
|
||||
/// ### save_as
|
||||
///
|
||||
/// Define the name of the file to be saved
|
||||
pub fn save_as<S: AsRef<str>>(mut self, n: S) -> Self {
|
||||
self.save_as = Some(n.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// ### check_replace
|
||||
///
|
||||
/// Set whether to check if the file being transferred will "replace" an existing one
|
||||
pub fn check_replace(mut self, opt: bool) -> Self {
|
||||
self.check_replace = opt;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
@@ -265,4 +306,16 @@ mod test {
|
||||
states.reset();
|
||||
assert_eq!(states.aborted(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_opts() {
|
||||
let opts = TransferOpts::default();
|
||||
assert_eq!(opts.check_replace, true);
|
||||
assert!(opts.save_as.is_none());
|
||||
let opts = TransferOpts::default()
|
||||
.check_replace(false)
|
||||
.save_as("omar.txt");
|
||||
assert_eq!(opts.save_as.as_deref().unwrap(), "omar.txt");
|
||||
assert_eq!(opts.check_replace, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ use crate::host::Localhost;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
pub(self) use lib::browser;
|
||||
use lib::browser::Browser;
|
||||
use lib::transfer::TransferStates;
|
||||
use lib::transfer::{TransferOpts, TransferStates};
|
||||
pub(self) use session::TransferPayload;
|
||||
|
||||
// Includes
|
||||
@@ -57,6 +57,7 @@ use tuirealm::View;
|
||||
// -- Storage keys
|
||||
|
||||
const STORAGE_EXPLORER_WIDTH: &str = "FILETRANSFER_EXPLORER_WIDTH";
|
||||
const STORAGE_PENDING_TRANSFER: &str = "FILETRANSFER_PENDING_TRANSFER";
|
||||
|
||||
// -- components
|
||||
|
||||
@@ -80,6 +81,7 @@ const COMPONENT_INPUT_OPEN_WITH: &str = "INPUT_OPEN_WITH";
|
||||
const COMPONENT_INPUT_RENAME: &str = "INPUT_RENAME";
|
||||
const COMPONENT_INPUT_SAVEAS: &str = "INPUT_SAVEAS";
|
||||
const COMPONENT_RADIO_DELETE: &str = "RADIO_DELETE";
|
||||
const COMPONENT_RADIO_REPLACE: &str = "RADIO_REPLACE"; // NOTE: used for file transfers, to choose whether to replace files
|
||||
const COMPONENT_RADIO_DISCONNECT: &str = "RADIO_DISCONNECT";
|
||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||
const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING";
|
||||
|
||||
@@ -1187,4 +1187,14 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- file exist
|
||||
|
||||
pub(crate) fn local_file_exists(&mut self, p: &Path) -> bool {
|
||||
self.host.file_exists(p)
|
||||
}
|
||||
|
||||
pub(crate) fn remote_file_exists(&mut self, p: &Path) -> bool {
|
||||
self.client.stat(p).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
*/
|
||||
// locals
|
||||
use super::{
|
||||
actions::SelectedEntry, browser::FileExplorerTab, FileTransferActivity, LogLevel,
|
||||
actions::SelectedEntry, browser::FileExplorerTab, FileTransferActivity, LogLevel, TransferOpts,
|
||||
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_SORTING,
|
||||
COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP,
|
||||
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;
|
||||
@@ -358,7 +358,7 @@ impl Update for FileTransferActivity {
|
||||
}
|
||||
(COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_SPACE => {
|
||||
// Get entry
|
||||
self.action_find_transfer(None);
|
||||
self.action_find_transfer(TransferOpts::default());
|
||||
// Reload files
|
||||
match self.browser.tab() {
|
||||
// NOTE: swapped by purpose
|
||||
@@ -583,7 +583,7 @@ impl Update for FileTransferActivity {
|
||||
FileExplorerTab::Remote => self.action_remote_saveas(input.to_string()),
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
||||
// Get entry
|
||||
self.action_find_transfer(Some(input.to_string()));
|
||||
self.action_find_transfer(TransferOpts::default().save_as(input));
|
||||
}
|
||||
}
|
||||
self.umount_saveas();
|
||||
@@ -653,6 +653,21 @@ impl Update for FileTransferActivity {
|
||||
}
|
||||
}
|
||||
(COMPONENT_RADIO_DELETE, _) => None,
|
||||
// -- replace
|
||||
(COMPONENT_RADIO_REPLACE, key)
|
||||
if key == &MSG_KEY_ESC
|
||||
|| key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) =>
|
||||
{
|
||||
self.umount_radio_replace();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_REPLACE, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => {
|
||||
// Choice is 'YES'
|
||||
self.umount_radio_replace();
|
||||
self.action_finalize_pending_transfer();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_REPLACE, _) => None,
|
||||
// -- disconnect
|
||||
(COMPONENT_RADIO_DISCONNECT, key)
|
||||
if key == &MSG_KEY_ESC
|
||||
|
||||
@@ -308,6 +308,14 @@ impl FileTransferActivity {
|
||||
self.view.render(super::COMPONENT_RADIO_DELETE, f, popup);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_DISCONNECT) {
|
||||
if props.visible {
|
||||
let popup = draw_area_in(f.size(), 30, 10);
|
||||
@@ -698,6 +706,32 @@ impl FileTransferActivity {
|
||||
self.view.umount(super::COMPONENT_RADIO_DELETE);
|
||||
}
|
||||
|
||||
pub(super) fn mount_radio_replace(&mut self, file_name: &str) {
|
||||
let warn_color = self.theme().misc_warn_dialog;
|
||||
self.view.mount(
|
||||
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)
|
||||
.with_title(
|
||||
format!("File '{}' already exists. Overwrite file?", file_name),
|
||||
Alignment::Center,
|
||||
)
|
||||
.with_options(&[String::from("Yes"), String::from("No")])
|
||||
.with_value(0)
|
||||
.rewind(true)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.active(super::COMPONENT_RADIO_REPLACE);
|
||||
}
|
||||
|
||||
pub(super) fn umount_radio_replace(&mut self) {
|
||||
self.view.umount(super::COMPONENT_RADIO_REPLACE);
|
||||
}
|
||||
|
||||
pub(super) fn mount_file_info(&mut self, file: &FsEntry) {
|
||||
let mut texts: TableBuilder = TableBuilder::default();
|
||||
// Abs path
|
||||
|
||||
@@ -54,6 +54,7 @@ const COMPONENT_INPUT_TEXT_EDITOR: &str = "INPUT_TEXT_EDITOR";
|
||||
const COMPONENT_RADIO_DEFAULT_PROTOCOL: &str = "RADIO_DEFAULT_PROTOCOL";
|
||||
const COMPONENT_RADIO_HIDDEN_FILES: &str = "RADIO_HIDDEN_FILES";
|
||||
const COMPONENT_RADIO_UPDATES: &str = "RADIO_CHECK_UPDATES";
|
||||
const COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE: &str = "RADIO_PROMPT_ON_FILE_REPLACE";
|
||||
const COMPONENT_RADIO_GROUP_DIRS: &str = "RADIO_GROUP_DIRS";
|
||||
const COMPONENT_INPUT_LOCAL_FILE_FMT: &str = "INPUT_LOCAL_FILE_FMT";
|
||||
const COMPONENT_INPUT_REMOTE_FILE_FMT: &str = "INPUT_REMOTE_FILE_FMT";
|
||||
|
||||
@@ -43,8 +43,8 @@ use super::{
|
||||
COMPONENT_INPUT_REMOTE_FILE_FMT, COMPONENT_INPUT_SSH_HOST, COMPONENT_INPUT_SSH_USERNAME,
|
||||
COMPONENT_INPUT_TEXT_EDITOR, COMPONENT_LIST_SSH_KEYS, COMPONENT_RADIO_DEFAULT_PROTOCOL,
|
||||
COMPONENT_RADIO_DEL_SSH_KEY, COMPONENT_RADIO_GROUP_DIRS, COMPONENT_RADIO_HIDDEN_FILES,
|
||||
COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SAVE, COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR,
|
||||
COMPONENT_TEXT_HELP,
|
||||
COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SAVE,
|
||||
COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP,
|
||||
};
|
||||
use crate::ui::keymap::*;
|
||||
use crate::utils::parser::parse_color;
|
||||
@@ -87,6 +87,10 @@ impl SetupActivity {
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_RADIO_GROUP_DIRS);
|
||||
None
|
||||
}
|
||||
@@ -112,6 +116,10 @@ impl SetupActivity {
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_RADIO_UPDATES);
|
||||
None
|
||||
}
|
||||
|
||||
@@ -109,6 +109,19 @@ impl SetupActivity {
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE,
|
||||
Box::new(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightCyan)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan)
|
||||
.with_title("Prompt when replacing existing files?", Alignment::Left)
|
||||
.with_options(&[String::from("Yes"), String::from("No")])
|
||||
.rewind(true)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_GROUP_DIRS,
|
||||
Box::new(Radio::new(
|
||||
@@ -178,6 +191,7 @@ impl SetupActivity {
|
||||
Constraint::Length(3), // Protocol tab
|
||||
Constraint::Length(3), // Hidden files
|
||||
Constraint::Length(3), // Updates tab
|
||||
Constraint::Length(3), // Prompt file replace
|
||||
Constraint::Length(3), // Group dirs
|
||||
Constraint::Length(3), // Local Format input
|
||||
Constraint::Length(3), // Remote Format input
|
||||
@@ -193,12 +207,17 @@ impl SetupActivity {
|
||||
.render(super::COMPONENT_RADIO_HIDDEN_FILES, f, ui_cfg_chunks[2]);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_UPDATES, f, ui_cfg_chunks[3]);
|
||||
self.view.render(
|
||||
super::COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE,
|
||||
f,
|
||||
ui_cfg_chunks[4],
|
||||
);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_GROUP_DIRS, f, ui_cfg_chunks[4]);
|
||||
.render(super::COMPONENT_RADIO_GROUP_DIRS, f, ui_cfg_chunks[5]);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_LOCAL_FILE_FMT, f, ui_cfg_chunks[5]);
|
||||
.render(super::COMPONENT_INPUT_LOCAL_FILE_FMT, f, ui_cfg_chunks[6]);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_REMOTE_FILE_FMT, f, ui_cfg_chunks[6]);
|
||||
.render(super::COMPONENT_INPUT_REMOTE_FILE_FMT, f, ui_cfg_chunks[7]);
|
||||
// Popups
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_TEXT_ERROR) {
|
||||
if props.visible {
|
||||
@@ -282,6 +301,20 @@ impl SetupActivity {
|
||||
let props = RadioPropsBuilder::from(props).with_value(updates).build();
|
||||
let _ = self.view.update(super::COMPONENT_RADIO_UPDATES, props);
|
||||
}
|
||||
// File replace
|
||||
if let Some(props) = self
|
||||
.view
|
||||
.get_props(super::COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE)
|
||||
{
|
||||
let updates: usize = match self.config().get_prompt_on_file_replace() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let props = RadioPropsBuilder::from(props).with_value(updates).build();
|
||||
let _ = self
|
||||
.view
|
||||
.update(super::COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE, props);
|
||||
}
|
||||
// Group dirs
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_GROUP_DIRS) {
|
||||
let dirs: usize = match self.config().get_group_dirs() {
|
||||
@@ -344,6 +377,13 @@ impl SetupActivity {
|
||||
let check: bool = matches!(opt, 0);
|
||||
self.config_mut().set_check_for_updates(check);
|
||||
}
|
||||
if let Some(Payload::One(Value::Usize(opt))) = self
|
||||
.view
|
||||
.get_state(super::COMPONENT_RADIO_PROMPT_ON_FILE_REPLACE)
|
||||
{
|
||||
let check: bool = matches!(opt, 0);
|
||||
self.config_mut().set_prompt_on_file_replace(check);
|
||||
}
|
||||
if let Some(Payload::One(Value::Str(fmt))) =
|
||||
self.view.get_state(super::COMPONENT_INPUT_LOCAL_FILE_FMT)
|
||||
{
|
||||
|
||||
@@ -67,6 +67,7 @@ impl Store {
|
||||
}
|
||||
|
||||
// -- getters
|
||||
|
||||
/// ### get_string
|
||||
///
|
||||
/// Get string from store
|
||||
@@ -168,6 +169,58 @@ impl Store {
|
||||
pub fn set(&mut self, key: &str) {
|
||||
self.store.insert(key.to_string(), StoreState::Flag);
|
||||
}
|
||||
|
||||
// -- Consumers
|
||||
|
||||
/// ### take_string
|
||||
///
|
||||
/// Take string from store
|
||||
pub fn take_string(&mut self, key: &str) -> Option<String> {
|
||||
match self.store.remove(key) {
|
||||
Some(StoreState::Str(s)) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### take_signed
|
||||
///
|
||||
/// Take signed from store
|
||||
pub fn take_signed(&mut self, key: &str) -> Option<isize> {
|
||||
match self.store.remove(key) {
|
||||
Some(StoreState::Signed(i)) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### take_unsigned
|
||||
///
|
||||
/// Take unsigned from store
|
||||
pub fn take_unsigned(&mut self, key: &str) -> Option<usize> {
|
||||
match self.store.remove(key) {
|
||||
Some(StoreState::Unsigned(u)) => Some(u),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### get_float
|
||||
///
|
||||
/// Take float from store
|
||||
pub fn take_float(&mut self, key: &str) -> Option<f64> {
|
||||
match self.store.remove(key) {
|
||||
Some(StoreState::Float(f)) => Some(f),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### get_boolean
|
||||
///
|
||||
/// Take boolean from store
|
||||
pub fn take_boolean(&mut self, key: &str) -> Option<bool> {
|
||||
match self.store.remove(key) {
|
||||
Some(StoreState::Boolean(b)) => Some(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -184,20 +237,30 @@ mod tests {
|
||||
// Test string
|
||||
store.set_string("test", String::from("hello"));
|
||||
assert_eq!(*store.get_string("test").as_ref().unwrap(), "hello");
|
||||
assert_eq!(store.take_string("test").unwrap(), "hello".to_string());
|
||||
assert_eq!(store.take_string("test"), None);
|
||||
// Test isize
|
||||
store.set_signed("number", 3005);
|
||||
assert_eq!(store.get_signed("number").unwrap(), 3005);
|
||||
assert_eq!(store.take_signed("number").unwrap(), 3005);
|
||||
assert_eq!(store.take_signed("number"), None);
|
||||
store.set_signed("number", -123);
|
||||
assert_eq!(store.get_signed("number").unwrap(), -123);
|
||||
// Test usize
|
||||
store.set_unsigned("unumber", 1024);
|
||||
assert_eq!(store.get_unsigned("unumber").unwrap(), 1024);
|
||||
assert_eq!(store.take_unsigned("unumber").unwrap(), 1024);
|
||||
assert_eq!(store.take_unsigned("unumber"), None);
|
||||
// Test float
|
||||
store.set_float("float", 3.33);
|
||||
assert_eq!(store.get_float("float").unwrap(), 3.33);
|
||||
assert_eq!(store.take_float("float").unwrap(), 3.33);
|
||||
assert_eq!(store.take_float("float"), None);
|
||||
// Test boolean
|
||||
store.set_boolean("bool", true);
|
||||
assert_eq!(store.get_boolean("bool").unwrap(), true);
|
||||
assert_eq!(store.take_boolean("bool").unwrap(), true);
|
||||
assert_eq!(store.take_boolean("bool"), None);
|
||||
// Test flag
|
||||
store.set("myflag");
|
||||
assert_eq!(store.isset("myflag"), true);
|
||||
|
||||
Reference in New Issue
Block a user