From 6435271be8230a63314d581d5c4062b23c6f661f Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 28 Feb 2021 12:21:28 +0100 Subject: [PATCH 1/6] Parse semver util --- src/utils/parser.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/utils/parser.rs b/src/utils/parser.rs index ff90037..8e9c2b6 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -54,6 +54,13 @@ lazy_static! { * - group 5: Some(path) | None */ static ref REMOTE_OPT_REGEX: Regex = Regex::new(r"(?:([a-z]+)://)?(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?").ok().unwrap(); + /** + * Regex matches: + * - group 1: Version + * E.g. termscp-0.3.2 => 0.3.2 + * v0.4.0 => 0.4.0 + */ + static ref SEMVER_REGEX: Regex = Regex::new(r".*(:?[0-9]\.[0-9]\.[0-9])").unwrap(); } pub struct RemoteOptions { @@ -211,6 +218,19 @@ pub fn parse_datetime(tm: &str, fmt: &str) -> Result { } } +/// ### parse_semver +/// +/// Parse semver string +pub fn parse_semver(haystack: &str) -> Option { + match SEMVER_REGEX.captures(haystack) { + Some(groups) => match groups.get(1) { + Some(version) => Some(version.as_str().to_string()), + None => None, + }, + None => None, + } +} + #[cfg(test)] mod tests { @@ -384,4 +404,15 @@ mod tests { // Not enough argument for datetime assert!(parse_datetime("04-08-14", "%d-%m-%y").is_err()); } + + #[test] + fn test_utils_parse_semver() { + assert_eq!( + parse_semver("termscp-0.3.2").unwrap(), + String::from("0.3.2") + ); + assert_eq!(parse_semver("v0.4.1").unwrap(), String::from("0.4.1"),); + assert_eq!(parse_semver("1.0.0").unwrap(), String::from("1.0.0"),); + assert!(parse_semver("v1.1").is_none()); + } } From 4e887c3429d522edc008e8c7e6771e85ffbff7a0 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 28 Feb 2021 12:33:12 +0100 Subject: [PATCH 2/6] Git: check for new updates (utils) --- Cargo.lock | 195 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/utils/git.rs | 87 +++++++++++++++++++++ src/utils/mod.rs | 1 + 4 files changed, 284 insertions(+) create mode 100644 src/utils/git.rs diff --git a/Cargo.lock b/Cargo.lock index 29c8cf7..763c5d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + [[package]] name = "cipher" version = "0.2.5" @@ -433,6 +439,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "ftp4" version = "4.0.2" @@ -535,6 +551,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "idna" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "instant" version = "0.1.9" @@ -544,6 +571,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "js-sys" version = "0.3.46" @@ -654,6 +687,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "md-5" version = "0.9.1" @@ -891,6 +930,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pkg-config" version = "0.3.19" @@ -1055,6 +1100,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rpassword" version = "5.0.1" @@ -1077,6 +1137,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rustls" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "schannel" version = "0.1.19" @@ -1093,6 +1172,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secret-service" version = "1.1.3" @@ -1175,6 +1264,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.9.2" @@ -1231,6 +1331,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "ssh2" version = "0.9.0" @@ -1301,6 +1407,7 @@ dependencies = [ "toml", "tui", "unicode-width", + "ureq", "users", "whoami", ] @@ -1346,6 +1453,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinyvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "toml" version = "0.5.8" @@ -1374,6 +1496,24 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -1392,6 +1532,42 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585dcbf3483242f77b502864478ede62431baf3442b99367d3456ec20c1707b" +dependencies = [ + "base64", + "chunked_transfer", + "log", + "once_cell", + "rustls", + "serde", + "serde_json", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "users" version = "0.11.0" @@ -1490,6 +1666,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" +dependencies = [ + "webpki", +] + [[package]] name = "which" version = "3.1.1" diff --git a/Cargo.toml b/Cargo.toml index 657817d..b639bc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ textwrap = "0.13.1" toml = "0.5.8" tui = { version = "0.14.0", features = ["crossterm"], default-features = false } unicode-width = "0.1.7" +ureq = { version = "2.0.2", features = ["json"] } whoami = "1.1.0" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] diff --git a/src/utils/git.rs b/src/utils/git.rs new file mode 100644 index 0000000..1221c91 --- /dev/null +++ b/src/utils/git.rs @@ -0,0 +1,87 @@ +//! ## git +//! +//! `git` is the module which provides utilities to interact through the GIT API and to perform some stuff at git level + +/* +* +* Copyright (C) 2020-2021 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 . +* +*/ + +// Deps +extern crate ureq; +// Locals +use super::parser::parse_semver; +// Others +use serde::Deserialize; + +#[derive(Deserialize)] +struct TagInfo { + tag_name: String, +} + +/// ### check_for_updates +/// +/// Check if there is a new version available for termscp. +/// This is performed through the Github API +/// In case of success returns Ok(Option), where the Option is Some(new_version); otherwise if no version is available, return None +/// In case of error returns Error with the error description + +pub fn check_for_updates(current_version: &str) -> Result, String> { + // Send request + let github_version: Result = match ureq::get("https://api.github.com/repos/veeso/termscp/releases/latest").call() { + Ok(response) => match response.into_json::() { + Ok(tag_info) => Ok(tag_info.tag_name.clone()), + Err(err) => Err(err.to_string()) + } + Err(err) => Err(err.to_string()) + }; + // Check version + match github_version { + Err(err) => Err(err), + Ok(version) => { + // Parse version + match parse_semver(version.as_str()) { + Some(new_version) => { + // Check if version is different + if new_version.as_str() > current_version { + Ok(Some(new_version)) // New version is available + } else { + Ok(None) // No new version + } + } + None => { + Err(String::from("Got bad response from Github")) + } + } + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_utils_git_check_for_updates() { + assert!(check_for_updates("100.0.0").ok().unwrap().is_none()); + assert!(check_for_updates("0.0.1").ok().unwrap().is_some()); + } + +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ea844e7..68c96c3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -26,5 +26,6 @@ // modules pub mod crypto; pub mod fmt; +pub mod git; pub mod parser; pub mod random; From 6682c07eb66a2efa61bec25983840a17eb97789a Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 28 Feb 2021 12:44:00 +0100 Subject: [PATCH 3/6] Added check_for_updates to config --- src/config/mod.rs | 5 +++++ src/config/serializer.rs | 8 +++++++- src/system/config_client.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 2225d2a..af62155 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -55,6 +55,7 @@ pub struct UserInterfaceConfig { pub text_editor: PathBuf, pub default_protocol: String, pub show_hidden_files: bool, + pub check_for_updates: Option, // @! Since 0.3.3 pub group_dirs: Option, pub file_fmt: Option, } @@ -85,6 +86,7 @@ impl Default for UserInterfaceConfig { }, default_protocol: FileTransferProtocol::Sftp.to_string(), show_hidden_files: false, + check_for_updates: Some(true), group_dirs: None, file_fmt: None, } @@ -172,6 +174,7 @@ mod tests { default_protocol: String::from("SFTP"), text_editor: PathBuf::from("nano"), show_hidden_files: true, + check_for_updates: Some(true), group_dirs: Some(String::from("first")), file_fmt: Some(String::from("{NAME}")), }; @@ -189,6 +192,7 @@ mod tests { assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP")); assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano")); assert_eq!(cfg.user_interface.show_hidden_files, true); + assert_eq!(cfg.user_interface.check_for_updates, Some(true)); assert_eq!(cfg.user_interface.group_dirs, Some(String::from("first"))); assert_eq!(cfg.user_interface.file_fmt, Some(String::from("{NAME}"))); } @@ -201,6 +205,7 @@ mod tests { let cfg: UserConfig = UserConfig::default(); assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP")); assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim")); + assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true); assert_eq!(cfg.remote.ssh_keys.len(), 0); } diff --git a/src/config/serializer.rs b/src/config/serializer.rs index 544b594..fd927c6 100644 --- a/src/config/serializer.rs +++ b/src/config/serializer.rs @@ -107,8 +107,12 @@ mod tests { assert_eq!(cfg.user_interface.default_protocol, String::from("SCP")); assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim")); assert_eq!(cfg.user_interface.show_hidden_files, true); + assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true); assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last"))); - assert_eq!(cfg.user_interface.file_fmt, Some(String::from("{NAME} {PEX}"))); + assert_eq!( + cfg.user_interface.file_fmt, + Some(String::from("{NAME} {PEX}")) + ); // Verify keys assert_eq!( *cfg.remote @@ -144,6 +148,7 @@ mod tests { assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim")); assert_eq!(cfg.user_interface.show_hidden_files, true); assert_eq!(cfg.user_interface.group_dirs, None); + assert!(cfg.user_interface.check_for_updates.is_none()); assert_eq!(cfg.user_interface.file_fmt, None); // Verify keys assert_eq!( @@ -200,6 +205,7 @@ mod tests { default_protocol = "SCP" text_editor = "vim" show_hidden_files = true + check_for_updates = true group_dirs = "last" file_fmt = "{NAME} {PEX}" diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 6b994c5..b9b464c 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -138,6 +138,20 @@ impl ConfigClient { self.config.user_interface.show_hidden_files = value; } + /// ### get_check_for_updates + /// + /// Get value of `check_for_updates` + pub fn get_check_for_updates(&self) -> bool { + self.config.user_interface.check_for_updates.unwrap_or(true) + } + + /// ### set_check_for_updates + /// + /// Set new value for `check_for_updates` + pub fn set_check_for_updates(&mut self, value: bool) { + self.config.user_interface.check_for_updates = Some(value); + } + /// ### get_group_dirs /// /// Get GroupDirs value from configuration (will be converted from string) @@ -455,6 +469,20 @@ mod tests { assert_eq!(client.get_show_hidden_files(), true); } + #[test] + fn test_system_config_check_for_updates() { + let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) + .ok() + .unwrap(); + assert_eq!(client.get_check_for_updates(), true); // Null ? + client.set_check_for_updates(true); + assert_eq!(client.get_check_for_updates(), true); + client.set_check_for_updates(false); + assert_eq!(client.get_check_for_updates(), false); + } + #[test] fn test_system_config_group_dirs() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); From 85c57ce027bb66dc6288a91966c6919c3a6fcbe6 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 28 Feb 2021 12:47:55 +0100 Subject: [PATCH 4/6] Handle check for updates in setup activity --- src/ui/activities/setup_activity/input.rs | 17 ++++++- src/ui/activities/setup_activity/layout.rs | 53 ++++++++++++++++++++-- src/ui/activities/setup_activity/mod.rs | 1 + 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/ui/activities/setup_activity/input.rs b/src/ui/activities/setup_activity/input.rs index aecd916..7fee6cc 100644 --- a/src/ui/activities/setup_activity/input.rs +++ b/src/ui/activities/setup_activity/input.rs @@ -238,6 +238,10 @@ impl SetupActivity { // Move left config_cli.set_show_hidden_files(true); } + UserInterfaceInputField::CheckForUpdates => { + // move left + config_cli.set_check_for_updates(true); + } _ => { /* Not a tab field */ } } } @@ -275,6 +279,10 @@ impl SetupActivity { // Move right config_cli.set_show_hidden_files(false); } + UserInterfaceInputField::CheckForUpdates => { + // move right + config_cli.set_check_for_updates(false); + } _ => { /* Not a tab field */ } } } @@ -284,6 +292,9 @@ impl SetupActivity { self.tab = SetupTab::UserInterface(match field { UserInterfaceInputField::FileFmt => UserInterfaceInputField::GroupDirs, UserInterfaceInputField::GroupDirs => { + UserInterfaceInputField::CheckForUpdates + } + UserInterfaceInputField::CheckForUpdates => { UserInterfaceInputField::ShowHiddenFiles } UserInterfaceInputField::ShowHiddenFiles => { @@ -305,6 +316,9 @@ impl SetupActivity { UserInterfaceInputField::ShowHiddenFiles } UserInterfaceInputField::ShowHiddenFiles => { + UserInterfaceInputField::CheckForUpdates + } + UserInterfaceInputField::CheckForUpdates => { UserInterfaceInputField::GroupDirs } UserInterfaceInputField::GroupDirs => UserInterfaceInputField::FileFmt, @@ -354,7 +368,8 @@ impl SetupActivity { } UserInterfaceInputField::FileFmt => { // Push char to current file fmt - let mut file_fmt = config_cli.get_file_fmt().unwrap_or_default(); + let mut file_fmt = + config_cli.get_file_fmt().unwrap_or_default(); file_fmt.push(ch); // update value config_cli.set_file_fmt(file_fmt); diff --git a/src/ui/activities/setup_activity/layout.rs b/src/ui/activities/setup_activity/layout.rs index f6eaa06..003e917 100644 --- a/src/ui/activities/setup_activity/layout.rs +++ b/src/ui/activities/setup_activity/layout.rs @@ -90,6 +90,7 @@ impl SetupActivity { Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), + Constraint::Length(3), Constraint::Length(1), ] .as_ref(), @@ -105,12 +106,15 @@ impl SetupActivity { if let Some(tab) = self.draw_hidden_files_tab() { f.render_widget(tab, ui_cfg_chunks[2]); } - if let Some(tab) = self.draw_default_group_dirs_tab() { + if let Some(tab) = self.draw_check_for_updates_tab() { f.render_widget(tab, ui_cfg_chunks[3]); } - if let Some(tab) = self.draw_file_fmt_input() { + if let Some(tab) = self.draw_default_group_dirs_tab() { f.render_widget(tab, ui_cfg_chunks[4]); } + if let Some(tab) = self.draw_file_fmt_input() { + f.render_widget(tab, ui_cfg_chunks[5]); + } // Set cursor if let Some(cli) = &self.config_cli { match form_field { @@ -317,9 +321,9 @@ impl SetupActivity { } } - /// ### draw_default_protocol_tab + /// ### draw_hidden_files_tab /// - /// Draw default protocol input tab + /// Draw default hidden files tab fn draw_hidden_files_tab(&self) -> Option { // Check if config client is some match &self.config_cli { @@ -358,6 +362,47 @@ impl SetupActivity { } } + /// ### draw_check_for_updates_tab + /// + /// Draw check for updates tab + fn draw_check_for_updates_tab(&self) -> Option { + // Check if config client is some + match &self.config_cli { + Some(cli) => { + let choices: Vec = vec![Spans::from("Yes"), Spans::from("No")]; + let index: usize = match cli.get_check_for_updates() { + true => 0, + false => 1, + }; + let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab { + SetupTab::UserInterface(field) => match field { + UserInterfaceInputField::CheckForUpdates => { + (Color::LightYellow, Color::Black, Color::LightYellow) + } + _ => (Color::Reset, Color::LightYellow, Color::Reset), + }, + _ => (Color::Reset, Color::Reset, Color::Reset), + }; + Some( + Tabs::new(choices) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .style(Style::default().fg(block_fg)) + .title("Check for updates?"), + ) + .select(index) + .style(Style::default()) + .highlight_style( + Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg), + ), + ) + } + None => None, + } + } + /// ### draw_default_group_dirs_tab /// /// Draw group dirs input tab diff --git a/src/ui/activities/setup_activity/mod.rs b/src/ui/activities/setup_activity/mod.rs index 7caa8f2..6e7bc9b 100644 --- a/src/ui/activities/setup_activity/mod.rs +++ b/src/ui/activities/setup_activity/mod.rs @@ -54,6 +54,7 @@ enum UserInterfaceInputField { DefaultProtocol, TextEditor, ShowHiddenFiles, + CheckForUpdates, GroupDirs, FileFmt, } From da5e1f315d30d89a394ffd01ee32e5c856ad458e Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 28 Feb 2021 13:01:51 +0100 Subject: [PATCH 5/6] Show new version available in auth activity --- src/ui/activities/auth_activity/layout.rs | 47 +++++++++++++++-------- src/ui/activities/auth_activity/mod.rs | 27 +++++++++++++ src/utils/git.rs | 22 +++++------ 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index 020b0e0..351ed4f 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -62,6 +62,7 @@ impl AuthActivity { .constraints( [ Constraint::Length(5), + Constraint::Length(1), // Version Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), @@ -80,32 +81,33 @@ impl AuthActivity { .split(chunks[1]); // Draw header f.render_widget(self.draw_header(), auth_chunks[0]); + f.render_widget(self.draw_new_version(), auth_chunks[1]); // Draw input fields - f.render_widget(self.draw_remote_address(), auth_chunks[1]); - f.render_widget(self.draw_remote_port(), auth_chunks[2]); - f.render_widget(self.draw_protocol_select(), auth_chunks[3]); - f.render_widget(self.draw_protocol_username(), auth_chunks[4]); - f.render_widget(self.draw_protocol_password(), auth_chunks[5]); + f.render_widget(self.draw_remote_address(), auth_chunks[2]); + f.render_widget(self.draw_remote_port(), auth_chunks[3]); + f.render_widget(self.draw_protocol_select(), auth_chunks[4]); + f.render_widget(self.draw_protocol_username(), auth_chunks[5]); + f.render_widget(self.draw_protocol_password(), auth_chunks[6]); // Draw footer - f.render_widget(self.draw_footer(), auth_chunks[6]); + f.render_widget(self.draw_footer(), auth_chunks[7]); // Set cursor if let InputForm::AuthCredentials = self.input_form { match self.selected_field { InputField::Address => f.set_cursor( - auth_chunks[1].x + self.address.width() as u16 + 1, - auth_chunks[1].y + 1, - ), - InputField::Port => f.set_cursor( - auth_chunks[2].x + self.port.width() as u16 + 1, + auth_chunks[2].x + self.address.width() as u16 + 1, auth_chunks[2].y + 1, ), + InputField::Port => f.set_cursor( + auth_chunks[3].x + self.port.width() as u16 + 1, + auth_chunks[3].y + 1, + ), InputField::Username => f.set_cursor( - auth_chunks[4].x + self.username.width() as u16 + 1, - auth_chunks[4].y + 1, + auth_chunks[5].x + self.username.width() as u16 + 1, + auth_chunks[5].y + 1, ), InputField::Password => f.set_cursor( - auth_chunks[5].x + self.password_placeholder.width() as u16 + 1, - auth_chunks[5].y + 1, + auth_chunks[6].x + self.password_placeholder.width() as u16 + 1, + auth_chunks[6].y + 1, ), _ => {} } @@ -284,6 +286,21 @@ impl AuthActivity { .style(Style::default().fg(Color::White).add_modifier(Modifier::BOLD)) } + /// ### draw_new_version + /// + /// Draw new version disclaimer + fn draw_new_version(&self) -> Paragraph { + let content: String = match self.new_version.as_ref() { + Some(ver) => format!("TermSCP {} is now available! Download it from ", ver), + None => String::new(), + }; + Paragraph::new(content).style( + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ) + } + /// ### draw_footer /// /// Draw authentication page footer diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index effcad9..ffe55d3 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -40,6 +40,7 @@ use crate::filetransfer::FileTransferProtocol; use crate::system::bookmarks_client::BookmarksClient; use crate::system::config_client::ConfigClient; use crate::system::environment; +use crate::utils::git; // Includes use crossterm::event::Event as InputEvent; @@ -118,6 +119,8 @@ pub struct AuthActivity { bookmarks_list: Vec, // List of bookmarks recents_idx: usize, // Index of selected recent recents_list: Vec, // list of recents + // misc + new_version: Option, // Contains new version of termscp } impl Default for AuthActivity { @@ -154,6 +157,7 @@ impl AuthActivity { bookmarks_list: Vec::new(), recents_idx: 0, recents_list: Vec::new(), + new_version: None, } } @@ -192,6 +196,27 @@ impl AuthActivity { } } } + + /// ### on_create + /// + /// If enabled in configuration, check for updates from Github + fn check_for_updates(&mut self) { + if let Some(client) = self.config_client.as_ref() { + if client.get_check_for_updates() { + // Send request + match git::check_for_updates(env!("CARGO_PKG_VERSION")) { + Ok(version) => self.new_version = version, + Err(err) => { + // Report error + self.popup = Some(Popup::Alert( + Color::Red, + format!("Could not check for new updates: {}", err), + )) + } + } + } + } + } } impl Activity for AuthActivity { @@ -216,6 +241,8 @@ impl Activity for AuthActivity { if self.config_client.is_none() { self.init_config_client(); } + // If check for updates is enabled, check for updates + self.check_for_updates(); } /// ### on_draw diff --git a/src/utils/git.rs b/src/utils/git.rs index 1221c91..a6b5e4f 100644 --- a/src/utils/git.rs +++ b/src/utils/git.rs @@ -44,13 +44,14 @@ struct TagInfo { pub fn check_for_updates(current_version: &str) -> Result, String> { // Send request - let github_version: Result = match ureq::get("https://api.github.com/repos/veeso/termscp/releases/latest").call() { - Ok(response) => match response.into_json::() { - Ok(tag_info) => Ok(tag_info.tag_name.clone()), - Err(err) => Err(err.to_string()) - } - Err(err) => Err(err.to_string()) - }; + let github_version: Result = + match ureq::get("https://api.github.com/repos/veeso/termscp/releases/latest").call() { + Ok(response) => match response.into_json::() { + Ok(tag_info) => Ok(tag_info.tag_name), + Err(err) => Err(err.to_string()), + }, + Err(err) => Err(err.to_string()), + }; // Check version match github_version { Err(err) => Err(err), @@ -65,9 +66,7 @@ pub fn check_for_updates(current_version: &str) -> Result, String Ok(None) // No new version } } - None => { - Err(String::from("Got bad response from Github")) - } + None => Err(String::from("Got bad response from Github")), } } } @@ -83,5 +82,4 @@ mod tests { assert!(check_for_updates("100.0.0").ok().unwrap().is_none()); assert!(check_for_updates("0.0.1").ok().unwrap().is_some()); } - -} \ No newline at end of file +} From 61045fa548cde2639320252dd848fd8083c11cb2 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 28 Feb 2021 13:10:59 +0100 Subject: [PATCH 6/6] Check for updates OK --- CHANGELOG.md | 5 ++++- README.md | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a7c94d..3635b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,12 @@ Released on 28/02/2021 -- Format key attributes: +- **Format key attributes**: - Added `EXTRA` and `LENGTH` parameters to format keys. - Now keys are provided with this syntax `{KEY_NAME[:LEN[:EXTRA]}` +- **Check for updates**: + - TermSCP will now check for updates on startup and will show in the main page if there is a new version available + - This feature may be disabled from setup (Check for updates => No) - Enhancements: - Default choice for deleting file set to "NO" (way too easy to delete files by mistake) - Added CLI options to set starting workind directory on both local and remote hosts diff --git a/README.md b/README.md index 13808ba..3fd658e 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ These parameters can be changed: - **Default Protocol**: the default protocol is the default value for the file transfer protocol to be used in termscp. This applies for the login page and for the address CLI argument. - **Text Editor**: the text editor to use. By default termscp will find the default editor for you; with this option you can force an editor to be used (e.g. `vim`). **Also GUI editors are supported**, unless they `nohup` from the parent process so if you ask: yes, you can use `notepad.exe`, and no: **Visual Studio Code doesn't work**. - **Show Hidden Files**: select whether hidden files shall be displayed by default. You will be able to decide whether to show or not hidden files at runtime pressing `A` anyway. +- **Check for updates**: if set to `yes`, termscp will fetch the Github API to check if there is a new version of termscp available. - **Group Dirs**: select whether directories should be groupped or not in file explorers. If `Display first` is selected, directories will be sorted using the configured method but displayed before files, viceversa if `Display last` is selected. ### SSH Key Storage 🔐