diff --git a/src/activity_manager.rs b/src/activity_manager.rs new file mode 100644 index 0000000..041ca54 --- /dev/null +++ b/src/activity_manager.rs @@ -0,0 +1,106 @@ +//! ## ActivityManager +//! +//! `activity_manager` is the module which provides run methods and handling for activities + +/* +* +* 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 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::context::Context; + +/// ### NextActivity +/// +/// NextActivity identified the next identity to run once the current has ended +pub enum NextActivity { + Authentication, + 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 +pub struct ActivityManager { + context: Context, + ftparams: Option, +} + +impl ActivityManager { + /// ### new + /// + /// Initializes a new Activity Manager + pub fn new(client: Box, local_dir: &PathBuf) -> Result { + // Prepare Context + let host: Localhost = match Localhost::new(local_dir.clone()) { + Ok(h) => h, + Err(_) => return Err(()), + }; + let ctx: Context = Context::new(client, host); + Ok(ActivityManager { + context: ctx, + ftparams: None, + }) + } + + /// ### set_filetransfer_params + /// + /// Set file transfer params + pub fn set_filetransfer_params( + &mut self, + address: String, + port: u16, + protocol: ScpProtocol, + username: Option, + password: Option, + ) { + self.ftparams = Some(FileTransferParams { + address: address, + port: port, + protocol: protocol, + username: username, + password: password, + }); + } + + /// ### run + /// + /// + /// Loop for activity manager. You need to provide the activity to start with + /// Returns the exitcode + pub fn run(&mut self, launch_activity: NextActivity) -> i32 { + 0 + } +} diff --git a/src/lib.rs b/src/lib.rs index 0f3bfdc..621a363 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ * */ +pub mod activity_manager; pub mod filetransfer; pub mod fs; pub mod host; diff --git a/src/main.rs b/src/main.rs index 20ee3b2..bb57dfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,31 +28,44 @@ extern crate getopts; // External libs use getopts::Options; use std::env; +use std::path::PathBuf; +use std::time::Duration; // Include +mod activity_manager; mod filetransfer; mod fs; mod host; +mod ui; +mod utils; + +// namespaces +use activity_manager::{ActivityManager, NextActivity}; +use filetransfer::{FileTransfer, sftp_transfer::SftpFileTransfer}; +use ui::activities::auth_activity::ScpProtocol; /// ### print_usage /// /// Print usage fn print_usage(opts: Options) { - let brief = format!("Usage: termscp [Options]... Remote"); + let brief = format!("Usage: termscp [Options]... [protocol:user@address:port]"); print!("{}", opts.usage(&brief)); } fn main() { let args: Vec = env::args().collect(); //Program CLI options - // TODO: insert opts here + 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 protocol: ScpProtocol = ScpProtocol::Sftp; // Default protocol + let mut ticks: Duration = Duration::from_micros(50); //Process options + // FIXME: there is no way to provide password from CLI atm let mut opts = Options::new(); - // opts.optopt("c", "command", "Specify command to run. Shell returns after running the command", ""); - // opts.optopt("C", "config", "Specify YAML configuration file", ""); - // opts.optopt("l", "lang", "Specify shell language", ""); - // opts.optopt("s", "shell", "Force the shell binary path", ""); + opts.optopt("T", "ticks", "Set UI ticks; default 50µs", "<µs>"); opts.optflag("v", "version", ""); opts.optflag("h", "help", "Print this menu"); let matches = match opts.parse(&args[1..]) { @@ -75,6 +88,89 @@ fn main() { ); std::process::exit(255); } - // TODO: ... - std::process::exit(0); + // Match protocol first + if let Some(val) = matches.opt_str("P") { + match val.as_str() { + "sftp" => { + protocol = ScpProtocol::Sftp; + // Set port to 22 as default + port = 22; + // Set default username to current + username = Some(whoami::username()); + } + "ftp" => { + protocol = ScpProtocol::Ftp; + // Set port to 21 + port = 21; + } + _ => { + eprintln!("Unknown protocol '{}'", val); + print_usage(opts); + std::process::exit(255); + } + } + } + // Match ticks + if let Some(val) = matches.opt_str("T") { + match val.parse::() { + Ok(val) => ticks = Duration::from_micros(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.clone(); + if let Some(remote) = extra_args.get(0) { + // Parse address + match utils::parse_remote_opt(remote) { + Ok((addr, portn, proto, user)) => { + // Set params + address = Some(addr); + port = portn; + protocol = proto; + username = user; + }, + Err(err) => { + eprintln!("Bad address option: {}", err); + print_usage(opts); + std::process::exit(255); + } + } + } + // 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) { + Ok(m) => m, + Err(_) => { + eprintln!("Invalid directory '{}'", wrkdir.display()); + std::process::exit(255); + } + }; + // Initialize client if necessary + let mut start_activity: NextActivity = NextActivity::Authentication; + if let Some(address) = address { + // In this case the first activity will be FileTransfer + start_activity = NextActivity::FileTransfer; + manager.set_filetransfer_params(address, port, protocol, username, password); + } + // Run + let rc: i32 = manager.run(start_activity); + // Then return + std::process::exit(rc); } diff --git a/src/ui/activities/auth_activity.rs b/src/ui/activities/auth_activity.rs index 2c254e0..182cc72 100644 --- a/src/ui/activities/auth_activity.rs +++ b/src/ui/activities/auth_activity.rs @@ -33,9 +33,14 @@ use super::{Activity, Context}; // Includes use crossterm::event::Event as InputEvent; use crossterm::event::{KeyCode, KeyEvent}; -use tui::layout; -use tui::style; -use tui::widgets; +use tui::{ + backend::CrosstermBackend, + layout::{Constraint, Direction, Layout}, + style::{Color, Modifier, Style}, + text::{Span, Spans, Text}, + widgets::{Block, Borders, List, ListItem, Paragraph}, + Terminal, +}; /// ### InputField /// @@ -52,6 +57,7 @@ enum InputField { /// ### ScpProtocol /// /// ScpProtocol describes the communication protocol selected by the user to communicate with the remote +#[derive(std::cmp::PartialEq, std::fmt::Debug)] pub enum ScpProtocol { Sftp, Ftp, @@ -147,6 +153,7 @@ impl Activity for AuthActivity { } // Everything OK, set enter self.form_submit = true; + popup = Some(format!("Connecting to {}:{}...", self.address, self.port)); } KeyCode::Backspace => { // Pop last char @@ -225,7 +232,40 @@ impl Activity for AuthActivity { } } } - // TODO: draw interface + // draw interface + context.terminal.draw(|f| { + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(2) + .constraints( + [ + Constraint::Length(1), + Constraint::Length(3), + Constraint::Min(1), + ] + .as_ref(), + ) + .split(f.size()); + // Write header + let (header, h_style) = ( + vec![ + Span::raw("Press "), + Span::styled("", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to exit, "), + Span::styled("", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to change input field,"), + Span::styled("", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to submit form"), + ], + Style::default().add_modifier(Modifier::RAPID_BLINK), + ); + let mut header_text = Text::from(Spans::from(header)); + header_text.patch_style(h_style); + let header_message = Paragraph::new(header_text); + f.render_widget(header_message, chunks[0]); + // Create input fields + // TODO: + }); } /// ### on_destroy