mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -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.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# 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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.15"
|
version = "0.7.15"
|
||||||
@@ -50,12 +60,75 @@ dependencies = [
|
|||||||
"constant_time_eq",
|
"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]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
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]]
|
[[package]]
|
||||||
name = "bytesize"
|
name = "bytesize"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -99,6 +172,15 @@ dependencies = [
|
|||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "cloudabi"
|
name = "cloudabi"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
@@ -130,6 +212,21 @@ version = "0.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
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]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -166,6 +263,41 @@ dependencies = [
|
|||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
@@ -213,6 +345,34 @@ dependencies = [
|
|||||||
"regex",
|
"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]]
|
[[package]]
|
||||||
name = "getopts"
|
name = "getopts"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -327,12 +487,41 @@ dependencies = [
|
|||||||
"cfg-if 0.1.10",
|
"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]]
|
[[package]]
|
||||||
name = "match_cfg"
|
name = "match_cfg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
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]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.3.4"
|
||||||
@@ -408,6 +597,12 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.30"
|
version = "0.10.30"
|
||||||
@@ -686,6 +881,19 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -779,6 +987,8 @@ dependencies = [
|
|||||||
"getopts",
|
"getopts",
|
||||||
"hostname",
|
"hostname",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"magic-crypt",
|
||||||
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"rpassword",
|
"rpassword",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -811,6 +1021,17 @@ dependencies = [
|
|||||||
"lazy_static",
|
"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]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@@ -844,6 +1065,12 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
@@ -878,6 +1105,12 @@ version = "0.2.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ ftp4 = { version = "^4.0.1", features = ["secure"] }
|
|||||||
getopts = "0.2.21"
|
getopts = "0.2.21"
|
||||||
hostname = "0.3.1"
|
hostname = "0.3.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
magic-crypt = "3.1.6"
|
||||||
|
rand = "0.7.3"
|
||||||
regex = "1.4.2"
|
regex = "1.4.2"
|
||||||
rpassword = "5.0.0"
|
rpassword = "5.0.0"
|
||||||
serde = { version = "1.0.118", features = ["derive"] }
|
serde = { version = "1.0.118", features = ["derive"] }
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ impl ActivityManager {
|
|||||||
0 => None,
|
0 => None,
|
||||||
_ => Some(activity.password.clone()),
|
_ => Some(activity.password.clone()),
|
||||||
},
|
},
|
||||||
protocol: activity.protocol.clone(),
|
protocol: activity.protocol,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub struct Bookmark {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub protocol: String,
|
pub protocol: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
pub password: Option<String>, // Password is optional; base64, aes-128 encrypted password
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ mod tests {
|
|||||||
assert_eq!(host.port, 22);
|
assert_eq!(host.port, 22);
|
||||||
assert_eq!(host.protocol, String::from("SCP"));
|
assert_eq!(host.protocol, String::from("SCP"));
|
||||||
assert_eq!(host.username, String::from("root"));
|
assert_eq!(host.username, String::from("root"));
|
||||||
|
assert_eq!(host.password, None);
|
||||||
// Verify bookmarks
|
// Verify bookmarks
|
||||||
assert_eq!(hosts.bookmarks.len(), 3);
|
assert_eq!(hosts.bookmarks.len(), 3);
|
||||||
let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap();
|
let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap();
|
||||||
@@ -117,16 +118,19 @@ mod tests {
|
|||||||
assert_eq!(host.port, 22);
|
assert_eq!(host.port, 22);
|
||||||
assert_eq!(host.protocol, String::from("SFTP"));
|
assert_eq!(host.protocol, String::from("SFTP"));
|
||||||
assert_eq!(host.username, String::from("root"));
|
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();
|
let host: &Bookmark = hosts.bookmarks.get("msi-estrem").unwrap();
|
||||||
assert_eq!(host.address, String::from("192.168.1.30"));
|
assert_eq!(host.address, String::from("192.168.1.30"));
|
||||||
assert_eq!(host.port, 22);
|
assert_eq!(host.port, 22);
|
||||||
assert_eq!(host.protocol, String::from("SFTP"));
|
assert_eq!(host.protocol, String::from("SFTP"));
|
||||||
assert_eq!(host.username, String::from("cvisintin"));
|
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();
|
let host: &Bookmark = hosts.bookmarks.get("aws-server-prod1").unwrap();
|
||||||
assert_eq!(host.address, String::from("51.23.67.12"));
|
assert_eq!(host.address, String::from("51.23.67.12"));
|
||||||
assert_eq!(host.port, 21);
|
assert_eq!(host.port, 21);
|
||||||
assert_eq!(host.protocol, String::from("FTPS"));
|
assert_eq!(host.protocol, String::from("FTPS"));
|
||||||
assert_eq!(host.username, String::from("aws001"));
|
assert_eq!(host.username, String::from("aws001"));
|
||||||
|
assert_eq!(host.password, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -150,6 +154,7 @@ mod tests {
|
|||||||
port: 22,
|
port: 22,
|
||||||
protocol: String::from("SFTP"),
|
protocol: String::from("SFTP"),
|
||||||
username: String::from("root"),
|
username: String::from("root"),
|
||||||
|
password: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
bookmarks.insert(
|
bookmarks.insert(
|
||||||
@@ -159,6 +164,7 @@ mod tests {
|
|||||||
port: 4022,
|
port: 4022,
|
||||||
protocol: String::from("SFTP"),
|
protocol: String::from("SFTP"),
|
||||||
username: String::from("cvisintin"),
|
username: String::from("cvisintin"),
|
||||||
|
password: Some(String::from("password")),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let mut recents: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
let mut recents: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||||
@@ -169,6 +175,7 @@ mod tests {
|
|||||||
port: 3022,
|
port: 3022,
|
||||||
protocol: String::from("SCP"),
|
protocol: String::from("SCP"),
|
||||||
username: String::from("omar"),
|
username: String::from("omar"),
|
||||||
|
password: Some(String::from("aaa")),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||||
@@ -183,8 +190,8 @@ mod tests {
|
|||||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||||
let file_content: &str = r#"
|
let file_content: &str = r#"
|
||||||
[bookmarks]
|
[bookmarks]
|
||||||
raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root" }
|
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" }
|
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" }
|
aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" }
|
||||||
|
|
||||||
[recents]
|
[recents]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub mod sftp_transfer;
|
|||||||
///
|
///
|
||||||
/// This enum defines the different transfer protocol available in TermSCP
|
/// 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 {
|
pub enum FileTransferProtocol {
|
||||||
Sftp,
|
Sftp,
|
||||||
Scp,
|
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 activity_manager;
|
||||||
pub mod bookmarks;
|
pub mod bookmarks;
|
||||||
pub mod filetransfer;
|
pub mod filetransfer;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod host;
|
pub mod host;
|
||||||
|
pub mod system;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
|||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate magic_crypt;
|
||||||
extern crate rpassword;
|
extern crate rpassword;
|
||||||
|
|
||||||
// External libs
|
// External libs
|
||||||
@@ -40,6 +42,7 @@ mod bookmarks;
|
|||||||
mod filetransfer;
|
mod filetransfer;
|
||||||
mod fs;
|
mod fs;
|
||||||
mod host;
|
mod host;
|
||||||
|
mod system;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod utils;
|
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;
|
extern crate dirs;
|
||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
use super::{AuthActivity, Color, FileTransferProtocol, InputMode, PopupType, UserHosts};
|
use super::{AuthActivity, Color, DialogYesNoOption, InputMode, PopupType};
|
||||||
use crate::bookmarks::serializer::BookmarkSerializer;
|
use crate::system::bookmarks_client::BookmarksClient;
|
||||||
use crate::bookmarks::Bookmark;
|
use crate::system::environment;
|
||||||
use crate::utils::time_to_str;
|
|
||||||
|
|
||||||
// Ext
|
// Ext
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
impl AuthActivity {
|
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
|
/// ### del_bookmark
|
||||||
///
|
///
|
||||||
/// 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(hosts) = self.bookmarks.as_mut() {
|
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||||
// Iterate over kyes
|
// Iterate over kyes
|
||||||
let mut name: Option<String> = None;
|
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 {
|
if i == idx {
|
||||||
name = Some(key.clone());
|
name = Some(key.clone());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(name) = name {
|
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
|
/// 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(hosts) = self.bookmarks.as_mut() {
|
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
|
||||||
// Iterate over bookmarks
|
// Iterate over bookmarks
|
||||||
for (i, bookmark) in hosts.bookmarks.values().enumerate() {
|
for (i, key) in bookmarks_cli.iter_bookmarks().enumerate() {
|
||||||
if i == idx {
|
if i == idx {
|
||||||
// Load parameters
|
if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) {
|
||||||
self.address = bookmark.address.clone();
|
// Load parameters
|
||||||
self.port = bookmark.port.to_string();
|
self.address = bookmark.0;
|
||||||
self.protocol = match bookmark.protocol.as_str().to_uppercase().as_str() {
|
self.port = bookmark.1.to_string();
|
||||||
"FTP" => FileTransferProtocol::Ftp(false),
|
self.protocol = bookmark.2;
|
||||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
self.username = bookmark.3;
|
||||||
"SCP" => FileTransferProtocol::Scp,
|
if let Some(password) = bookmark.4 {
|
||||||
_ => FileTransferProtocol::Sftp, // Default to SFTP
|
self.password = password;
|
||||||
};
|
}
|
||||||
self.username = bookmark.username.clone();
|
}
|
||||||
// Break
|
// Break
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -132,29 +85,61 @@ impl AuthActivity {
|
|||||||
///
|
///
|
||||||
/// Save current input fields as a bookmark
|
/// Save current input fields as a bookmark
|
||||||
pub(super) fn save_bookmark(&mut self, name: String) {
|
pub(super) fn save_bookmark(&mut self, name: String) {
|
||||||
if let Ok(host) = self.make_user_host() {
|
// Check port
|
||||||
if let Some(hosts) = self.bookmarks.as_mut() {
|
let port: u16 = match self.port.parse::<usize>() {
|
||||||
hosts.bookmarks.insert(name, host);
|
Ok(val) => {
|
||||||
// Write bookmarks
|
if val > 65535 {
|
||||||
self.write_bookmarks();
|
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
|
/// ### del_recent
|
||||||
///
|
///
|
||||||
/// 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(hosts) = self.bookmarks.as_mut() {
|
if let Some(client) = self.bookmarks_client.as_mut() {
|
||||||
// Iterate over kyes
|
// Iterate over kyes
|
||||||
let mut name: Option<String> = None;
|
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 {
|
if i == idx {
|
||||||
name = Some(key.clone());
|
name = Some(key.clone());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(name) = name {
|
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
|
/// 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(hosts) = self.bookmarks.as_mut() {
|
if let Some(client) = self.bookmarks_client.as_ref() {
|
||||||
// Iterate over bookmarks
|
// Iterate over bookmarks
|
||||||
for (i, bookmark) in hosts.recents.values().enumerate() {
|
for (i, key) in client.iter_recents().enumerate() {
|
||||||
if i == idx {
|
if i == idx {
|
||||||
// Load parameters
|
if let Some(bookmark) = client.get_recent(key) {
|
||||||
self.address = bookmark.address.clone();
|
// Load parameters
|
||||||
self.port = bookmark.port.to_string();
|
self.address = bookmark.0;
|
||||||
self.protocol = match bookmark.protocol.as_str().to_uppercase().as_str() {
|
self.port = bookmark.1.to_string();
|
||||||
"FTP" => FileTransferProtocol::Ftp(false),
|
self.protocol = bookmark.2;
|
||||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
self.username = bookmark.3;
|
||||||
"SCP" => FileTransferProtocol::Scp,
|
// Break
|
||||||
_ => FileTransferProtocol::Sftp, // Default to SFTP
|
break;
|
||||||
};
|
}
|
||||||
self.username = bookmark.username.clone();
|
|
||||||
// Break
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,46 +170,6 @@ impl AuthActivity {
|
|||||||
///
|
///
|
||||||
/// Save current input fields as a "recent"
|
/// Save current input fields as a "recent"
|
||||||
pub(super) fn save_recent(&mut self) {
|
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
|
// Check port
|
||||||
let port: u16 = match self.port.parse::<usize>() {
|
let port: u16 = match self.port.parse::<usize>() {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
@@ -236,7 +178,7 @@ impl AuthActivity {
|
|||||||
Color::Red,
|
Color::Red,
|
||||||
String::from("Specified port must be in range 0-65535"),
|
String::from("Specified port must be in range 0-65535"),
|
||||||
));
|
));
|
||||||
return Err(());
|
return;
|
||||||
}
|
}
|
||||||
val as u16
|
val as u16
|
||||||
}
|
}
|
||||||
@@ -245,156 +187,72 @@ impl AuthActivity {
|
|||||||
Color::Red,
|
Color::Red,
|
||||||
String::from("Specified port is not a number"),
|
String::from("Specified port is not a number"),
|
||||||
));
|
));
|
||||||
return Err(());
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Bookmark {
|
if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() {
|
||||||
address: self.address.clone(),
|
bookmarks_cli.add_recent(
|
||||||
port,
|
self.address.clone(),
|
||||||
protocol: match self.protocol {
|
port,
|
||||||
FileTransferProtocol::Ftp(secure) => match secure {
|
self.protocol,
|
||||||
true => String::from("FTPS"),
|
self.username.clone(),
|
||||||
false => String::from("FTP"),
|
);
|
||||||
},
|
// Save bookmarks
|
||||||
FileTransferProtocol::Scp => String::from("SCP"),
|
self.write_bookmarks();
|
||||||
FileTransferProtocol::Sftp => String::from("SFTP"),
|
}
|
||||||
},
|
|
||||||
username: self.username.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### write_bookmarks
|
/// ### write_bookmarks
|
||||||
///
|
///
|
||||||
/// Write bookmarks to file
|
/// Write bookmarks to file
|
||||||
fn write_bookmarks(&mut self) {
|
fn write_bookmarks(&mut self) {
|
||||||
if self.bookmarks.is_some() && self.context.is_some() {
|
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
|
||||||
// Open file for write
|
if let Err(err) = bookmarks_cli.write_bookmarks() {
|
||||||
if let Some(bookmarks_file) = self.init_bookmarks() {
|
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||||
match self
|
Color::Red,
|
||||||
.context
|
format!("Could not write bookmarks: {}", err),
|
||||||
.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
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### init_bookmarks
|
/// ### init_bookmarks_client
|
||||||
///
|
///
|
||||||
/// Initialize bookmarks directory
|
/// Initialize bookmarks client
|
||||||
/// Returns bookmark path
|
pub(super) fn init_bookmarks_client(&mut self) {
|
||||||
fn init_bookmarks(&mut self) -> Option<PathBuf> {
|
// Get config dir
|
||||||
// Get file
|
match environment::init_config_dir() {
|
||||||
lazy_static! {
|
Ok(path) => {
|
||||||
static ref CONF_DIR: Option<PathBuf> = dirs::config_dir();
|
// 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 {
|
||||||
if CONF_DIR.is_some() {
|
// Prepare paths
|
||||||
// Get path of bookmarks
|
let mut bookmarks_file: PathBuf = path.clone();
|
||||||
let mut p: PathBuf = CONF_DIR.as_ref().unwrap().clone();
|
bookmarks_file.push("bookmarks.toml");
|
||||||
// Append termscp dir
|
let mut key_file: PathBuf = path;
|
||||||
p.push("termscp/");
|
key_file.push(".bookmarks.key"); // key file is hidden
|
||||||
// Mkdir if doesn't exist
|
// Initialize client
|
||||||
if self.context.is_some() {
|
match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path()) {
|
||||||
if let Err(err) = self
|
Ok(cli) => self.bookmarks_client = Some(cli),
|
||||||
.context
|
Err(err) => {
|
||||||
.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) {
|
|
||||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||||
Color::Yellow,
|
Color::Red,
|
||||||
format!(
|
format!(
|
||||||
"Could not write default bookmarks at \"{}\": {}",
|
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
||||||
p.display(),
|
bookmarks_file.display(),
|
||||||
|
key_file.display(),
|
||||||
err
|
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
|
Err(err) => {
|
||||||
Some(p)
|
self.input_mode = InputMode::Popup(PopupType::Alert(
|
||||||
} else {
|
Color::Red,
|
||||||
None
|
format!("Could not initialize configuration directory: {}", err),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ impl AuthActivity {
|
|||||||
///
|
///
|
||||||
/// Callback used to save bookmark with name
|
/// Callback used to save bookmark with name
|
||||||
pub(super) fn callback_save_bookmark(&mut self, input: String) {
|
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::{
|
use super::{
|
||||||
AuthActivity, DialogCallback, DialogYesNoOption, FileTransferProtocol, InputEvent, InputField,
|
AuthActivity, DialogCallback, DialogYesNoOption, FileTransferProtocol, InputEvent, InputField,
|
||||||
InputForm, InputMode, OnInputSubmitCallback, PopupType,
|
InputForm, InputMode, PopupType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
@@ -160,11 +160,10 @@ impl AuthActivity {
|
|||||||
self.input_mode = InputMode::Popup(PopupType::Help);
|
self.input_mode = InputMode::Popup(PopupType::Help);
|
||||||
}
|
}
|
||||||
'S' | 's' => {
|
'S' | 's' => {
|
||||||
|
// Default choice option to no
|
||||||
|
self.choice_opt = DialogYesNoOption::No;
|
||||||
// Save bookmark as...
|
// Save bookmark as...
|
||||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
self.input_mode = InputMode::Popup(PopupType::SaveBookmark);
|
||||||
String::from("Save bookmark as..."),
|
|
||||||
AuthActivity::callback_save_bookmark,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_ => { /* Nothing to do */ }
|
_ => { /* Nothing to do */ }
|
||||||
}
|
}
|
||||||
@@ -234,14 +233,14 @@ impl AuthActivity {
|
|||||||
// Move bookmarks index up
|
// Move bookmarks index up
|
||||||
if self.bookmarks_idx > 0 {
|
if self.bookmarks_idx > 0 {
|
||||||
self.bookmarks_idx -= 1;
|
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)
|
// Put to last index (wrap)
|
||||||
self.bookmarks_idx = hosts.bookmarks.len() - 1;
|
self.bookmarks_idx = bookmarks_cli.iter_bookmarks().count() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
if let Some(hosts) = &self.bookmarks {
|
if let Some(bookmarks_cli) = &self.bookmarks_client {
|
||||||
let size: usize = hosts.bookmarks.len();
|
let size: usize = bookmarks_cli.iter_bookmarks().count();
|
||||||
// Check if can move down
|
// Check if can move down
|
||||||
if self.bookmarks_idx + 1 >= size {
|
if self.bookmarks_idx + 1 >= size {
|
||||||
// Move bookmarks index down
|
// Move bookmarks index down
|
||||||
@@ -282,11 +281,10 @@ impl AuthActivity {
|
|||||||
self.input_mode = InputMode::Popup(PopupType::Help);
|
self.input_mode = InputMode::Popup(PopupType::Help);
|
||||||
}
|
}
|
||||||
'S' | 's' => {
|
'S' | 's' => {
|
||||||
|
// Default choice option to no
|
||||||
|
self.choice_opt = DialogYesNoOption::No;
|
||||||
// Save bookmark as...
|
// Save bookmark as...
|
||||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
self.input_mode = InputMode::Popup(PopupType::SaveBookmark);
|
||||||
String::from("Save bookmark as..."),
|
|
||||||
AuthActivity::callback_save_bookmark,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_ => { /* Nothing to do */ }
|
_ => { /* Nothing to do */ }
|
||||||
},
|
},
|
||||||
@@ -315,14 +313,14 @@ impl AuthActivity {
|
|||||||
// Move bookmarks index up
|
// Move bookmarks index up
|
||||||
if self.recents_idx > 0 {
|
if self.recents_idx > 0 {
|
||||||
self.recents_idx -= 1;
|
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)
|
// Put to last index (wrap)
|
||||||
self.recents_idx = hosts.recents.len() - 1;
|
self.recents_idx = bookmarks_cli.iter_recents().count() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
if let Some(hosts) = &self.bookmarks {
|
if let Some(bookmarks_cli) = &self.bookmarks_client {
|
||||||
let size: usize = hosts.recents.len();
|
let size: usize = bookmarks_cli.iter_recents().count();
|
||||||
// Check if can move down
|
// Check if can move down
|
||||||
if self.recents_idx + 1 >= size {
|
if self.recents_idx + 1 >= size {
|
||||||
// Move bookmarks index down
|
// Move bookmarks index down
|
||||||
@@ -363,11 +361,10 @@ impl AuthActivity {
|
|||||||
self.input_mode = InputMode::Popup(PopupType::Help);
|
self.input_mode = InputMode::Popup(PopupType::Help);
|
||||||
}
|
}
|
||||||
'S' | 's' => {
|
'S' | 's' => {
|
||||||
|
// Default choice option to no
|
||||||
|
self.choice_opt = DialogYesNoOption::No;
|
||||||
// Save bookmark as...
|
// Save bookmark as...
|
||||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
self.input_mode = InputMode::Popup(PopupType::SaveBookmark);
|
||||||
String::from("Save bookmark as..."),
|
|
||||||
AuthActivity::callback_save_bookmark,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_ => { /* Nothing to do */ }
|
_ => { /* Nothing to do */ }
|
||||||
},
|
},
|
||||||
@@ -383,7 +380,7 @@ impl AuthActivity {
|
|||||||
match ptype {
|
match ptype {
|
||||||
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
|
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
|
||||||
PopupType::Help => self.handle_input_event_mode_popup_help(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) => {
|
PopupType::YesNo(_, yes_cb, no_cb) => {
|
||||||
self.handle_input_event_mode_popup_yesno(ev, 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
|
/// Input event handler for SaveBookmark popup
|
||||||
pub(super) fn handle_input_event_mode_popup_input(
|
pub(super) fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) {
|
||||||
&mut self,
|
|
||||||
ev: &InputEvent,
|
|
||||||
cb: OnInputSubmitCallback,
|
|
||||||
) {
|
|
||||||
// If enter, close popup, otherwise push chars to input
|
// If enter, close popup, otherwise push chars to input
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
match key.code {
|
match key.code {
|
||||||
@@ -435,6 +428,8 @@ impl AuthActivity {
|
|||||||
self.input_txt.clear();
|
self.input_txt.clear();
|
||||||
// Set mode back to form
|
// Set mode back to form
|
||||||
self.input_mode = InputMode::Form;
|
self.input_mode = InputMode::Form;
|
||||||
|
// Reset choice option to yes
|
||||||
|
self.choice_opt = DialogYesNoOption::Yes;
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// Submit
|
// Submit
|
||||||
@@ -444,8 +439,12 @@ impl AuthActivity {
|
|||||||
// Set mode back to form BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
|
// Set mode back to form BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
|
||||||
self.input_mode = InputMode::Form;
|
self.input_mode = InputMode::Form;
|
||||||
// Call cb
|
// 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::Char(ch) => self.input_txt.push(ch),
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
let _ = self.input_txt.pop();
|
let _ = self.input_txt.pop();
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ use super::{
|
|||||||
AuthActivity, Context, DialogYesNoOption, FileTransferProtocol, InputField, InputForm,
|
AuthActivity, Context, DialogYesNoOption, FileTransferProtocol, InputField, InputForm,
|
||||||
InputMode, PopupType,
|
InputMode, PopupType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::bookmarks::Bookmark;
|
|
||||||
use crate::utils::align_text_center;
|
use crate::utils::align_text_center;
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
@@ -128,7 +126,7 @@ impl AuthActivity {
|
|||||||
let (width, height): (u16, u16) = match popup {
|
let (width, height): (u16, u16) = match popup {
|
||||||
PopupType::Alert(_, _) => (50, 10),
|
PopupType::Alert(_, _) => (50, 10),
|
||||||
PopupType::Help => (50, 70),
|
PopupType::Help => (50, 70),
|
||||||
PopupType::Input(_, _) => (40, 10),
|
PopupType::SaveBookmark => (20, 20),
|
||||||
PopupType::YesNo(_, _, _) => (30, 10),
|
PopupType::YesNo(_, _, _) => (30, 10),
|
||||||
};
|
};
|
||||||
let popup_area: Rect = self.draw_popup_area(f.size(), width, height);
|
let popup_area: Rect = self.draw_popup_area(f.size(), width, height);
|
||||||
@@ -139,12 +137,25 @@ impl AuthActivity {
|
|||||||
popup_area,
|
popup_area,
|
||||||
),
|
),
|
||||||
PopupType::Help => f.render_widget(self.draw_popup_help(), popup_area),
|
PopupType::Help => f.render_widget(self.draw_popup_help(), popup_area),
|
||||||
PopupType::Input(txt, _) => {
|
PopupType::SaveBookmark => {
|
||||||
f.render_widget(self.draw_popup_input(txt.clone()), popup_area);
|
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
|
// Set cursor
|
||||||
f.set_cursor(
|
f.set_cursor(
|
||||||
popup_area.x + self.input_txt.width() as u16 + 1,
|
popup_chunks[0].x + self.input_txt.width() as u16 + 1,
|
||||||
popup_area.y + 1,
|
popup_chunks[0].y + 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PopupType::YesNo(txt, _, _) => {
|
PopupType::YesNo(txt, _, _) => {
|
||||||
@@ -273,21 +284,26 @@ impl AuthActivity {
|
|||||||
///
|
///
|
||||||
/// Draw local explorer list
|
/// Draw local explorer list
|
||||||
pub(super) fn draw_bookmarks_tab(&self) -> Option<List> {
|
pub(super) fn draw_bookmarks_tab(&self) -> Option<List> {
|
||||||
self.bookmarks.as_ref()?;
|
self.bookmarks_client.as_ref()?;
|
||||||
let hosts: Vec<ListItem> = self
|
let hosts: Vec<ListItem> = self
|
||||||
.bookmarks
|
.bookmarks_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.bookmarks
|
.iter_bookmarks()
|
||||||
.iter()
|
.map(|key: &String| {
|
||||||
.map(|(key, entry): (&String, &Bookmark)| {
|
let entry: (String, u16, FileTransferProtocol, String, _) = self
|
||||||
|
.bookmarks_client
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get_bookmark(key)
|
||||||
|
.unwrap();
|
||||||
ListItem::new(Span::from(format!(
|
ListItem::new(Span::from(format!(
|
||||||
"{} ({}://{}@{}:{})",
|
"{} ({}://{}@{}:{})",
|
||||||
key,
|
key,
|
||||||
entry.protocol.to_lowercase(),
|
AuthActivity::protocol_to_str(entry.2),
|
||||||
entry.username,
|
entry.3,
|
||||||
entry.address,
|
entry.0,
|
||||||
entry.port
|
entry.1
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -316,20 +332,25 @@ impl AuthActivity {
|
|||||||
///
|
///
|
||||||
/// Draw local explorer list
|
/// Draw local explorer list
|
||||||
pub(super) fn draw_recents_tab(&self) -> Option<List> {
|
pub(super) fn draw_recents_tab(&self) -> Option<List> {
|
||||||
self.bookmarks.as_ref()?;
|
self.bookmarks_client.as_ref()?;
|
||||||
let hosts: Vec<ListItem> = self
|
let hosts: Vec<ListItem> = self
|
||||||
.bookmarks
|
.bookmarks_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.recents
|
.iter_recents()
|
||||||
.values()
|
.map(|key: &String| {
|
||||||
.map(|entry: &Bookmark| {
|
let entry: (String, u16, FileTransferProtocol, String) = self
|
||||||
|
.bookmarks_client
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get_recent(key)
|
||||||
|
.unwrap();
|
||||||
ListItem::new(Span::from(format!(
|
ListItem::new(Span::from(format!(
|
||||||
"{}://{}@{}:{}",
|
"{}://{}@{}:{}",
|
||||||
entry.protocol.to_lowercase(),
|
AuthActivity::protocol_to_str(entry.2),
|
||||||
entry.username,
|
entry.3,
|
||||||
entry.address,
|
entry.0,
|
||||||
entry.port
|
entry.1
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -406,10 +427,33 @@ impl AuthActivity {
|
|||||||
/// ### draw_popup_input
|
/// ### draw_popup_input
|
||||||
///
|
///
|
||||||
/// Draw input popup
|
/// Draw input popup
|
||||||
pub(super) fn draw_popup_input(&self, text: String) -> Paragraph {
|
pub(super) fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) {
|
||||||
Paragraph::new(self.input_txt.as_ref())
|
let input: Paragraph = Paragraph::new(self.input_txt.as_ref())
|
||||||
.style(Style::default().fg(Color::White))
|
.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
|
/// ### draw_popup_yesno
|
||||||
@@ -538,4 +582,18 @@ impl AuthActivity {
|
|||||||
)
|
)
|
||||||
.start_corner(Corner::TopLeft)
|
.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
|
// locals
|
||||||
use super::{Activity, Context};
|
use super::{Activity, Context};
|
||||||
use crate::bookmarks::UserHosts;
|
|
||||||
use crate::filetransfer::FileTransferProtocol;
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
use crate::system::bookmarks_client::BookmarksClient;
|
||||||
|
|
||||||
// Includes
|
// Includes
|
||||||
use crossterm::event::Event as InputEvent;
|
use crossterm::event::Event as InputEvent;
|
||||||
@@ -46,7 +46,6 @@ use tui::style::Color;
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
type DialogCallback = fn(&mut AuthActivity);
|
type DialogCallback = fn(&mut AuthActivity);
|
||||||
type OnInputSubmitCallback = fn(&mut AuthActivity, String);
|
|
||||||
|
|
||||||
/// ### InputField
|
/// ### InputField
|
||||||
///
|
///
|
||||||
@@ -76,7 +75,7 @@ enum DialogYesNoOption {
|
|||||||
enum PopupType {
|
enum PopupType {
|
||||||
Alert(Color, String), // Show a message displaying text with the provided color
|
Alert(Color, String), // Show a message displaying text with the provided color
|
||||||
Help, // Help page
|
Help, // Help page
|
||||||
Input(String, OnInputSubmitCallback), // Input description; Callback for submit
|
SaveBookmark,
|
||||||
YesNo(String, DialogCallback, DialogCallback), // Yes, no callback
|
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 submit: bool, // becomes true after user has submitted fields
|
||||||
pub quit: bool, // Becomes true if user has pressed esc
|
pub quit: bool, // Becomes true if user has pressed esc
|
||||||
context: Option<Context>,
|
context: Option<Context>,
|
||||||
bookmarks: Option<UserHosts>,
|
bookmarks_client: Option<BookmarksClient>,
|
||||||
selected_field: InputField, // Selected field in AuthCredentials Form
|
selected_field: InputField, // Selected field in AuthCredentials Form
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
input_form: InputForm,
|
input_form: InputForm,
|
||||||
@@ -143,7 +142,7 @@ impl AuthActivity {
|
|||||||
submit: false,
|
submit: false,
|
||||||
quit: false,
|
quit: false,
|
||||||
context: None,
|
context: None,
|
||||||
bookmarks: None,
|
bookmarks_client: None,
|
||||||
selected_field: InputField::Address,
|
selected_field: InputField::Address,
|
||||||
input_mode: InputMode::Form,
|
input_mode: InputMode::Form,
|
||||||
input_form: InputForm::AuthCredentials,
|
input_form: InputForm::AuthCredentials,
|
||||||
@@ -171,9 +170,9 @@ impl Activity for AuthActivity {
|
|||||||
// Put raw mode on enabled
|
// Put raw mode on enabled
|
||||||
let _ = enable_raw_mode();
|
let _ = enable_raw_mode();
|
||||||
self.input_mode = InputMode::Form;
|
self.input_mode = InputMode::Form;
|
||||||
// Read bookmarks
|
// Init bookmarks client
|
||||||
if self.bookmarks.is_none() {
|
if self.bookmarks_client.is_none() {
|
||||||
self.read_bookmarks();
|
self.init_bookmarks_client();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ impl FileTransferActivity {
|
|||||||
///
|
///
|
||||||
/// Instantiates a new FileTransferActivity
|
/// Instantiates a new FileTransferActivity
|
||||||
pub fn new(params: FileTransferParams) -> FileTransferActivity {
|
pub fn new(params: FileTransferParams) -> FileTransferActivity {
|
||||||
let protocol: FileTransferProtocol = params.protocol.clone();
|
let protocol: FileTransferProtocol = params.protocol;
|
||||||
FileTransferActivity {
|
FileTransferActivity {
|
||||||
disconnected: false,
|
disconnected: false,
|
||||||
quit: false,
|
quit: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user