feat: cli args for double remote
Linux / build (push) Waiting to run
MacOS / build (push) Waiting to run
Windows / build (push) Waiting to run

This commit is contained in:
veeso
2024-10-06 21:14:26 +02:00
parent 418b132f7e
commit 4d4c71106d
23 changed files with 742 additions and 221 deletions
+4
View File
@@ -42,6 +42,10 @@
Released on Released on
- [**Multi Host support**](https://github.com/veeso/termscp/issues/285):
- Now it is possible to work on two different remotes `remote A -> remote B` instead of just `localhost -> remote`
- Cli arguments now accept an additional `remote-args` for the left panel.
- For more details read this issue <https://github.com/veeso/termscp/issues/285>.
- [Issue 290](https://github.com/veeso/termscp/issues/290): Password prompt was broken - [Issue 290](https://github.com/veeso/termscp/issues/290): Password prompt was broken
## 0.15.0 ## 0.15.0
+2 -2
View File
@@ -62,11 +62,11 @@
termscp kann mit den folgenden Optionen gestartet werden: termscp kann mit den folgenden Optionen gestartet werden:
`termscp [Optionen]... [protokoll://benutzer@adresse:port:arbeitsverzeichnis] [lokales-arbeitsverzeichnis]` `termscp [Optionen]... [protokoll://benutzer@adresse:port:arbeitsverzeichnis] [protokoll://benutzer@adresse:port:arbeitsverzeichnis] [lokales-arbeitsverzeichnis]`
ODER ODER
`termscp [Optionen]... -b [Lesezeichen-Name] [lokales-arbeitsverzeichnis]` `termscp [Optionen]... -b [Lesezeichen-Name] -b [Lesezeichen-Name] [lokales-arbeitsverzeichnis]`
- `-P, --password <Passwort>` wenn Adresse angegeben wird, ist das Passwort dieses Argument - `-P, --password <Passwort>` wenn Adresse angegeben wird, ist das Passwort dieses Argument
- `-b, --address-as-bookmark` löst das Adressargument als Lesezeichenname auf - `-b, --address-as-bookmark` löst das Adressargument als Lesezeichenname auf
+2 -2
View File
@@ -39,11 +39,11 @@
termscp se puede iniciar con las siguientes opciones: termscp se puede iniciar con las siguientes opciones:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
OR OR
`termscp [options]... -b [bookmark-name] [local-wrkdir]` `termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento - `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento
- `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador - `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador
+2 -2
View File
@@ -37,11 +37,11 @@
termscp peut être démarré avec les options suivantes : termscp peut être démarré avec les options suivantes :
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
ou ou
`termscp [options]... -b [bookmark-name] [local-wrkdir]` `termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument - `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument
- `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet - `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet
+2 -2
View File
@@ -37,11 +37,11 @@
termscp può essere lanciato con questi argomenti: termscp può essere lanciato con questi argomenti:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
O O
`termscp [options]... -b [bookmark-name] [local-wrkdir]` `termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi - `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi
- `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro - `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro
+5 -3
View File
@@ -40,13 +40,15 @@
termscp can be started with the following options: termscp can be started with the following options:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
OR OR
`termscp [options]... -b [bookmark-name] [local-wrkdir]` `termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` if address is provided, password will be this argument AND any combination of the two
- `-P, --password <password>` if address is provided, password will be this argument. A password *can* be specified for each remote provided. The order must be the same of the address argument. The use of this parameter is discouraged.
- `-b, --address-as-bookmark` resolve address argument as a bookmark name - `-b, --address-as-bookmark` resolve address argument as a bookmark name
- `-q, --quiet` Disable logging - `-q, --quiet` Disable logging
- `-v, --version` Print version info - `-v, --version` Print version info
+2 -2
View File
@@ -40,11 +40,11 @@
O termscp pode ser iniciado com as seguintes opções: O termscp pode ser iniciado com as seguintes opções:
`termscp [opções]... [protocol://usuário@endereço:porta:diretório-trabalho] [diretório-trabalho-local]` `termscp [opções]... [protocol://usuário@endereço:porta:diretório-trabalho] [protocol://usuário@endereço:porta:diretório-trabalho] [diretório-trabalho-local]`
OU OU
`termscp [opções]... -b [nome-do-favorito] [diretório-trabalho-local]` `termscp [opções]... -b [nome-do-favorito] -b [nome-do-favorito] [diretório-trabalho-local]`
- `-P, --password <senha>` se o endereço for fornecido, a senha será este argumento - `-P, --password <senha>` se o endereço for fornecido, a senha será este argumento
- `-b, --address-as-bookmark` resolve o argumento do endereço como um nome de favorito - `-b, --address-as-bookmark` resolve o argumento do endereço como um nome de favorito
+2 -2
View File
@@ -37,11 +37,11 @@
termscp启动时可以使用以下选项: termscp启动时可以使用以下选项:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
或作为 或作为
`termscp [options]... -b [bookmark-name] [local-wrkdir]` `termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` 登陆密码 - `-P, --password <password>` 登陆密码
- `-b, --address-as-bookmark` 将地址参数解析为书签名称 - `-b, --address-as-bookmark` 将地址参数解析为书签名称
+144 -34
View File
@@ -2,14 +2,16 @@
//! //!
//! `activity_manager` is the module which provides run methods and handling for activities //! `activity_manager` is the module which provides run methods and handling for activities
// Deps use std::env;
// Namespaces
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait; use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
use crate::filetransfer::{FileTransferParams, FileTransferProtocol, HostBridgeParams}; use crate::cli::{Remote, RemoteArgs};
use crate::filetransfer::{
FileTransferParams, FileTransferProtocol, HostBridgeParams, ProtocolParams,
};
use crate::host::HostError; use crate::host::HostError;
use crate::system::bookmarks_client::BookmarksClient; use crate::system::bookmarks_client::BookmarksClient;
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
@@ -30,6 +32,16 @@ pub enum NextActivity {
SetupActivity, SetupActivity,
} }
pub enum Host {
HostBridge,
Remote,
}
pub enum HostParams {
HostBridge(HostBridgeParams),
Remote(FileTransferParams),
}
/// The activity manager takes care of running activities and handling them until the application has ended /// The activity manager takes care of running activities and handling them until the application has ended
pub struct ActivityManager { pub struct ActivityManager {
context: Option<Context>, context: Option<Context>,
@@ -62,10 +74,100 @@ impl ActivityManager {
}) })
} }
/// Configure remote args
pub fn configure_remote_args(&mut self, remote_args: RemoteArgs) -> Result<(), String> {
// Set for host bridge
match remote_args.host_bridge {
Remote::Bookmark(params) => self.resolve_bookmark_name(
Host::HostBridge,
&params.name,
params.password.as_deref(),
),
Remote::Host(host_params) => self.set_host_params(
HostParams::HostBridge(HostBridgeParams::Remote(
host_params.file_transfer_params.protocol,
host_params.file_transfer_params.params,
)),
host_params.password.as_deref(),
),
Remote::None => self.set_host_params(
HostParams::HostBridge(HostBridgeParams::Localhost(
env::current_dir()
.map_err(|e| format!("Could not get current directory: {e}"))?,
)),
None,
),
}?;
// set remote
match remote_args.remote {
Remote::Bookmark(params) => {
self.resolve_bookmark_name(Host::Remote, &params.name, params.password.as_deref())
}
Remote::Host(host_params) => self.set_host_params(
HostParams::Remote(host_params.file_transfer_params),
host_params.password.as_deref(),
),
Remote::None => Ok(()),
}
}
/// Set file transfer params /// Set file transfer params
pub fn set_filetransfer_params( pub fn set_host_params(
&mut self, &mut self,
mut params: FileTransferParams, host: HostParams,
password: Option<&str>,
) -> Result<(), String> {
let (remote_local_path, remote_remote_path) = match &host {
HostParams::Remote(params) => (params.local_path.clone(), params.remote_path.clone()),
_ => (None, None),
};
let mut remote_params = match &host {
HostParams::HostBridge(HostBridgeParams::Remote(protocol, protocol_params)) => {
Some((*protocol, protocol_params.clone()))
}
HostParams::HostBridge(HostBridgeParams::Localhost(_)) => None,
HostParams::Remote(ft_params) => Some((ft_params.protocol, ft_params.params.clone())),
};
// Put params into the context
if let Some((protocol, params)) = remote_params.as_mut() {
self.resolve_password_for_protocol_params(*protocol, params, password)?;
}
match host {
HostParams::HostBridge(HostBridgeParams::Localhost(path)) => {
self.context
.as_mut()
.unwrap()
.set_host_bridge_params(HostBridgeParams::Localhost(path));
}
HostParams::HostBridge(HostBridgeParams::Remote(_, _)) => {
let (protocol, params) = remote_params.unwrap();
self.context
.as_mut()
.unwrap()
.set_host_bridge_params(HostBridgeParams::Remote(protocol, params));
}
HostParams::Remote(_) => {
let (protocol, params) = remote_params.unwrap();
let params = FileTransferParams {
local_path: remote_local_path,
remote_path: remote_remote_path,
protocol,
params,
};
self.context.as_mut().unwrap().set_remote_params(params);
}
}
Ok(())
}
fn resolve_password_for_protocol_params(
&mut self,
protocol: FileTransferProtocol,
params: &mut ProtocolParams,
password: Option<&str>, password: Option<&str>,
) -> Result<(), String> { ) -> Result<(), String> {
// Set password if provided // Set password if provided
@@ -73,13 +175,13 @@ impl ActivityManager {
if let Some(password) = password { if let Some(password) = password {
params.set_default_secret(password.to_string()); params.set_default_secret(password.to_string());
} else if matches!( } else if matches!(
params.protocol, protocol,
FileTransferProtocol::Scp | FileTransferProtocol::Sftp, FileTransferProtocol::Scp | FileTransferProtocol::Sftp,
) && params.params.generic_params().is_some() ) && params.generic_params().is_some()
{ {
// * if protocol is SCP or SFTP check whether a SSH key is registered for this remote, in case not ask password // * if protocol is SCP or SFTP check whether a SSH key is registered for this remote, in case not ask password
let storage = SshKeyStorage::from(self.context.as_ref().unwrap().config()); let storage = SshKeyStorage::from(self.context.as_ref().unwrap().config());
let generic_params = params.params.generic_params().unwrap(); let generic_params = params.generic_params().unwrap();
if storage if storage
.resolve( .resolve(
&generic_params.address, &generic_params.address,
@@ -94,7 +196,7 @@ impl ActivityManager {
"storage could not find any suitable key for {}... prompting for password", "storage could not find any suitable key for {}... prompting for password",
generic_params.address generic_params.address
); );
self.prompt_password(&mut params)?; self.prompt_password(params)?;
} else { } else {
debug!( debug!(
"a key is already set for {}; password is not required", "a key is already set for {}; password is not required",
@@ -102,19 +204,19 @@ impl ActivityManager {
); );
} }
} else { } else {
self.prompt_password(&mut params)?; self.prompt_password(params)?;
} }
} }
// Put params into the context
self.context.as_mut().unwrap().set_ftparams(params);
Ok(()) Ok(())
} }
/// Prompt user for password to set into params. /// Prompt user for password to set into params.
fn prompt_password(&mut self, params: &mut FileTransferParams) -> Result<(), String> { fn prompt_password(&mut self, params: &mut ProtocolParams) -> Result<(), String> {
let ctx = self.context.as_mut().unwrap(); let ctx = self.context.as_mut().unwrap();
let prompt = format!("Password for {}: ", params.host_name());
match tty::read_secret_from_tty(ctx.terminal(), "Password: ") { match tty::read_secret_from_tty(ctx.terminal(), prompt) {
Err(err) => Err(format!("Could not read password: {err}")), Err(err) => Err(format!("Could not read password: {err}")),
Ok(Some(secret)) => { Ok(Some(secret)) => {
debug!( debug!(
@@ -132,16 +234,28 @@ impl ActivityManager {
/// Returns error if bookmark is not found /// Returns error if bookmark is not found
pub fn resolve_bookmark_name( pub fn resolve_bookmark_name(
&mut self, &mut self,
host: Host,
bookmark_name: &str, bookmark_name: &str,
password: Option<&str>, password: Option<&str>,
) -> Result<(), String> { ) -> Result<(), String> {
if let Some(bookmarks_client) = self.context.as_mut().unwrap().bookmarks_client_mut() { if let Some(bookmarks_client) = self.context.as_mut().unwrap().bookmarks_client_mut() {
match bookmarks_client.get_bookmark(bookmark_name) { let params = match bookmarks_client.get_bookmark(bookmark_name) {
None => Err(format!( None => {
r#"Could not resolve bookmark name: "{bookmark_name}" no such bookmark"# return Err(format!(
)), r#"Could not resolve bookmark name: "{bookmark_name}" no such bookmark"#
Some(params) => self.set_filetransfer_params(params, password), ))
} }
Some(params) => params,
};
let params = match host {
Host::Remote => HostParams::Remote(params),
Host::HostBridge => {
HostParams::HostBridge(HostBridgeParams::Remote(params.protocol, params.params))
}
};
self.set_host_params(params, password)
} else { } else {
Err(String::from( Err(String::from(
"Could not resolve bookmark name: bookmarks client not initialized", "Could not resolve bookmark name: bookmarks client not initialized",
@@ -235,8 +349,17 @@ impl ActivityManager {
return None; return None;
} }
}; };
let host_bridge_params = match ctx.host_bridge_params() {
Some(params) => params.clone(),
None => {
error!("Failed to start FileTransferActivity: host bridge params is None");
return None;
}
};
// If ft params is None, return None // If ft params is None, return None
let remote_params: &FileTransferParams = match ctx.ft_params() { let remote_params: &FileTransferParams = match ctx.remote_params() {
Some(ft_params) => ft_params, Some(ft_params) => ft_params,
None => { None => {
error!("Failed to start FileTransferActivity: file transfer params is None"); error!("Failed to start FileTransferActivity: file transfer params is None");
@@ -244,19 +367,6 @@ impl ActivityManager {
} }
}; };
// get local path:
// - if set in file transfer params, get it from there
// - otherwise is env current dir
// - otherwise is /
let local_wrkdir = remote_params
.local_path
.clone()
.or(std::env::current_dir().ok())
.unwrap_or(PathBuf::from("/"));
// TODO: get host params from prev activity
let host_bridge_params = HostBridgeParams::Localhost(local_wrkdir);
let mut activity: FileTransferActivity = let mut activity: FileTransferActivity =
FileTransferActivity::new(host_bridge_params, remote_params, self.ticks); FileTransferActivity::new(host_bridge_params, remote_params, self.ticks);
// Prepare result // Prepare result
+12 -47
View File
@@ -2,13 +2,15 @@
//! //!
//! defines the types for main.rs types //! defines the types for main.rs types
mod remote;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
use argh::FromArgs; use argh::FromArgs;
pub use remote::{Remote, RemoteArgs};
use crate::activity_manager::NextActivity; use crate::activity_manager::NextActivity;
use crate::filetransfer::FileTransferParams;
use crate::system::logging::LogLevel; use crate::system::logging::LogLevel;
pub enum Task { pub enum Task {
@@ -17,7 +19,7 @@ pub enum Task {
InstallUpdate, InstallUpdate,
} }
#[derive(FromArgs)] #[derive(Default, FromArgs)]
#[argh(description = " #[argh(description = "
where positional can be: where positional can be:
- [address_a] [address_b] [local-wrkdir] - [address_a] [address_b] [local-wrkdir]
@@ -39,14 +41,15 @@ pub struct Args {
#[argh(subcommand)] #[argh(subcommand)]
pub nested: Option<ArgsSubcommands>, pub nested: Option<ArgsSubcommands>,
/// resolve address argument as a bookmark name /// resolve address argument as a bookmark name
#[argh(switch, short = 'b')] #[argh(option, short = 'b')]
pub address_as_bookmark: bool, pub bookmark: Vec<String>,
/// enable TRACE log level /// enable TRACE log level
#[argh(switch, short = 'D')] #[argh(switch, short = 'D')]
pub debug: bool, pub debug: bool,
/// provide password from CLI /// provide password from CLI; if you need to provide multiple passwords, use multiple -P flags.
/// In case just respect the order of the addresses
#[argh(option, short = 'P')] #[argh(option, short = 'P')]
pub password: Option<String>, pub password: Vec<String>,
/// disable logging /// disable logging
#[argh(switch, short = 'q')] #[argh(switch, short = 'q')]
pub quiet: bool, pub quiet: bool,
@@ -57,10 +60,7 @@ pub struct Args {
#[argh(switch, short = 'v')] #[argh(switch, short = 'v')]
pub version: bool, pub version: bool,
// -- positional // -- positional
#[argh( #[argh(positional, description = "address1 address2 local-wrkdir")]
positional,
description = "protocol://user@address:port:wrkdir local-wrkdir"
)]
pub positional: Vec<String>, pub positional: Vec<String>,
} }
@@ -92,7 +92,7 @@ pub struct LoadThemeArgs {
} }
pub struct RunOpts { pub struct RunOpts {
pub remote: Remote, pub remote: RemoteArgs,
pub ticks: Duration, pub ticks: Duration,
pub log_level: LogLevel, pub log_level: LogLevel,
pub task: Task, pub task: Task,
@@ -124,45 +124,10 @@ impl RunOpts {
impl Default for RunOpts { impl Default for RunOpts {
fn default() -> Self { fn default() -> Self {
Self { Self {
remote: Remote::None, remote: RemoteArgs::default(),
ticks: Duration::from_millis(10), ticks: Duration::from_millis(10),
log_level: LogLevel::Info, log_level: LogLevel::Info,
task: Task::Activity(NextActivity::Authentication), task: Task::Activity(NextActivity::Authentication),
} }
} }
} }
#[allow(clippy::large_enum_variant)]
pub enum Remote {
Bookmark(BookmarkParams),
Host(HostParams),
None,
}
pub struct BookmarkParams {
pub name: String,
pub password: Option<String>,
}
pub struct HostParams {
pub params: FileTransferParams,
pub password: Option<String>,
}
impl BookmarkParams {
pub fn new<S: AsRef<str>>(name: S, password: Option<S>) -> Self {
Self {
name: name.as_ref().to_string(),
password: password.map(|x| x.as_ref().to_string()),
}
}
}
impl HostParams {
pub fn new<S: AsRef<str>>(params: FileTransferParams, password: Option<S>) -> Self {
Self {
params,
password: password.map(|x| x.as_ref().to_string()),
}
}
}
+262
View File
@@ -0,0 +1,262 @@
use std::path::{Path, PathBuf};
use super::Args;
use crate::filetransfer::FileTransferParams;
use crate::utils;
/// Address type
enum AddrType {
Address,
Bookmark,
}
/// Args for remote connection
#[derive(Debug)]
pub struct RemoteArgs {
pub host_bridge: Remote,
pub remote: Remote,
pub local_dir: Option<PathBuf>,
}
impl Default for RemoteArgs {
fn default() -> Self {
Self {
host_bridge: Remote::None,
remote: Remote::None,
local_dir: None,
}
}
}
impl TryFrom<&Args> for RemoteArgs {
type Error = String;
fn try_from(args: &Args) -> Result<Self, Self::Error> {
let mut remote_args = RemoteArgs::default();
// validate arguments
match (args.bookmark.len(), args.positional.len()) {
(0, positional) if positional < 4 => Ok(()),
(1, positional) if positional < 3 => Ok(()),
(2, positional) if positional < 2 => Ok(()),
(_, _) => Err("Too many arguments".to_string()),
}?;
// parse bookmark first
let last_item_index = (args.bookmark.len() + args.positional.len())
.checked_sub(1)
.unwrap_or_default();
let mut hosts = vec![];
for (i, (addr_type, arg)) in args
.bookmark
.iter()
.map(|x| (AddrType::Bookmark, x))
.chain(args.positional.iter().map(|x| (AddrType::Address, x)))
.enumerate()
{
// check if has password
let password = args.password.get(i).cloned();
// check if is last item and so a possible local dir
if i == last_item_index && Path::new(arg).exists() {
remote_args.local_dir = Some(PathBuf::from(arg));
continue;
}
let remote = match addr_type {
AddrType::Address => Self::parse_remote_address(arg)
.map(|x| Remote::Host(HostParams::new(x, password)))?,
AddrType::Bookmark => Remote::Bookmark(BookmarkParams::new(arg, password.as_ref())),
};
// set remote
hosts.push(remote);
}
// set args based on hosts len
if hosts.len() == 1 {
remote_args.remote = hosts.pop().unwrap();
} else if hosts.len() == 2 {
remote_args.host_bridge = hosts.pop().unwrap();
remote_args.remote = hosts.pop().unwrap();
}
Ok(remote_args)
}
}
impl RemoteArgs {
/// Parse remote address
fn parse_remote_address(remote: &str) -> Result<FileTransferParams, String> {
utils::parser::parse_remote_opt(remote).map_err(|e| format!("Bad address option: {e}"))
}
}
/// Remote argument type
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Remote {
/// Bookmark name argument
Bookmark(BookmarkParams),
/// Host argument
Host(HostParams),
/// Unspecified
None,
}
/// Bookmark parameters
#[derive(Debug)]
pub struct BookmarkParams {
/// bookmark name
pub name: String,
/// bookmark password
pub password: Option<String>,
}
/// Host parameters
#[derive(Debug)]
pub struct HostParams {
/// file transfer parameters
pub file_transfer_params: FileTransferParams,
/// host password specified in arguments
pub password: Option<String>,
}
impl BookmarkParams {
pub fn new<S: AsRef<str>>(name: S, password: Option<S>) -> Self {
Self {
name: name.as_ref().to_string(),
password: password.map(|x| x.as_ref().to_string()),
}
}
}
impl HostParams {
pub fn new<S: AsRef<str>>(params: FileTransferParams, password: Option<S>) -> Self {
Self {
file_transfer_params: params,
password: password.map(|x| x.as_ref().to_string()),
}
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_should_make_remote_args_from_args_one_remote() {
let args = Args {
positional: vec!["scp://host1".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::None));
assert!(matches!(remote_args.remote, Remote::Host(_)));
assert_eq!(remote_args.local_dir, None);
}
#[test]
fn test_should_make_remote_args_from_args_two_remotes() {
let args = Args {
positional: vec!["scp://host1".to_string(), "scp://host2".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
assert!(matches!(remote_args.remote, Remote::Host(_)));
assert_eq!(remote_args.local_dir, None);
}
#[test]
fn test_should_make_remote_args_from_two_remotes_and_local_dir() {
let args = Args {
positional: vec![
"scp://host1".to_string(),
"scp://host2".to_string(),
"/home".to_string(),
],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
assert!(matches!(remote_args.remote, Remote::Host(_)));
assert_eq!(remote_args.local_dir, Some(PathBuf::from("/home")));
}
#[test]
fn test_should_make_remote_args_from_args_one_bookmarks() {
let args = Args {
bookmark: vec!["foo".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::None));
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
assert_eq!(remote_args.local_dir, None);
}
#[test]
fn test_should_make_remote_args_from_args_two_bookmarks() {
let args = Args {
bookmark: vec!["foo".to_string(), "bar".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::Bookmark(_)));
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
assert_eq!(remote_args.local_dir, None);
}
#[test]
fn test_should_make_remote_args_from_two_bookmarks_and_local_dir() {
let args = Args {
bookmark: vec!["foo".to_string(), "bar".to_string()],
positional: vec!["/home".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::Bookmark(_)));
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
assert_eq!(remote_args.local_dir, Some(PathBuf::from("/home")));
}
#[test]
fn test_should_make_remote_args_from_one_bookmark_and_one_remote() {
let args = Args {
bookmark: vec!["foo".to_string()],
positional: vec!["scp://host1".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
assert_eq!(remote_args.local_dir, None);
}
#[test]
fn test_should_make_remote_args_from_one_bookmark_and_one_remote_with_local_dir() {
let args = Args {
positional: vec!["scp://host1".to_string(), "/home".to_string()],
bookmark: vec!["foo".to_string()],
..Default::default()
};
let remote_args = RemoteArgs::try_from(&args).unwrap();
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
assert_eq!(remote_args.local_dir, Some(PathBuf::from("/home")));
}
}
+50 -14
View File
@@ -24,6 +24,15 @@ pub enum HostBridgeParams {
Remote(FileTransferProtocol, ProtocolParams), Remote(FileTransferProtocol, ProtocolParams),
} }
impl HostBridgeParams {
pub fn unwrap_protocol_params(&self) -> &ProtocolParams {
match self {
HostBridgeParams::Localhost(_) => panic!("Localhost has no protocol params"),
HostBridgeParams::Remote(_, params) => params,
}
}
}
/// Holds connection parameters for file transfers /// Holds connection parameters for file transfers
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FileTransferParams { pub struct FileTransferParams {
@@ -43,6 +52,43 @@ pub enum ProtocolParams {
WebDAV(WebDAVProtocolParams), WebDAV(WebDAVProtocolParams),
} }
impl ProtocolParams {
pub fn password_missing(&self) -> bool {
match self {
ProtocolParams::AwsS3(params) => params.password_missing(),
ProtocolParams::Generic(params) => params.password_missing(),
ProtocolParams::Kube(params) => params.password_missing(),
ProtocolParams::Smb(params) => params.password_missing(),
ProtocolParams::WebDAV(params) => params.password_missing(),
}
}
/// Set the secret to ft params for the default secret field for this protocol
pub fn set_default_secret(&mut self, secret: String) {
match self {
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
ProtocolParams::Generic(params) => params.set_default_secret(secret),
ProtocolParams::Kube(params) => params.set_default_secret(secret),
ProtocolParams::Smb(params) => params.set_default_secret(secret),
ProtocolParams::WebDAV(params) => params.set_default_secret(secret),
}
}
pub fn host_name(&self) -> String {
match self {
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
ProtocolParams::Generic(params) => params.address.clone(),
ProtocolParams::Kube(params) => params
.namespace
.as_ref()
.cloned()
.unwrap_or_else(|| String::from("default")),
ProtocolParams::Smb(params) => params.address.clone(),
ProtocolParams::WebDAV(params) => params.uri.clone(),
}
}
}
/// Protocol params used by most common protocols /// Protocol params used by most common protocols
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GenericProtocolParams { pub struct GenericProtocolParams {
@@ -77,25 +123,15 @@ impl FileTransferParams {
/// Returns whether a password is supposed to be required for this protocol params. /// Returns whether a password is supposed to be required for this protocol params.
/// The result true is returned ONLY if the supposed secret is MISSING!!! /// The result true is returned ONLY if the supposed secret is MISSING!!!
#[cfg(test)]
pub fn password_missing(&self) -> bool { pub fn password_missing(&self) -> bool {
match &self.params { self.params.password_missing()
ProtocolParams::AwsS3(params) => params.password_missing(),
ProtocolParams::Generic(params) => params.password_missing(),
ProtocolParams::Kube(params) => params.password_missing(),
ProtocolParams::Smb(params) => params.password_missing(),
ProtocolParams::WebDAV(params) => params.password_missing(),
}
} }
/// Set the secret to ft params for the default secret field for this protocol /// Set the secret to ft params for the default secret field for this protocol
#[cfg(test)]
pub fn set_default_secret(&mut self, secret: String) { pub fn set_default_secret(&mut self, secret: String) {
match &mut self.params { self.params.set_default_secret(secret);
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
ProtocolParams::Generic(params) => params.set_default_secret(secret),
ProtocolParams::Kube(params) => params.set_default_secret(secret),
ProtocolParams::Smb(params) => params.set_default_secret(secret),
ProtocolParams::WebDAV(params) => params.set_default_secret(secret),
}
} }
} }
+12
View File
@@ -12,6 +12,18 @@ use super::HostResult;
/// implement a real bridge when the resource is first loaded on the local /// implement a real bridge when the resource is first loaded on the local
/// filesystem and then processed on the remote. /// filesystem and then processed on the remote.
pub trait HostBridge { pub trait HostBridge {
/// Connect to host
fn connect(&mut self) -> HostResult<()>;
/// Disconnect from host
fn disconnect(&mut self) -> HostResult<()>;
/// Returns whether the host is connected
fn is_connected(&mut self) -> bool;
/// Returns whether the host is localhost
fn is_localhost(&self) -> bool;
/// Print working directory /// Print working directory
fn pwd(&mut self) -> HostResult<PathBuf>; fn pwd(&mut self) -> HostResult<PathBuf>;
+16
View File
@@ -62,6 +62,22 @@ impl Localhost {
} }
impl HostBridge for Localhost { impl HostBridge for Localhost {
fn connect(&mut self) -> HostResult<()> {
Ok(())
}
fn disconnect(&mut self) -> HostResult<()> {
Ok(())
}
fn is_connected(&mut self) -> bool {
true
}
fn is_localhost(&self) -> bool {
true
}
fn pwd(&mut self) -> HostResult<PathBuf> { fn pwd(&mut self) -> HostResult<PathBuf> {
Ok(self.wrkdir.clone()) Ok(self.wrkdir.clone())
} }
+18 -1
View File
@@ -49,8 +49,25 @@ impl From<Box<dyn RemoteFs>> for RemoteBridged {
} }
impl HostBridge for RemoteBridged { impl HostBridge for RemoteBridged {
fn connect(&mut self) -> HostResult<()> {
self.remote.connect().map(|_| ()).map_err(HostError::from)
}
fn disconnect(&mut self) -> HostResult<()> {
self.remote.disconnect().map_err(HostError::from)
}
fn is_connected(&mut self) -> bool {
self.remote.is_connected()
}
fn is_localhost(&self) -> bool {
false
}
fn pwd(&mut self) -> HostResult<PathBuf> { fn pwd(&mut self) -> HostResult<PathBuf> {
todo!() debug!("Getting working directory");
self.remote.pwd().map_err(HostError::from)
} }
fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult<PathBuf> { fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult<PathBuf> {
+33 -70
View File
@@ -1,5 +1,13 @@
const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION"); mod activity_manager;
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); mod cli;
mod config;
mod explorer;
mod filetransfer;
mod host;
mod support;
mod system;
mod ui;
mod utils;
// Crates // Crates
#[macro_use] #[macro_use]
@@ -13,28 +21,18 @@ extern crate log;
#[macro_use] #[macro_use]
extern crate magic_crypt; extern crate magic_crypt;
// External libs
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::time::Duration; use std::time::Duration;
// Include use self::activity_manager::{ActivityManager, NextActivity};
mod activity_manager; use self::cli::{Args, ArgsSubcommands, RemoteArgs, RunOpts, Task};
mod cli_opts; use self::system::logging::{self, LogLevel};
mod config;
mod explorer;
mod filetransfer;
mod host;
mod support;
mod system;
mod ui;
mod utils;
// namespaces const EXIT_CODE_SUCCESS: i32 = 0;
use activity_manager::{ActivityManager, NextActivity}; const EXIT_CODE_ERROR: i32 = 1;
use cli_opts::{Args, ArgsSubcommands, BookmarkParams, HostParams, Remote, RunOpts, Task}; const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
use filetransfer::FileTransferParams; const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
use system::logging::{self, LogLevel};
fn main() { fn main() {
let args: Args = argh::from_env(); let args: Args = argh::from_env();
@@ -84,9 +82,8 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
// Match ticks // Match ticks
run_opts.ticks = Duration::from_millis(args.ticks); run_opts.ticks = Duration::from_millis(args.ticks);
// Remote argument // Remote argument
match parse_address_arg(&args) { match RemoteArgs::try_from(&args) {
Err(err) => return Err(err), Err(err) => return Err(err),
Ok(Remote::None) => {}
Ok(remote) => { Ok(remote) => {
// Set params // Set params
run_opts.remote = remote; run_opts.remote = remote;
@@ -96,10 +93,8 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
} }
// Local directory // Local directory
if let Some(localdir) = args.positional.get(1) { if let Some(localdir) = run_opts.remote.local_dir.as_deref() {
// Change working directory if local dir is set if let Err(err) = env::set_current_dir(localdir) {
let localdir: PathBuf = PathBuf::from(localdir);
if let Err(err) = env::set_current_dir(localdir.as_path()) {
return Err(format!("Bad working directory argument: {err}")); return Err(format!("Bad working directory argument: {err}"));
} }
} }
@@ -111,29 +106,6 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
Ok(run_opts) Ok(run_opts)
} }
/// Parse address argument from cli args
fn parse_address_arg(args: &Args) -> Result<Remote, String> {
if let Some(remote) = args.positional.first() {
if args.address_as_bookmark {
Ok(Remote::Bookmark(BookmarkParams::new(
remote,
args.password.as_ref(),
)))
} else {
// Parse address
parse_remote_address(remote.as_str())
.map(|x| Remote::Host(HostParams::new(x, args.password.as_deref())))
}
} else {
Ok(Remote::None)
}
}
/// Parse remote address
fn parse_remote_address(remote: &str) -> Result<FileTransferParams, String> {
utils::parser::parse_remote_opt(remote).map_err(|e| format!("Bad address option: {e}"))
}
/// Run task and return rc /// Run task and return rc
fn run(run_opts: RunOpts) -> i32 { fn run(run_opts: RunOpts) -> i32 {
match run_opts.task { match run_opts.task {
@@ -147,11 +119,11 @@ fn run_import_theme(theme: &Path) -> i32 {
match support::import_theme(theme) { match support::import_theme(theme) {
Ok(_) => { Ok(_) => {
println!("Theme has been successfully imported!"); println!("Theme has been successfully imported!");
0 EXIT_CODE_ERROR
} }
Err(err) => { Err(err) => {
eprintln!("{err}"); eprintln!("{err}");
1 EXIT_CODE_ERROR
} }
} }
} }
@@ -160,41 +132,32 @@ fn run_install_update() -> i32 {
match support::install_update() { match support::install_update() {
Ok(msg) => { Ok(msg) => {
println!("{msg}"); println!("{msg}");
0 EXIT_CODE_SUCCESS
} }
Err(err) => { Err(err) => {
eprintln!("Could not install update: {err}"); eprintln!("Could not install update: {err}");
1 EXIT_CODE_ERROR
} }
} }
} }
fn run_activity(activity: NextActivity, ticks: Duration, remote: Remote) -> i32 { fn run_activity(activity: NextActivity, ticks: Duration, remote_args: RemoteArgs) -> i32 {
// Create activity manager (and context too) // Create activity manager (and context too)
let mut manager: ActivityManager = match ActivityManager::new(ticks) { let mut manager: ActivityManager = match ActivityManager::new(ticks) {
Ok(m) => m, Ok(m) => m,
Err(err) => { Err(err) => {
eprintln!("Could not start activity manager: {err}"); eprintln!("Could not start activity manager: {err}");
return 1; return EXIT_CODE_ERROR;
} }
}; };
// Set file transfer params if set // Set file transfer params if set
match remote { if let Err(err) = manager.configure_remote_args(remote_args) {
Remote::Bookmark(BookmarkParams { name, password }) => { eprintln!("{err}");
if let Err(err) = manager.resolve_bookmark_name(&name, password.as_deref()) { return EXIT_CODE_ERROR;
eprintln!("{err}");
return 1;
}
}
Remote::Host(HostParams { params, password }) => {
if let Err(err) = manager.set_filetransfer_params(params, password.as_deref()) {
eprintln!("{err}");
return 1;
}
}
Remote::None => {}
} }
manager.run(activity); manager.run(activity);
0 EXIT_CODE_SUCCESS
} }
+1 -1
View File
@@ -275,7 +275,7 @@ impl Activity for AuthActivity {
fn on_create(&mut self, mut context: Context) { fn on_create(&mut self, mut context: Context) {
debug!("Initializing activity"); debug!("Initializing activity");
// Initialize file transfer params // Initialize file transfer params
context.set_ftparams(FileTransferParams::default()); context.set_remote_params(FileTransferParams::default());
// Set context // Set context
self.context = Some(context); self.context = Some(context);
// Clear terminal // Clear terminal
+1 -1
View File
@@ -29,7 +29,7 @@ impl AuthActivity {
Ok(params) => { Ok(params) => {
self.save_recent(); self.save_recent();
// Set file transfer params to context // Set file transfer params to context
self.context_mut().set_ftparams(params); self.context_mut().set_remote_params(params);
// Set exit reason // Set exit reason
self.exit_reason = Some(super::ExitReason::Connect); self.exit_reason = Some(super::ExitReason::Connect);
} }
+26 -13
View File
@@ -9,7 +9,7 @@ use tuirealm::{PollStrategy, Update};
use super::browser::FileExplorerTab; use super::browser::FileExplorerTab;
use super::{ConfigClient, FileTransferActivity, Id, LogLevel, LogRecord, TransferPayload}; use super::{ConfigClient, FileTransferActivity, Id, LogLevel, LogRecord, TransferPayload};
use crate::filetransfer::ProtocolParams; use crate::filetransfer::{HostBridgeParams, ProtocolParams};
use crate::system::environment; use crate::system::environment;
use crate::system::notifications::Notification; use crate::system::notifications::Notification;
use crate::utils::fmt::{fmt_millis, fmt_path_elide_ex}; use crate::utils::fmt::{fmt_millis, fmt_path_elide_ex};
@@ -105,8 +105,28 @@ impl FileTransferActivity {
/// Get remote hostname /// Get remote hostname
pub(super) fn get_remote_hostname(&self) -> String { pub(super) fn get_remote_hostname(&self) -> String {
let ft_params = self.context().ft_params().unwrap(); let ft_params = self.context().remote_params().unwrap();
match &ft_params.params { self.get_hostname(&ft_params.params)
}
pub(super) fn get_hostbridge_hostname(&self) -> String {
let host_bridge_params = self.context().host_bridge_params().unwrap();
match host_bridge_params {
HostBridgeParams::Localhost(_) => {
let hostname = match hostname::get() {
Ok(h) => h,
Err(_) => return String::from("localhost"),
};
let hostname: String = hostname.as_os_str().to_string_lossy().to_string();
let tokens: Vec<&str> = hostname.split('.').collect();
String::from(*tokens.first().unwrap_or(&"localhost"))
}
HostBridgeParams::Remote(_, params) => self.get_hostname(params),
}
}
fn get_hostname(&self, params: &ProtocolParams) -> String {
match params {
ProtocolParams::Generic(params) => params.address.clone(), ProtocolParams::Generic(params) => params.address.clone(),
ProtocolParams::AwsS3(params) => params.bucket_name.clone(), ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
ProtocolParams::Kube(params) => { ProtocolParams::Kube(params) => {
@@ -226,17 +246,10 @@ impl FileTransferActivity {
.size() .size()
.map(|x| (x.width / 2) - 2) .map(|x| (x.width / 2) - 2)
.unwrap_or(0) as usize; .unwrap_or(0) as usize;
let hostname: String = match hostname::get() { let hostname = self.get_hostbridge_hostname();
Ok(h) => {
let hostname: String = h.as_os_str().to_string_lossy().to_string();
let tokens: Vec<&str> = hostname.split('.').collect();
String::from(*tokens.first().unwrap_or(&"localhost"))
}
Err(_) => String::from("localhost"),
};
let hostname: String = format!( let hostname: String = format!(
"{}:{} ", "{hostname}:{} ",
hostname,
fmt_path_elide_ex( fmt_path_elide_ex(
self.host_bridge().wrkdir.as_path(), self.host_bridge().wrkdir.as_path(),
width, width,
+41 -9
View File
@@ -231,8 +231,10 @@ pub struct FileTransferActivity {
cache: Option<TempDir>, cache: Option<TempDir>,
/// Fs watcher /// Fs watcher
fswatcher: Option<FsWatcher>, fswatcher: Option<FsWatcher>,
/// connected once /// host bridge connected
connected: bool, host_bridge_connected: bool,
/// remote connected once
remote_connected: bool,
} }
impl FileTransferActivity { impl FileTransferActivity {
@@ -244,6 +246,9 @@ impl FileTransferActivity {
) -> Self { ) -> Self {
// Get config client // Get config client
let config_client: ConfigClient = Self::init_config_client(); let config_client: ConfigClient = Self::init_config_client();
// init host bridge
let host_bridge = HostBridgeBuilder::build(host_bridge_params, &config_client);
let host_bridge_connected = host_bridge.is_localhost();
Self { Self {
exit_reason: None, exit_reason: None,
context: None, context: None,
@@ -253,7 +258,7 @@ impl FileTransferActivity {
.default_input_listener(ticks), .default_input_listener(ticks),
), ),
redraw: true, redraw: true,
host_bridge: HostBridgeBuilder::build(host_bridge_params, &config_client), host_bridge,
client: RemoteFsBuilder::build( client: RemoteFsBuilder::build(
remote_params.protocol, remote_params.protocol,
remote_params.params.clone(), remote_params.params.clone(),
@@ -274,7 +279,8 @@ impl FileTransferActivity {
None None
} }
}, },
connected: false, host_bridge_connected,
remote_connected: false,
} }
} }
@@ -371,7 +377,10 @@ impl Activity for FileTransferActivity {
error!("Failed to enter raw mode: {}", err); error!("Failed to enter raw mode: {}", err);
} }
// Get files at current pwd // Get files at current pwd
self.reload_host_bridge_dir(); if self.host_bridge.is_localhost() {
debug!("Reloading host bridge directory");
self.reload_host_bridge_dir();
}
debug!("Read working directory"); debug!("Read working directory");
// Configure text editor // Configure text editor
self.setup_text_editor(); self.setup_text_editor();
@@ -394,15 +403,34 @@ impl Activity for FileTransferActivity {
if self.context.is_none() { if self.context.is_none() {
return; return;
} }
// Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error) // Check if connected to host bridge (popup must be None, otherwise would try reconnecting in loop in case of error)
if (!self.client.is_connected() || !self.connected) && !self.app.mounted(&Id::FatalPopup) { if (!self.host_bridge.is_connected() || !self.host_bridge_connected)
let ftparams = self.context().ft_params().unwrap(); && !self.app.mounted(&Id::FatalPopup)
&& !self.host_bridge.is_localhost()
{
let host_bridge_params = self.context().host_bridge_params().unwrap();
let ft_params = host_bridge_params.unwrap_protocol_params();
// print params
let msg: String = Self::get_connection_msg(ft_params);
// Set init state to connecting popup
self.mount_blocking_wait(msg.as_str());
// Connect to remote
self.connect_to_host_bridge();
// Redraw
self.redraw = true;
}
// Check if connected to remote (popup must be None, otherwise would try reconnecting in loop in case of error)
if (!self.client.is_connected() || !self.remote_connected)
&& !self.app.mounted(&Id::FatalPopup)
&& self.host_bridge.is_connected()
{
let ftparams = self.context().remote_params().unwrap();
// print params // print params
let msg: String = Self::get_connection_msg(&ftparams.params); let msg: String = Self::get_connection_msg(&ftparams.params);
// Set init state to connecting popup // Set init state to connecting popup
self.mount_blocking_wait(msg.as_str()); self.mount_blocking_wait(msg.as_str());
// Connect to remote // Connect to remote
self.connect(); self.connect_to_remote();
// Redraw // Redraw
self.redraw = true; self.redraw = true;
} }
@@ -442,6 +470,10 @@ impl Activity for FileTransferActivity {
if self.client.is_connected() { if self.client.is_connected() {
let _ = self.client.disconnect(); let _ = self.client.disconnect();
} }
// disconnect host bridge
if self.host_bridge.is_connected() {
let _ = self.host_bridge.disconnect();
}
self.context.take() self.context.take()
} }
} }
+87 -8
View File
@@ -45,15 +45,57 @@ pub(super) enum TransferPayload {
} }
impl FileTransferActivity { impl FileTransferActivity {
pub(super) fn connect_to_host_bridge(&mut self) {
let ft_params = self.context().remote_params().unwrap().clone();
let entry_dir: Option<PathBuf> = ft_params.local_path;
// Connect to host bridge
match self.host_bridge.connect() {
Ok(()) => {
self.host_bridge_connected = self.host_bridge.is_connected();
if !self.host_bridge_connected {
return;
}
// Log welcome
self.log(
LogLevel::Info,
format!(
"Established connection with '{}'",
self.get_hostbridge_hostname()
),
);
// Try to change directory to entry directory
let mut remote_chdir: Option<PathBuf> = None;
if let Some(remote_path) = &entry_dir {
remote_chdir = Some(remote_path.clone());
}
if let Some(remote_path) = remote_chdir {
self.local_changedir(remote_path.as_path(), false);
}
// Set state to explorer
self.umount_wait();
self.reload_host_bridge_dir();
// Update file lists
self.update_host_bridge_filelist();
}
Err(err) => {
// Set popup fatal error
self.umount_wait();
self.mount_fatal(err.to_string());
}
}
}
/// Connect to remote /// Connect to remote
pub(super) fn connect(&mut self) { pub(super) fn connect_to_remote(&mut self) {
let ft_params = self.context().ft_params().unwrap().clone(); let ft_params = self.context().remote_params().unwrap().clone();
let entry_dir: Option<PathBuf> = ft_params.remote_path; let entry_dir: Option<PathBuf> = ft_params.remote_path;
// Connect to remote // Connect to remote
match self.client.connect() { match self.client.connect() {
Ok(Welcome { banner, .. }) => { Ok(Welcome { banner, .. }) => {
self.connected = self.client.is_connected(); self.remote_connected = self.client.is_connected();
if !self.connected { if !self.remote_connected {
return; return;
} }
@@ -119,7 +161,7 @@ impl FileTransferActivity {
/// Reload remote directory entries and update browser /// Reload remote directory entries and update browser
pub(super) fn reload_remote_dir(&mut self) { pub(super) fn reload_remote_dir(&mut self) {
if !self.connected { if !self.remote_connected {
return; return;
} }
// Get current entries // Get current entries
@@ -146,11 +188,21 @@ impl FileTransferActivity {
/// Reload host_bridge directory entries and update browser /// Reload host_bridge directory entries and update browser
pub(super) fn reload_host_bridge_dir(&mut self) { pub(super) fn reload_host_bridge_dir(&mut self) {
if !self.host_bridge_connected {
return;
}
self.mount_blocking_wait("Loading host bridge directory..."); self.mount_blocking_wait("Loading host bridge directory...");
let Ok(wrkdir) = self.host_bridge.pwd() else { let wrkdir = match self.host_bridge.pwd() {
error!("failed to get host working directory"); Ok(wrkdir) => wrkdir,
return; Err(err) => {
self.log_and_alert(
LogLevel::Error,
format!("Could not scan current host bridge directory: {err}"),
);
return;
}
}; };
let res = self.host_bridge_scan(wrkdir.as_path()); let res = self.host_bridge_scan(wrkdir.as_path());
@@ -1122,6 +1174,33 @@ impl FileTransferActivity {
} }
} }
pub(super) fn local_changedir(&mut self, path: &Path, push: bool) {
// Get current directory
let prev_dir: PathBuf = self.host_bridge().wrkdir.clone();
// Change directory
match self.host_bridge.change_wrkdir(path) {
Ok(_) => {
self.log(
LogLevel::Info,
format!("Changed directory on host bridge: {}", path.display()),
);
// Update files
self.reload_host_bridge_dir();
// Push prev_dir to stack
if push {
self.host_bridge_mut().pushd(prev_dir.as_path())
}
}
Err(err) => {
// Report err
self.log_and_alert(
LogLevel::Error,
format!("Could not change working directory: {err}"),
);
}
}
}
pub(super) fn remote_changedir(&mut self, path: &Path, push: bool) { pub(super) fn remote_changedir(&mut self, path: &Path, push: bool) {
// Get current directory // Get current directory
let prev_dir: PathBuf = self.remote().wrkdir.clone(); let prev_dir: PathBuf = self.remote().wrkdir.clone();
+17 -7
View File
@@ -6,14 +6,15 @@
use tuirealm::terminal::TerminalBridge; use tuirealm::terminal::TerminalBridge;
use super::store::Store; use super::store::Store;
use crate::filetransfer::FileTransferParams; use crate::filetransfer::{FileTransferParams, HostBridgeParams};
use crate::system::bookmarks_client::BookmarksClient; use crate::system::bookmarks_client::BookmarksClient;
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
use crate::system::theme_provider::ThemeProvider; use crate::system::theme_provider::ThemeProvider;
/// Context holds data structures shared by the activities /// Context holds data structures shared by the activities
pub struct Context { pub struct Context {
ft_params: Option<FileTransferParams>, host_bridge_params: Option<HostBridgeParams>,
remote_params: Option<FileTransferParams>,
bookmarks_client: Option<BookmarksClient>, bookmarks_client: Option<BookmarksClient>,
config_client: ConfigClient, config_client: ConfigClient,
pub(crate) store: Store, pub(crate) store: Store,
@@ -33,7 +34,8 @@ impl Context {
let mut ctx = Context { let mut ctx = Context {
bookmarks_client, bookmarks_client,
config_client, config_client,
ft_params: None, host_bridge_params: None,
remote_params: None,
store: Store::init(), store: Store::init(),
terminal: TerminalBridge::new().expect("Could not initialize terminal"), terminal: TerminalBridge::new().expect("Could not initialize terminal"),
theme_provider, theme_provider,
@@ -49,8 +51,12 @@ impl Context {
// -- getters // -- getters
pub fn ft_params(&self) -> Option<&FileTransferParams> { pub fn remote_params(&self) -> Option<&FileTransferParams> {
self.ft_params.as_ref() self.remote_params.as_ref()
}
pub fn host_bridge_params(&self) -> Option<&HostBridgeParams> {
self.host_bridge_params.as_ref()
} }
pub fn bookmarks_client(&self) -> Option<&BookmarksClient> { pub fn bookmarks_client(&self) -> Option<&BookmarksClient> {
@@ -91,8 +97,12 @@ impl Context {
// -- setter // -- setter
pub fn set_ftparams(&mut self, params: FileTransferParams) { pub fn set_remote_params(&mut self, params: FileTransferParams) {
self.ft_params = Some(params); self.remote_params = Some(params);
}
pub fn set_host_bridge_params(&mut self, params: HostBridgeParams) {
self.host_bridge_params = Some(params);
} }
// -- error // -- error
+1 -1
View File
@@ -7,7 +7,7 @@ use tuirealm::terminal::TerminalBridge;
/// Read a secret from tty with customisable prompt /// Read a secret from tty with customisable prompt
pub fn read_secret_from_tty( pub fn read_secret_from_tty(
terminal_bridge: &mut TerminalBridge, terminal_bridge: &mut TerminalBridge,
prompt: &str, prompt: impl ToString,
) -> std::io::Result<Option<String>> { ) -> std::io::Result<Option<String>> {
let _ = terminal_bridge.disable_raw_mode(); let _ = terminal_bridge.disable_raw_mode();
let _ = terminal_bridge.leave_alternate_screen(); let _ = terminal_bridge.leave_alternate_screen();