Bookmark name as hostname for cli args (#111)

bookmark name as hostname for cli args
This commit is contained in:
Christian Visintin
2022-05-03 11:54:48 +02:00
committed by GitHub
parent f094979ddb
commit e0d8b80cdf
21 changed files with 629 additions and 292 deletions

View File

@@ -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
View 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()),
}
}
}

View File

@@ -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"
);
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
View 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),
}
}