>(mut self, dir: Option) -> Self {
+ self.entry_directory = dir.map(|x| x.as_ref().to_path_buf());
+ self
+ }
+}
+
+impl Default for FileTransferParams {
+ fn default() -> Self {
+ Self::new("localhost")
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn test_filetransfer_params() {
+ let params: FileTransferParams = FileTransferParams::new("test.rebex.net")
+ .port(2222)
+ .protocol(FileTransferProtocol::Scp)
+ .username(Some("omar"))
+ .password(Some("foobar"))
+ .entry_directory(Some(&Path::new("/tmp")));
+ assert_eq!(params.address.as_str(), "test.rebex.net");
+ assert_eq!(params.port, 2222);
+ assert_eq!(params.protocol, FileTransferProtocol::Scp);
+ assert_eq!(params.username.as_ref().unwrap(), "omar");
+ assert_eq!(params.password.as_ref().unwrap(), "foobar");
+ }
+
+ #[test]
+ fn test_filetransfer_params_default() {
+ let params: FileTransferParams = FileTransferParams::default();
+ assert_eq!(params.address.as_str(), "localhost");
+ assert_eq!(params.port, 22);
+ assert_eq!(params.protocol, FileTransferProtocol::Sftp);
+ assert!(params.username.is_none());
+ assert!(params.password.is_none());
+ }
+}
diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs
index 34907a0..dbeeca1 100644
--- a/src/filetransfer/scp_transfer.rs
+++ b/src/filetransfer/scp_transfer.rs
@@ -25,12 +25,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-// Dependencies
-#[cfg(os_target = "windows")]
-extern crate path_slash;
-extern crate regex;
-extern crate ssh2;
-
// Locals
use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile};
diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs
index 5f353f0..54dcf51 100644
--- a/src/filetransfer/sftp_transfer.rs
+++ b/src/filetransfer/sftp_transfer.rs
@@ -25,9 +25,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-// Dependencies
-extern crate ssh2;
-
// Locals
use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile};
diff --git a/src/fs/explorer/formatter.rs b/src/fs/explorer/formatter.rs
index 96bdbba..54ba14d 100644
--- a/src/fs/explorer/formatter.rs
+++ b/src/fs/explorer/formatter.rs
@@ -25,11 +25,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-// Deps
-extern crate bytesize;
-extern crate regex;
-#[cfg(target_family = "unix")]
-extern crate users;
// Locals
use super::FsEntry;
use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time};
@@ -321,13 +316,13 @@ impl Formatter {
};
let name: &str = fsentry.get_name();
let last_idx: usize = match fsentry.is_dir() {
- // NOTE: For directories is 19, since we push '/' to name
- true => file_len - 5,
- false => file_len - 4,
+ // NOTE: For directories is l - 2, since we push '/' to name
+ true => file_len - 2,
+ false => file_len - 1,
};
let mut name: String = match name.len() >= file_len {
false => name.to_string(),
- true => format!("{}...", &name[0..last_idx]),
+ true => format!("{}β¦", &name[0..last_idx]),
};
if fsentry.is_dir() {
name.push('/');
@@ -640,7 +635,7 @@ mod tests {
assert_eq!(
formatter.fmt(&entry),
format!(
- "piroparoporoperoperu... -rw-r--r-- root 8.2 KB {}",
+ "piroparoporoperoperupup⦠-rw-r--r-- root 8.2 KB {}",
fmt_time(t, "%b %d %Y %H:%M")
)
);
@@ -648,7 +643,7 @@ mod tests {
assert_eq!(
formatter.fmt(&entry),
format!(
- "piroparoporoperoperu... -rw-r--r-- 0 8.2 KB {}",
+ "piroparoporoperoperupup⦠-rw-r--r-- 0 8.2 KB {}",
fmt_time(t, "%b %d %Y %H:%M")
)
);
diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs
index 320867b..3d33421 100644
--- a/src/fs/explorer/mod.rs
+++ b/src/fs/explorer/mod.rs
@@ -28,8 +28,6 @@
// Mods
pub(crate) mod builder;
mod formatter;
-// Deps
-extern crate bitflags;
// Locals
use super::FsEntry;
use formatter::Formatter;
diff --git a/src/host/mod.rs b/src/host/mod.rs
index f832f6e..f9913de 100644
--- a/src/host/mod.rs
+++ b/src/host/mod.rs
@@ -25,8 +25,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-// dependencies
-extern crate wildmatch;
// ext
use std::fs::{self, File, Metadata, OpenOptions};
use std::path::{Path, PathBuf};
diff --git a/src/lib.rs b/src/lib.rs
index 3847ecd..b6e3d0e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -32,19 +32,43 @@
#[macro_use]
extern crate bitflags;
+extern crate bytesize;
+extern crate chrono;
+extern crate content_inspector;
+extern crate crossterm;
+extern crate dirs;
+extern crate edit;
+extern crate ftp4;
+extern crate hostname;
+#[cfg(feature = "with-keyring")]
+extern crate keyring;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate magic_crypt;
+extern crate open;
+#[cfg(target_os = "windows")]
+extern crate path_slash;
+extern crate rand;
+extern crate regex;
+extern crate ssh2;
+extern crate tempfile;
+extern crate textwrap;
+extern crate tuirealm;
+extern crate ureq;
+#[cfg(target_family = "unix")]
+extern crate users;
+extern crate whoami;
+extern crate wildmatch;
pub mod activity_manager;
-pub mod bookmarks;
pub mod config;
pub mod filetransfer;
pub mod fs;
pub mod host;
+pub mod support;
pub mod system;
pub mod ui;
pub mod utils;
diff --git a/src/main.rs b/src/main.rs
index 83e44d9..2797cbd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -26,7 +26,7 @@ const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
// Crates
-extern crate getopts;
+extern crate argh;
#[macro_use]
extern crate bitflags;
#[macro_use]
@@ -38,182 +38,237 @@ extern crate magic_crypt;
extern crate rpassword;
// External libs
-use getopts::Options;
+use argh::FromArgs;
use std::env;
use std::path::PathBuf;
use std::time::Duration;
// Include
mod activity_manager;
-mod bookmarks;
mod config;
mod filetransfer;
mod fs;
mod host;
+mod support;
mod system;
mod ui;
mod utils;
// namespaces
use activity_manager::{ActivityManager, NextActivity};
-use filetransfer::FileTransferProtocol;
+use filetransfer::FileTransferParams;
use system::logging;
-/// ### print_usage
-///
-/// Print usage
+enum Task {
+ Activity(NextActivity),
+ ImportTheme(PathBuf),
+}
-fn print_usage(opts: Options) {
- let brief = String::from(
- "Usage: termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]",
- );
- print!("{}", opts.usage(&brief));
- println!("\nPlease, report issues to ");
- println!("Please, consider supporting the author ")
+#[derive(FromArgs)]
+#[argh(description = "
+where positional can be: [protocol://user@address:port:wrkdir] [local-wrkdir]
+
+Please, report issues to
+Please, consider supporting the author ")]
+struct Args {
+ #[argh(switch, short = 'c', description = "open termscp configuration")]
+ config: bool,
+ #[argh(option, short = 'P', description = "provide password from CLI")]
+ password: Option,
+ #[argh(switch, short = 'q', description = "disable logging")]
+ quiet: bool,
+ #[argh(option, short = 't', description = "import specified theme")]
+ theme: Option,
+ #[argh(
+ option,
+ short = 'T',
+ default = "10",
+ description = "set UI ticks; default 10ms"
+ )]
+ ticks: u64,
+ #[argh(switch, short = 'v', description = "print version")]
+ version: bool,
+ // -- positional
+ #[argh(
+ positional,
+ description = "protocol://user@address:port:wrkdir local-wrkdir"
+ )]
+ positional: Vec,
+}
+
+struct RunOpts {
+ remote: Option,
+ ticks: Duration,
+ log_enabled: bool,
+ task: Task,
+}
+
+impl Default for RunOpts {
+ fn default() -> Self {
+ Self {
+ remote: None,
+ ticks: Duration::from_millis(10),
+ log_enabled: true,
+ task: Task::Activity(NextActivity::Authentication),
+ }
+ }
}
fn main() {
- let args: Vec = env::args().collect();
- //Program CLI options
- let mut address: Option = None; // None
- let mut port: u16 = 22; // Default port
- let mut username: Option = None; // Default username
- let mut password: Option = None; // Default password
- let mut remote_wrkdir: Option = None;
- let mut protocol: FileTransferProtocol = FileTransferProtocol::Sftp; // Default protocol
- let mut ticks: Duration = Duration::from_millis(10);
- let mut log_enabled: bool = true;
- //Process options
- let mut opts = Options::new();
- opts.optopt("P", "password", "Provide password from CLI", "");
- opts.optopt("T", "ticks", "Set UI ticks; default 10ms", "");
- opts.optflag("q", "quiet", "Disable logging");
- opts.optflag("v", "version", "");
- opts.optflag("h", "help", "Print this menu");
- let matches = match opts.parse(&args[1..]) {
- Ok(m) => m,
- Err(f) => {
- println!("{}", f.to_string());
+ let args: Args = argh::from_env();
+ // Parse args
+ let mut run_opts: RunOpts = match parse_args(args) {
+ Ok(opts) => opts,
+ Err(err) => {
+ eprintln!("{}", err);
std::process::exit(255);
}
};
- // Help
- if matches.opt_present("h") {
- print_usage(opts);
- std::process::exit(255);
- }
- // Version
- if matches.opt_present("v") {
- eprintln!(
- "termscp - {} - Developed by {}",
- TERMSCP_VERSION, TERMSCP_AUTHORS,
- );
- std::process::exit(255);
- }
- // Logging
- if matches.opt_present("q") {
- log_enabled = false;
- }
- // Match password
- if let Some(passwd) = matches.opt_str("P") {
- password = Some(passwd);
- }
- // Match ticks
- if let Some(val) = matches.opt_str("T") {
- match val.parse::() {
- Ok(val) => ticks = Duration::from_millis(val as u64),
- Err(_) => {
- eprintln!("Ticks is not a number '{}'", val);
- print_usage(opts);
- std::process::exit(255);
- }
- }
- }
- // Check free args
- let extra_args: Vec = matches.free;
- // Remote argument
- if let Some(remote) = extra_args.get(0) {
- // Parse address
- match utils::parser::parse_remote_opt(remote) {
- Ok(host_opts) => {
- // Set params
- address = Some(host_opts.hostname);
- port = host_opts.port;
- protocol = host_opts.protocol;
- username = host_opts.username;
- remote_wrkdir = host_opts.wrkdir;
- }
- Err(err) => {
- eprintln!("Bad address option: {}", err);
- print_usage(opts);
- std::process::exit(255);
- }
- }
- }
- // Local directory
- if let Some(localdir) = extra_args.get(1) {
- // Change working directory if local dir is set
- let localdir: PathBuf = PathBuf::from(localdir);
- if let Err(err) = env::set_current_dir(localdir.as_path()) {
- eprintln!("Bad working directory argument: {}", err);
- std::process::exit(255);
- }
- }
- // Get working directory
- let wrkdir: PathBuf = match env::current_dir() {
- Ok(dir) => dir,
- Err(_) => PathBuf::from("/"),
- };
// Setup logging
- if log_enabled {
+ if run_opts.log_enabled {
if let Err(err) = logging::init() {
eprintln!("Failed to initialize logging: {}", err);
}
}
+ // Read password from remote
+ if let Err(err) = read_password(&mut run_opts) {
+ eprintln!("{}", err);
+ std::process::exit(255);
+ }
info!("termscp {} started!", TERMSCP_VERSION);
+ // Run
+ info!("Starting activity manager...");
+ let rc: i32 = run(run_opts);
+ info!("termscp terminated");
+ // Then return
+ std::process::exit(rc);
+}
+
+/// ### parse_args
+///
+/// Parse arguments
+/// In case of success returns `RunOpts`
+/// in case something is wrong returns the error message
+fn parse_args(args: Args) -> Result {
+ let mut run_opts: RunOpts = RunOpts::default();
+ // Version
+ if args.version {
+ return Err(format!(
+ "termscp - {} - Developed by {}",
+ TERMSCP_VERSION, TERMSCP_AUTHORS,
+ ));
+ }
+ // Setup activity?
+ if args.config {
+ run_opts.task = Task::Activity(NextActivity::SetupActivity);
+ }
+ // Logging
+ if args.quiet {
+ run_opts.log_enabled = false;
+ }
+ // Match ticks
+ run_opts.ticks = Duration::from_millis(args.ticks);
+ // @! extra modes
+ if let Some(theme) = args.theme {
+ run_opts.task = Task::ImportTheme(PathBuf::from(theme));
+ }
+ // @! Ordinary mode
+ // Remote argument
+ if let Some(remote) = args.positional.get(0) {
+ // Parse address
+ match utils::parser::parse_remote_opt(remote.as_str()) {
+ Ok(mut remote) => {
+ // If password is provided, set password
+ if let Some(passwd) = args.password {
+ remote = remote.password(Some(passwd));
+ }
+ // Set params
+ run_opts.remote = Some(remote);
+ // In this case the first activity will be FileTransfer
+ run_opts.task = Task::Activity(NextActivity::FileTransfer);
+ }
+ Err(err) => {
+ return Err(format!("Bad address option: {}", err));
+ }
+ }
+ }
+ // Local directory
+ if let Some(localdir) = args.positional.get(1) {
+ // Change working directory if local dir is set
+ let localdir: PathBuf = PathBuf::from(localdir);
+ if let Err(err) = env::set_current_dir(localdir.as_path()) {
+ return Err(format!("Bad working directory argument: {}", err));
+ }
+ }
+ Ok(run_opts)
+}
+
+/// ### read_password
+///
+/// Read password from tty if address is specified
+fn read_password(run_opts: &mut RunOpts) -> Result<(), String> {
// Initialize client if necessary
- let mut start_activity: NextActivity = NextActivity::Authentication;
- if address.is_some() {
- debug!("User has specified remote options: address: {:?}, port: {:?}, protocol: {:?}, user: {:?}, password: {}", address, port, protocol, username, utils::fmt::shadow_password(password.as_deref().unwrap_or("")));
- if password.is_none() {
+ if let Some(remote) = run_opts.remote.as_mut() {
+ debug!("User has specified remote options: address: {:?}, port: {:?}, protocol: {:?}, user: {:?}, password: {}", remote.address, remote.port, remote.protocol, remote.username, utils::fmt::shadow_password(remote.password.as_deref().unwrap_or("")));
+ if remote.password.is_none() {
// Ask password if unspecified
- password = match rpassword::read_password_from_tty(Some("Password: ")) {
+ remote.password = match rpassword::read_password_from_tty(Some("Password: ")) {
Ok(p) => {
if p.is_empty() {
None
} else {
+ debug!(
+ "Read password from tty: {}",
+ utils::fmt::shadow_password(p.as_str())
+ );
Some(p)
}
}
Err(_) => {
- eprintln!("Could not read password from prompt");
- std::process::exit(255);
+ return Err("Could not read password from prompt".to_string());
}
};
- debug!(
- "Read password from tty: {}",
- utils::fmt::shadow_password(password.as_deref().unwrap_or(""))
- );
}
- // In this case the first activity will be FileTransfer
- start_activity = NextActivity::FileTransfer;
}
- // Create activity manager (and context too)
- let mut manager: ActivityManager = match ActivityManager::new(&wrkdir, ticks) {
- Ok(m) => m,
- Err(err) => {
- eprintln!("Could not start activity manager: {}", err);
- std::process::exit(255);
- }
- };
- // Set file transfer params if set
- if let Some(address) = address {
- manager.set_filetransfer_params(address, port, protocol, username, password, remote_wrkdir);
- }
- // Run
- info!("Starting activity manager...");
- manager.run(start_activity);
- info!("termscp terminated");
- // Then return
- std::process::exit(0);
+ Ok(())
+}
+
+/// ### run
+///
+/// Run task and return rc
+fn run(mut run_opts: RunOpts) -> i32 {
+ match run_opts.task {
+ Task::ImportTheme(theme) => match support::import_theme(theme.as_path()) {
+ Ok(_) => {
+ println!("Theme has been successfully imported!");
+ 0
+ }
+ Err(err) => {
+ eprintln!("{}", err);
+ 1
+ }
+ },
+ Task::Activity(activity) => {
+ // Get working directory
+ let wrkdir: PathBuf = match env::current_dir() {
+ Ok(dir) => dir,
+ Err(_) => PathBuf::from("/"),
+ };
+ // Create activity manager (and context too)
+ let mut manager: ActivityManager =
+ match ActivityManager::new(wrkdir.as_path(), run_opts.ticks) {
+ Ok(m) => m,
+ Err(err) => {
+ eprintln!("Could not start activity manager: {}", err);
+ return 1;
+ }
+ };
+ // Set file transfer params if set
+ if let Some(remote) = run_opts.remote.take() {
+ manager.set_filetransfer_params(remote);
+ }
+ manager.run(activity);
+ 0
+ }
+ }
}
diff --git a/src/support.rs b/src/support.rs
new file mode 100644
index 0000000..150fb42
--- /dev/null
+++ b/src/support.rs
@@ -0,0 +1,68 @@
+//! ## Support
+//!
+//! this module exposes some extra run modes for termscp, meant to be used for "support", such as installing themes
+
+/**
+ * MIT License
+ *
+ * termscp - Copyright (c) 2021 Christian Visintin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+// mod
+use crate::system::{environment, theme_provider::ThemeProvider};
+use std::fs;
+use std::path::{Path, PathBuf};
+
+/// ### import_theme
+///
+/// Import theme at provided path into termscp
+pub fn import_theme(p: &Path) -> Result<(), String> {
+ if !p.exists() {
+ return Err(String::from(
+ "Could not import theme: No such file or directory",
+ ));
+ }
+ // Validate theme file
+ ThemeProvider::new(p).map_err(|e| format!("Invalid theme error: {}", e))?;
+ // get config dir
+ let cfg_dir: PathBuf = get_config_dir()?;
+ // Get theme directory
+ let theme_file: PathBuf = environment::get_theme_path(cfg_dir.as_path());
+ // Copy theme to theme_dir
+ fs::copy(p, theme_file.as_path())
+ .map(|_| ())
+ .map_err(|e| format!("Could not import theme: {}", e))
+}
+
+/// ### get_config_dir
+///
+/// Get configuration directory
+fn get_config_dir() -> Result {
+ match environment::init_config_dir() {
+ Ok(Some(config_dir)) => Ok(config_dir),
+ Ok(None) => Err(String::from(
+ "Your system doesn't provide a configuration directory",
+ )),
+ Err(err) => Err(format!(
+ "Could not initialize configuration directory: {}",
+ err
+ )),
+ }
+}
diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs
index f17680f..ed40f65 100644
--- a/src/system/bookmarks_client.rs
+++ b/src/system/bookmarks_client.rs
@@ -25,15 +25,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-// Deps
-extern crate whoami;
// Crate
-#[cfg(any(target_os = "windows", target_os = "macos"))]
+#[cfg(feature = "with-keyring")]
use super::keys::keyringstorage::KeyringStorage;
use super::keys::{filestorage::FileStorage, KeyStorage, KeyStorageError};
// Local
-use crate::bookmarks::serializer::BookmarkSerializer;
-use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts};
+use crate::config::{
+ bookmarks::{Bookmark, UserHosts},
+ serialization::{deserialize, serialize, SerializerError, SerializerErrorKind},
+};
use crate::filetransfer::FileTransferProtocol;
use crate::utils::crypto;
use crate::utils::fmt::fmt_time;
@@ -67,10 +67,10 @@ impl BookmarksClient {
recents_size: usize,
) -> Result {
// Create default hosts
- let default_hosts: UserHosts = Default::default();
+ let default_hosts: UserHosts = UserHosts::default();
debug!("Setting up bookmarks client...");
- // Make a key storage (windows / macos)
- #[cfg(any(target_os = "windows", target_os = "macos"))]
+ // Make a key storage (with-keyring)
+ #[cfg(feature = "with-keyring")]
let (key_storage, service_id): (Box, &str) = {
debug!("Setting up KeyStorage");
let username: String = whoami::username();
@@ -91,13 +91,8 @@ impl BookmarksClient {
}
}
};
- // Make a key storage (linux / unix)
- #[cfg(any(
- target_os = "linux",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "netbsd"
- ))]
+ // Make a key storage (wno-keyring)
+ #[cfg(not(feature = "with-keyring"))]
let (key_storage, service_id): (Box, &str) = {
#[cfg(not(test))]
let app_name: &str = "bookmarks";
@@ -329,10 +324,7 @@ impl BookmarksClient {
.truncate(true)
.open(self.bookmarks_file.as_path())
{
- Ok(writer) => {
- let serializer: BookmarkSerializer = BookmarkSerializer {};
- serializer.serialize(Box::new(writer), &self.hosts)
- }
+ Ok(writer) => serialize(&self.hosts, Box::new(writer)),
Err(err) => {
error!("Failed to write bookmarks: {}", err);
Err(SerializerError::new_ex(
@@ -355,8 +347,7 @@ impl BookmarksClient {
{
Ok(reader) => {
// Deserialize
- let deserializer: BookmarkSerializer = BookmarkSerializer {};
- match deserializer.deserialize(Box::new(reader)) {
+ match deserialize(Box::new(reader)) {
Ok(hosts) => {
self.hosts = hosts;
Ok(())
@@ -455,7 +446,7 @@ mod tests {
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
- target_os = "netbsd"
+ target_os = "openbsd"
))]
fn test_system_bookmarks_new_err() {
assert!(BookmarksClient::new(
@@ -709,6 +700,21 @@ mod tests {
);
}
+ #[test]
+ fn test_system_bookmarks_decrypt_str() {
+ let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
+ 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(), 16).unwrap();
+ client.key = "MYSUPERSECRETKEY".to_string();
+ assert_eq!(
+ client.decrypt_str("z4Z6LpcpYqBW4+bkIok+5A==").ok().unwrap(),
+ "Hello world!"
+ );
+ assert!(client.decrypt_str("bidoof").is_err());
+ }
+
/// ### get_paths
///
/// Get paths for configuration and key for bookmarks
diff --git a/src/system/config_client.rs b/src/system/config_client.rs
index 9caaa0b..12a77d3 100644
--- a/src/system/config_client.rs
+++ b/src/system/config_client.rs
@@ -25,11 +25,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-// Deps
-extern crate rand;
// Locals
-use crate::config::serializer::ConfigSerializer;
-use crate::config::{SerializerError, SerializerErrorKind, UserConfig};
+use crate::config::{
+ params::UserConfig,
+ serialization::{deserialize, serialize, SerializerError, SerializerErrorKind},
+};
use crate::filetransfer::FileTransferProtocol;
use crate::fs::explorer::GroupDirs;
// Ext
@@ -49,13 +49,14 @@ pub struct ConfigClient {
config: UserConfig, // Configuration loaded
config_path: PathBuf, // Configuration TOML Path
ssh_key_dir: PathBuf, // SSH Key storage directory
+ degraded: bool, // Indicates the `ConfigClient` is working in degraded mode
}
impl ConfigClient {
/// ### new
///
/// Instantiate a new `ConfigClient` with provided path
- pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result {
+ pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result {
// Initialize a default configuration
let default_config: UserConfig = UserConfig::default();
info!(
@@ -68,6 +69,7 @@ impl ConfigClient {
config: default_config,
config_path: PathBuf::from(config_path),
ssh_key_dir: PathBuf::from(ssh_key_dir),
+ degraded: false,
};
// If ssh key directory doesn't exist, create it
if !ssh_key_dir.exists() {
@@ -102,6 +104,20 @@ impl ConfigClient {
Ok(client)
}
+ /// ### degraded
+ ///
+ /// Instantiate a ConfigClient in degraded mode.
+ /// When in degraded mode, the configuration in use will be the default configuration
+ /// and the IO operation on configuration won't be available
+ pub fn degraded() -> Self {
+ Self {
+ config: UserConfig::default(),
+ config_path: PathBuf::default(),
+ ssh_key_dir: PathBuf::default(),
+ degraded: true,
+ }
+ }
+
// Text editor
/// ### get_text_editor
@@ -234,6 +250,12 @@ impl ConfigClient {
username: &str,
ssh_key: &str,
) -> Result<(), SerializerError> {
+ if self.degraded {
+ return Err(SerializerError::new_ex(
+ SerializerErrorKind::GenericError,
+ String::from("Configuration won't be saved, since in degraded mode"),
+ ));
+ }
let host_name: String = Self::make_ssh_host_key(host, username);
// Get key path
let ssh_key_path: PathBuf = {
@@ -267,6 +289,12 @@ impl ConfigClient {
/// This operation also unlinks the key file in `ssh_key_dir`
/// and also commits changes to configuration, to prevent incoerent data
pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> {
+ if self.degraded {
+ return Err(SerializerError::new_ex(
+ SerializerErrorKind::GenericError,
+ String::from("Configuration won't be saved, since in degraded mode"),
+ ));
+ }
// Remove key from configuration and get key path
info!("Removing key for {}@{}", host, username);
let key_path: PathBuf = match self
@@ -293,6 +321,9 @@ impl ConfigClient {
/// None is returned if key doesn't exist
/// `std::io::Error` is returned in case it was not possible to read the key file
pub fn get_ssh_key(&self, mkey: &str) -> std::io::Result