diff --git a/src/main.rs b/src/main.rs index fa0f29c..f41067d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ use std::time::Duration; // Include mod activity_manager; +mod bookmarks; mod filetransfer; mod fs; mod host; diff --git a/src/ui/activities/auth_activity/bookmarks.rs b/src/ui/activities/auth_activity/bookmarks.rs new file mode 100644 index 0000000..493f4a9 --- /dev/null +++ b/src/ui/activities/auth_activity/bookmarks.rs @@ -0,0 +1,328 @@ +//! ## AuthActivity +//! +//! `auth_activity` is the module which implements the authentication activity + +/* +* +* 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 . +* +*/ + +// Dependencies +extern crate dirs; + +// Locals +use super::{AuthActivity, Color, FileTransferProtocol, InputMode, PopupType, UserHosts}; +use crate::bookmarks::serializer::BookmarkSerializer; +use crate::bookmarks::Bookmark; +use crate::utils::time_to_str; + +// Ext +use std::path::PathBuf; +use std::time::SystemTime; + +impl AuthActivity { + /// ### read_bookmarks + /// + /// Read bookmarks from data file; Show popup if necessary + pub(super) fn read_bookmarks(&mut self) { + // Init bookmarks + if let Some(bookmark_file) = self.init_bookmarks() { + // Read + if self.context.is_some() { + match self + .context + .as_ref() + .unwrap() + .local + .open_file_read(bookmark_file.as_path()) + { + Ok(reader) => { + // Read bookmarks + let deserializer: BookmarkSerializer = BookmarkSerializer {}; + match deserializer.deserialize(Box::new(reader)) { + Ok(bookmarks) => self.bookmarks = Some(bookmarks), + Err(err) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Yellow, + format!( + "Could not read bookmarks from \"{}\": {}", + bookmark_file.display(), + err + ), + )) + } + } + } + Err(err) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Yellow, + format!( + "Could not read bookmarks from \"{}\": {}", + bookmark_file.display(), + err + ), + )) + } + } + } + } + } + + /// ### del_bookmark + /// + /// Delete bookmark + pub(super) fn del_bookmark(&mut self, name: String) { + if let Some(hosts) = self.bookmarks.as_mut() { + if hosts.bookmarks.contains_key(name.as_str()) { + hosts.bookmarks.remove(name.as_str()); + } + } + } + + /// ### save_bookmark + /// + /// Save current input fields as a bookmark + pub(super) fn save_bookmark(&mut self, name: String) { + if let Ok(host) = self.make_user_host() { + if let Some(hosts) = self.bookmarks.as_mut() { + hosts.bookmarks.insert(name, host); + // Write bookmarks + self.write_bookmarks(); + } + } + } + + /// ### save_recent + /// + /// Save current input fields as a "recent" + pub(super) fn save_recent(&mut self) { + if let Ok(host) = self.make_user_host() { + if let Some(hosts) = self.bookmarks.as_mut() { + // Check if duplicated + for recent_host in hosts.recents.values() { + if *recent_host == host { + // Don't save duplicates + return; + } + } + // If hosts size is bigger than 16; pop last + if hosts.recents.len() >= 16 { + let mut keys: Vec = 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 { + // Check port + let port: u16 = match self.port.parse::() { + Ok(val) => { + if val > 65535 { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Red, + String::from("Specified port must be in range 0-65535"), + )); + return Err(()); + } + val as u16 + } + Err(_) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Red, + String::from("Specified port is not a number"), + )); + return Err(()); + } + }; + Ok(Bookmark { + address: self.address.clone(), + port: port, + protocol: match self.protocol { + FileTransferProtocol::Ftp(secure) => match secure { + true => String::from("FTPS"), + false => String::from("FTP"), + }, + FileTransferProtocol::Scp => String::from("SCP"), + FileTransferProtocol::Sftp => String::from("SFTP"), + }, + username: self.username.clone(), + }) + } + + /// ### write_bookmarks + /// + /// Write bookmarks to file + fn write_bookmarks(&mut self) { + if self.bookmarks.is_some() { + if self.context.is_some() { + // Open file for write + if let Some(bookmarks_file) = self.init_bookmarks() { + match self + .context + .as_ref() + .unwrap() + .local + .open_file_write(bookmarks_file.as_path()) + { + Ok(writer) => { + let serializer: BookmarkSerializer = BookmarkSerializer {}; + if let Err(err) = serializer + .serialize(Box::new(writer), &self.bookmarks.as_ref().unwrap()) + { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Yellow, + format!( + "Could not write default bookmarks at \"{}\": {}", + bookmarks_file.display(), + err + ), + )); + } + } + Err(err) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Yellow, + format!( + "Could not write default bookmarks at \"{}\": {}", + bookmarks_file.display(), + err + ), + )) + } + } + } + } + } + } + + /// ### init_bookmarks + /// + /// Initialize bookmarks directory + /// Returns bookmark path + fn init_bookmarks(&mut self) -> Option { + // Get file + lazy_static! { + static ref CONF_DIR: Option = dirs::config_dir(); + } + if CONF_DIR.is_some() { + // Get path of bookmarks + let mut p: PathBuf = CONF_DIR.as_ref().unwrap().clone(); + // Append termscp dir + p.push("termscp/"); + // Mkdir if doesn't exist + if self.context.is_some() { + if let Err(err) = self + .context + .as_mut() + .unwrap() + .local + .mkdir_ex(p.as_path(), true) + { + // Show popup + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Yellow, + format!( + "Could not create configuration directory at \"{}\": {}", + p.display(), + err + ), + )); + // Return None + return None; + } + } + // Append bookmarks.toml + p.push("bookmarks.toml"); + // If bookmarks.toml doesn't exist, initializae it + if self.context.is_some() { + if !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( + Color::Yellow, + format!( + "Could not write default bookmarks at \"{}\": {}", + p.display(), + err + ), + )); + return None; + } + } + Err(err) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Yellow, + format!( + "Could not write default bookmarks at \"{}\": {}", + p.display(), + err + ), + )); + return None; + } + } + } + } + // return path + Some(p) + } else { + None + } + } +} diff --git a/src/ui/activities/auth_activity/input.rs b/src/ui/activities/auth_activity/input.rs index 2c2fcd1..5e9b008 100644 --- a/src/ui/activities/auth_activity/input.rs +++ b/src/ui/activities/auth_activity/input.rs @@ -98,6 +98,8 @@ impl AuthActivity { return; } } + // Save recent + self.save_recent(); // Everything OK, set enter self.submit = true; } diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index 97cf482..7584ec2 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -24,6 +24,7 @@ */ // Sub modules +mod bookmarks; mod input; mod layout; @@ -34,6 +35,7 @@ extern crate unicode_width; // locals use super::{Activity, Context}; +use crate::bookmarks::UserHosts; use crate::filetransfer::FileTransferProtocol; // Includes @@ -91,6 +93,7 @@ pub struct AuthActivity { pub submit: bool, // becomes true after user has submitted fields pub quit: bool, // Becomes true if user has pressed esc context: Option, + bookmarks: Option, selected_field: InputField, // Selected field in AuthCredentials Form input_mode: InputMode, input_form: InputForm, @@ -118,6 +121,7 @@ impl AuthActivity { submit: false, quit: false, context: None, + bookmarks: None, selected_field: InputField::Address, input_mode: InputMode::Form, input_form: InputForm::AuthCredentials, @@ -141,6 +145,10 @@ impl Activity for AuthActivity { // Put raw mode on enabled let _ = enable_raw_mode(); self.input_mode = InputMode::Form; + // Read bookmarks + if self.bookmarks.is_none() { + self.read_bookmarks(); + } } /// ### on_draw