From d871497a49d0d5d7cb2f7e1de27a2b86f177f721 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Tue, 24 Nov 2020 22:05:11 +0100 Subject: [PATCH] Working on main activity (FileTransferActivity) --- src/activity_manager.rs | 75 ++++-- src/main.rs | 12 +- src/ui/activities/auth_activity.rs | 2 +- src/ui/activities/filetransfer_activity.rs | 292 +++++++++++++++++++++ src/ui/activities/mod.rs | 1 + src/ui/context.rs | 12 +- 6 files changed, 355 insertions(+), 39 deletions(-) create mode 100644 src/ui/activities/filetransfer_activity.rs diff --git a/src/activity_manager.rs b/src/activity_manager.rs index bc26d78..63747a3 100644 --- a/src/activity_manager.rs +++ b/src/activity_manager.rs @@ -26,9 +26,12 @@ use std::path::PathBuf; // Deps -use crate::filetransfer::FileTransfer; use crate::host::Localhost; -use crate::ui::activities::{auth_activity::AuthActivity, auth_activity::ScpProtocol, Activity}; +use crate::ui::activities::{ + auth_activity::AuthActivity, auth_activity::ScpProtocol, + filetransfer_activity::FileTransferActivity, filetransfer_activity::FileTransferParams, + Activity, +}; use crate::ui::context::Context; // Namespaces @@ -43,17 +46,6 @@ pub enum NextActivity { FileTransfer, } -/// ### FileTransferParams -/// -/// Holds connection parameters for file transfers -struct FileTransferParams { - address: String, - port: u16, - protocol: ScpProtocol, - username: Option, - password: Option, -} - /// ### ActivityManager /// /// The activity manager takes care of running activities and handling them until the application has ended @@ -68,7 +60,6 @@ impl ActivityManager { /// /// Initializes a new Activity Manager pub fn new( - client: Box, local_dir: &PathBuf, interval: Duration, ) -> Result { @@ -77,7 +68,7 @@ impl ActivityManager { Ok(h) => h, Err(_) => return Err(()), }; - let ctx: Context = Context::new(client, host); + let ctx: Context = Context::new(host); Ok(ActivityManager { context: ctx, ftparams: None, @@ -116,7 +107,7 @@ impl ActivityManager { current_activity = match current_activity { Some(activity) => match activity { NextActivity::Authentication => self.run_authentication(), - NextActivity::FileTransfer => self.run_authentication(), // FIXME: change + NextActivity::FileTransfer => self.run_filetransfer(), }, None => break, // Exit } @@ -149,6 +140,58 @@ impl ActivityManager { if activity.submit { // User submitted, set next activity result = Some(NextActivity::FileTransfer); + // Get params + self.ftparams = Some(FileTransferParams { + address: activity.address.clone(), + port: activity.port.parse::().ok().unwrap(), + username: match activity.username.len() { + 0 => None, + _ => Some(activity.username.clone()), + }, + password: match activity.password.len() { + 0 => None, + _ => Some(activity.password.clone()), + }, + protocol: activity.protocol.clone(), + }); + break; + } + // Sleep for ticks + sleep(self.interval); + } + // Destroy activity + activity.on_destroy(&mut self.context); + result + } + + /// ### run_filetransfer + /// + /// Loop for FileTransfer activity. + /// Returns when activity terminates. + /// Returns the next activity to run + fn run_filetransfer(&mut self) -> Option { + if self.ftparams.is_none() { + return Some(NextActivity::Authentication); + } + // Prepare activity + let mut activity: FileTransferActivity = + FileTransferActivity::new(self.ftparams.take().unwrap()); + // Prepare result + let result: Option; + // Create activity + activity.on_create(&mut self.context); + loop { + // Draw activity + activity.on_draw(&mut self.context); + // Check if has to be terminated + if activity.quit { + // Quit activities + result = None; + break; + } + if activity.disconnected { + // User disconnected, set next activity to authentication + result = Some(NextActivity::Authentication); break; } // Sleep for ticks diff --git a/src/main.rs b/src/main.rs index 4366d96..da563e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,6 @@ mod utils; // namespaces use activity_manager::{ActivityManager, NextActivity}; -use filetransfer::{sftp_transfer::SftpFileTransfer, FileTransfer}; use ui::activities::auth_activity::ScpProtocol; /// ### print_usage @@ -119,22 +118,13 @@ fn main() { } } } - // Prepare file transfer - let file_transfer: Box = match protocol { - ScpProtocol::Sftp => Box::new(SftpFileTransfer::new()), - _ => { - // FIXME: complete with ftp client - eprintln!("Unsupported protocol!"); - std::process::exit(255); - } - }; // Get working directory let wrkdir: PathBuf = match env::current_dir() { Ok(dir) => dir, Err(_) => PathBuf::from("/"), }; // Create activity manager - let mut manager: ActivityManager = match ActivityManager::new(file_transfer, &wrkdir, ticks) { + let mut manager: ActivityManager = match ActivityManager::new(&wrkdir, ticks) { Ok(m) => m, Err(_) => { eprintln!("Invalid directory '{}'", wrkdir.display()); diff --git a/src/ui/activities/auth_activity.rs b/src/ui/activities/auth_activity.rs index f72d199..fd8f10d 100644 --- a/src/ui/activities/auth_activity.rs +++ b/src/ui/activities/auth_activity.rs @@ -68,7 +68,7 @@ enum InputMode { /// ### ScpProtocol /// /// ScpProtocol describes the communication protocol selected by the user to communicate with the remote -#[derive(std::cmp::PartialEq, std::fmt::Debug)] +#[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone)] pub enum ScpProtocol { Sftp, Ftp, diff --git a/src/ui/activities/filetransfer_activity.rs b/src/ui/activities/filetransfer_activity.rs new file mode 100644 index 0000000..070cb8d --- /dev/null +++ b/src/ui/activities/filetransfer_activity.rs @@ -0,0 +1,292 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/* +* +* 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 crossterm; +extern crate tui; +extern crate unicode_width; + +// locals +use super::auth_activity::ScpProtocol; +use super::{Activity, Context}; + +// File transfer +use crate::filetransfer::sftp_transfer::SftpFileTransfer; +use crate::filetransfer::FileTransfer; + +// Includes +use crossterm::event::Event as InputEvent; +use crossterm::event::KeyCode; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use std::io::Stdout; +use tui::{ + backend::CrosstermBackend, + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + terminal::Frame, + text::{Span, Spans, Text}, + widgets::{Block, Borders, Clear, Paragraph, Tabs}, +}; +use unicode_width::UnicodeWidthStr; + +// Types +type DialogCallback = fn(); + +/// ### FileTransferParams +/// +/// Holds connection parameters for file transfers +pub struct FileTransferParams { + pub address: String, + pub port: u16, + pub protocol: ScpProtocol, + pub username: Option, + pub password: Option, +} + +/// ## PopupType +/// +/// PopupType describes the type of popup +#[derive(std::cmp::PartialEq)] +enum PopupType { + Alert(Color, String), + Progress(String), + YesNo(String, DialogCallback, DialogCallback), // Yes, no callback +} + +/// ## InputMode +/// +/// InputMode describes the current input mode +/// Each input mode handle the input events in a different way +#[derive(std::cmp::PartialEq)] +enum InputMode { + Explorer, + Popup(PopupType), +} + +/// ## FileExplorer +/// +/// File explorer states +struct FileExplorer { + pub index: usize, +} + +impl FileExplorer { + /// ### new + /// + /// Instantiates a new FileExplorer + pub fn new() -> FileExplorer { + FileExplorer { index: 0 } + } +} + +/// ## FileExplorerTab +/// +/// File explorer tab +enum FileExplorerTab { + Local, + Remote, +} + +/// ## LogLevel +/// +/// Log level type +enum LogLevel { + Error, + Warn, + Info, +} + +/// ## LogRecord +/// +/// Log record entry +struct LogRecord { + pub level: LogLevel, + pub msg: String, +} + +impl LogRecord { + /// ### new + /// + /// Instantiates a new LogRecord + pub fn new(level: LogLevel, msg: &str) -> LogRecord { + LogRecord { + level: level, + msg: String::from(msg), + } + } +} + +/// ## FileTransferActivity +/// +/// FileTransferActivity is the data holder for the file transfer activity +pub struct FileTransferActivity { + pub disconnected: bool, + pub quit: bool, + params: FileTransferParams, + local: FileExplorer, + remote: FileExplorer, + tab: FileExplorerTab, + log_records: Vec, + progress: usize, + input_mode: InputMode, + client: Box, +} + +impl FileTransferActivity { + /// ### new + /// + /// Instantiates a new FileTransferActivity + pub fn new(params: FileTransferParams) -> FileTransferActivity { + let protocol: ScpProtocol = params.protocol.clone(); + FileTransferActivity { + disconnected: false, + quit: false, + params: params, + local: FileExplorer::new(), + remote: FileExplorer::new(), + tab: FileExplorerTab::Local, + log_records: Vec::new(), + progress: 0, + input_mode: InputMode::Explorer, + client: match protocol { + ScpProtocol::Sftp => Box::new(SftpFileTransfer::new()), + ScpProtocol::Ftp => Box::new(SftpFileTransfer::new()), // FIXME: FTP + }, + } + } + + /// ### draw + /// + /// Draw UI + fn draw(&mut self, frame: &mut Frame>) { + // TODO: implement + } + + /// ### draw_header + /// + /// Draw header + fn draw_header(&self) -> Paragraph { + Paragraph::new(" _____ ____ ____ ____ \n|_ _|__ _ __ _ __ ___ / ___| / ___| _ \\ \n | |/ _ \\ '__| '_ ` _ \\\\___ \\| | | |_) |\n | | __/ | | | | | | |___) | |___| __/ \n |_|\\___|_| |_| |_| |_|____/ \\____|_| \n") + .style(Style::default().fg(Color::LightYellow).add_modifier(Modifier::BOLD)) + } + + /// ### draw_footer + /// + /// Draw authentication page footer + fn draw_footer(&self) -> Paragraph { + // Write header + let footer = vec![ + Span::styled( + "", + Style::default() + .bg(Color::Cyan) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::raw("quit\t"), + Span::styled( + "", + Style::default() + .bg(Color::Cyan) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::raw("change input field\t"), + Span::styled( + "", + Style::default() + .bg(Color::Cyan) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::raw("change explorer tab\t"), + Span::styled( + "", + Style::default() + .bg(Color::Cyan) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::raw("to upload/download file\t"), + Span::styled( + "", + Style::default() + .bg(Color::Cyan) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::raw("to rename file\t"), + Span::styled( + "", + Style::default() + .bg(Color::Cyan) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::raw("to delete file\t"), + ]; + Paragraph::new(Text::from(Spans::from(footer))) + } +} + +impl Activity for FileTransferActivity { + /// ### on_create + /// + /// `on_create` is the function which must be called to initialize the activity. + /// `on_create` must initialize all the data structures used by the activity + fn on_create(&mut self, context: &mut Context) { + // Put raw mode on enabled + let _ = enable_raw_mode(); + // Clear terminal + let _ = context.terminal.clear(); + } + + /// ### on_draw + /// + /// `on_draw` is the function which draws the graphical interface. + /// This function must be called at each tick to refresh the interface + fn on_draw(&mut self, context: &mut Context) { + // TODO: logic + // draw interface + let _ = context.terminal.draw(|f| { + self.draw(f); + }); + } + + /// ### on_destroy + /// + /// `on_destroy` is the function which cleans up runtime variables and data before terminating the activity. + /// This function must be called once before terminating the activity. + fn on_destroy(&mut self, context: &mut Context) { + // Disable raw mode + let _ = disable_raw_mode(); + // Clear terminal + let _ = context.terminal.clear(); + // Disconnect client + if self.client.is_connected() { + let _ = self.client.disconnect(); + } + } +} diff --git a/src/ui/activities/mod.rs b/src/ui/activities/mod.rs index 77476be..5d675a5 100644 --- a/src/ui/activities/mod.rs +++ b/src/ui/activities/mod.rs @@ -29,6 +29,7 @@ use super::context::Context; // Activities pub mod auth_activity; +pub mod filetransfer_activity; // Activity trait diff --git a/src/ui/context.rs b/src/ui/context.rs index 98a0b3f..1e8693c 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -29,7 +29,6 @@ extern crate tui; // Locals use super::input::InputHandler; -use crate::filetransfer::FileTransfer; use crate::host::Localhost; // Includes @@ -44,7 +43,6 @@ use tui::Terminal; /// /// Context holds data structures used by the ui pub struct Context { - pub scp_client: Box, pub local: Localhost, pub(crate) input_hnd: InputHandler, pub(crate) terminal: Terminal>, @@ -54,12 +52,11 @@ impl Context { /// ### new /// /// Instantiates a new Context - pub fn new(scp_client: Box, local: Localhost) -> Context { + pub fn new(local: Localhost) -> Context { // Create terminal let mut stdout = stdout(); assert!(execute!(stdout, EnterAlternateScreen, EnableMouseCapture).is_ok()); Context { - scp_client: scp_client, local: local, input_hnd: InputHandler::new(), terminal: Terminal::new(CrosstermBackend::new(stdout)).unwrap() @@ -67,13 +64,6 @@ impl Context { } } -impl Drop for Context { - fn drop(&mut self) { - // Disconnect client - let _ = self.scp_client.disconnect(); - } -} - /* #[cfg(test)] mod tests {