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

@@ -8,6 +8,7 @@ ignore:
- "../*" - "../*"
- src/main.rs - src/main.rs
- src/activity_manager.rs - src/activity_manager.rs
- src/cli_opts.rs
- src/support.rs - src/support.rs
- src/system/notifications.rs - src/system/notifications.rs
- "src/ui/activities/*" - "src/ui/activities/*"

View File

@@ -1,6 +1,7 @@
# Changelog # Changelog
- [Changelog](#changelog) - [Changelog](#changelog)
- [0.9.0](#090)
- [0.8.2](#082) - [0.8.2](#082)
- [0.8.1](#081) - [0.8.1](#081)
- [0.8.0](#080) - [0.8.0](#080)
@@ -24,6 +25,25 @@
--- ---
## 0.9.0
Released on FIXME:
> 🏖️ Tenerife Update 🍹
- **Bookmark name as hostname for CLI arguments**
- It is now possible to provide the name of the bookmark you want to connect to, instead of the address in command line arguments
To do so it is enough to run termscp as follows:
```sh
termscp -b <bookmark-name>
```
If the password is stored in the bookmark, it will be used, otherwise you will be prompted to type the password in.
- Dependencies:
- Updated `tui-realm` to `1.6.0`
## 0.8.2 ## 0.8.2
Released on 26/04/2022 Released on 26/04/2022

150
Cargo.lock generated
View File

@@ -441,45 +441,20 @@ dependencies = [
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.20.0" version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crossterm_winapi 0.8.0", "crossterm_winapi",
"libc", "libc",
"mio 0.7.14", "mio",
"parking_lot", "parking_lot 0.12.0",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
] ]
[[package]]
name = "crossterm"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
dependencies = [
"bitflags",
"crossterm_winapi 0.9.0",
"libc",
"mio 0.7.14",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "crossterm_winapi" name = "crossterm_winapi"
version = "0.9.0" version = "0.9.0"
@@ -1275,19 +1250,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.0" version = "0.8.0"
@@ -1580,7 +1542,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [ dependencies = [
"instant", "instant",
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core 0.8.5",
]
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core 0.9.3",
] ]
[[package]] [[package]]
@@ -1597,6 +1569,19 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "parking_lot_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall 0.2.11",
"smallvec",
"windows-sys",
]
[[package]] [[package]]
name = "path-slash" name = "path-slash"
version = "0.1.4" version = "0.1.4"
@@ -2148,7 +2133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"parking_lot", "parking_lot 0.11.2",
"serial_test_derive", "serial_test_derive",
] ]
@@ -2188,12 +2173,12 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-mio" name = "signal-hook-mio"
version = "0.2.1" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [ dependencies = [
"libc", "libc",
"mio 0.7.14", "mio",
"signal-hook", "signal-hook",
] ]
@@ -2254,7 +2239,7 @@ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
"libssh2-sys", "libssh2-sys",
"parking_lot", "parking_lot 0.11.2",
] ]
[[package]] [[package]]
@@ -2372,7 +2357,7 @@ dependencies = [
[[package]] [[package]]
name = "termscp" name = "termscp"
version = "0.8.2" version = "0.9.0"
dependencies = [ dependencies = [
"argh", "argh",
"bitflags", "bitflags",
@@ -2488,7 +2473,7 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"memchr", "memchr",
"mio 0.8.0", "mio",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
@@ -2573,13 +2558,13 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]] [[package]]
name = "tui" name = "tui"
version = "0.17.0" version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ed0a32c88b039b73f1b6c5acbd0554bfa5b6be94467375fd947c4de3a02271" checksum = "96fe69244ec2af261bced1d9046a6fee6c8c2a6b0228e59e5ba39bc8ba4ed729"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",
"crossterm 0.22.1", "crossterm",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
] ]
@@ -2597,12 +2582,12 @@ dependencies = [
[[package]] [[package]]
name = "tuirealm" name = "tuirealm"
version = "1.5.0" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1d5d2db30d1e003c37b9f7aba038c3b28015a8cc0c1c364f049b211d649cfb" checksum = "ef13e401c3c7d1f1b51d6193409d231c316057ad088431467a4e6882b4e25447"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crossterm 0.20.0", "crossterm",
"lazy_static", "lazy_static",
"regex", "regex",
"thiserror", "thiserror",
@@ -2892,36 +2877,79 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9f39345ae0c8ab072c0ac7fe8a8b411636aa34f89be19ddd0d9226544f13944" checksum = "a9f39345ae0c8ab072c0ac7fe8a8b411636aa34f89be19ddd0d9226544f13944"
dependencies = [ dependencies = [
"windows_i686_gnu", "windows_i686_gnu 0.24.0",
"windows_i686_msvc", "windows_i686_msvc 0.24.0",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.24.0",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.24.0",
] ]
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu 0.36.1",
"windows_i686_msvc 0.36.1",
"windows_x86_64_gnu 0.36.1",
"windows_x86_64_msvc 0.36.1",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.24.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd" checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.24.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6" checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.24.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4" checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.24.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399" checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.7.0" version = "0.7.0"

View File

@@ -59,7 +59,7 @@ tempfile = "3.2.0"
thiserror = "^1.0.0" thiserror = "^1.0.0"
toml = "0.5.8" toml = "0.5.8"
tui-realm-stdlib = "1.1.6" tui-realm-stdlib = "1.1.6"
tuirealm = "1.5.0" tuirealm = "1.6.0"
unicode-width = "0.1.8" unicode-width = "0.1.8"
whoami = "1.2.1" whoami = "1.2.1"
wildmatch = "2.1.0" wildmatch = "2.1.0"

View File

@@ -223,10 +223,6 @@ The user manual can be found on the [termscp's website](https://veeso.github.io/
For **2022** there will be two major updates during the year. For **2022** there will be two major updates during the year.
Planned for *Summer update 2022*:
- **File system watcher 🔭**: The feature consists in the possibility to track some files in order to automatically sync them with remote host. For the implementation [notify](https://github.com/notify-rs/notify) will be used.
Planned for *future updates ⏲️*: Planned for *future updates ⏲️*:
- **Translations 🌐**: The feature consists in the possibility for the user to install the language pack for the language he prefers in order to replace the default English interface. The following language will be provided along to English: - **Translations 🌐**: The feature consists in the possibility for the user to install the language pack for the language he prefers in order to replace the default English interface. The following language will be provided along to English:

View File

@@ -37,7 +37,12 @@ termscp can be started with the following options:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
OR
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` if address is provided, password will be this argument - `-P, --password <password>` if address is provided, password will be this argument
- `-b, --address-as-bookmark` resolve address argument as a bookmark name
- `-c, --config` Open termscp starting from the configuration page - `-c, --config` Open termscp starting from the configuration page
- `-q, --quiet` Disable logging - `-q, --quiet` Disable logging
- `-t, --theme <path>` Import specified theme - `-t, --theme <path>` Import specified theme
@@ -45,11 +50,11 @@ termscp can be started with the following options:
- `-v, --version` Print version info - `-v, --version` Print version info
- `-h, --help` Print help page - `-h, --help` Print help page
termscp can be started in two different mode, if no extra arguments is provided, termscp will show the authentication form, where the user will be able to provide the parameters required to connect to the remote peer. termscp can be started in three different modes, if no extra arguments is provided, termscp will show the authentication form, where the user will be able to provide the parameters required to connect to the remote peer.
Alternatively, the user can provide an address as argument to skip the authentication form and starting directly the connection to the remote server. Alternatively, the user can provide an address as argument to skip the authentication form and starting directly the connection to the remote server.
If address argument is provided you can also provide the start working directory for local host If address argument or bookmark name is provided you can also provide the start working directory for local host
### Address argument 🌎 ### Address argument 🌎

View File

@@ -37,7 +37,12 @@ termscp se puede iniciar con las siguientes opciones:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
OR
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento - `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento
- `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador
- `-c, --config` Abrir termscp comenzando desde la página de configuración - `-c, --config` Abrir termscp comenzando desde la página de configuración
- `-q, --quiet` Deshabilitar el registro - `-q, --quiet` Deshabilitar el registro
- `-t, --theme <path>` Importar tema especificado - `-t, --theme <path>` Importar tema especificado

View File

@@ -35,7 +35,12 @@ 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] [local-wrkdir]`
ou
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument - `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument
- `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet
- `-c, --config` Ouvrir termscp à partir de la page de configuration - `-c, --config` Ouvrir termscp à partir de la page de configuration
- `-q, --quiet` Désactiver la journalisation - `-q, --quiet` Désactiver la journalisation
- `-t, --theme <path>` Importer le thème spécifié - `-t, --theme <path>` Importer le thème spécifié

View File

@@ -35,7 +35,12 @@ termscp può essere lanciato con questi argomenti:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
O
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi - `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi
- `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro
- `-c, --config` Apri la configurazione di termscp - `-c, --config` Apri la configurazione di termscp
- `-q, --quiet` Disabilita i log - `-q, --quiet` Disabilita i log
- `-t, --theme <path>` Importa il tema al percorso fornito - `-t, --theme <path>` Importa il tema al percorso fornito

View File

@@ -35,7 +35,12 @@ termscp can be started with the following options:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
OR
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` if address is provided, password will be this argument - `-P, --password <password>` if address is provided, password will be this argument
- `-b, --address-as-bookmark` resolve address argument as a bookmark name
- `-c, --config` Open termscp starting from the configuration page - `-c, --config` Open termscp starting from the configuration page
- `-q, --quiet` Disable logging - `-q, --quiet` Disable logging
- `-t, --theme <path>` Import specified theme - `-t, --theme <path>` Import specified theme
@@ -43,11 +48,11 @@ termscp can be started with the following options:
- `-v, --version` Print version info - `-v, --version` Print version info
- `-h, --help` Print help page - `-h, --help` Print help page
termscp can be started in two different mode, if no extra arguments is provided, termscp will show the authentication form, where the user will be able to provide the parameters required to connect to the remote peer. termscp can be started in three different modes, if no extra arguments is provided, termscp will show the authentication form, where the user will be able to provide the parameters required to connect to the remote peer.
Alternatively, the user can provide an address as argument to skip the authentication form and starting directly the connection to the remote server. Alternatively, the user can provide an address as argument to skip the authentication form and starting directly the connection to the remote server.
If address argument is provided you can also provide the start working directory for local host If address argument or bookmark name is provided you can also provide the start working directory for local host
### Address argument 🌎 ### Address argument 🌎

View File

@@ -35,7 +35,12 @@ termscp启动时可以使用以下选项:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
或作为
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
- `-P, --password <password>` 登陆密码 - `-P, --password <password>` 登陆密码
- `-b, --address-as-bookmark` 将地址参数解析为书签名称
- `-c, --config` 打开termscp时打开配置页面 - `-c, --config` 打开termscp时打开配置页面
- `-q, --quiet` 禁用日志 - `-q, --quiet` 禁用日志
- `-t, --theme <path>` 导入自定义主题 - `-t, --theme <path>` 导入自定义主题

View File

@@ -28,6 +28,7 @@
// Deps // Deps
use crate::filetransfer::FileTransferParams; use crate::filetransfer::FileTransferParams;
use crate::host::{HostError, Localhost}; use crate::host::{HostError, Localhost};
use crate::system::bookmarks_client::BookmarksClient;
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
use crate::system::environment; use crate::system::environment;
use crate::system::theme_provider::ThemeProvider; use crate::system::theme_provider::ThemeProvider;
@@ -36,6 +37,8 @@ use crate::ui::activities::{
ExitReason, ExitReason,
}; };
use crate::ui::context::Context; use crate::ui::context::Context;
use crate::utils::fmt;
use crate::utils::tty;
// Namespaces // Namespaces
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -64,7 +67,7 @@ impl ActivityManager {
pub fn new(local_dir: &Path, ticks: Duration) -> Result<ActivityManager, HostError> { pub fn new(local_dir: &Path, ticks: Duration) -> Result<ActivityManager, HostError> {
// Prepare Context // Prepare Context
// Initialize configuration client // Initialize configuration client
let (config_client, error): (ConfigClient, Option<String>) = let (config_client, error_config): (ConfigClient, Option<String>) =
match Self::init_config_client() { match Self::init_config_client() {
Ok(cli) => (cli, None), Ok(cli) => (cli, None),
Err(err) => { Err(err) => {
@@ -72,8 +75,13 @@ impl ActivityManager {
(ConfigClient::degraded(), Some(err)) (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 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 { Ok(ActivityManager {
context: Some(ctx), context: Some(ctx),
local_dir: local_dir.to_path_buf(), local_dir: local_dir.to_path_buf(),
@@ -82,9 +90,54 @@ impl ActivityManager {
} }
/// Set file transfer params /// 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 // Put params into the context
self.context.as_mut().unwrap().set_ftparams(params); 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 // -- 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 /// Initialize configuration client
fn init_config_client() -> Result<ConfigClient, String> { fn init_config_client() -> Result<ConfigClient, String> {
// Get config dir // 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.entry_directory = dir.map(|x| x.as_ref().to_path_buf());
self 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 { impl Default for FileTransferParams {
@@ -108,6 +125,7 @@ impl ProtocolParams {
} }
} }
#[cfg(test)]
/// Get a mutable reference to the inner generic protocol params /// Get a mutable reference to the inner generic protocol params
pub fn mut_generic_params(&mut self) -> Option<&mut GenericProtocolParams> { pub fn mut_generic_params(&mut self) -> Option<&mut GenericProtocolParams> {
match self { match self {
@@ -163,6 +181,17 @@ impl GenericProtocolParams {
self.password = password.map(|x| x.as_ref().to_string()); self.password = password.map(|x| x.as_ref().to_string());
self 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 // -- S3 params
@@ -218,6 +247,17 @@ impl AwsS3Params {
self.new_path_style = new_path_style; self.new_path_style = new_path_style;
self 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)] #[cfg(test)]
@@ -300,4 +340,83 @@ mod test {
assert!(params.generic_params().is_some()); assert!(params.generic_params().is_some());
assert!(params.mut_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"); const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
// Crates // Crates
extern crate argh;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[macro_use] #[macro_use]
@@ -37,13 +36,13 @@ extern crate log;
extern crate magic_crypt; extern crate magic_crypt;
// External libs // External libs
use argh::FromArgs;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
// Include // Include
mod activity_manager; mod activity_manager;
mod cli_opts;
mod config; mod config;
mod explorer; mod explorer;
mod filetransfer; mod filetransfer;
@@ -55,82 +54,14 @@ mod utils;
// namespaces // namespaces
use activity_manager::{ActivityManager, NextActivity}; use activity_manager::{ActivityManager, NextActivity};
use cli_opts::{Args, BookmarkParams, HostParams, Remote, RunOpts, Task};
use filetransfer::FileTransferParams; use filetransfer::FileTransferParams;
use system::logging::{self, LogLevel}; 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() { fn main() {
let args: Args = argh::from_env(); let args: Args = argh::from_env();
// Parse args // Parse args
let mut run_opts: RunOpts = match parse_args(args) { let run_opts: RunOpts = match parse_args(args) {
Ok(opts) => opts, Ok(opts) => opts,
Err(err) => { Err(err) => {
eprintln!("{}", err); eprintln!("{}", err);
@@ -141,22 +72,15 @@ fn main() {
if let Err(err) = logging::init(run_opts.log_level) { if let Err(err) = logging::init(run_opts.log_level) {
eprintln!("Failed to initialize logging: {}", err); 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); info!("termscp {} started!", TERMSCP_VERSION);
// Run // Run
info!("Starting activity manager..."); info!("Starting activity manager...");
let rc: i32 = run(run_opts); let rc = run(run_opts);
info!("termscp terminated with exitcode {}", rc); info!("termscp terminated with exitcode {}", rc);
// Then return // Then return
std::process::exit(rc); std::process::exit(rc);
} }
/// ### parse_args
///
/// Parse arguments /// Parse arguments
/// In case of success returns `RunOpts` /// In case of success returns `RunOpts`
/// in case something is wrong returns the error message /// in case something is wrong returns the error message
@@ -182,7 +106,7 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
// Match ticks // Match ticks
run_opts.ticks = Duration::from_millis(args.ticks); run_opts.ticks = Duration::from_millis(args.ticks);
// @! extra modes // @! extra modes
if let Some(theme) = args.theme { if let Some(theme) = args.theme.as_deref() {
run_opts.task = Task::ImportTheme(PathBuf::from(theme)); run_opts.task = Task::ImportTheme(PathBuf::from(theme));
} }
if args.update { if args.update {
@@ -190,26 +114,17 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
} }
// @! Ordinary mode // @! Ordinary mode
// Remote argument // Remote argument
if let Some(remote) = args.positional.get(0) { match parse_address_arg(&args) {
// Parse address Err(err) => return Err(err),
match utils::parser::parse_remote_opt(remote.as_str()) { Ok(Remote::None) => {}
Ok(mut remote) => { Ok(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 // Set params
run_opts.remote = Some(remote); run_opts.remote = remote;
// In this case the first activity will be FileTransfer // In this case the first activity will be FileTransfer
run_opts.task = Task::Activity(NextActivity::FileTransfer); run_opts.task = Task::Activity(NextActivity::FileTransfer);
} }
Err(err) => {
return Err(format!("Bad address option: {}", err));
}
}
} }
// Local directory // Local directory
if let Some(localdir) = args.positional.get(1) { if let Some(localdir) = args.positional.get(1) {
// Change working directory if local dir is set // Change working directory if local dir is set
@@ -221,43 +136,33 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
Ok(run_opts) Ok(run_opts)
} }
/// ### read_password /// Parse address argument from cli args
/// fn parse_address_arg(args: &Args) -> Result<Remote, String> {
/// Read password from tty if address is specified if let Some(remote) = args.positional.get(0) {
fn read_password(run_opts: &mut RunOpts) -> Result<(), String> { if args.address_as_bookmark {
// Initialize client if necessary Ok(Remote::Bookmark(BookmarkParams::new(
if let Some(remote) = run_opts.remote.as_mut() { remote,
// Ask password for generic params args.password.as_ref(),
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 { } else {
debug!( // Parse address
"Read password from tty: {}", parse_remote_address(remote.as_str())
utils::fmt::shadow_password(p.as_str()) .map(|x| Remote::Host(HostParams::new(x, args.password.as_deref())))
);
Some(p)
} }
} else {
Ok(Remote::None)
} }
Err(_) => { }
return Err("Could not read password from prompt".to_string());
} /// 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))
}
}
Ok(())
} }
/// ### run /// ### run
/// ///
/// Run task and return rc /// Run task and return rc
fn run(mut run_opts: RunOpts) -> i32 { fn run(run_opts: RunOpts) -> i32 {
match run_opts.task { match run_opts.task {
Task::ImportTheme(theme) => match support::import_theme(theme.as_path()) { Task::ImportTheme(theme) => match support::import_theme(theme.as_path()) {
Ok(_) => { Ok(_) => {
@@ -295,8 +200,20 @@ fn run(mut run_opts: RunOpts) -> i32 {
} }
}; };
// Set file transfer params if set // Set file transfer params if set
if let Some(remote) = run_opts.remote.take() { match run_opts.remote {
manager.set_filetransfer_params(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); manager.run(activity);
0 0

View File

@@ -28,20 +28,15 @@
// Locals // Locals
use super::{AuthActivity, FileTransferParams}; use super::{AuthActivity, FileTransferParams};
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams}; use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
use crate::system::bookmarks_client::BookmarksClient;
use crate::system::environment;
// Ext
use std::path::PathBuf;
impl AuthActivity { impl AuthActivity {
/// Delete bookmark /// Delete bookmark
pub(super) fn del_bookmark(&mut self, idx: usize) { 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 // Iterate over kyes
let name: Option<&String> = self.bookmarks_list.get(idx);
if let Some(name) = name { if let Some(name) = name {
bookmarks_cli.del_bookmark(name); bookmarks_cli.del_bookmark(&name);
// Write bookmarks // Write bookmarks
self.write_bookmarks(); self.write_bookmarks();
} }
@@ -52,7 +47,7 @@ impl AuthActivity {
/// Load selected bookmark (at index) to input fields /// Load selected bookmark (at index) to input fields
pub(super) fn load_bookmark(&mut self, idx: usize) { 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 // Iterate over bookmarks
if let Some(key) = self.bookmarks_list.get(idx) { if let Some(key) = self.bookmarks_list.get(idx) {
if let Some(bookmark) = bookmarks_cli.get_bookmark(key) { if let Some(bookmark) = bookmarks_cli.get_bookmark(key) {
@@ -72,7 +67,7 @@ impl AuthActivity {
return; 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); bookmarks_cli.add_bookmark(name.clone(), params, save_password);
// Save bookmarks // Save bookmarks
self.write_bookmarks(); self.write_bookmarks();
@@ -85,10 +80,10 @@ impl AuthActivity {
} }
/// Delete recent /// Delete recent
pub(super) fn del_recent(&mut self, idx: usize) { pub(super) fn del_recent(&mut self, idx: usize) {
if let Some(client) = self.bookmarks_client.as_mut() { let name = self.recents_list.get(idx).cloned();
let name: Option<&String> = self.recents_list.get(idx); if let Some(client) = self.bookmarks_client_mut() {
if let Some(name) = name { if let Some(name) = name {
client.del_recent(name); client.del_recent(&name);
// Write bookmarks // Write bookmarks
self.write_bookmarks(); self.write_bookmarks();
} }
@@ -99,7 +94,7 @@ impl AuthActivity {
/// Load selected recent (at index) to input fields /// Load selected recent (at index) to input fields
pub(super) fn load_recent(&mut self, idx: usize) { 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 // Iterate over bookmarks
if let Some(key) = self.recents_list.get(idx) { if let Some(key) = self.recents_list.get(idx) {
if let Some(bookmark) = client.get_recent(key) { if let Some(bookmark) = client.get_recent(key) {
@@ -119,7 +114,7 @@ impl AuthActivity {
return; 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); bookmarks_cli.add_recent(params);
// Save bookmarks // Save bookmarks
self.write_bookmarks(); self.write_bookmarks();
@@ -128,7 +123,7 @@ impl AuthActivity {
/// Write bookmarks to file /// Write bookmarks to file
fn write_bookmarks(&mut self) { 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() { if let Err(err) = bookmarks_cli.write_bookmarks() {
self.mount_error(format!("Could not write bookmarks: {}", err).as_str()); self.mount_error(format!("Could not write bookmarks: {}", err).as_str());
} }
@@ -137,59 +132,23 @@ impl AuthActivity {
/// Initialize bookmarks client /// Initialize bookmarks client
pub(super) fn init_bookmarks_client(&mut self) { pub(super) fn init_bookmarks_client(&mut self) {
// Get config dir if let Some(cli) = self.bookmarks_client_mut() {
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 // Load bookmarks into list
let mut bookmarks_list: Vec<String> = let mut bookmarks_list: Vec<String> = Vec::with_capacity(cli.iter_bookmarks().count());
Vec::with_capacity(cli.iter_bookmarks().count());
for bookmark in cli.iter_bookmarks() { for bookmark in cli.iter_bookmarks() {
bookmarks_list.push(bookmark.clone()); bookmarks_list.push(bookmark.clone());
} }
// Load recents into list // Load recents into list
let mut recents_list: Vec<String> = let mut recents_list: Vec<String> = Vec::with_capacity(cli.iter_recents().count());
Vec::with_capacity(cli.iter_recents().count());
for recent in cli.iter_recents() { for recent in cli.iter_recents() {
recents_list.push(recent.clone()); recents_list.push(recent.clone());
} }
self.bookmarks_client = Some(cli);
self.bookmarks_list = bookmarks_list; self.bookmarks_list = bookmarks_list;
self.recents_list = recents_list; self.recents_list = recents_list;
// Sort bookmark list // Sort bookmark list
self.sort_bookmarks(); self.sort_bookmarks();
self.sort_recents(); self.sort_recents();
} }
Err(err) => {
self.mount_error(
format!(
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
bookmarks_file.display(),
config_dir_path.display(),
err
)
.as_str(),
);
}
}
}
}
Err(err) => {
self.mount_error(
format!("Could not initialize configuration directory: {}", err).as_str(),
);
}
}
} }
// -- privates // -- privates

View File

@@ -172,7 +172,6 @@ const STORE_KEY_RELEASE_NOTES: &str = "AUTH_RELEASE_NOTES";
/// AuthActivity is the data holder for the authentication activity /// AuthActivity is the data holder for the authentication activity
pub struct AuthActivity { pub struct AuthActivity {
app: Application<Id, Msg, NoUserEvent>, app: Application<Id, Msg, NoUserEvent>,
bookmarks_client: Option<BookmarksClient>,
/// List of bookmarks /// List of bookmarks
bookmarks_list: Vec<String>, bookmarks_list: Vec<String>,
/// List of recent hosts /// List of recent hosts
@@ -196,7 +195,6 @@ impl AuthActivity {
.poll_timeout(ticks), .poll_timeout(ticks),
), ),
context: None, context: None,
bookmarks_client: None,
bookmarks_list: Vec::new(), bookmarks_list: Vec::new(),
exit_reason: None, exit_reason: None,
recents_list: Vec::new(), recents_list: Vec::new(),
@@ -220,6 +218,14 @@ impl AuthActivity {
self.context().config() 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 /// Returns a reference to theme
fn theme(&self) -> &Theme { fn theme(&self) -> &Theme {
self.context().theme_provider().theme() self.context().theme_provider().theme()
@@ -259,9 +265,8 @@ impl Activity for AuthActivity {
// Initialize view // Initialize view
self.init(); self.init();
// Init bookmarks client // Init bookmarks client
if self.bookmarks_client.is_none() { if self.bookmarks_client().is_some() {
self.init_bookmarks_client(); self.init_bookmarks_client();
// View bookarmsk
self.view_bookmarks(); self.view_bookmarks();
self.view_recent_connections(); self.view_recent_connections();
} }

View File

@@ -299,14 +299,7 @@ impl AuthActivity {
.bookmarks_list .bookmarks_list
.iter() .iter()
.map(|x| { .map(|x| {
Self::fmt_bookmark( Self::fmt_bookmark(x, self.bookmarks_client().unwrap().get_bookmark(x).unwrap())
x,
self.bookmarks_client
.as_ref()
.unwrap()
.get_bookmark(x)
.unwrap(),
)
}) })
.collect(); .collect();
let bookmarks_color = self.theme().auth_bookmarks; let bookmarks_color = self.theme().auth_bookmarks;
@@ -325,15 +318,7 @@ impl AuthActivity {
let bookmarks: Vec<String> = self let bookmarks: Vec<String> = self
.recents_list .recents_list
.iter() .iter()
.map(|x| { .map(|x| Self::fmt_recent(self.bookmarks_client().unwrap().get_recent(x).unwrap()))
Self::fmt_recent(
self.bookmarks_client
.as_ref()
.unwrap()
.get_recent(x)
.unwrap(),
)
})
.collect(); .collect();
let recents_color = self.theme().auth_recents; let recents_color = self.theme().auth_recents;
assert!(self assert!(self

View File

@@ -28,6 +28,7 @@
// Locals // Locals
use super::store::Store; use super::store::Store;
use crate::filetransfer::FileTransferParams; use crate::filetransfer::FileTransferParams;
use crate::system::bookmarks_client::BookmarksClient;
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
use crate::system::theme_provider::ThemeProvider; use crate::system::theme_provider::ThemeProvider;
@@ -36,6 +37,7 @@ use tuirealm::terminal::TerminalBridge;
/// Context holds data structures shared by the activities /// Context holds data structures shared by the activities
pub struct Context { pub struct Context {
ft_params: Option<FileTransferParams>, ft_params: Option<FileTransferParams>,
bookmarks_client: Option<BookmarksClient>,
config_client: ConfigClient, config_client: ConfigClient,
pub(crate) store: Store, pub(crate) store: Store,
pub(crate) terminal: TerminalBridge, pub(crate) terminal: TerminalBridge,
@@ -46,13 +48,15 @@ pub struct Context {
impl Context { impl Context {
/// Instantiates a new Context /// Instantiates a new Context
pub fn new( pub fn new(
bookmarks_client: Option<BookmarksClient>,
config_client: ConfigClient, config_client: ConfigClient,
theme_provider: ThemeProvider, theme_provider: ThemeProvider,
error: Option<String>, error: Option<String>,
) -> Context { ) -> Context {
Context { Context {
ft_params: None, bookmarks_client,
config_client, config_client,
ft_params: None,
store: Store::init(), store: Store::init(),
terminal: TerminalBridge::new().expect("Could not initialize terminal"), terminal: TerminalBridge::new().expect("Could not initialize terminal"),
theme_provider, theme_provider,
@@ -66,6 +70,14 @@ impl Context {
self.ft_params.as_ref() 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 { pub fn config(&self) -> &ConfigClient {
&self.config_client &self.config_client
} }

View File

@@ -33,6 +33,7 @@ pub mod parser;
pub mod path; pub mod path;
pub mod random; pub mod random;
pub mod string; pub mod string;
pub mod tty;
pub mod ui; pub mod ui;
#[cfg(test)] #[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),
}
}