mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Bookmark name as hostname for cli args (#111)
bookmark name as hostname for cli args
This commit is contained in:
committed by
GitHub
parent
f094979ddb
commit
e0d8b80cdf
@@ -28,6 +28,7 @@
|
||||
// Deps
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::host::{HostError, Localhost};
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::environment;
|
||||
use crate::system::theme_provider::ThemeProvider;
|
||||
@@ -36,6 +37,8 @@ use crate::ui::activities::{
|
||||
ExitReason,
|
||||
};
|
||||
use crate::ui::context::Context;
|
||||
use crate::utils::fmt;
|
||||
use crate::utils::tty;
|
||||
|
||||
// Namespaces
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -64,7 +67,7 @@ impl ActivityManager {
|
||||
pub fn new(local_dir: &Path, ticks: Duration) -> Result<ActivityManager, HostError> {
|
||||
// Prepare Context
|
||||
// Initialize configuration client
|
||||
let (config_client, error): (ConfigClient, Option<String>) =
|
||||
let (config_client, error_config): (ConfigClient, Option<String>) =
|
||||
match Self::init_config_client() {
|
||||
Ok(cli) => (cli, None),
|
||||
Err(err) => {
|
||||
@@ -72,8 +75,13 @@ impl ActivityManager {
|
||||
(ConfigClient::degraded(), Some(err))
|
||||
}
|
||||
};
|
||||
let (bookmarks_client, error_bookmark) = match Self::init_bookmarks_client() {
|
||||
Ok(cli) => (cli, None),
|
||||
Err(err) => (None, Some(err)),
|
||||
};
|
||||
let error = error_config.or(error_bookmark);
|
||||
let theme_provider: ThemeProvider = Self::init_theme_provider();
|
||||
let ctx: Context = Context::new(config_client, theme_provider, error);
|
||||
let ctx: Context = Context::new(bookmarks_client, config_client, theme_provider, error);
|
||||
Ok(ActivityManager {
|
||||
context: Some(ctx),
|
||||
local_dir: local_dir.to_path_buf(),
|
||||
@@ -82,9 +90,54 @@ impl ActivityManager {
|
||||
}
|
||||
|
||||
/// Set file transfer params
|
||||
pub fn set_filetransfer_params(&mut self, params: FileTransferParams) {
|
||||
pub fn set_filetransfer_params(
|
||||
&mut self,
|
||||
mut params: FileTransferParams,
|
||||
password: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
// Set password if provided
|
||||
if params.password_missing() {
|
||||
if let Some(password) = password {
|
||||
params.set_default_secret(password.to_string());
|
||||
} else {
|
||||
match tty::read_secret_from_tty("Password: ") {
|
||||
Err(err) => return Err(format!("Could not read password: {}", err)),
|
||||
Ok(Some(secret)) => {
|
||||
debug!(
|
||||
"Read password from tty: {}",
|
||||
fmt::shadow_password(secret.as_str())
|
||||
);
|
||||
params.set_default_secret(secret);
|
||||
}
|
||||
Ok(None) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Put params into the context
|
||||
self.context.as_mut().unwrap().set_ftparams(params);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve provided bookmark name and set it as file transfer params.
|
||||
/// Returns error if bookmark is not found
|
||||
pub fn resolve_bookmark_name(
|
||||
&mut self,
|
||||
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: "{}" no such bookmark"#,
|
||||
bookmark_name
|
||||
)),
|
||||
Some(params) => self.set_filetransfer_params(params, password),
|
||||
}
|
||||
} else {
|
||||
Err(String::from(
|
||||
"Could not resolve bookmark name: bookmarks client not initialized",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
@@ -256,6 +309,33 @@ impl ActivityManager {
|
||||
|
||||
// -- misc
|
||||
|
||||
fn init_bookmarks_client() -> Result<Option<BookmarksClient>, String> {
|
||||
// Get config dir
|
||||
match environment::init_config_dir() {
|
||||
Ok(path) => {
|
||||
// If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system.
|
||||
if let Some(config_dir_path) = path {
|
||||
let bookmarks_file: PathBuf =
|
||||
environment::get_bookmarks_paths(config_dir_path.as_path());
|
||||
// Initialize client
|
||||
BookmarksClient::new(bookmarks_file.as_path(), config_dir_path.as_path(), 16)
|
||||
.map(Option::Some)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
||||
bookmarks_file.display(),
|
||||
config_dir_path.display(),
|
||||
e
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize configuration client
|
||||
fn init_config_client() -> Result<ConfigClient, String> {
|
||||
// Get config dir
|
||||
|
||||
148
src/cli_opts.rs
Normal file
148
src/cli_opts.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//! ## CLI opts
|
||||
//!
|
||||
//! defines the types for main.rs types
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use argh::FromArgs;
|
||||
|
||||
use crate::activity_manager::NextActivity;
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::system::logging::LogLevel;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
pub enum Task {
|
||||
Activity(NextActivity),
|
||||
ImportTheme(PathBuf),
|
||||
InstallUpdate,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(description = "
|
||||
where positional can be:
|
||||
- [address] [local-wrkdir]
|
||||
OR
|
||||
- [bookmark-Name] [local-wrkdir]
|
||||
|
||||
Address syntax can be:
|
||||
|
||||
- `protocol://user@address:port:wrkdir` for protocols such as Sftp, Scp, Ftp
|
||||
- `s3://bucket-name@region:profile:/wrkdir` for Aws S3 protocol
|
||||
|
||||
Please, report issues to <https://github.com/veeso/termscp>
|
||||
Please, consider supporting the author <https://ko-fi.com/veeso>")]
|
||||
pub struct Args {
|
||||
#[argh(
|
||||
switch,
|
||||
short = 'b',
|
||||
description = "resolve address argument as a bookmark name"
|
||||
)]
|
||||
pub address_as_bookmark: bool,
|
||||
#[argh(switch, short = 'c', description = "open termscp configuration")]
|
||||
pub config: bool,
|
||||
#[argh(switch, short = 'D', description = "enable TRACE log level")]
|
||||
pub debug: bool,
|
||||
#[argh(option, short = 'P', description = "provide password from CLI")]
|
||||
pub password: Option<String>,
|
||||
#[argh(switch, short = 'q', description = "disable logging")]
|
||||
pub quiet: bool,
|
||||
#[argh(option, short = 't', description = "import specified theme")]
|
||||
pub theme: Option<String>,
|
||||
#[argh(
|
||||
switch,
|
||||
short = 'u',
|
||||
description = "update termscp to the latest version"
|
||||
)]
|
||||
pub update: bool,
|
||||
#[argh(
|
||||
option,
|
||||
short = 'T',
|
||||
default = "10",
|
||||
description = "set UI ticks; default 10ms"
|
||||
)]
|
||||
pub ticks: u64,
|
||||
#[argh(switch, short = 'v', description = "print version")]
|
||||
pub version: bool,
|
||||
// -- positional
|
||||
#[argh(
|
||||
positional,
|
||||
description = "protocol://user@address:port:wrkdir local-wrkdir"
|
||||
)]
|
||||
pub positional: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct RunOpts {
|
||||
pub remote: Remote,
|
||||
pub ticks: Duration,
|
||||
pub log_level: LogLevel,
|
||||
pub task: Task,
|
||||
}
|
||||
|
||||
impl Default for RunOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
remote: Remote::None,
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,23 @@ impl FileTransferParams {
|
||||
self.entry_directory = dir.map(|x| x.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
/// 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!!!
|
||||
pub fn password_missing(&self) -> bool {
|
||||
match &self.params {
|
||||
ProtocolParams::AwsS3(params) => params.password_missing(),
|
||||
ProtocolParams::Generic(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 &mut self.params {
|
||||
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
|
||||
ProtocolParams::Generic(params) => params.set_default_secret(secret),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileTransferParams {
|
||||
@@ -108,6 +125,7 @@ impl ProtocolParams {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Get a mutable reference to the inner generic protocol params
|
||||
pub fn mut_generic_params(&mut self) -> Option<&mut GenericProtocolParams> {
|
||||
match self {
|
||||
@@ -163,6 +181,17 @@ impl GenericProtocolParams {
|
||||
self.password = password.map(|x| x.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// 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!!!
|
||||
pub fn password_missing(&self) -> bool {
|
||||
self.password.is_none()
|
||||
}
|
||||
|
||||
/// Set password
|
||||
pub fn set_default_secret(&mut self, secret: String) {
|
||||
self.password = Some(secret);
|
||||
}
|
||||
}
|
||||
|
||||
// -- S3 params
|
||||
@@ -218,6 +247,17 @@ impl AwsS3Params {
|
||||
self.new_path_style = new_path_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// 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!!!
|
||||
pub fn password_missing(&self) -> bool {
|
||||
self.secret_access_key.is_none() && self.security_token.is_none()
|
||||
}
|
||||
|
||||
/// Set password
|
||||
pub fn set_default_secret(&mut self, secret: String) {
|
||||
self.secret_access_key = Some(secret);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -300,4 +340,83 @@ mod test {
|
||||
assert!(params.generic_params().is_some());
|
||||
assert!(params.mut_generic_params().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_missing() {
|
||||
assert!(FileTransferParams::new(
|
||||
FileTransferProtocol::Scp,
|
||||
ProtocolParams::AwsS3(AwsS3Params::new("omar", Some("eu-west-1"), Some("test")))
|
||||
)
|
||||
.password_missing());
|
||||
assert_eq!(
|
||||
FileTransferParams::new(
|
||||
FileTransferProtocol::Scp,
|
||||
ProtocolParams::AwsS3(
|
||||
AwsS3Params::new("omar", Some("eu-west-1"), Some("test"))
|
||||
.secret_access_key(Some("test"))
|
||||
)
|
||||
)
|
||||
.password_missing(),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
FileTransferParams::new(
|
||||
FileTransferProtocol::Scp,
|
||||
ProtocolParams::AwsS3(
|
||||
AwsS3Params::new("omar", Some("eu-west-1"), Some("test"))
|
||||
.security_token(Some("test"))
|
||||
)
|
||||
)
|
||||
.password_missing(),
|
||||
false
|
||||
);
|
||||
assert!(
|
||||
FileTransferParams::new(FileTransferProtocol::Scp, ProtocolParams::default())
|
||||
.password_missing()
|
||||
);
|
||||
assert_eq!(
|
||||
FileTransferParams::new(
|
||||
FileTransferProtocol::Scp,
|
||||
ProtocolParams::Generic(GenericProtocolParams::default().password(Some("Hello")))
|
||||
)
|
||||
.password_missing(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_default_secret_aws_s3() {
|
||||
let mut params = FileTransferParams::new(
|
||||
FileTransferProtocol::Scp,
|
||||
ProtocolParams::AwsS3(AwsS3Params::new("omar", Some("eu-west-1"), Some("test"))),
|
||||
);
|
||||
params.set_default_secret(String::from("secret"));
|
||||
assert_eq!(
|
||||
params
|
||||
.params
|
||||
.s3_params()
|
||||
.unwrap()
|
||||
.secret_access_key
|
||||
.as_deref()
|
||||
.unwrap(),
|
||||
"secret"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_default_secret_generic() {
|
||||
let mut params =
|
||||
FileTransferParams::new(FileTransferProtocol::Scp, ProtocolParams::default());
|
||||
params.set_default_secret(String::from("secret"));
|
||||
assert_eq!(
|
||||
params
|
||||
.params
|
||||
.generic_params()
|
||||
.unwrap()
|
||||
.password
|
||||
.as_deref()
|
||||
.unwrap(),
|
||||
"secret"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
179
src/main.rs
179
src/main.rs
@@ -26,7 +26,6 @@ const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
|
||||
// Crates
|
||||
extern crate argh;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
@@ -37,13 +36,13 @@ extern crate log;
|
||||
extern crate magic_crypt;
|
||||
|
||||
// External libs
|
||||
use argh::FromArgs;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
// Include
|
||||
mod activity_manager;
|
||||
mod cli_opts;
|
||||
mod config;
|
||||
mod explorer;
|
||||
mod filetransfer;
|
||||
@@ -55,82 +54,14 @@ mod utils;
|
||||
|
||||
// namespaces
|
||||
use activity_manager::{ActivityManager, NextActivity};
|
||||
use cli_opts::{Args, BookmarkParams, HostParams, Remote, RunOpts, Task};
|
||||
use filetransfer::FileTransferParams;
|
||||
use system::logging::{self, LogLevel};
|
||||
|
||||
enum Task {
|
||||
Activity(NextActivity),
|
||||
ImportTheme(PathBuf),
|
||||
InstallUpdate,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(description = "
|
||||
where positional can be: [address] [local-wrkdir]
|
||||
|
||||
Address syntax can be:
|
||||
|
||||
- `protocol://user@address:port:wrkdir` for protocols such as Sftp, Scp, Ftp
|
||||
- `s3://bucket-name@region:profile:/wrkdir` for Aws S3 protocol
|
||||
|
||||
Please, report issues to <https://github.com/veeso/termscp>
|
||||
Please, consider supporting the author <https://ko-fi.com/veeso>")]
|
||||
struct Args {
|
||||
#[argh(switch, short = 'c', description = "open termscp configuration")]
|
||||
config: bool,
|
||||
#[argh(switch, short = 'D', description = "enable TRACE log level")]
|
||||
debug: bool,
|
||||
#[argh(option, short = 'P', description = "provide password from CLI")]
|
||||
password: Option<String>,
|
||||
#[argh(switch, short = 'q', description = "disable logging")]
|
||||
quiet: bool,
|
||||
#[argh(option, short = 't', description = "import specified theme")]
|
||||
theme: Option<String>,
|
||||
#[argh(
|
||||
switch,
|
||||
short = 'u',
|
||||
description = "update termscp to the latest version"
|
||||
)]
|
||||
update: bool,
|
||||
#[argh(
|
||||
option,
|
||||
short = 'T',
|
||||
default = "10",
|
||||
description = "set UI ticks; default 10ms"
|
||||
)]
|
||||
ticks: u64,
|
||||
#[argh(switch, short = 'v', description = "print version")]
|
||||
version: bool,
|
||||
// -- positional
|
||||
#[argh(
|
||||
positional,
|
||||
description = "protocol://user@address:port:wrkdir local-wrkdir"
|
||||
)]
|
||||
positional: Vec<String>,
|
||||
}
|
||||
|
||||
struct RunOpts {
|
||||
remote: Option<FileTransferParams>,
|
||||
ticks: Duration,
|
||||
log_level: LogLevel,
|
||||
task: Task,
|
||||
}
|
||||
|
||||
impl Default for RunOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
remote: None,
|
||||
ticks: Duration::from_millis(10),
|
||||
log_level: LogLevel::Info,
|
||||
task: Task::Activity(NextActivity::Authentication),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Args = argh::from_env();
|
||||
// Parse args
|
||||
let mut run_opts: RunOpts = match parse_args(args) {
|
||||
let run_opts: RunOpts = match parse_args(args) {
|
||||
Ok(opts) => opts,
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
@@ -141,22 +72,15 @@ fn main() {
|
||||
if let Err(err) = logging::init(run_opts.log_level) {
|
||||
eprintln!("Failed to initialize logging: {}", err);
|
||||
}
|
||||
// Read password from remote
|
||||
if let Err(err) = read_password(&mut run_opts) {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(255);
|
||||
}
|
||||
info!("termscp {} started!", TERMSCP_VERSION);
|
||||
// Run
|
||||
info!("Starting activity manager...");
|
||||
let rc: i32 = run(run_opts);
|
||||
let rc = run(run_opts);
|
||||
info!("termscp terminated with exitcode {}", rc);
|
||||
// Then return
|
||||
std::process::exit(rc);
|
||||
}
|
||||
|
||||
/// ### parse_args
|
||||
///
|
||||
/// Parse arguments
|
||||
/// In case of success returns `RunOpts`
|
||||
/// in case something is wrong returns the error message
|
||||
@@ -182,7 +106,7 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||
// Match ticks
|
||||
run_opts.ticks = Duration::from_millis(args.ticks);
|
||||
// @! extra modes
|
||||
if let Some(theme) = args.theme {
|
||||
if let Some(theme) = args.theme.as_deref() {
|
||||
run_opts.task = Task::ImportTheme(PathBuf::from(theme));
|
||||
}
|
||||
if args.update {
|
||||
@@ -190,26 +114,17 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||
}
|
||||
// @! Ordinary mode
|
||||
// Remote argument
|
||||
if let Some(remote) = args.positional.get(0) {
|
||||
// Parse address
|
||||
match utils::parser::parse_remote_opt(remote.as_str()) {
|
||||
Ok(mut remote) => {
|
||||
// If password is provided, set password
|
||||
if let Some(passwd) = args.password {
|
||||
if let Some(mut params) = remote.params.mut_generic_params() {
|
||||
params.password = Some(passwd);
|
||||
}
|
||||
}
|
||||
// Set params
|
||||
run_opts.remote = Some(remote);
|
||||
// In this case the first activity will be FileTransfer
|
||||
run_opts.task = Task::Activity(NextActivity::FileTransfer);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("Bad address option: {}", err));
|
||||
}
|
||||
match parse_address_arg(&args) {
|
||||
Err(err) => return Err(err),
|
||||
Ok(Remote::None) => {}
|
||||
Ok(remote) => {
|
||||
// Set params
|
||||
run_opts.remote = remote;
|
||||
// In this case the first activity will be FileTransfer
|
||||
run_opts.task = Task::Activity(NextActivity::FileTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
// Local directory
|
||||
if let Some(localdir) = args.positional.get(1) {
|
||||
// Change working directory if local dir is set
|
||||
@@ -221,43 +136,33 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||
Ok(run_opts)
|
||||
}
|
||||
|
||||
/// ### read_password
|
||||
///
|
||||
/// Read password from tty if address is specified
|
||||
fn read_password(run_opts: &mut RunOpts) -> Result<(), String> {
|
||||
// Initialize client if necessary
|
||||
if let Some(remote) = run_opts.remote.as_mut() {
|
||||
// Ask password for generic params
|
||||
if let Some(mut params) = remote.params.mut_generic_params() {
|
||||
// Ask password only if generic protocol params
|
||||
if params.password.is_none() {
|
||||
// Ask password if unspecified
|
||||
params.password = match rpassword::read_password_from_tty(Some("Password: ")) {
|
||||
Ok(p) => {
|
||||
if p.is_empty() {
|
||||
None
|
||||
} else {
|
||||
debug!(
|
||||
"Read password from tty: {}",
|
||||
utils::fmt::shadow_password(p.as_str())
|
||||
);
|
||||
Some(p)
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err("Could not read password from prompt".to_string());
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Parse address argument from cli args
|
||||
fn parse_address_arg(args: &Args) -> Result<Remote, String> {
|
||||
if let Some(remote) = args.positional.get(0) {
|
||||
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)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// Run task and return rc
|
||||
fn run(mut run_opts: RunOpts) -> i32 {
|
||||
fn run(run_opts: RunOpts) -> i32 {
|
||||
match run_opts.task {
|
||||
Task::ImportTheme(theme) => match support::import_theme(theme.as_path()) {
|
||||
Ok(_) => {
|
||||
@@ -295,8 +200,20 @@ fn run(mut run_opts: RunOpts) -> i32 {
|
||||
}
|
||||
};
|
||||
// Set file transfer params if set
|
||||
if let Some(remote) = run_opts.remote.take() {
|
||||
manager.set_filetransfer_params(remote);
|
||||
match run_opts.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 => {}
|
||||
}
|
||||
manager.run(activity);
|
||||
0
|
||||
|
||||
@@ -28,20 +28,15 @@
|
||||
// Locals
|
||||
use super::{AuthActivity, FileTransferParams};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::environment;
|
||||
|
||||
// Ext
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl AuthActivity {
|
||||
/// Delete bookmark
|
||||
pub(super) fn del_bookmark(&mut self, idx: usize) {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||
let name = self.bookmarks_list.get(idx).cloned();
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client_mut() {
|
||||
// Iterate over kyes
|
||||
let name: Option<&String> = self.bookmarks_list.get(idx);
|
||||
if let Some(name) = name {
|
||||
bookmarks_cli.del_bookmark(name);
|
||||
bookmarks_cli.del_bookmark(&name);
|
||||
// Write bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
@@ -52,7 +47,7 @@ impl AuthActivity {
|
||||
|
||||
/// Load selected bookmark (at index) to input fields
|
||||
pub(super) fn load_bookmark(&mut self, idx: usize) {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client() {
|
||||
// Iterate over bookmarks
|
||||
if let Some(key) = self.bookmarks_list.get(idx) {
|
||||
if let Some(bookmark) = bookmarks_cli.get_bookmark(key) {
|
||||
@@ -72,7 +67,7 @@ impl AuthActivity {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client_mut() {
|
||||
bookmarks_cli.add_bookmark(name.clone(), params, save_password);
|
||||
// Save bookmarks
|
||||
self.write_bookmarks();
|
||||
@@ -85,10 +80,10 @@ impl AuthActivity {
|
||||
}
|
||||
/// Delete recent
|
||||
pub(super) fn del_recent(&mut self, idx: usize) {
|
||||
if let Some(client) = self.bookmarks_client.as_mut() {
|
||||
let name: Option<&String> = self.recents_list.get(idx);
|
||||
let name = self.recents_list.get(idx).cloned();
|
||||
if let Some(client) = self.bookmarks_client_mut() {
|
||||
if let Some(name) = name {
|
||||
client.del_recent(name);
|
||||
client.del_recent(&name);
|
||||
// Write bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
@@ -99,7 +94,7 @@ impl AuthActivity {
|
||||
|
||||
/// Load selected recent (at index) to input fields
|
||||
pub(super) fn load_recent(&mut self, idx: usize) {
|
||||
if let Some(client) = self.bookmarks_client.as_ref() {
|
||||
if let Some(client) = self.bookmarks_client() {
|
||||
// Iterate over bookmarks
|
||||
if let Some(key) = self.recents_list.get(idx) {
|
||||
if let Some(bookmark) = client.get_recent(key) {
|
||||
@@ -119,7 +114,7 @@ impl AuthActivity {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client_mut() {
|
||||
bookmarks_cli.add_recent(params);
|
||||
// Save bookmarks
|
||||
self.write_bookmarks();
|
||||
@@ -128,7 +123,7 @@ impl AuthActivity {
|
||||
|
||||
/// Write bookmarks to file
|
||||
fn write_bookmarks(&mut self) {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client() {
|
||||
if let Err(err) = bookmarks_cli.write_bookmarks() {
|
||||
self.mount_error(format!("Could not write bookmarks: {}", err).as_str());
|
||||
}
|
||||
@@ -137,58 +132,22 @@ impl AuthActivity {
|
||||
|
||||
/// Initialize bookmarks client
|
||||
pub(super) fn init_bookmarks_client(&mut self) {
|
||||
// Get config dir
|
||||
match environment::init_config_dir() {
|
||||
Ok(path) => {
|
||||
// If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system.
|
||||
if let Some(config_dir_path) = path {
|
||||
let bookmarks_file: PathBuf =
|
||||
environment::get_bookmarks_paths(config_dir_path.as_path());
|
||||
// Initialize client
|
||||
match BookmarksClient::new(
|
||||
bookmarks_file.as_path(),
|
||||
config_dir_path.as_path(),
|
||||
16,
|
||||
) {
|
||||
Ok(cli) => {
|
||||
// Load bookmarks into list
|
||||
let mut bookmarks_list: Vec<String> =
|
||||
Vec::with_capacity(cli.iter_bookmarks().count());
|
||||
for bookmark in cli.iter_bookmarks() {
|
||||
bookmarks_list.push(bookmark.clone());
|
||||
}
|
||||
// Load recents into list
|
||||
let mut recents_list: Vec<String> =
|
||||
Vec::with_capacity(cli.iter_recents().count());
|
||||
for recent in cli.iter_recents() {
|
||||
recents_list.push(recent.clone());
|
||||
}
|
||||
self.bookmarks_client = Some(cli);
|
||||
self.bookmarks_list = bookmarks_list;
|
||||
self.recents_list = recents_list;
|
||||
// Sort bookmark list
|
||||
self.sort_bookmarks();
|
||||
self.sort_recents();
|
||||
}
|
||||
Err(err) => {
|
||||
self.mount_error(
|
||||
format!(
|
||||
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
||||
bookmarks_file.display(),
|
||||
config_dir_path.display(),
|
||||
err
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(cli) = self.bookmarks_client_mut() {
|
||||
// Load bookmarks into list
|
||||
let mut bookmarks_list: Vec<String> = Vec::with_capacity(cli.iter_bookmarks().count());
|
||||
for bookmark in cli.iter_bookmarks() {
|
||||
bookmarks_list.push(bookmark.clone());
|
||||
}
|
||||
Err(err) => {
|
||||
self.mount_error(
|
||||
format!("Could not initialize configuration directory: {}", err).as_str(),
|
||||
);
|
||||
// Load recents into list
|
||||
let mut recents_list: Vec<String> = Vec::with_capacity(cli.iter_recents().count());
|
||||
for recent in cli.iter_recents() {
|
||||
recents_list.push(recent.clone());
|
||||
}
|
||||
self.bookmarks_list = bookmarks_list;
|
||||
self.recents_list = recents_list;
|
||||
// Sort bookmark list
|
||||
self.sort_bookmarks();
|
||||
self.sort_recents();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,6 @@ const STORE_KEY_RELEASE_NOTES: &str = "AUTH_RELEASE_NOTES";
|
||||
/// AuthActivity is the data holder for the authentication activity
|
||||
pub struct AuthActivity {
|
||||
app: Application<Id, Msg, NoUserEvent>,
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
/// List of bookmarks
|
||||
bookmarks_list: Vec<String>,
|
||||
/// List of recent hosts
|
||||
@@ -196,7 +195,6 @@ impl AuthActivity {
|
||||
.poll_timeout(ticks),
|
||||
),
|
||||
context: None,
|
||||
bookmarks_client: None,
|
||||
bookmarks_list: Vec::new(),
|
||||
exit_reason: None,
|
||||
recents_list: Vec::new(),
|
||||
@@ -220,6 +218,14 @@ impl AuthActivity {
|
||||
self.context().config()
|
||||
}
|
||||
|
||||
fn bookmarks_client(&self) -> Option<&BookmarksClient> {
|
||||
self.context().bookmarks_client()
|
||||
}
|
||||
|
||||
fn bookmarks_client_mut(&mut self) -> Option<&mut BookmarksClient> {
|
||||
self.context_mut().bookmarks_client_mut()
|
||||
}
|
||||
|
||||
/// Returns a reference to theme
|
||||
fn theme(&self) -> &Theme {
|
||||
self.context().theme_provider().theme()
|
||||
@@ -259,9 +265,8 @@ impl Activity for AuthActivity {
|
||||
// Initialize view
|
||||
self.init();
|
||||
// Init bookmarks client
|
||||
if self.bookmarks_client.is_none() {
|
||||
if self.bookmarks_client().is_some() {
|
||||
self.init_bookmarks_client();
|
||||
// View bookarmsk
|
||||
self.view_bookmarks();
|
||||
self.view_recent_connections();
|
||||
}
|
||||
|
||||
@@ -299,14 +299,7 @@ impl AuthActivity {
|
||||
.bookmarks_list
|
||||
.iter()
|
||||
.map(|x| {
|
||||
Self::fmt_bookmark(
|
||||
x,
|
||||
self.bookmarks_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_bookmark(x)
|
||||
.unwrap(),
|
||||
)
|
||||
Self::fmt_bookmark(x, self.bookmarks_client().unwrap().get_bookmark(x).unwrap())
|
||||
})
|
||||
.collect();
|
||||
let bookmarks_color = self.theme().auth_bookmarks;
|
||||
@@ -325,15 +318,7 @@ impl AuthActivity {
|
||||
let bookmarks: Vec<String> = self
|
||||
.recents_list
|
||||
.iter()
|
||||
.map(|x| {
|
||||
Self::fmt_recent(
|
||||
self.bookmarks_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_recent(x)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.map(|x| Self::fmt_recent(self.bookmarks_client().unwrap().get_recent(x).unwrap()))
|
||||
.collect();
|
||||
let recents_color = self.theme().auth_recents;
|
||||
assert!(self
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
// Locals
|
||||
use super::store::Store;
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::theme_provider::ThemeProvider;
|
||||
|
||||
@@ -36,6 +37,7 @@ use tuirealm::terminal::TerminalBridge;
|
||||
/// Context holds data structures shared by the activities
|
||||
pub struct Context {
|
||||
ft_params: Option<FileTransferParams>,
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
config_client: ConfigClient,
|
||||
pub(crate) store: Store,
|
||||
pub(crate) terminal: TerminalBridge,
|
||||
@@ -46,13 +48,15 @@ pub struct Context {
|
||||
impl Context {
|
||||
/// Instantiates a new Context
|
||||
pub fn new(
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
config_client: ConfigClient,
|
||||
theme_provider: ThemeProvider,
|
||||
error: Option<String>,
|
||||
) -> Context {
|
||||
Context {
|
||||
ft_params: None,
|
||||
bookmarks_client,
|
||||
config_client,
|
||||
ft_params: None,
|
||||
store: Store::init(),
|
||||
terminal: TerminalBridge::new().expect("Could not initialize terminal"),
|
||||
theme_provider,
|
||||
@@ -66,6 +70,14 @@ impl Context {
|
||||
self.ft_params.as_ref()
|
||||
}
|
||||
|
||||
pub fn bookmarks_client(&self) -> Option<&BookmarksClient> {
|
||||
self.bookmarks_client.as_ref()
|
||||
}
|
||||
|
||||
pub fn bookmarks_client_mut(&mut self) -> Option<&mut BookmarksClient> {
|
||||
self.bookmarks_client.as_mut()
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &ConfigClient {
|
||||
&self.config_client
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ pub mod parser;
|
||||
pub mod path;
|
||||
pub mod random;
|
||||
pub mod string;
|
||||
pub mod tty;
|
||||
pub mod ui;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
36
src/utils/tty.rs
Normal file
36
src/utils/tty.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
//! ## Utils
|
||||
//!
|
||||
//! `Utils` implements utilities functions to work with layouts
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2022 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/// Read a secret from tty with customisable prompt
|
||||
pub fn read_secret_from_tty(prompt: &str) -> std::io::Result<Option<String>> {
|
||||
match rpassword::read_password_from_tty(Some(prompt)) {
|
||||
Ok(p) if p.is_empty() => Ok(None),
|
||||
Ok(p) => Ok(Some(p)),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user