mirror of
https://github.com/veeso/termscp.git
synced 2026-09-25 13:31:26 -07:00
feat: cli args for double remote
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
¶ms.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, ¶ms.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
|
||||||
|
|||||||
@@ -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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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,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>;
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user