mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
feat: cli args for double remote
This commit is contained in:
@@ -42,6 +42,10 @@
|
||||
|
||||
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
|
||||
|
||||
## 0.15.0
|
||||
|
||||
@@ -62,11 +62,11 @@
|
||||
|
||||
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
|
||||
|
||||
`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
|
||||
- `-b, --address-as-bookmark` löst das Adressargument als Lesezeichenname auf
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
|
||||
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
|
||||
|
||||
`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
|
||||
- `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
|
||||
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
|
||||
|
||||
`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
|
||||
- `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
|
||||
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
|
||||
|
||||
`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
|
||||
- `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro
|
||||
|
||||
@@ -40,13 +40,15 @@
|
||||
|
||||
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
|
||||
|
||||
`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
|
||||
- `-q, --quiet` Disable logging
|
||||
- `-v, --version` Print version info
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
|
||||
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
|
||||
|
||||
`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
|
||||
- `-b, --address-as-bookmark` resolve o argumento do endereço como um nome de favorito
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
|
||||
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>` 登陆密码
|
||||
- `-b, --address-as-bookmark` 将地址参数解析为书签名称
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
//!
|
||||
//! `activity_manager` is the module which provides run methods and handling for activities
|
||||
|
||||
// Deps
|
||||
// Namespaces
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
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::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
@@ -30,6 +32,16 @@ pub enum NextActivity {
|
||||
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
|
||||
pub struct ActivityManager {
|
||||
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
|
||||
pub fn set_filetransfer_params(
|
||||
pub fn set_host_params(
|
||||
&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>,
|
||||
) -> Result<(), String> {
|
||||
// Set password if provided
|
||||
@@ -73,13 +175,13 @@ impl ActivityManager {
|
||||
if let Some(password) = password {
|
||||
params.set_default_secret(password.to_string());
|
||||
} else if matches!(
|
||||
params.protocol,
|
||||
protocol,
|
||||
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
|
||||
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
|
||||
.resolve(
|
||||
&generic_params.address,
|
||||
@@ -94,7 +196,7 @@ impl ActivityManager {
|
||||
"storage could not find any suitable key for {}... prompting for password",
|
||||
generic_params.address
|
||||
);
|
||||
self.prompt_password(&mut params)?;
|
||||
self.prompt_password(params)?;
|
||||
} else {
|
||||
debug!(
|
||||
"a key is already set for {}; password is not required",
|
||||
@@ -102,19 +204,19 @@ impl ActivityManager {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.prompt_password(&mut params)?;
|
||||
self.prompt_password(params)?;
|
||||
}
|
||||
}
|
||||
// Put params into the context
|
||||
self.context.as_mut().unwrap().set_ftparams(params);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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 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}")),
|
||||
Ok(Some(secret)) => {
|
||||
debug!(
|
||||
@@ -132,16 +234,28 @@ impl ActivityManager {
|
||||
/// Returns error if bookmark is not found
|
||||
pub fn resolve_bookmark_name(
|
||||
&mut self,
|
||||
host: Host,
|
||||
bookmark_name: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(bookmarks_client) = self.context.as_mut().unwrap().bookmarks_client_mut() {
|
||||
match bookmarks_client.get_bookmark(bookmark_name) {
|
||||
None => Err(format!(
|
||||
r#"Could not resolve bookmark name: "{bookmark_name}" no such bookmark"#
|
||||
)),
|
||||
Some(params) => self.set_filetransfer_params(params, password),
|
||||
}
|
||||
let params = match bookmarks_client.get_bookmark(bookmark_name) {
|
||||
None => {
|
||||
return Err(format!(
|
||||
r#"Could not resolve bookmark name: "{bookmark_name}" no such bookmark"#
|
||||
))
|
||||
}
|
||||
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 {
|
||||
Err(String::from(
|
||||
"Could not resolve bookmark name: bookmarks client not initialized",
|
||||
@@ -235,8 +349,17 @@ impl ActivityManager {
|
||||
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
|
||||
let remote_params: &FileTransferParams = match ctx.ft_params() {
|
||||
let remote_params: &FileTransferParams = match ctx.remote_params() {
|
||||
Some(ft_params) => ft_params,
|
||||
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 =
|
||||
FileTransferActivity::new(host_bridge_params, remote_params, self.ticks);
|
||||
// Prepare result
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
//!
|
||||
//! defines the types for main.rs types
|
||||
|
||||
mod remote;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use argh::FromArgs;
|
||||
pub use remote::{Remote, RemoteArgs};
|
||||
|
||||
use crate::activity_manager::NextActivity;
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::system::logging::LogLevel;
|
||||
|
||||
pub enum Task {
|
||||
@@ -17,7 +19,7 @@ pub enum Task {
|
||||
InstallUpdate,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[derive(Default, FromArgs)]
|
||||
#[argh(description = "
|
||||
where positional can be:
|
||||
- [address_a] [address_b] [local-wrkdir]
|
||||
@@ -39,14 +41,15 @@ pub struct Args {
|
||||
#[argh(subcommand)]
|
||||
pub nested: Option<ArgsSubcommands>,
|
||||
/// resolve address argument as a bookmark name
|
||||
#[argh(switch, short = 'b')]
|
||||
pub address_as_bookmark: bool,
|
||||
#[argh(option, short = 'b')]
|
||||
pub bookmark: Vec<String>,
|
||||
/// enable TRACE log level
|
||||
#[argh(switch, short = 'D')]
|
||||
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')]
|
||||
pub password: Option<String>,
|
||||
pub password: Vec<String>,
|
||||
/// disable logging
|
||||
#[argh(switch, short = 'q')]
|
||||
pub quiet: bool,
|
||||
@@ -57,10 +60,7 @@ pub struct Args {
|
||||
#[argh(switch, short = 'v')]
|
||||
pub version: bool,
|
||||
// -- positional
|
||||
#[argh(
|
||||
positional,
|
||||
description = "protocol://user@address:port:wrkdir local-wrkdir"
|
||||
)]
|
||||
#[argh(positional, description = "address1 address2 local-wrkdir")]
|
||||
pub positional: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ pub struct LoadThemeArgs {
|
||||
}
|
||||
|
||||
pub struct RunOpts {
|
||||
pub remote: Remote,
|
||||
pub remote: RemoteArgs,
|
||||
pub ticks: Duration,
|
||||
pub log_level: LogLevel,
|
||||
pub task: Task,
|
||||
@@ -124,45 +124,10 @@ impl RunOpts {
|
||||
impl Default for RunOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
remote: Remote::None,
|
||||
remote: RemoteArgs::default(),
|
||||
ticks: Duration::from_millis(10),
|
||||
log_level: LogLevel::Info,
|
||||
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
src/cli/remote.rs
Normal file
262
src/cli/remote.rs
Normal 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")));
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,15 @@ pub enum HostBridgeParams {
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileTransferParams {
|
||||
@@ -43,6 +52,43 @@ pub enum ProtocolParams {
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GenericProtocolParams {
|
||||
@@ -77,25 +123,15 @@ impl FileTransferParams {
|
||||
|
||||
/// 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!!!
|
||||
#[cfg(test)]
|
||||
pub fn password_missing(&self) -> bool {
|
||||
match &self.params {
|
||||
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(),
|
||||
}
|
||||
self.params.password_missing()
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
match &mut self.params {
|
||||
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),
|
||||
}
|
||||
self.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
|
||||
/// filesystem and then processed on the remote.
|
||||
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
|
||||
fn pwd(&mut self) -> HostResult<PathBuf>;
|
||||
|
||||
|
||||
@@ -62,6 +62,22 @@ impl 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> {
|
||||
Ok(self.wrkdir.clone())
|
||||
}
|
||||
|
||||
@@ -49,8 +49,25 @@ impl From<Box<dyn RemoteFs>> 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> {
|
||||
todo!()
|
||||
debug!("Getting working directory");
|
||||
self.remote.pwd().map_err(HostError::from)
|
||||
}
|
||||
|
||||
fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult<PathBuf> {
|
||||
|
||||
103
src/main.rs
103
src/main.rs
@@ -1,5 +1,13 @@
|
||||
const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
mod activity_manager;
|
||||
mod cli;
|
||||
mod config;
|
||||
mod explorer;
|
||||
mod filetransfer;
|
||||
mod host;
|
||||
mod support;
|
||||
mod system;
|
||||
mod ui;
|
||||
mod utils;
|
||||
|
||||
// Crates
|
||||
#[macro_use]
|
||||
@@ -13,28 +21,18 @@ extern crate log;
|
||||
#[macro_use]
|
||||
extern crate magic_crypt;
|
||||
|
||||
// External libs
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
// Include
|
||||
mod activity_manager;
|
||||
mod cli_opts;
|
||||
mod config;
|
||||
mod explorer;
|
||||
mod filetransfer;
|
||||
mod host;
|
||||
mod support;
|
||||
mod system;
|
||||
mod ui;
|
||||
mod utils;
|
||||
use self::activity_manager::{ActivityManager, NextActivity};
|
||||
use self::cli::{Args, ArgsSubcommands, RemoteArgs, RunOpts, Task};
|
||||
use self::system::logging::{self, LogLevel};
|
||||
|
||||
// namespaces
|
||||
use activity_manager::{ActivityManager, NextActivity};
|
||||
use cli_opts::{Args, ArgsSubcommands, BookmarkParams, HostParams, Remote, RunOpts, Task};
|
||||
use filetransfer::FileTransferParams;
|
||||
use system::logging::{self, LogLevel};
|
||||
const EXIT_CODE_SUCCESS: i32 = 0;
|
||||
const EXIT_CODE_ERROR: i32 = 1;
|
||||
const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
|
||||
fn main() {
|
||||
let args: Args = argh::from_env();
|
||||
@@ -84,9 +82,8 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||
// Match ticks
|
||||
run_opts.ticks = Duration::from_millis(args.ticks);
|
||||
// Remote argument
|
||||
match parse_address_arg(&args) {
|
||||
match RemoteArgs::try_from(&args) {
|
||||
Err(err) => return Err(err),
|
||||
Ok(Remote::None) => {}
|
||||
Ok(remote) => {
|
||||
// Set params
|
||||
run_opts.remote = remote;
|
||||
@@ -96,10 +93,8 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||
}
|
||||
|
||||
// Local directory
|
||||
if let Some(localdir) = args.positional.get(1) {
|
||||
// Change working directory if local dir is set
|
||||
let localdir: PathBuf = PathBuf::from(localdir);
|
||||
if let Err(err) = env::set_current_dir(localdir.as_path()) {
|
||||
if let Some(localdir) = run_opts.remote.local_dir.as_deref() {
|
||||
if let Err(err) = env::set_current_dir(localdir) {
|
||||
return Err(format!("Bad working directory argument: {err}"));
|
||||
}
|
||||
}
|
||||
@@ -111,29 +106,6 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||
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
|
||||
fn run(run_opts: RunOpts) -> i32 {
|
||||
match run_opts.task {
|
||||
@@ -147,11 +119,11 @@ fn run_import_theme(theme: &Path) -> i32 {
|
||||
match support::import_theme(theme) {
|
||||
Ok(_) => {
|
||||
println!("Theme has been successfully imported!");
|
||||
0
|
||||
EXIT_CODE_ERROR
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
1
|
||||
EXIT_CODE_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,41 +132,32 @@ fn run_install_update() -> i32 {
|
||||
match support::install_update() {
|
||||
Ok(msg) => {
|
||||
println!("{msg}");
|
||||
0
|
||||
EXIT_CODE_SUCCESS
|
||||
}
|
||||
Err(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)
|
||||
let mut manager: ActivityManager = match ActivityManager::new(ticks) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
eprintln!("Could not start activity manager: {err}");
|
||||
return 1;
|
||||
return EXIT_CODE_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
// Set file transfer params if set
|
||||
match remote {
|
||||
Remote::Bookmark(BookmarkParams { name, password }) => {
|
||||
if let Err(err) = manager.resolve_bookmark_name(&name, password.as_deref()) {
|
||||
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 => {}
|
||||
if let Err(err) = manager.configure_remote_args(remote_args) {
|
||||
eprintln!("{err}");
|
||||
return EXIT_CODE_ERROR;
|
||||
}
|
||||
|
||||
manager.run(activity);
|
||||
|
||||
0
|
||||
EXIT_CODE_SUCCESS
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ impl Activity for AuthActivity {
|
||||
fn on_create(&mut self, mut context: Context) {
|
||||
debug!("Initializing activity");
|
||||
// Initialize file transfer params
|
||||
context.set_ftparams(FileTransferParams::default());
|
||||
context.set_remote_params(FileTransferParams::default());
|
||||
// Set context
|
||||
self.context = Some(context);
|
||||
// Clear terminal
|
||||
|
||||
@@ -29,7 +29,7 @@ impl AuthActivity {
|
||||
Ok(params) => {
|
||||
self.save_recent();
|
||||
// Set file transfer params to context
|
||||
self.context_mut().set_ftparams(params);
|
||||
self.context_mut().set_remote_params(params);
|
||||
// Set exit reason
|
||||
self.exit_reason = Some(super::ExitReason::Connect);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use tuirealm::{PollStrategy, Update};
|
||||
|
||||
use super::browser::FileExplorerTab;
|
||||
use super::{ConfigClient, FileTransferActivity, Id, LogLevel, LogRecord, TransferPayload};
|
||||
use crate::filetransfer::ProtocolParams;
|
||||
use crate::filetransfer::{HostBridgeParams, ProtocolParams};
|
||||
use crate::system::environment;
|
||||
use crate::system::notifications::Notification;
|
||||
use crate::utils::fmt::{fmt_millis, fmt_path_elide_ex};
|
||||
@@ -105,8 +105,28 @@ impl FileTransferActivity {
|
||||
|
||||
/// Get remote hostname
|
||||
pub(super) fn get_remote_hostname(&self) -> String {
|
||||
let ft_params = self.context().ft_params().unwrap();
|
||||
match &ft_params.params {
|
||||
let ft_params = self.context().remote_params().unwrap();
|
||||
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::AwsS3(params) => params.bucket_name.clone(),
|
||||
ProtocolParams::Kube(params) => {
|
||||
@@ -226,17 +246,10 @@ impl FileTransferActivity {
|
||||
.size()
|
||||
.map(|x| (x.width / 2) - 2)
|
||||
.unwrap_or(0) as usize;
|
||||
let hostname: String = match hostname::get() {
|
||||
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 = self.get_hostbridge_hostname();
|
||||
|
||||
let hostname: String = format!(
|
||||
"{}:{} ",
|
||||
hostname,
|
||||
"{hostname}:{} ",
|
||||
fmt_path_elide_ex(
|
||||
self.host_bridge().wrkdir.as_path(),
|
||||
width,
|
||||
|
||||
@@ -231,8 +231,10 @@ pub struct FileTransferActivity {
|
||||
cache: Option<TempDir>,
|
||||
/// Fs watcher
|
||||
fswatcher: Option<FsWatcher>,
|
||||
/// connected once
|
||||
connected: bool,
|
||||
/// host bridge connected
|
||||
host_bridge_connected: bool,
|
||||
/// remote connected once
|
||||
remote_connected: bool,
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -244,6 +246,9 @@ impl FileTransferActivity {
|
||||
) -> Self {
|
||||
// Get 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 {
|
||||
exit_reason: None,
|
||||
context: None,
|
||||
@@ -253,7 +258,7 @@ impl FileTransferActivity {
|
||||
.default_input_listener(ticks),
|
||||
),
|
||||
redraw: true,
|
||||
host_bridge: HostBridgeBuilder::build(host_bridge_params, &config_client),
|
||||
host_bridge,
|
||||
client: RemoteFsBuilder::build(
|
||||
remote_params.protocol,
|
||||
remote_params.params.clone(),
|
||||
@@ -274,7 +279,8 @@ impl FileTransferActivity {
|
||||
None
|
||||
}
|
||||
},
|
||||
connected: false,
|
||||
host_bridge_connected,
|
||||
remote_connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +377,10 @@ impl Activity for FileTransferActivity {
|
||||
error!("Failed to enter raw mode: {}", err);
|
||||
}
|
||||
// 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");
|
||||
// Configure text editor
|
||||
self.setup_text_editor();
|
||||
@@ -394,15 +403,34 @@ impl Activity for FileTransferActivity {
|
||||
if self.context.is_none() {
|
||||
return;
|
||||
}
|
||||
// Check if connected (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) {
|
||||
let ftparams = self.context().ft_params().unwrap();
|
||||
// Check if connected to host bridge (popup must be None, otherwise would try reconnecting in loop in case of error)
|
||||
if (!self.host_bridge.is_connected() || !self.host_bridge_connected)
|
||||
&& !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
|
||||
let msg: String = Self::get_connection_msg(&ftparams.params);
|
||||
// Set init state to connecting popup
|
||||
self.mount_blocking_wait(msg.as_str());
|
||||
// Connect to remote
|
||||
self.connect();
|
||||
self.connect_to_remote();
|
||||
// Redraw
|
||||
self.redraw = true;
|
||||
}
|
||||
@@ -442,6 +470,10 @@ impl Activity for FileTransferActivity {
|
||||
if self.client.is_connected() {
|
||||
let _ = self.client.disconnect();
|
||||
}
|
||||
// disconnect host bridge
|
||||
if self.host_bridge.is_connected() {
|
||||
let _ = self.host_bridge.disconnect();
|
||||
}
|
||||
self.context.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,15 +45,57 @@ pub(super) enum TransferPayload {
|
||||
}
|
||||
|
||||
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
|
||||
pub(super) fn connect(&mut self) {
|
||||
let ft_params = self.context().ft_params().unwrap().clone();
|
||||
pub(super) fn connect_to_remote(&mut self) {
|
||||
let ft_params = self.context().remote_params().unwrap().clone();
|
||||
let entry_dir: Option<PathBuf> = ft_params.remote_path;
|
||||
// Connect to remote
|
||||
match self.client.connect() {
|
||||
Ok(Welcome { banner, .. }) => {
|
||||
self.connected = self.client.is_connected();
|
||||
if !self.connected {
|
||||
self.remote_connected = self.client.is_connected();
|
||||
if !self.remote_connected {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,7 +161,7 @@ impl FileTransferActivity {
|
||||
|
||||
/// Reload remote directory entries and update browser
|
||||
pub(super) fn reload_remote_dir(&mut self) {
|
||||
if !self.connected {
|
||||
if !self.remote_connected {
|
||||
return;
|
||||
}
|
||||
// Get current entries
|
||||
@@ -146,11 +188,21 @@ impl FileTransferActivity {
|
||||
|
||||
/// Reload host_bridge directory entries and update browser
|
||||
pub(super) fn reload_host_bridge_dir(&mut self) {
|
||||
if !self.host_bridge_connected {
|
||||
return;
|
||||
}
|
||||
|
||||
self.mount_blocking_wait("Loading host bridge directory...");
|
||||
|
||||
let Ok(wrkdir) = self.host_bridge.pwd() else {
|
||||
error!("failed to get host working directory");
|
||||
return;
|
||||
let wrkdir = match self.host_bridge.pwd() {
|
||||
Ok(wrkdir) => wrkdir,
|
||||
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());
|
||||
@@ -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) {
|
||||
// Get current directory
|
||||
let prev_dir: PathBuf = self.remote().wrkdir.clone();
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
use tuirealm::terminal::TerminalBridge;
|
||||
|
||||
use super::store::Store;
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::filetransfer::{FileTransferParams, HostBridgeParams};
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::theme_provider::ThemeProvider;
|
||||
|
||||
/// Context holds data structures shared by the activities
|
||||
pub struct Context {
|
||||
ft_params: Option<FileTransferParams>,
|
||||
host_bridge_params: Option<HostBridgeParams>,
|
||||
remote_params: Option<FileTransferParams>,
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
config_client: ConfigClient,
|
||||
pub(crate) store: Store,
|
||||
@@ -33,7 +34,8 @@ impl Context {
|
||||
let mut ctx = Context {
|
||||
bookmarks_client,
|
||||
config_client,
|
||||
ft_params: None,
|
||||
host_bridge_params: None,
|
||||
remote_params: None,
|
||||
store: Store::init(),
|
||||
terminal: TerminalBridge::new().expect("Could not initialize terminal"),
|
||||
theme_provider,
|
||||
@@ -49,8 +51,12 @@ impl Context {
|
||||
|
||||
// -- getters
|
||||
|
||||
pub fn ft_params(&self) -> Option<&FileTransferParams> {
|
||||
self.ft_params.as_ref()
|
||||
pub fn remote_params(&self) -> Option<&FileTransferParams> {
|
||||
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> {
|
||||
@@ -91,8 +97,12 @@ impl Context {
|
||||
|
||||
// -- setter
|
||||
|
||||
pub fn set_ftparams(&mut self, params: FileTransferParams) {
|
||||
self.ft_params = Some(params);
|
||||
pub fn set_remote_params(&mut self, params: FileTransferParams) {
|
||||
self.remote_params = Some(params);
|
||||
}
|
||||
|
||||
pub fn set_host_bridge_params(&mut self, params: HostBridgeParams) {
|
||||
self.host_bridge_params = Some(params);
|
||||
}
|
||||
|
||||
// -- error
|
||||
|
||||
@@ -7,7 +7,7 @@ use tuirealm::terminal::TerminalBridge;
|
||||
/// Read a secret from tty with customisable prompt
|
||||
pub fn read_secret_from_tty(
|
||||
terminal_bridge: &mut TerminalBridge,
|
||||
prompt: &str,
|
||||
prompt: impl ToString,
|
||||
) -> std::io::Result<Option<String>> {
|
||||
let _ = terminal_bridge.disable_raw_mode();
|
||||
let _ = terminal_bridge.leave_alternate_screen();
|
||||
|
||||
Reference in New Issue
Block a user