Sync browsing prompts to create dir (#85)

This commit is contained in:
Christian Visintin
2021-12-12 20:41:10 +01:00
parent a93dbde0c8
commit 54c23079be
16 changed files with 361 additions and 82 deletions

View File

@@ -26,84 +26,87 @@
* SOFTWARE.
*/
// locals
use super::FileTransferActivity;
use super::{FileExplorerTab, FileTransferActivity, LogLevel, Msg, PendingActionMsg};
use remotefs::Directory;
use std::path::PathBuf;
/// Describes destination for sync browsing
enum SyncBrowsingDestination {
Path(String),
ParentDir,
PreviousDir,
}
impl FileTransferActivity {
/// Enter a directory on local host from entry
/// Return true whether the directory changed
pub(crate) fn action_enter_local_dir(&mut self, dir: Directory, block_sync: bool) -> bool {
pub(crate) fn action_enter_local_dir(&mut self, dir: Directory) {
self.local_changedir(dir.path.as_path(), true);
if self.browser.sync_browsing && !block_sync {
self.action_change_remote_dir(dir.name, true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name));
}
true
}
/// Enter a directory on local host from entry
/// Return true whether the directory changed
pub(crate) fn action_enter_remote_dir(&mut self, dir: Directory, block_sync: bool) -> bool {
pub(crate) fn action_enter_remote_dir(&mut self, dir: Directory) {
self.remote_changedir(dir.path.as_path(), true);
if self.browser.sync_browsing && !block_sync {
self.action_change_local_dir(dir.name, true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name));
}
true
}
/// Change local directory reading value from input
pub(crate) fn action_change_local_dir(&mut self, input: String, block_sync: bool) {
pub(crate) fn action_change_local_dir(&mut self, input: String) {
let dir_path: PathBuf = self.local_to_abs_path(PathBuf::from(input.as_str()).as_path());
self.local_changedir(dir_path.as_path(), true);
// Check whether to sync
if self.browser.sync_browsing && !block_sync {
self.action_change_remote_dir(input, true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::Path(input));
}
}
/// Change remote directory reading value from input
pub(crate) fn action_change_remote_dir(&mut self, input: String, block_sync: bool) {
pub(crate) fn action_change_remote_dir(&mut self, input: String) {
let dir_path: PathBuf = self.remote_to_abs_path(PathBuf::from(input.as_str()).as_path());
self.remote_changedir(dir_path.as_path(), true);
// Check whether to sync
if self.browser.sync_browsing && !block_sync {
self.action_change_local_dir(input, true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::Path(input));
}
}
/// Go to previous directory from localhost
pub(crate) fn action_go_to_previous_local_dir(&mut self, block_sync: bool) {
pub(crate) fn action_go_to_previous_local_dir(&mut self) {
if let Some(d) = self.local_mut().popd() {
self.local_changedir(d.as_path(), false);
// Check whether to sync
if self.browser.sync_browsing && !block_sync {
self.action_go_to_previous_remote_dir(true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::PreviousDir);
}
}
}
/// Go to previous directory from remote host
pub(crate) fn action_go_to_previous_remote_dir(&mut self, block_sync: bool) {
pub(crate) fn action_go_to_previous_remote_dir(&mut self) {
if let Some(d) = self.remote_mut().popd() {
self.remote_changedir(d.as_path(), false);
// Check whether to sync
if self.browser.sync_browsing && !block_sync {
self.action_go_to_previous_local_dir(true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::PreviousDir);
}
}
}
/// Go to upper directory on local host
pub(crate) fn action_go_to_local_upper_dir(&mut self, block_sync: bool) {
pub(crate) fn action_go_to_local_upper_dir(&mut self) {
// Get pwd
let path: PathBuf = self.local().wrkdir.clone();
// Go to parent directory
if let Some(parent) = path.as_path().parent() {
self.local_changedir(parent, true);
// If sync is enabled update remote too
if self.browser.sync_browsing && !block_sync {
self.action_go_to_remote_upper_dir(true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::ParentDir);
}
}
}
@@ -111,15 +114,140 @@ impl FileTransferActivity {
/// #### action_go_to_remote_upper_dir
///
/// Go to upper directory on remote host
pub(crate) fn action_go_to_remote_upper_dir(&mut self, block_sync: bool) {
pub(crate) fn action_go_to_remote_upper_dir(&mut self) {
// Get pwd
let path: PathBuf = self.remote().wrkdir.clone();
// Go to parent directory
if let Some(parent) = path.as_path().parent() {
self.remote_changedir(parent, true);
// If sync is enabled update local too
if self.browser.sync_browsing && !block_sync {
self.action_go_to_local_upper_dir(true);
if self.browser.sync_browsing {
self.synchronize_browsing(SyncBrowsingDestination::ParentDir);
}
}
}
// -- sync browsing
/// Synchronize browsing on the target browser.
/// If destination doesn't exist, then prompt for directory creation.
fn synchronize_browsing(&mut self, destination: SyncBrowsingDestination) {
// Get destination path
let path = match self.resolve_sync_browsing_destination(&destination) {
Some(p) => p,
None => return,
};
trace!("Synchronizing browsing to path {}", path.display());
// Check whether destination exists on host
let exists = match self.browser.tab() {
FileExplorerTab::Local => match self.client.exists(path.as_path()) {
Ok(e) => e,
Err(err) => {
error!(
"Failed to check whether {} exists on remote: {}",
path.display(),
err
);
return;
}
},
FileExplorerTab::Remote => self.host.file_exists(path.as_path()),
_ => return,
};
let name = path
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap();
// If file doesn't exist, ask whether to create directory
if !exists {
trace!("Directory doesn't exist; asking to user if I should create it");
// Mount dialog
self.mount_sync_browsing_mkdir_popup(&name);
// Wait for dialog dismiss
if self.wait_for_pending_msg(&[
Msg::PendingAction(PendingActionMsg::MakePendingDirectory),
Msg::PendingAction(PendingActionMsg::CloseSyncBrowsingMkdirPopup),
]) == Msg::PendingAction(PendingActionMsg::MakePendingDirectory)
{
trace!("User wants to create the unexisting directory");
// Make directory
match self.browser.tab() {
FileExplorerTab::Local => self.action_remote_mkdir(name.clone()),
FileExplorerTab::Remote => self.action_local_mkdir(name.clone()),
_ => {}
}
} else {
// Do not synchronize, disable sync browsing and return
trace!("The user doesn't want to create the directory; disabling synchronized browsing");
self.log(
LogLevel::Warn,
format!(
"Refused to create '{}'; synchronized browsing disabled",
name
),
);
self.browser.toggle_sync_browsing();
self.refresh_remote_status_bar();
self.umount_sync_browsing_mkdir_popup();
return;
}
// Umount dialog
self.umount_sync_browsing_mkdir_popup();
}
trace!("Entering on the other explorer directory {}", name);
// Enter directory
match destination {
SyncBrowsingDestination::ParentDir => match self.browser.tab() {
FileExplorerTab::Local => self.remote_changedir(path.as_path(), true),
FileExplorerTab::Remote => self.local_changedir(path.as_path(), true),
_ => {}
},
SyncBrowsingDestination::Path(_) => match self.browser.tab() {
FileExplorerTab::Local => self.remote_changedir(path.as_path(), true),
FileExplorerTab::Remote => self.local_changedir(path.as_path(), true),
_ => {}
},
SyncBrowsingDestination::PreviousDir => match self.browser.tab() {
FileExplorerTab::Local => self.remote_changedir(path.as_path(), false),
FileExplorerTab::Remote => self.local_changedir(path.as_path(), false),
_ => {}
},
}
}
/// Resolve synchronized browsing destination
fn resolve_sync_browsing_destination(
&mut self,
destination: &SyncBrowsingDestination,
) -> Option<PathBuf> {
match (destination, self.browser.tab()) {
// NOTE: tab and methods are switched on purpose
(SyncBrowsingDestination::ParentDir, FileExplorerTab::Local) => {
self.remote().wrkdir.parent().map(|x| x.to_path_buf())
}
(SyncBrowsingDestination::ParentDir, FileExplorerTab::Remote) => {
self.local().wrkdir.parent().map(|x| x.to_path_buf())
}
(SyncBrowsingDestination::PreviousDir, FileExplorerTab::Local) => {
if let Some(p) = self.remote_mut().popd() {
Some(p)
} else {
warn!("Cannot synchronize browsing: remote has no previous directory in stack");
None
}
}
(SyncBrowsingDestination::PreviousDir, FileExplorerTab::Remote) => {
if let Some(p) = self.local_mut().popd() {
Some(p)
} else {
warn!("Cannot synchronize browsing: local has no previous directory in stack");
None
}
}
(SyncBrowsingDestination::Path(p), _) => Some(PathBuf::from(p.as_str())),
_ => {
warn!("Cannot synchronize browsing for current explorer");
None
}
}
}

View File

@@ -26,7 +26,8 @@
* SOFTWARE.
*/
pub(self) use super::{
browser::FileExplorerTab, FileTransferActivity, Id, LogLevel, TransferOpts, TransferPayload,
browser::FileExplorerTab, FileTransferActivity, Id, LogLevel, Msg, PendingActionMsg,
TransferOpts, TransferPayload,
};
pub(self) use remotefs::Entry;
use tuirealm::{State, StateValue};
@@ -41,6 +42,7 @@ pub(crate) mod find;
pub(crate) mod mkdir;
pub(crate) mod newfile;
pub(crate) mod open;
mod pending;
pub(crate) mod rename;
pub(crate) mod save;
pub(crate) mod submit;

View File

@@ -0,0 +1,63 @@
//! ## Pending actions
//!
//! this little module exposes the routine to create a pending action on the file transfer activity.
//! A pending action is an action which blocks the execution of the application in await of a certain `Msg`.
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use super::{FileTransferActivity, Msg};
use tuirealm::PollStrategy;
impl FileTransferActivity {
/// Block execution of activity, preventing ANY kind of message not specified in the `wait_for` argument.
/// Once `wait_for` clause is satisfied, the function returns.
///
/// Returns the message which satisfied the clause
///
/// NOTE: The view is redrawn as usual
pub(super) fn wait_for_pending_msg(&mut self, wait_for: &[Msg]) -> Msg {
self.redraw = true;
loop {
// Poll
match self.app.tick(PollStrategy::Once) {
Ok(messages) => {
if !messages.is_empty() {
self.redraw = true;
}
if let Some(msg) = messages.into_iter().find(|m| wait_for.contains(m)) {
return msg;
}
}
Err(err) => {
error!("Application error: {}", err);
}
}
// Redraw
if self.redraw {
self.view();
}
}
}
}

View File

@@ -38,7 +38,7 @@ enum SubmitAction {
impl FileTransferActivity {
/// Decides which action to perform on submit for local explorer
/// Return true whether the directory changed
pub(crate) fn action_submit_local(&mut self, entry: Entry) -> bool {
pub(crate) fn action_submit_local(&mut self, entry: Entry) {
let (action, entry) = match &entry {
Entry::Directory(_) => (SubmitAction::ChangeDir, entry),
Entry::File(File {
@@ -67,18 +67,14 @@ impl FileTransferActivity {
}
Entry::File(_) => (SubmitAction::None, entry),
};
match (action, entry) {
(SubmitAction::ChangeDir, Entry::Directory(dir)) => {
self.action_enter_local_dir(dir, false)
}
(SubmitAction::ChangeDir, _) => false,
(SubmitAction::None, _) => false,
if let (SubmitAction::ChangeDir, Entry::Directory(dir)) = (action, entry) {
self.action_enter_local_dir(dir)
}
}
/// Decides which action to perform on submit for remote explorer
/// Return true whether the directory changed
pub(crate) fn action_submit_remote(&mut self, entry: Entry) -> bool {
pub(crate) fn action_submit_remote(&mut self, entry: Entry) {
let (action, entry) = match &entry {
Entry::Directory(_) => (SubmitAction::ChangeDir, entry),
Entry::File(File {
@@ -107,12 +103,8 @@ impl FileTransferActivity {
}
Entry::File(_) => (SubmitAction::None, entry),
};
match (action, entry) {
(SubmitAction::ChangeDir, Entry::Directory(dir)) => {
self.action_enter_remote_dir(dir, false)
}
(SubmitAction::ChangeDir, _) => false,
(SubmitAction::None, _) => false,
if let (SubmitAction::ChangeDir, Entry::Directory(dir)) = (action, entry) {
self.action_enter_remote_dir(dir)
}
}
}

View File

@@ -25,7 +25,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use super::{Msg, TransferMsg, UiMsg};
use super::{Msg, PendingActionMsg, TransferMsg, UiMsg};
use tui_realm_stdlib::Phantom;
use tuirealm::{
@@ -45,7 +45,8 @@ pub use popups::{
CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup, FileInfoPopup,
FindPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup, OpenWithPopup,
ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup,
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote, WaitPopup,
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote,
SyncBrowsingMkdirPopup, WaitPopup,
};
pub use transfer::{ExplorerFind, ExplorerLocal, ExplorerRemote};

View File

@@ -26,7 +26,7 @@
* SOFTWARE.
*/
use super::super::Browser;
use super::{Msg, TransferMsg, UiMsg};
use super::{Msg, PendingActionMsg, TransferMsg, UiMsg};
use crate::explorer::FileSorting;
use crate::utils::fmt::fmt_time;
@@ -1657,6 +1657,70 @@ fn hidden_files_label(visible: bool) -> &'static str {
}
}
#[derive(MockComponent)]
pub struct SyncBrowsingMkdirPopup {
component: Radio,
}
impl SyncBrowsingMkdirPopup {
pub fn new(color: Color, dir_name: &str) -> Self {
Self {
component: Radio::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(&["Yes", "No"])
.title(
format!(
r#"Sync browsing: directory "{}" doesn't exist. Do you want to create it?"#,
dir_name
),
Alignment::Center,
),
}
}
}
impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {
self.perform(Cmd::Move(Direction::Left));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Right, ..
}) => {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction(
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
if matches!(
self.perform(Cmd::Submit),
CmdResult::Submit(State::One(StateValue::Usize(0)))
) {
Some(Msg::PendingAction(PendingActionMsg::MakePendingDirectory))
} else {
Some(Msg::PendingAction(
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
))
}
}
_ => None,
}
}
}
#[derive(MockComponent)]
pub struct WaitPopup {
component: Paragraph,

View File

@@ -56,8 +56,10 @@ use tuirealm::{Application, EventListenerCfg, NoUserEvent};
// -- Storage keys
const STORAGE_EXPLORER_WIDTH: &str = "FILETRANSFER_EXPLORER_WIDTH";
const STORAGE_PENDING_TRANSFER: &str = "FILETRANSFER_PENDING_TRANSFER";
/// Stores the explorer width
const STORAGE_EXPLORER_WIDTH: &str = "FT_EW";
/// Stores the filename of the entry to transfer, when the replace file dialog must be shown
const STORAGE_PENDING_TRANSFER: &str = "FT_PT";
// -- components
@@ -92,16 +94,24 @@ enum Id {
SortingPopup,
StatusBarLocal,
StatusBarRemote,
SyncBrowsingMkdirPopup,
WaitPopup,
}
#[derive(Debug, PartialEq)]
enum Msg {
PendingAction(PendingActionMsg),
Transfer(TransferMsg),
Ui(UiMsg),
None,
}
#[derive(Debug, PartialEq)]
enum PendingActionMsg {
CloseSyncBrowsingMkdirPopup,
MakePendingDirectory,
}
#[derive(Debug, PartialEq)]
enum TransferMsg {
AbortTransfer,

View File

@@ -42,6 +42,10 @@ impl Update<Msg> for FileTransferActivity {
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
match msg.unwrap_or(Msg::None) {
Msg::None => None,
Msg::PendingAction(_) => {
// NOTE: Pending actions must be handled directly in the action
None
}
Msg::Transfer(msg) => self.update_transfer(msg),
Msg::Ui(msg) => self.update_ui(msg),
}
@@ -106,24 +110,22 @@ impl FileTransferActivity {
}
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Local => {
if let SelectedEntry::One(entry) = self.get_local_selected_entries() {
if self.action_submit_local(entry) {
// Update file list if sync
if self.browser.sync_browsing {
let _ = self.update_remote_filelist();
}
self.update_local_filelist();
self.action_submit_local(entry);
// Update file list if sync
if self.browser.sync_browsing {
let _ = self.update_remote_filelist();
}
self.update_local_filelist();
}
}
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Remote => {
if let SelectedEntry::One(entry) = self.get_remote_selected_entries() {
if self.action_submit_remote(entry) {
// Update file list if sync
if self.browser.sync_browsing {
let _ = self.update_local_filelist();
}
self.update_remote_filelist();
self.action_submit_remote(entry);
// Update file list if sync
if self.browser.sync_browsing {
let _ = self.update_local_filelist();
}
self.update_remote_filelist();
}
}
TransferMsg::EnterDirectory => {
@@ -152,8 +154,8 @@ impl FileTransferActivity {
}
TransferMsg::GoTo(dir) => {
match self.browser.tab() {
FileExplorerTab::Local => self.action_change_local_dir(dir, false),
FileExplorerTab::Remote => self.action_change_remote_dir(dir, false),
FileExplorerTab::Local => self.action_change_local_dir(dir),
FileExplorerTab::Remote => self.action_change_remote_dir(dir),
_ => panic!("Found tab doesn't support GOTO"),
}
// Umount
@@ -168,7 +170,7 @@ impl FileTransferActivity {
TransferMsg::GoToParentDirectory => {
match self.browser.tab() {
FileExplorerTab::Local => {
self.action_go_to_local_upper_dir(false);
self.action_go_to_local_upper_dir();
if self.browser.sync_browsing {
let _ = self.update_remote_filelist();
}
@@ -176,7 +178,7 @@ impl FileTransferActivity {
self.update_local_filelist()
}
FileExplorerTab::Remote => {
self.action_go_to_remote_upper_dir(false);
self.action_go_to_remote_upper_dir();
if self.browser.sync_browsing {
let _ = self.update_local_filelist();
}
@@ -189,7 +191,7 @@ impl FileTransferActivity {
TransferMsg::GoToPreviousDirectory => {
match self.browser.tab() {
FileExplorerTab::Local => {
self.action_go_to_previous_local_dir(false);
self.action_go_to_previous_local_dir();
if self.browser.sync_browsing {
let _ = self.update_remote_filelist();
}
@@ -197,7 +199,7 @@ impl FileTransferActivity {
self.update_local_filelist()
}
FileExplorerTab::Remote => {
self.action_go_to_previous_remote_dir(false);
self.action_go_to_previous_remote_dir();
if self.browser.sync_browsing {
let _ = self.update_local_filelist();
}

View File

@@ -307,6 +307,11 @@ impl FileTransferActivity {
f.render_widget(Clear, popup);
// make popup
self.app.view(&Id::WaitPopup, f, popup);
} else if self.app.mounted(&Id::SyncBrowsingMkdirPopup) {
let popup = draw_area_in(f.size(), 60, 10);
f.render_widget(Clear, popup);
// make popup
self.app.view(&Id::SyncBrowsingMkdirPopup, f, popup);
} else if self.app.mounted(&Id::KeybindingsPopup) {
let popup = draw_area_in(f.size(), 50, 80);
f.render_widget(Clear, popup);
@@ -798,6 +803,23 @@ impl FileTransferActivity {
.is_ok());
}
pub(super) fn mount_sync_browsing_mkdir_popup(&mut self, dir_name: &str) {
let color = self.theme().misc_info_dialog;
assert!(self
.app
.remount(
Id::SyncBrowsingMkdirPopup,
Box::new(components::SyncBrowsingMkdirPopup::new(color, dir_name,)),
vec![],
)
.is_ok());
assert!(self.app.active(&Id::SyncBrowsingMkdirPopup).is_ok());
}
pub(super) fn umount_sync_browsing_mkdir_popup(&mut self) {
let _ = self.app.umount(&Id::SyncBrowsingMkdirPopup);
}
/// Mount help
pub(super) fn mount_help(&mut self) {
let key_color = self.theme().misc_keys;
@@ -949,9 +971,14 @@ impl FileTransferActivity {
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
Id::FindPopup,
)))),
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
Id::WaitPopup,
)))),
Box::new(SubClause::And(
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
Id::SyncBrowsingMkdirPopup,
)))),
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
Id::WaitPopup,
)))),
)),
)),
)),
)),