mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 01:26:04 -08:00
Merge branch 'bookmarks' into 0.2.0
This commit is contained in:
233
Cargo.lock
generated
233
Cargo.lock
generated
@@ -1,5 +1,15 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aes-soft"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.15"
|
||||
@@ -50,12 +60,75 @@ dependencies = [
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d70f2a8c3126a2aec089e0aebcd945607e1155bfb5b89682eddf43c3ce386718"
|
||||
dependencies = [
|
||||
"block-padding 0.1.5",
|
||||
"byte-tools 0.2.0",
|
||||
"generic-array 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-modes"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0"
|
||||
dependencies = [
|
||||
"block-padding 0.2.1",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
dependencies = [
|
||||
"byte-tools 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "bytesize"
|
||||
version = "1.0.1"
|
||||
@@ -99,6 +172,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
@@ -130,6 +212,21 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||
|
||||
[[package]]
|
||||
name = "cpuid-bool"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||
|
||||
[[package]]
|
||||
name = "crc-any"
|
||||
version = "2.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3784befdf9469f4d51c69ef0b774f6a99de6bcc655285f746f16e0dd63d9007"
|
||||
dependencies = [
|
||||
"debug-helper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.1"
|
||||
@@ -166,6 +263,41 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-helper"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8a5bb894f24f42c247f19b25928a88e31867c0f84552c05df41a9dd527435e"
|
||||
|
||||
[[package]]
|
||||
name = "des"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b24e7c748888aa2fa8bce21d8c64a52efc810663285315ac7476f7197a982fae"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
|
||||
dependencies = [
|
||||
"generic-array 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "3.0.1"
|
||||
@@ -213,6 +345,34 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8107dafa78c80c848b71b60133954b4a58609a3a1a5f9af037ecc7f67280f369"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
@@ -327,12 +487,41 @@ dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magic-crypt"
|
||||
version = "3.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a01cf5086c27e3daff2a06886ab2fc44fe4fdec7d2df7a82e5329483011bfd7"
|
||||
dependencies = [
|
||||
"aes-soft",
|
||||
"base64",
|
||||
"block-modes",
|
||||
"crc-any",
|
||||
"des",
|
||||
"digest 0.7.6",
|
||||
"digest 0.9.0",
|
||||
"md-5",
|
||||
"sha2",
|
||||
"tiger-digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
@@ -408,6 +597,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.30"
|
||||
@@ -686,6 +881,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if 1.0.0",
|
||||
"cpuid-bool",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.16"
|
||||
@@ -779,6 +987,8 @@ dependencies = [
|
||||
"getopts",
|
||||
"hostname",
|
||||
"lazy_static",
|
||||
"magic-crypt",
|
||||
"rand",
|
||||
"regex",
|
||||
"rpassword",
|
||||
"serde",
|
||||
@@ -811,6 +1021,17 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiger-digest"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68067e91b4b9bb2e1ce3dc55077c984bbe2fa2be65308264dab403c165257545"
|
||||
dependencies = [
|
||||
"block-buffer 0.5.1",
|
||||
"byte-tools 0.2.0",
|
||||
"digest 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
@@ -844,6 +1065,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
@@ -878,6 +1105,12 @@ version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
|
||||
@@ -23,6 +23,8 @@ ftp4 = { version = "^4.0.1", features = ["secure"] }
|
||||
getopts = "0.2.21"
|
||||
hostname = "0.3.1"
|
||||
lazy_static = "1.4.0"
|
||||
magic-crypt = "3.1.6"
|
||||
rand = "0.7.3"
|
||||
regex = "1.4.2"
|
||||
rpassword = "5.0.0"
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
|
||||
@@ -160,7 +160,7 @@ impl ActivityManager {
|
||||
0 => None,
|
||||
_ => Some(activity.password.clone()),
|
||||
},
|
||||
protocol: activity.protocol.clone(),
|
||||
protocol: activity.protocol,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ pub struct Bookmark {
|
||||
pub port: u16,
|
||||
pub protocol: String,
|
||||
pub username: String,
|
||||
pub password: Option<String>, // Password is optional; base64, aes-128 encrypted password
|
||||
}
|
||||
|
||||
// Errors
|
||||
|
||||
@@ -110,6 +110,7 @@ mod tests {
|
||||
assert_eq!(host.port, 22);
|
||||
assert_eq!(host.protocol, String::from("SCP"));
|
||||
assert_eq!(host.username, String::from("root"));
|
||||
assert_eq!(host.password, None);
|
||||
// Verify bookmarks
|
||||
assert_eq!(hosts.bookmarks.len(), 3);
|
||||
let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap();
|
||||
@@ -117,16 +118,19 @@ mod tests {
|
||||
assert_eq!(host.port, 22);
|
||||
assert_eq!(host.protocol, String::from("SFTP"));
|
||||
assert_eq!(host.username, String::from("root"));
|
||||
assert_eq!(*host.password.as_ref().unwrap(), String::from("mypassword"));
|
||||
let host: &Bookmark = hosts.bookmarks.get("msi-estrem").unwrap();
|
||||
assert_eq!(host.address, String::from("192.168.1.30"));
|
||||
assert_eq!(host.port, 22);
|
||||
assert_eq!(host.protocol, String::from("SFTP"));
|
||||
assert_eq!(host.username, String::from("cvisintin"));
|
||||
assert_eq!(*host.password.as_ref().unwrap(), String::from("mysecret"));
|
||||
let host: &Bookmark = hosts.bookmarks.get("aws-server-prod1").unwrap();
|
||||
assert_eq!(host.address, String::from("51.23.67.12"));
|
||||
assert_eq!(host.port, 21);
|
||||
assert_eq!(host.protocol, String::from("FTPS"));
|
||||
assert_eq!(host.username, String::from("aws001"));
|
||||
assert_eq!(host.password, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -150,6 +154,7 @@ mod tests {
|
||||
port: 22,
|
||||
protocol: String::from("SFTP"),
|
||||
username: String::from("root"),
|
||||
password: None,
|
||||
},
|
||||
);
|
||||
bookmarks.insert(
|
||||
@@ -159,6 +164,7 @@ mod tests {
|
||||
port: 4022,
|
||||
protocol: String::from("SFTP"),
|
||||
username: String::from("cvisintin"),
|
||||
password: Some(String::from("password")),
|
||||
},
|
||||
);
|
||||
let mut recents: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||
@@ -169,6 +175,7 @@ mod tests {
|
||||
port: 3022,
|
||||
protocol: String::from("SCP"),
|
||||
username: String::from("omar"),
|
||||
password: Some(String::from("aaa")),
|
||||
},
|
||||
);
|
||||
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
@@ -183,8 +190,8 @@ mod tests {
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[bookmarks]
|
||||
raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root" }
|
||||
msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP", username = "cvisintin" }
|
||||
raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root", password = "mypassword" }
|
||||
msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP", username = "cvisintin", password = "mysecret" }
|
||||
aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" }
|
||||
|
||||
[recents]
|
||||
|
||||
@@ -37,7 +37,7 @@ pub mod sftp_transfer;
|
||||
///
|
||||
/// This enum defines the different transfer protocol available in TermSCP
|
||||
|
||||
#[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone)]
|
||||
#[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone, Copy)]
|
||||
pub enum FileTransferProtocol {
|
||||
Sftp,
|
||||
Scp,
|
||||
|
||||
@@ -19,12 +19,16 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate magic_crypt;
|
||||
|
||||
pub mod activity_manager;
|
||||
pub mod bookmarks;
|
||||
pub mod filetransfer;
|
||||
pub mod fs;
|
||||
pub mod host;
|
||||
pub mod system;
|
||||
pub mod ui;
|
||||
pub mod utils;
|
||||
|
||||
@@ -26,6 +26,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
extern crate getopts;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate magic_crypt;
|
||||
extern crate rpassword;
|
||||
|
||||
// External libs
|
||||
@@ -40,6 +42,7 @@ mod bookmarks;
|
||||
mod filetransfer;
|
||||
mod fs;
|
||||
mod host;
|
||||
mod system;
|
||||
mod ui;
|
||||
mod utils;
|
||||
|
||||
|
||||
562
src/system/bookmarks_client.rs
Normal file
562
src/system/bookmarks_client.rs
Normal file
@@ -0,0 +1,562 @@
|
||||
//! ## BookmarksClient
|
||||
//!
|
||||
//! `bookmarks_client` is the module which provides an API between the Bookmarks module and the system
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Deps
|
||||
extern crate magic_crypt;
|
||||
extern crate rand;
|
||||
|
||||
// Local
|
||||
use crate::bookmarks::serializer::BookmarkSerializer;
|
||||
use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts};
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::utils::time_to_str;
|
||||
// Ext
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use std::fs::{OpenOptions, Permissions};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// ## BookmarksClient
|
||||
///
|
||||
/// BookmarksClient provides a layer between the host system and the bookmarks module
|
||||
pub struct BookmarksClient {
|
||||
hosts: UserHosts,
|
||||
bookmarks_file: PathBuf,
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl BookmarksClient {
|
||||
/// ### BookmarksClient
|
||||
///
|
||||
/// Instantiates a new BookmarksClient
|
||||
/// Bookmarks file path must be provided
|
||||
/// Key file must be provided
|
||||
pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result<BookmarksClient, SerializerError> {
|
||||
// Create default hosts
|
||||
let default_hosts: UserHosts = Default::default();
|
||||
// If key file doesn't exist, create key, otherwise read it
|
||||
let key: String = match key_file.exists() {
|
||||
true => match BookmarksClient::load_key(key_file) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
false => match BookmarksClient::generate_key(key_file) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
};
|
||||
let mut client: BookmarksClient = BookmarksClient {
|
||||
hosts: default_hosts,
|
||||
bookmarks_file: PathBuf::from(bookmarks_file),
|
||||
key,
|
||||
};
|
||||
// If bookmark file doesn't exist, initialize it
|
||||
if !bookmarks_file.exists() {
|
||||
if let Err(err) = client.write_bookmarks() {
|
||||
return Err(err);
|
||||
}
|
||||
} else {
|
||||
// Load bookmarks from file
|
||||
if let Err(err) = client.read_bookmarks() {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
// Load key
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// ### iter_bookmarks
|
||||
///
|
||||
/// Iterate over bookmarks keys
|
||||
pub fn iter_bookmarks(&self) -> Box<dyn Iterator<Item = &String> + '_> {
|
||||
Box::new(self.hosts.bookmarks.keys())
|
||||
}
|
||||
|
||||
/// ### get_bookmark
|
||||
///
|
||||
/// Get bookmark associated to key
|
||||
pub fn get_bookmark(
|
||||
&self,
|
||||
key: &str,
|
||||
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
|
||||
let entry: &Bookmark = self.hosts.bookmarks.get(key)?;
|
||||
Some((
|
||||
entry.address.clone(),
|
||||
entry.port,
|
||||
match entry.protocol.to_ascii_uppercase().as_str() {
|
||||
"FTP" => FileTransferProtocol::Ftp(false),
|
||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
||||
"SCP" => FileTransferProtocol::Scp,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
},
|
||||
entry.username.clone(),
|
||||
match &entry.password {
|
||||
// Decrypted password if Some; if decryption fails return None
|
||||
Some(pwd) => match self.decrypt_str(pwd.as_str()) {
|
||||
Ok(decrypted_pwd) => Some(decrypted_pwd),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// ### add_recent
|
||||
///
|
||||
/// Add a new recent to bookmarks
|
||||
pub fn add_bookmark(
|
||||
&mut self,
|
||||
name: String,
|
||||
addr: String,
|
||||
port: u16,
|
||||
protocol: FileTransferProtocol,
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
) {
|
||||
if name.is_empty() {
|
||||
panic!("Bookmark name can't be empty");
|
||||
}
|
||||
// Make bookmark
|
||||
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password);
|
||||
self.hosts.bookmarks.insert(name, host);
|
||||
}
|
||||
|
||||
/// ### del_bookmark
|
||||
///
|
||||
/// Delete entry from bookmarks
|
||||
pub fn del_bookmark(&mut self, name: &str) {
|
||||
let _ = self.hosts.bookmarks.remove(name);
|
||||
}
|
||||
/// ### iter_recents
|
||||
///
|
||||
/// Iterate over recents keys
|
||||
pub fn iter_recents(&self) -> Box<dyn Iterator<Item = &String> + '_> {
|
||||
Box::new(self.hosts.recents.keys())
|
||||
}
|
||||
|
||||
/// ### get_recent
|
||||
///
|
||||
/// Get recent associated to key
|
||||
pub fn get_recent(&self, key: &str) -> Option<(String, u16, FileTransferProtocol, String)> {
|
||||
// NOTE: password is not decrypted; recents will never have password
|
||||
let entry: &Bookmark = self.hosts.recents.get(key)?;
|
||||
Some((
|
||||
entry.address.clone(),
|
||||
entry.port,
|
||||
match entry.protocol.to_ascii_uppercase().as_str() {
|
||||
"FTP" => FileTransferProtocol::Ftp(false),
|
||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
||||
"SCP" => FileTransferProtocol::Scp,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
},
|
||||
entry.username.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
/// ### add_recent
|
||||
///
|
||||
/// Add a new recent to bookmarks
|
||||
pub fn add_recent(
|
||||
&mut self,
|
||||
addr: String,
|
||||
port: u16,
|
||||
protocol: FileTransferProtocol,
|
||||
username: String,
|
||||
) {
|
||||
// Make bookmark
|
||||
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, None);
|
||||
// Check if duplicated
|
||||
for recent_host in self.hosts.recents.values() {
|
||||
if *recent_host == host {
|
||||
// Don't save duplicates
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If hosts size is bigger than 16; pop last
|
||||
if self.hosts.recents.len() >= 16 {
|
||||
let mut keys: Vec<String> = Vec::with_capacity(self.hosts.recents.len());
|
||||
for key in self.hosts.recents.keys() {
|
||||
keys.push(key.clone());
|
||||
}
|
||||
// Sort keys; NOTE: most recent is the last element
|
||||
keys.sort();
|
||||
// Delete keys starting from the last one
|
||||
for key in keys.iter() {
|
||||
let _ = self.hosts.recents.remove(key);
|
||||
// If length is < 16; break
|
||||
if self.hosts.recents.len() < 16 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let name: String = time_to_str(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
|
||||
self.hosts.recents.insert(name, host);
|
||||
}
|
||||
|
||||
/// ### del_recent
|
||||
///
|
||||
/// Delete entry from recents
|
||||
pub fn del_recent(&mut self, name: &str) {
|
||||
let _ = self.hosts.recents.remove(name);
|
||||
}
|
||||
|
||||
/// ### write_bookmarks
|
||||
///
|
||||
/// Write bookmarks to file
|
||||
pub fn write_bookmarks(&self) -> Result<(), SerializerError> {
|
||||
// Open file
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(self.bookmarks_file.as_path())
|
||||
{
|
||||
Ok(writer) => {
|
||||
let serializer: BookmarkSerializer = BookmarkSerializer {};
|
||||
serializer.serialize(Box::new(writer), &self.hosts)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### read_bookmarks
|
||||
///
|
||||
/// Read bookmarks from file
|
||||
fn read_bookmarks(&mut self) -> Result<(), SerializerError> {
|
||||
// Open bookmarks file for read
|
||||
match OpenOptions::new()
|
||||
.read(true)
|
||||
.open(self.bookmarks_file.as_path())
|
||||
{
|
||||
Ok(reader) => {
|
||||
// Deserialize
|
||||
let deserializer: BookmarkSerializer = BookmarkSerializer {};
|
||||
match deserializer.deserialize(Box::new(reader)) {
|
||||
Ok(hosts) => {
|
||||
self.hosts = hosts;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### generate_key
|
||||
///
|
||||
/// Generate a new AES key and write it to key file
|
||||
fn generate_key(key_file: &Path) -> Result<String, SerializerError> {
|
||||
// Generate 256 bytes (2048 bits) key
|
||||
let key: String = rand::thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(256)
|
||||
.collect::<String>();
|
||||
// Write file
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(key_file)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
// Write key to file
|
||||
if let Err(err) = file.write_all(key.as_bytes()) {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
));
|
||||
}
|
||||
// Set file to readonly
|
||||
let mut permissions: Permissions = file.metadata().unwrap().permissions();
|
||||
permissions.set_readonly(true);
|
||||
let _ = file.set_permissions(permissions);
|
||||
Ok(key)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### make_bookmark
|
||||
///
|
||||
/// Make bookmark from credentials
|
||||
fn make_bookmark(
|
||||
&self,
|
||||
addr: String,
|
||||
port: u16,
|
||||
protocol: FileTransferProtocol,
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
) -> Bookmark {
|
||||
Bookmark {
|
||||
address: addr,
|
||||
port,
|
||||
username,
|
||||
protocol: match protocol {
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
true => String::from("FTPS"),
|
||||
false => String::from("FTP"),
|
||||
},
|
||||
FileTransferProtocol::Scp => String::from("SCP"),
|
||||
FileTransferProtocol::Sftp => String::from("SFTP"),
|
||||
},
|
||||
password: match password {
|
||||
Some(p) => Some(self.encrypt_str(p.as_str())), // Encrypt password if provided
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// ### load_key
|
||||
///
|
||||
/// Load key from key_file
|
||||
fn load_key(key_file: &Path) -> Result<String, SerializerError> {
|
||||
match OpenOptions::new().read(true).open(key_file) {
|
||||
Ok(mut file) => {
|
||||
let mut key: String = String::with_capacity(256);
|
||||
match file.read_to_string(&mut key) {
|
||||
Ok(_) => Ok(key),
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### encrypt_str
|
||||
///
|
||||
/// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64
|
||||
fn encrypt_str(&self, txt: &str) -> String {
|
||||
let crypter = new_magic_crypt!(self.key.clone(), 128);
|
||||
crypter.encrypt_str_to_base64(txt.to_string())
|
||||
}
|
||||
|
||||
/// ### decrypt_str
|
||||
///
|
||||
/// Decrypt provided string using AES-128
|
||||
fn decrypt_str(&self, secret: &str) -> Result<String, SerializerError> {
|
||||
let crypter = new_magic_crypt!(self.key.clone(), 128);
|
||||
match crypter.decrypt_base64_to_string(secret.to_string()) {
|
||||
Ok(txt) => Ok(txt),
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::SyntaxError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_system_bookmarks_new() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let client: BookmarksClient =
|
||||
BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap();
|
||||
// Verify client
|
||||
assert_eq!(client.hosts.bookmarks.len(), 0);
|
||||
assert_eq!(client.hosts.recents.len(), 0);
|
||||
assert!(client.key.len() > 0);
|
||||
assert_eq!(client.bookmarks_file, cfg_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_bookmarks_new_err() {
|
||||
assert!(
|
||||
BookmarksClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar"))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_bookmarks_new_from_existing() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap();
|
||||
// Add some bookmarks
|
||||
client.add_bookmark(
|
||||
String::from("raspberry"),
|
||||
String::from("192.168.1.31"),
|
||||
22,
|
||||
FileTransferProtocol::Sftp,
|
||||
String::from("pi"),
|
||||
Some(String::from("mypassword")),
|
||||
);
|
||||
client.add_recent(
|
||||
String::from("192.168.1.31"),
|
||||
22,
|
||||
FileTransferProtocol::Sftp,
|
||||
String::from("pi"),
|
||||
);
|
||||
let recent_key: String = String::from(client.iter_recents().next().unwrap());
|
||||
assert!(client.write_bookmarks().is_ok());
|
||||
let key: String = client.key.clone();
|
||||
// Re-initialize a client
|
||||
let client: BookmarksClient =
|
||||
BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap();
|
||||
// Verify it loaded parameters correctly
|
||||
assert_eq!(client.key, key);
|
||||
let bookmark: (String, u16, FileTransferProtocol, String, Option<String>) =
|
||||
client.get_bookmark(&String::from("raspberry")).unwrap();
|
||||
assert_eq!(bookmark.0, String::from("192.168.1.31"));
|
||||
assert_eq!(bookmark.1, 22);
|
||||
assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
|
||||
assert_eq!(bookmark.3, String::from("pi"));
|
||||
assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword"));
|
||||
let bookmark: (String, u16, FileTransferProtocol, String) =
|
||||
client.get_recent(&recent_key).unwrap();
|
||||
assert_eq!(bookmark.0, String::from("192.168.1.31"));
|
||||
assert_eq!(bookmark.1, 22);
|
||||
assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
|
||||
assert_eq!(bookmark.3, String::from("pi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_bookmarks_manipulate_bookmarks() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap();
|
||||
// Add bookmark
|
||||
client.add_bookmark(
|
||||
String::from("raspberry"),
|
||||
String::from("192.168.1.31"),
|
||||
22,
|
||||
FileTransferProtocol::Sftp,
|
||||
String::from("pi"),
|
||||
Some(String::from("mypassword")),
|
||||
);
|
||||
// Get bookmark
|
||||
let bookmark: (String, u16, FileTransferProtocol, String, Option<String>) =
|
||||
client.get_bookmark(&String::from("raspberry")).unwrap();
|
||||
assert_eq!(bookmark.0, String::from("192.168.1.31"));
|
||||
assert_eq!(bookmark.1, 22);
|
||||
assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
|
||||
assert_eq!(bookmark.3, String::from("pi"));
|
||||
assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword"));
|
||||
// Write bookmarks
|
||||
assert!(client.write_bookmarks().is_ok());
|
||||
// Delete bookmark
|
||||
client.del_bookmark(&String::from("raspberry"));
|
||||
// Get unexisting bookmark
|
||||
assert!(client.get_bookmark(&String::from("raspberry")).is_none());
|
||||
// Write bookmarks
|
||||
assert!(client.write_bookmarks().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_bookmarks_manipulate_recents() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap();
|
||||
// Add bookmark
|
||||
client.add_recent(
|
||||
String::from("192.168.1.31"),
|
||||
22,
|
||||
FileTransferProtocol::Sftp,
|
||||
String::from("pi"),
|
||||
);
|
||||
let key: String = String::from(client.iter_recents().next().unwrap());
|
||||
// Get bookmark
|
||||
let bookmark: (String, u16, FileTransferProtocol, String) =
|
||||
client.get_recent(&key).unwrap();
|
||||
assert_eq!(bookmark.0, String::from("192.168.1.31"));
|
||||
assert_eq!(bookmark.1, 22);
|
||||
assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
|
||||
assert_eq!(bookmark.3, String::from("pi"));
|
||||
// Write bookmarks
|
||||
assert!(client.write_bookmarks().is_ok());
|
||||
// Delete bookmark
|
||||
client.del_recent(&key);
|
||||
// Get unexisting bookmark
|
||||
assert!(client.get_bookmark(&key).is_none());
|
||||
// Write bookmarks
|
||||
assert!(client.write_bookmarks().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_system_bookmarks_add_bookmark_empty() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap();
|
||||
// Add bookmark
|
||||
client.add_bookmark(
|
||||
String::from(""),
|
||||
String::from("192.168.1.31"),
|
||||
22,
|
||||
FileTransferProtocol::Sftp,
|
||||
String::from("pi"),
|
||||
Some(String::from("mypassword")),
|
||||
);
|
||||
}
|
||||
|
||||
/// ### get_paths
|
||||
///
|
||||
/// Get paths for configuration and key for bookmarks
|
||||
fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
|
||||
let mut k: PathBuf = PathBuf::from(dir);
|
||||
let mut c: PathBuf = k.clone();
|
||||
k.push("bookmarks.key");
|
||||
c.push("bookmarks.toml");
|
||||
(c, k)
|
||||
}
|
||||
|
||||
/// ### create_tmp_dir
|
||||
///
|
||||
/// Create temporary directory
|
||||
fn create_tmp_dir() -> tempfile::TempDir {
|
||||
tempfile::TempDir::new().ok().unwrap()
|
||||
}
|
||||
}
|
||||
68
src/system/environment.rs
Normal file
68
src/system/environment.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
//! ## Environment
|
||||
//!
|
||||
//! `environment` is the module which provides Path and values for the system environment
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Deps
|
||||
extern crate dirs;
|
||||
|
||||
// Ext
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// ### get_config_dir
|
||||
///
|
||||
/// Get termscp configuration directory path.
|
||||
/// Returns None, if it's not possible to get it
|
||||
pub fn init_config_dir() -> Result<Option<PathBuf>, String> {
|
||||
// Get file
|
||||
lazy_static! {
|
||||
static ref CONF_DIR: Option<PathBuf> = dirs::config_dir();
|
||||
}
|
||||
if CONF_DIR.is_some() {
|
||||
// Get path of bookmarks
|
||||
let mut p: PathBuf = CONF_DIR.as_ref().unwrap().clone();
|
||||
// Append termscp dir
|
||||
p.push("termscp/");
|
||||
// If directory doesn't exist, create it
|
||||
match p.exists() {
|
||||
true => Ok(Some(p)),
|
||||
false => match std::fs::create_dir(p.as_path()) {
|
||||
Ok(_) => Ok(Some(p)),
|
||||
Err(err) => Err(err.to_string()),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_system_environment_get_config_dir() {
|
||||
assert!(init_config_dir().ok().unwrap().is_some());
|
||||
}
|
||||
}
|
||||
28
src/system/mod.rs
Normal file
28
src/system/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! ## System
|
||||
//!
|
||||
//! `system` is the module which contains functions and data types related to current system
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// modules
|
||||
pub mod bookmarks_client;
|
||||
pub mod environment;
|
||||
@@ -27,78 +27,31 @@
|
||||
extern crate dirs;
|
||||
|
||||
// Locals
|
||||
use super::{AuthActivity, Color, FileTransferProtocol, InputMode, PopupType, UserHosts};
|
||||
use crate::bookmarks::serializer::BookmarkSerializer;
|
||||
use crate::bookmarks::Bookmark;
|
||||
use crate::utils::time_to_str;
|
||||
use super::{AuthActivity, Color, DialogYesNoOption, InputMode, PopupType};
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::environment;
|
||||
|
||||
// Ext
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
|
||||
impl AuthActivity {
|
||||
/// ### read_bookmarks
|
||||
///
|
||||
/// Read bookmarks from data file; Show popup if necessary
|
||||
pub(super) fn read_bookmarks(&mut self) {
|
||||
// Init bookmarks
|
||||
if let Some(bookmark_file) = self.init_bookmarks() {
|
||||
// Read
|
||||
if self.context.is_some() {
|
||||
match self
|
||||
.context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.local
|
||||
.open_file_read(bookmark_file.as_path())
|
||||
{
|
||||
Ok(reader) => {
|
||||
// Read bookmarks
|
||||
let deserializer: BookmarkSerializer = BookmarkSerializer {};
|
||||
match deserializer.deserialize(Box::new(reader)) {
|
||||
Ok(bookmarks) => self.bookmarks = Some(bookmarks),
|
||||
Err(err) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
format!(
|
||||
"Could not read bookmarks from \"{}\": {}",
|
||||
bookmark_file.display(),
|
||||
err
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
format!(
|
||||
"Could not read bookmarks from \"{}\": {}",
|
||||
bookmark_file.display(),
|
||||
err
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### del_bookmark
|
||||
///
|
||||
/// Delete bookmark
|
||||
pub(super) fn del_bookmark(&mut self, idx: usize) {
|
||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||
// Iterate over kyes
|
||||
let mut name: Option<String> = None;
|
||||
for (i, key) in hosts.bookmarks.keys().enumerate() {
|
||||
for (i, key) in bookmarks_cli.iter_bookmarks().enumerate() {
|
||||
if i == idx {
|
||||
name = Some(key.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(name) = name {
|
||||
hosts.bookmarks.remove(name.as_str());
|
||||
bookmarks_cli.del_bookmark(&name);
|
||||
// Write bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,20 +60,20 @@ impl AuthActivity {
|
||||
///
|
||||
/// Load selected bookmark (at index) to input fields
|
||||
pub(super) fn load_bookmark(&mut self, idx: usize) {
|
||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
|
||||
// Iterate over bookmarks
|
||||
for (i, bookmark) in hosts.bookmarks.values().enumerate() {
|
||||
for (i, key) in bookmarks_cli.iter_bookmarks().enumerate() {
|
||||
if i == idx {
|
||||
// Load parameters
|
||||
self.address = bookmark.address.clone();
|
||||
self.port = bookmark.port.to_string();
|
||||
self.protocol = match bookmark.protocol.as_str().to_uppercase().as_str() {
|
||||
"FTP" => FileTransferProtocol::Ftp(false),
|
||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
||||
"SCP" => FileTransferProtocol::Scp,
|
||||
_ => FileTransferProtocol::Sftp, // Default to SFTP
|
||||
};
|
||||
self.username = bookmark.username.clone();
|
||||
if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) {
|
||||
// Load parameters
|
||||
self.address = bookmark.0;
|
||||
self.port = bookmark.1.to_string();
|
||||
self.protocol = bookmark.2;
|
||||
self.username = bookmark.3;
|
||||
if let Some(password) = bookmark.4 {
|
||||
self.password = password;
|
||||
}
|
||||
}
|
||||
// Break
|
||||
break;
|
||||
}
|
||||
@@ -132,29 +85,61 @@ impl AuthActivity {
|
||||
///
|
||||
/// Save current input fields as a bookmark
|
||||
pub(super) fn save_bookmark(&mut self, name: String) {
|
||||
if let Ok(host) = self.make_user_host() {
|
||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
||||
hosts.bookmarks.insert(name, host);
|
||||
// Write bookmarks
|
||||
self.write_bookmarks();
|
||||
// Check port
|
||||
let port: u16 = match self.port.parse::<usize>() {
|
||||
Ok(val) => {
|
||||
if val > 65535 {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Red,
|
||||
String::from("Specified port must be in range 0-65535"),
|
||||
));
|
||||
return;
|
||||
}
|
||||
val as u16
|
||||
}
|
||||
Err(_) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Red,
|
||||
String::from("Specified port is not a number"),
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||
// Check if password must be saved
|
||||
let password: Option<String> = match self.choice_opt {
|
||||
DialogYesNoOption::Yes => Some(self.password.clone()),
|
||||
DialogYesNoOption::No => None,
|
||||
};
|
||||
bookmarks_cli.add_bookmark(
|
||||
name,
|
||||
self.address.clone(),
|
||||
port,
|
||||
self.protocol,
|
||||
self.username.clone(),
|
||||
password,
|
||||
);
|
||||
// Save bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
}
|
||||
/// ### del_recent
|
||||
///
|
||||
/// Delete recent
|
||||
pub(super) fn del_recent(&mut self, idx: usize) {
|
||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
||||
if let Some(client) = self.bookmarks_client.as_mut() {
|
||||
// Iterate over kyes
|
||||
let mut name: Option<String> = None;
|
||||
for (i, key) in hosts.recents.keys().enumerate() {
|
||||
for (i, key) in client.iter_recents().enumerate() {
|
||||
if i == idx {
|
||||
name = Some(key.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(name) = name {
|
||||
hosts.recents.remove(name.as_str());
|
||||
client.del_recent(&name);
|
||||
// Save bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,22 +148,19 @@ impl AuthActivity {
|
||||
///
|
||||
/// Load selected recent (at index) to input fields
|
||||
pub(super) fn load_recent(&mut self, idx: usize) {
|
||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
||||
if let Some(client) = self.bookmarks_client.as_ref() {
|
||||
// Iterate over bookmarks
|
||||
for (i, bookmark) in hosts.recents.values().enumerate() {
|
||||
for (i, key) in client.iter_recents().enumerate() {
|
||||
if i == idx {
|
||||
// Load parameters
|
||||
self.address = bookmark.address.clone();
|
||||
self.port = bookmark.port.to_string();
|
||||
self.protocol = match bookmark.protocol.as_str().to_uppercase().as_str() {
|
||||
"FTP" => FileTransferProtocol::Ftp(false),
|
||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
||||
"SCP" => FileTransferProtocol::Scp,
|
||||
_ => FileTransferProtocol::Sftp, // Default to SFTP
|
||||
};
|
||||
self.username = bookmark.username.clone();
|
||||
// Break
|
||||
break;
|
||||
if let Some(bookmark) = client.get_recent(key) {
|
||||
// Load parameters
|
||||
self.address = bookmark.0;
|
||||
self.port = bookmark.1.to_string();
|
||||
self.protocol = bookmark.2;
|
||||
self.username = bookmark.3;
|
||||
// Break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,46 +170,6 @@ impl AuthActivity {
|
||||
///
|
||||
/// Save current input fields as a "recent"
|
||||
pub(super) fn save_recent(&mut self) {
|
||||
if let Ok(host) = self.make_user_host() {
|
||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
||||
// Check if duplicated
|
||||
for recent_host in hosts.recents.values() {
|
||||
if *recent_host == host {
|
||||
// Don't save duplicates
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If hosts size is bigger than 16; pop last
|
||||
if hosts.recents.len() >= 16 {
|
||||
let mut keys: Vec<String> = Vec::with_capacity(hosts.recents.len());
|
||||
for key in hosts.recents.keys() {
|
||||
keys.push(key.clone());
|
||||
}
|
||||
// Sort keys; NOTE: most recent is the last element
|
||||
keys.sort();
|
||||
// Delete keys starting from the last one
|
||||
for key in keys.iter() {
|
||||
let _ = hosts.recents.remove(key);
|
||||
// If length is < 16; break
|
||||
if hosts.recents.len() < 16 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create name
|
||||
let name: String = time_to_str(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
|
||||
// Save host to recents
|
||||
hosts.recents.insert(name, host);
|
||||
// Write bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### make_user_host
|
||||
///
|
||||
/// Make user host from current input fields
|
||||
fn make_user_host(&mut self) -> Result<Bookmark, ()> {
|
||||
// Check port
|
||||
let port: u16 = match self.port.parse::<usize>() {
|
||||
Ok(val) => {
|
||||
@@ -236,7 +178,7 @@ impl AuthActivity {
|
||||
Color::Red,
|
||||
String::from("Specified port must be in range 0-65535"),
|
||||
));
|
||||
return Err(());
|
||||
return;
|
||||
}
|
||||
val as u16
|
||||
}
|
||||
@@ -245,156 +187,72 @@ impl AuthActivity {
|
||||
Color::Red,
|
||||
String::from("Specified port is not a number"),
|
||||
));
|
||||
return Err(());
|
||||
return;
|
||||
}
|
||||
};
|
||||
Ok(Bookmark {
|
||||
address: self.address.clone(),
|
||||
port,
|
||||
protocol: match self.protocol {
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
true => String::from("FTPS"),
|
||||
false => String::from("FTP"),
|
||||
},
|
||||
FileTransferProtocol::Scp => String::from("SCP"),
|
||||
FileTransferProtocol::Sftp => String::from("SFTP"),
|
||||
},
|
||||
username: self.username.clone(),
|
||||
})
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||
bookmarks_cli.add_recent(
|
||||
self.address.clone(),
|
||||
port,
|
||||
self.protocol,
|
||||
self.username.clone(),
|
||||
);
|
||||
// Save bookmarks
|
||||
self.write_bookmarks();
|
||||
}
|
||||
}
|
||||
|
||||
/// ### write_bookmarks
|
||||
///
|
||||
/// Write bookmarks to file
|
||||
fn write_bookmarks(&mut self) {
|
||||
if self.bookmarks.is_some() && self.context.is_some() {
|
||||
// Open file for write
|
||||
if let Some(bookmarks_file) = self.init_bookmarks() {
|
||||
match self
|
||||
.context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.local
|
||||
.open_file_write(bookmarks_file.as_path())
|
||||
{
|
||||
Ok(writer) => {
|
||||
let serializer: BookmarkSerializer = BookmarkSerializer {};
|
||||
if let Err(err) = serializer
|
||||
.serialize(Box::new(writer), &self.bookmarks.as_ref().unwrap())
|
||||
{
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
format!(
|
||||
"Could not write default bookmarks at \"{}\": {}",
|
||||
bookmarks_file.display(),
|
||||
err
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
format!(
|
||||
"Could not write default bookmarks at \"{}\": {}",
|
||||
bookmarks_file.display(),
|
||||
err
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
|
||||
if let Err(err) = bookmarks_cli.write_bookmarks() {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Red,
|
||||
format!("Could not write bookmarks: {}", err),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### init_bookmarks
|
||||
/// ### init_bookmarks_client
|
||||
///
|
||||
/// Initialize bookmarks directory
|
||||
/// Returns bookmark path
|
||||
fn init_bookmarks(&mut self) -> Option<PathBuf> {
|
||||
// Get file
|
||||
lazy_static! {
|
||||
static ref CONF_DIR: Option<PathBuf> = dirs::config_dir();
|
||||
}
|
||||
if CONF_DIR.is_some() {
|
||||
// Get path of bookmarks
|
||||
let mut p: PathBuf = CONF_DIR.as_ref().unwrap().clone();
|
||||
// Append termscp dir
|
||||
p.push("termscp/");
|
||||
// Mkdir if doesn't exist
|
||||
if self.context.is_some() {
|
||||
if let Err(err) = self
|
||||
.context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.local
|
||||
.mkdir_ex(p.as_path(), true)
|
||||
{
|
||||
// Show popup
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
format!(
|
||||
"Could not create configuration directory at \"{}\": {}",
|
||||
p.display(),
|
||||
err
|
||||
),
|
||||
));
|
||||
// Return None
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Append bookmarks.toml
|
||||
p.push("bookmarks.toml");
|
||||
// If bookmarks.toml doesn't exist, initializae it
|
||||
if self.context.is_some()
|
||||
&& !self
|
||||
.context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.local
|
||||
.file_exists(p.as_path())
|
||||
{
|
||||
// Write file
|
||||
let default_hosts: UserHosts = Default::default();
|
||||
match self
|
||||
.context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.local
|
||||
.open_file_write(p.as_path())
|
||||
{
|
||||
Ok(writer) => {
|
||||
let serializer: BookmarkSerializer = BookmarkSerializer {};
|
||||
// Serialize and write
|
||||
if let Err(err) = serializer.serialize(Box::new(writer), &default_hosts) {
|
||||
/// 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(path) = path {
|
||||
// Prepare paths
|
||||
let mut bookmarks_file: PathBuf = path.clone();
|
||||
bookmarks_file.push("bookmarks.toml");
|
||||
let mut key_file: PathBuf = path;
|
||||
key_file.push(".bookmarks.key"); // key file is hidden
|
||||
// Initialize client
|
||||
match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path()) {
|
||||
Ok(cli) => self.bookmarks_client = Some(cli),
|
||||
Err(err) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
Color::Red,
|
||||
format!(
|
||||
"Could not write default bookmarks at \"{}\": {}",
|
||||
p.display(),
|
||||
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
||||
bookmarks_file.display(),
|
||||
key_file.display(),
|
||||
err
|
||||
),
|
||||
));
|
||||
return None;
|
||||
))
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Yellow,
|
||||
format!(
|
||||
"Could not write default bookmarks at \"{}\": {}",
|
||||
p.display(),
|
||||
err
|
||||
),
|
||||
));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// return path
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
Err(err) => {
|
||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||
Color::Red,
|
||||
format!("Could not initialize configuration directory: {}", err),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ impl AuthActivity {
|
||||
///
|
||||
/// Callback used to save bookmark with name
|
||||
pub(super) fn callback_save_bookmark(&mut self, input: String) {
|
||||
self.save_bookmark(input);
|
||||
if !input.is_empty() {
|
||||
self.save_bookmark(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
use super::{
|
||||
AuthActivity, DialogCallback, DialogYesNoOption, FileTransferProtocol, InputEvent, InputField,
|
||||
InputForm, InputMode, OnInputSubmitCallback, PopupType,
|
||||
InputForm, InputMode, PopupType,
|
||||
};
|
||||
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
@@ -160,11 +160,10 @@ impl AuthActivity {
|
||||
self.input_mode = InputMode::Popup(PopupType::Help);
|
||||
}
|
||||
'S' | 's' => {
|
||||
// Default choice option to no
|
||||
self.choice_opt = DialogYesNoOption::No;
|
||||
// Save bookmark as...
|
||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
||||
String::from("Save bookmark as..."),
|
||||
AuthActivity::callback_save_bookmark,
|
||||
));
|
||||
self.input_mode = InputMode::Popup(PopupType::SaveBookmark);
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
@@ -234,14 +233,14 @@ impl AuthActivity {
|
||||
// Move bookmarks index up
|
||||
if self.bookmarks_idx > 0 {
|
||||
self.bookmarks_idx -= 1;
|
||||
} else if let Some(hosts) = &self.bookmarks {
|
||||
} else if let Some(bookmarks_cli) = &self.bookmarks_client {
|
||||
// Put to last index (wrap)
|
||||
self.bookmarks_idx = hosts.bookmarks.len() - 1;
|
||||
self.bookmarks_idx = bookmarks_cli.iter_bookmarks().count() - 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Down => {
|
||||
if let Some(hosts) = &self.bookmarks {
|
||||
let size: usize = hosts.bookmarks.len();
|
||||
if let Some(bookmarks_cli) = &self.bookmarks_client {
|
||||
let size: usize = bookmarks_cli.iter_bookmarks().count();
|
||||
// Check if can move down
|
||||
if self.bookmarks_idx + 1 >= size {
|
||||
// Move bookmarks index down
|
||||
@@ -282,11 +281,10 @@ impl AuthActivity {
|
||||
self.input_mode = InputMode::Popup(PopupType::Help);
|
||||
}
|
||||
'S' | 's' => {
|
||||
// Default choice option to no
|
||||
self.choice_opt = DialogYesNoOption::No;
|
||||
// Save bookmark as...
|
||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
||||
String::from("Save bookmark as..."),
|
||||
AuthActivity::callback_save_bookmark,
|
||||
));
|
||||
self.input_mode = InputMode::Popup(PopupType::SaveBookmark);
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
},
|
||||
@@ -315,14 +313,14 @@ impl AuthActivity {
|
||||
// Move bookmarks index up
|
||||
if self.recents_idx > 0 {
|
||||
self.recents_idx -= 1;
|
||||
} else if let Some(hosts) = &self.bookmarks {
|
||||
} else if let Some(bookmarks_cli) = &self.bookmarks_client {
|
||||
// Put to last index (wrap)
|
||||
self.recents_idx = hosts.recents.len() - 1;
|
||||
self.recents_idx = bookmarks_cli.iter_recents().count() - 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Down => {
|
||||
if let Some(hosts) = &self.bookmarks {
|
||||
let size: usize = hosts.recents.len();
|
||||
if let Some(bookmarks_cli) = &self.bookmarks_client {
|
||||
let size: usize = bookmarks_cli.iter_recents().count();
|
||||
// Check if can move down
|
||||
if self.recents_idx + 1 >= size {
|
||||
// Move bookmarks index down
|
||||
@@ -363,11 +361,10 @@ impl AuthActivity {
|
||||
self.input_mode = InputMode::Popup(PopupType::Help);
|
||||
}
|
||||
'S' | 's' => {
|
||||
// Default choice option to no
|
||||
self.choice_opt = DialogYesNoOption::No;
|
||||
// Save bookmark as...
|
||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
||||
String::from("Save bookmark as..."),
|
||||
AuthActivity::callback_save_bookmark,
|
||||
));
|
||||
self.input_mode = InputMode::Popup(PopupType::SaveBookmark);
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
},
|
||||
@@ -383,7 +380,7 @@ impl AuthActivity {
|
||||
match ptype {
|
||||
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
|
||||
PopupType::Help => self.handle_input_event_mode_popup_help(ev),
|
||||
PopupType::Input(_, cb) => self.handle_input_event_mode_popup_input(ev, cb),
|
||||
PopupType::SaveBookmark => self.handle_input_event_mode_popup_save_bookmark(ev),
|
||||
PopupType::YesNo(_, yes_cb, no_cb) => {
|
||||
self.handle_input_event_mode_popup_yesno(ev, yes_cb, no_cb)
|
||||
}
|
||||
@@ -418,14 +415,10 @@ impl AuthActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_input
|
||||
/// ### handle_input_event_mode_popup_save_bookmark
|
||||
///
|
||||
/// Input event handler for input popup
|
||||
pub(super) fn handle_input_event_mode_popup_input(
|
||||
&mut self,
|
||||
ev: &InputEvent,
|
||||
cb: OnInputSubmitCallback,
|
||||
) {
|
||||
/// Input event handler for SaveBookmark popup
|
||||
pub(super) fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) {
|
||||
// If enter, close popup, otherwise push chars to input
|
||||
if let InputEvent::Key(key) = ev {
|
||||
match key.code {
|
||||
@@ -435,6 +428,8 @@ impl AuthActivity {
|
||||
self.input_txt.clear();
|
||||
// Set mode back to form
|
||||
self.input_mode = InputMode::Form;
|
||||
// Reset choice option to yes
|
||||
self.choice_opt = DialogYesNoOption::Yes;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// Submit
|
||||
@@ -444,8 +439,12 @@ impl AuthActivity {
|
||||
// Set mode back to form BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
|
||||
self.input_mode = InputMode::Form;
|
||||
// Call cb
|
||||
cb(self, input_text);
|
||||
self.callback_save_bookmark(input_text);
|
||||
// Reset choice option to yes
|
||||
self.choice_opt = DialogYesNoOption::Yes;
|
||||
}
|
||||
KeyCode::Left => self.choice_opt = DialogYesNoOption::Yes, // Move yes/no with arrows
|
||||
KeyCode::Right => self.choice_opt = DialogYesNoOption::No, // Move yes/no with arrows
|
||||
KeyCode::Char(ch) => self.input_txt.push(ch),
|
||||
KeyCode::Backspace => {
|
||||
let _ = self.input_txt.pop();
|
||||
|
||||
@@ -27,8 +27,6 @@ use super::{
|
||||
AuthActivity, Context, DialogYesNoOption, FileTransferProtocol, InputField, InputForm,
|
||||
InputMode, PopupType,
|
||||
};
|
||||
|
||||
use crate::bookmarks::Bookmark;
|
||||
use crate::utils::align_text_center;
|
||||
|
||||
use tui::{
|
||||
@@ -128,7 +126,7 @@ impl AuthActivity {
|
||||
let (width, height): (u16, u16) = match popup {
|
||||
PopupType::Alert(_, _) => (50, 10),
|
||||
PopupType::Help => (50, 70),
|
||||
PopupType::Input(_, _) => (40, 10),
|
||||
PopupType::SaveBookmark => (20, 20),
|
||||
PopupType::YesNo(_, _, _) => (30, 10),
|
||||
};
|
||||
let popup_area: Rect = self.draw_popup_area(f.size(), width, height);
|
||||
@@ -139,12 +137,25 @@ impl AuthActivity {
|
||||
popup_area,
|
||||
),
|
||||
PopupType::Help => f.render_widget(self.draw_popup_help(), popup_area),
|
||||
PopupType::Input(txt, _) => {
|
||||
f.render_widget(self.draw_popup_input(txt.clone()), popup_area);
|
||||
PopupType::SaveBookmark => {
|
||||
let popup_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Input form
|
||||
Constraint::Length(2), // Yes/No
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(popup_area);
|
||||
let (input, yes_no): (Paragraph, Tabs) = self.draw_popup_save_bookmark();
|
||||
// Render parts
|
||||
f.render_widget(input, popup_chunks[0]);
|
||||
f.render_widget(yes_no, popup_chunks[1]);
|
||||
// Set cursor
|
||||
f.set_cursor(
|
||||
popup_area.x + self.input_txt.width() as u16 + 1,
|
||||
popup_area.y + 1,
|
||||
popup_chunks[0].x + self.input_txt.width() as u16 + 1,
|
||||
popup_chunks[0].y + 1,
|
||||
)
|
||||
}
|
||||
PopupType::YesNo(txt, _, _) => {
|
||||
@@ -273,21 +284,26 @@ impl AuthActivity {
|
||||
///
|
||||
/// Draw local explorer list
|
||||
pub(super) fn draw_bookmarks_tab(&self) -> Option<List> {
|
||||
self.bookmarks.as_ref()?;
|
||||
self.bookmarks_client.as_ref()?;
|
||||
let hosts: Vec<ListItem> = self
|
||||
.bookmarks
|
||||
.bookmarks_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.bookmarks
|
||||
.iter()
|
||||
.map(|(key, entry): (&String, &Bookmark)| {
|
||||
.iter_bookmarks()
|
||||
.map(|key: &String| {
|
||||
let entry: (String, u16, FileTransferProtocol, String, _) = self
|
||||
.bookmarks_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_bookmark(key)
|
||||
.unwrap();
|
||||
ListItem::new(Span::from(format!(
|
||||
"{} ({}://{}@{}:{})",
|
||||
key,
|
||||
entry.protocol.to_lowercase(),
|
||||
entry.username,
|
||||
entry.address,
|
||||
entry.port
|
||||
AuthActivity::protocol_to_str(entry.2),
|
||||
entry.3,
|
||||
entry.0,
|
||||
entry.1
|
||||
)))
|
||||
})
|
||||
.collect();
|
||||
@@ -316,20 +332,25 @@ impl AuthActivity {
|
||||
///
|
||||
/// Draw local explorer list
|
||||
pub(super) fn draw_recents_tab(&self) -> Option<List> {
|
||||
self.bookmarks.as_ref()?;
|
||||
self.bookmarks_client.as_ref()?;
|
||||
let hosts: Vec<ListItem> = self
|
||||
.bookmarks
|
||||
.bookmarks_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.recents
|
||||
.values()
|
||||
.map(|entry: &Bookmark| {
|
||||
.iter_recents()
|
||||
.map(|key: &String| {
|
||||
let entry: (String, u16, FileTransferProtocol, String) = self
|
||||
.bookmarks_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_recent(key)
|
||||
.unwrap();
|
||||
ListItem::new(Span::from(format!(
|
||||
"{}://{}@{}:{}",
|
||||
entry.protocol.to_lowercase(),
|
||||
entry.username,
|
||||
entry.address,
|
||||
entry.port
|
||||
AuthActivity::protocol_to_str(entry.2),
|
||||
entry.3,
|
||||
entry.0,
|
||||
entry.1
|
||||
)))
|
||||
})
|
||||
.collect();
|
||||
@@ -406,10 +427,33 @@ impl AuthActivity {
|
||||
/// ### draw_popup_input
|
||||
///
|
||||
/// Draw input popup
|
||||
pub(super) fn draw_popup_input(&self, text: String) -> Paragraph {
|
||||
Paragraph::new(self.input_txt.as_ref())
|
||||
pub(super) fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) {
|
||||
let input: Paragraph = Paragraph::new(self.input_txt.as_ref())
|
||||
.style(Style::default().fg(Color::White))
|
||||
.block(Block::default().borders(Borders::ALL).title(text))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::TOP | Borders::RIGHT | Borders::LEFT)
|
||||
.title("Save bookmark as..."),
|
||||
);
|
||||
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
||||
let index: usize = match self.choice_opt {
|
||||
DialogYesNoOption::Yes => 0,
|
||||
DialogYesNoOption::No => 1,
|
||||
};
|
||||
let tabs: Tabs = Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::BOTTOM | Borders::RIGHT | Borders::LEFT)
|
||||
.title("Save password?"),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.fg(Color::LightRed),
|
||||
);
|
||||
(input, tabs)
|
||||
}
|
||||
|
||||
/// ### draw_popup_yesno
|
||||
@@ -538,4 +582,18 @@ impl AuthActivity {
|
||||
)
|
||||
.start_corner(Corner::TopLeft)
|
||||
}
|
||||
|
||||
/// ### protocol_to_str
|
||||
///
|
||||
/// Convert protocol to str for layouts
|
||||
fn protocol_to_str(proto: FileTransferProtocol) -> &'static str {
|
||||
match proto {
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
true => "ftps",
|
||||
false => "ftp",
|
||||
},
|
||||
FileTransferProtocol::Scp => "scp",
|
||||
FileTransferProtocol::Sftp => "sftp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ extern crate unicode_width;
|
||||
|
||||
// locals
|
||||
use super::{Activity, Context};
|
||||
use crate::bookmarks::UserHosts;
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
|
||||
// Includes
|
||||
use crossterm::event::Event as InputEvent;
|
||||
@@ -46,7 +46,6 @@ use tui::style::Color;
|
||||
|
||||
// Types
|
||||
type DialogCallback = fn(&mut AuthActivity);
|
||||
type OnInputSubmitCallback = fn(&mut AuthActivity, String);
|
||||
|
||||
/// ### InputField
|
||||
///
|
||||
@@ -76,7 +75,7 @@ enum DialogYesNoOption {
|
||||
enum PopupType {
|
||||
Alert(Color, String), // Show a message displaying text with the provided color
|
||||
Help, // Help page
|
||||
Input(String, OnInputSubmitCallback), // Input description; Callback for submit
|
||||
SaveBookmark,
|
||||
YesNo(String, DialogCallback, DialogCallback), // Yes, no callback
|
||||
}
|
||||
|
||||
@@ -111,7 +110,7 @@ pub struct AuthActivity {
|
||||
pub submit: bool, // becomes true after user has submitted fields
|
||||
pub quit: bool, // Becomes true if user has pressed esc
|
||||
context: Option<Context>,
|
||||
bookmarks: Option<UserHosts>,
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
selected_field: InputField, // Selected field in AuthCredentials Form
|
||||
input_mode: InputMode,
|
||||
input_form: InputForm,
|
||||
@@ -143,7 +142,7 @@ impl AuthActivity {
|
||||
submit: false,
|
||||
quit: false,
|
||||
context: None,
|
||||
bookmarks: None,
|
||||
bookmarks_client: None,
|
||||
selected_field: InputField::Address,
|
||||
input_mode: InputMode::Form,
|
||||
input_form: InputForm::AuthCredentials,
|
||||
@@ -171,9 +170,9 @@ impl Activity for AuthActivity {
|
||||
// Put raw mode on enabled
|
||||
let _ = enable_raw_mode();
|
||||
self.input_mode = InputMode::Form;
|
||||
// Read bookmarks
|
||||
if self.bookmarks.is_none() {
|
||||
self.read_bookmarks();
|
||||
// Init bookmarks client
|
||||
if self.bookmarks_client.is_none() {
|
||||
self.init_bookmarks_client();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ impl FileTransferActivity {
|
||||
///
|
||||
/// Instantiates a new FileTransferActivity
|
||||
pub fn new(params: FileTransferParams) -> FileTransferActivity {
|
||||
let protocol: FileTransferProtocol = params.protocol.clone();
|
||||
let protocol: FileTransferProtocol = params.protocol;
|
||||
FileTransferActivity {
|
||||
disconnected: false,
|
||||
quit: false,
|
||||
|
||||
Reference in New Issue
Block a user