diff --git a/src/config/mod.rs b/src/config/mod.rs
new file mode 100644
index 0000000..9215f9c
--- /dev/null
+++ b/src/config/mod.rs
@@ -0,0 +1,223 @@
+//! ## Config
+//!
+//! `config` is the module which provides access to termscp configuration
+
+/*
+*
+* 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 .
+*
+*/
+
+// Modules
+pub mod serializer;
+
+// Deps
+extern crate edit;
+
+// Locals
+use crate::filetransfer::FileTransferProtocol;
+
+// Ext
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+#[derive(Deserialize, Serialize, std::fmt::Debug)]
+/// ## UserConfig
+///
+/// UserConfig contains all the configurations for the user,
+/// supported by termscp
+pub struct UserConfig {
+ pub user_interface: UserInterfaceConfig,
+ pub remote: RemoteConfig,
+}
+
+#[derive(Deserialize, Serialize, std::fmt::Debug)]
+/// ## UserInterfaceConfig
+///
+/// UserInterfaceConfig provides all the keys to configure the user interface
+pub struct UserInterfaceConfig {
+ pub text_editor: PathBuf,
+ pub default_protocol: String,
+}
+
+#[derive(Deserialize, Serialize, std::fmt::Debug)]
+/// ## RemoteConfig
+///
+/// Contains configuratio related to remote hosts
+pub struct RemoteConfig {
+ pub ssh_keys: HashMap, // Association between host name and path to private key
+}
+
+impl Default for UserConfig {
+ fn default() -> Self {
+ UserConfig {
+ user_interface: UserInterfaceConfig::default(),
+ remote: RemoteConfig::default(),
+ }
+ }
+}
+
+impl Default for UserInterfaceConfig {
+ fn default() -> Self {
+ UserInterfaceConfig {
+ text_editor: match edit::get_editor() {
+ Ok(p) => p,
+ Err(_) => PathBuf::from("nano"), // Default to nano
+ },
+ default_protocol: FileTransferProtocol::Sftp.to_string(),
+ }
+ }
+}
+
+impl Default for RemoteConfig {
+ fn default() -> Self {
+ RemoteConfig {
+ ssh_keys: HashMap::new(),
+ }
+ }
+}
+
+// Errors
+
+/// ## SerializerError
+///
+/// Contains the error for serializer/deserializer
+#[derive(std::fmt::Debug)]
+pub struct SerializerError {
+ kind: SerializerErrorKind,
+ msg: Option,
+}
+
+/// ## SerializerErrorKind
+///
+/// Describes the kind of error for the serializer/deserializer
+#[derive(std::fmt::Debug, PartialEq)]
+pub enum SerializerErrorKind {
+ IoError,
+ SerializationError,
+ SyntaxError,
+}
+
+impl SerializerError {
+ /// ### new
+ ///
+ /// Instantiate a new `SerializerError`
+ pub fn new(kind: SerializerErrorKind) -> SerializerError {
+ SerializerError { kind, msg: None }
+ }
+
+ /// ### new_ex
+ ///
+ /// Instantiates a new `SerializerError` with description message
+ pub fn new_ex(kind: SerializerErrorKind, msg: String) -> SerializerError {
+ let mut err: SerializerError = SerializerError::new(kind);
+ err.msg = Some(msg);
+ err
+ }
+}
+
+impl std::fmt::Display for SerializerError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let err: String = match &self.kind {
+ SerializerErrorKind::IoError => String::from("IO error"),
+ SerializerErrorKind::SerializationError => String::from("Serialization error"),
+ SerializerErrorKind::SyntaxError => String::from("Syntax error"),
+ };
+ match &self.msg {
+ Some(msg) => write!(f, "{} ({})", err, msg),
+ None => write!(f, "{}", err),
+ }
+ }
+}
+
+// Tests
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use std::env;
+
+ #[test]
+ fn test_config_mod_new() {
+ let mut keys: HashMap = HashMap::with_capacity(1);
+ keys.insert(
+ String::from("192.168.1.31"),
+ PathBuf::from("/tmp/private.key"),
+ );
+ let remote: RemoteConfig = RemoteConfig { ssh_keys: keys };
+ let ui: UserInterfaceConfig = UserInterfaceConfig {
+ default_protocol: String::from("SFTP"),
+ text_editor: PathBuf::from("nano"),
+ };
+ let cfg: UserConfig = UserConfig {
+ user_interface: ui,
+ remote: remote,
+ };
+ assert_eq!(
+ *cfg
+ .remote
+ .ssh_keys
+ .get(&String::from("192.168.1.31"))
+ .unwrap(),
+ PathBuf::from("/tmp/private.key")
+ );
+ assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
+ assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
+ }
+
+ #[test]
+ fn test_config_mod_new_default() {
+ // Force vim editor
+ env::set_var(String::from("EDITOR"), String::from("vim"));
+ // Get default
+ 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.remote.ssh_keys.len(), 0);
+ }
+
+ #[test]
+ fn test_config_mod_errors() {
+ let error: SerializerError = SerializerError::new(SerializerErrorKind::SyntaxError);
+ assert_eq!(error.kind, SerializerErrorKind::SyntaxError);
+ assert!(error.msg.is_none());
+ assert_eq!(format!("{}", error), String::from("Syntax error"));
+ let error: SerializerError =
+ SerializerError::new_ex(SerializerErrorKind::SyntaxError, String::from("bad syntax"));
+ assert_eq!(error.kind, SerializerErrorKind::SyntaxError);
+ assert!(error.msg.is_some());
+ assert_eq!(
+ format!("{}", error),
+ String::from("Syntax error (bad syntax)")
+ );
+ // Fmt
+ assert_eq!(
+ format!("{}", SerializerError::new(SerializerErrorKind::IoError)),
+ String::from("IO error")
+ );
+ assert_eq!(
+ format!(
+ "{}",
+ SerializerError::new(SerializerErrorKind::SerializationError)
+ ),
+ String::from("Serialization error")
+ );
+ }
+}
diff --git a/src/config/serializer.rs b/src/config/serializer.rs
new file mode 100644
index 0000000..f96c58c
--- /dev/null
+++ b/src/config/serializer.rs
@@ -0,0 +1,185 @@
+//! ## Serializer
+//!
+//! `serializer` is the module which provides the serializer/deserializer for configuration
+
+/*
+*
+* 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 .
+*
+*/
+
+use super::{SerializerError, SerializerErrorKind, UserConfig};
+
+use std::io::{Read, Write};
+
+pub struct ConfigSerializer {}
+
+impl ConfigSerializer {
+ /// ### serialize
+ ///
+ /// Serialize `UserConfig` into TOML and write content to writable
+ pub fn serialize(
+ &self,
+ mut writable: Box,
+ cfg: &UserConfig,
+ ) -> Result<(), SerializerError> {
+ // Serialize content
+ let data: String = match toml::ser::to_string(cfg) {
+ Ok(dt) => dt,
+ Err(err) => {
+ return Err(SerializerError::new_ex(
+ SerializerErrorKind::SerializationError,
+ err.to_string(),
+ ))
+ }
+ };
+ // Write file
+ match writable.write_all(data.as_bytes()) {
+ Ok(_) => Ok(()),
+ Err(err) => Err(SerializerError::new_ex(
+ SerializerErrorKind::IoError,
+ err.to_string(),
+ )),
+ }
+ }
+
+ /// ### deserialize
+ ///
+ /// Read data from readable and deserialize its content as TOML
+ pub fn deserialize(&self, mut readable: Box) -> Result {
+ // Read file content
+ let mut data: String = String::new();
+ if let Err(err) = readable.read_to_string(&mut data) {
+ return Err(SerializerError::new_ex(
+ SerializerErrorKind::IoError,
+ err.to_string(),
+ ));
+ }
+ // Deserialize
+ match toml::de::from_str(data.as_str()) {
+ Ok(hosts) => Ok(hosts),
+ Err(err) => Err(SerializerError::new_ex(
+ SerializerErrorKind::SyntaxError,
+ err.to_string(),
+ )),
+ }
+ }
+}
+
+// Tests
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ use std::io::{Seek, SeekFrom};
+ use std::path::PathBuf;
+
+ #[test]
+ fn test_config_serializer_deserialize_ok() {
+ let toml_file: tempfile::NamedTempFile = create_good_toml();
+ toml_file.as_file().sync_all().unwrap();
+ toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
+ // Parse
+ let deserializer: ConfigSerializer = ConfigSerializer {};
+ let cfg = deserializer.deserialize(Box::new(toml_file));
+ println!("{:?}", cfg);
+ assert!(cfg.is_ok());
+ let cfg: UserConfig = cfg.ok().unwrap();
+ // Verify configuration
+ // Verify ui
+ assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
+ assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
+ // Verify keys
+ assert_eq!(
+ *cfg.remote
+ .ssh_keys
+ .get(&String::from("192.168.1.31"))
+ .unwrap(),
+ PathBuf::from("/home/omar/.ssh/raspberry.key")
+ );
+ assert_eq!(
+ *cfg.remote
+ .ssh_keys
+ .get(&String::from("192.168.1.32"))
+ .unwrap(),
+ PathBuf::from("/home/omar/.ssh/beaglebone.key")
+ );
+ assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
+ }
+
+ #[test]
+ fn test_config_serializer_deserialize_nok() {
+ let toml_file: tempfile::NamedTempFile = create_bad_toml();
+ toml_file.as_file().sync_all().unwrap();
+ toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
+ // Parse
+ let deserializer: ConfigSerializer = ConfigSerializer {};
+ assert!(deserializer.deserialize(Box::new(toml_file)).is_err());
+ }
+
+ #[test]
+ fn test_config_serializer_serialize() {
+ let mut cfg: UserConfig = UserConfig::default();
+ let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
+ // Insert key
+ cfg.remote.ssh_keys.insert(
+ String::from("192.168.1.31"),
+ PathBuf::from("/home/omar/.ssh/id_rsa"),
+ );
+ // Serialize
+ let serializer: ConfigSerializer = ConfigSerializer {};
+ let writer: Box = Box::new(std::fs::File::create(toml_file.path()).unwrap());
+ assert!(serializer.serialize(writer, &cfg).is_ok());
+ // Reload configuration and check if it's ok
+ toml_file.as_file().sync_all().unwrap();
+ toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
+ assert!(serializer.deserialize(Box::new(toml_file)).is_ok());
+ }
+
+ fn create_good_toml() -> tempfile::NamedTempFile {
+ // Write
+ let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
+ let file_content: &str = r#"
+ [user_interface]
+ default_protocol = "SCP"
+ text_editor = "vim"
+
+ [remote.ssh_keys]
+ "192.168.1.31" = "/home/omar/.ssh/raspberry.key"
+ "192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
+ "#;
+ tmpfile.write_all(file_content.as_bytes()).unwrap();
+ tmpfile
+ }
+
+ fn create_bad_toml() -> tempfile::NamedTempFile {
+ // Write
+ let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
+ let file_content: &str = r#"
+ [user_interface]
+ default_protocol = "SFTP"
+
+ [remote.ssh_keys]
+ "192.168.1.31" = "/home/omar/.ssh/raspberry.key"
+ "#;
+ tmpfile.write_all(file_content.as_bytes()).unwrap();
+ tmpfile
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9353ea5..087e803 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,6 +26,7 @@ extern crate magic_crypt;
pub mod activity_manager;
pub mod bookmarks;
+pub mod config;
pub mod filetransfer;
pub mod fs;
pub mod host;
diff --git a/src/main.rs b/src/main.rs
index 4682f1d..93eafb5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -39,6 +39,7 @@ use std::time::Duration;
// Include
mod activity_manager;
mod bookmarks;
+mod config;
mod filetransfer;
mod fs;
mod host;