termscp/src/main.rs

303 lines
9.1 KiB
Rust
Raw Normal View History

2021-03-26 22:25:10 +01:00
/**
* 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.
*/
2020-11-07 17:48:41 +01:00
const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
2020-11-07 17:48:41 +01:00
// Crates
2021-07-10 20:19:29 +02:00
extern crate argh;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate lazy_static;
2020-12-16 15:47:02 +01:00
#[macro_use]
2021-05-16 15:09:17 +02:00
extern crate log;
#[macro_use]
2020-12-16 15:47:02 +01:00
extern crate magic_crypt;
2020-11-22 20:36:53 +01:00
extern crate rpassword;
2020-11-07 17:48:41 +01:00
// External libs
2021-07-10 20:19:29 +02:00
use argh::FromArgs;
2020-11-07 17:48:41 +01:00
use std::env;
2021-07-08 16:21:39 +02:00
use std::path::PathBuf;
use std::time::Duration;
2020-11-07 17:48:41 +01:00
2020-11-14 15:41:17 +01:00
// Include
mod activity_manager;
2020-12-24 11:12:02 +01:00
mod config;
2020-11-14 15:41:17 +01:00
mod filetransfer;
mod fs;
mod host;
mod support;
2020-12-15 20:48:43 +01:00
mod system;
mod ui;
mod utils;
// namespaces
use activity_manager::{ActivityManager, NextActivity};
use filetransfer::FileTransferParams;
2021-05-16 15:09:17 +02:00
use system::logging;
2020-11-14 15:41:17 +01:00
2021-07-08 16:21:39 +02:00
enum Task {
Activity(NextActivity),
ImportTheme(PathBuf),
2021-09-10 22:22:15 +02:00
InstallUpdate,
2021-07-08 16:21:39 +02:00
}
2020-11-07 17:48:41 +01:00
2021-07-10 20:19:29 +02:00
#[derive(FromArgs)]
#[argh(description = "
2021-08-26 11:24:13 +02:00
where positional can be: [address] [local-wrkdir]
Address syntax can be:
- `protocol://user@address:port:wrkdir` for protocols such as Sftp, Scp, Ftp
- `s3://bucket-name@region:profile:/wrkdir` for Aws S3 protocol
2021-07-10 20:19:29 +02:00
Please, report issues to <https://github.com/veeso/termscp>
Please, consider supporting the author <https://www.buymeacoffee.com/veeso>")]
struct Args {
#[argh(switch, short = 'c', description = "open termscp configuration")]
config: bool,
#[argh(option, short = 'P', description = "provide password from CLI")]
password: Option<String>,
#[argh(switch, short = 'q', description = "disable logging")]
quiet: bool,
#[argh(option, short = 't', description = "import specified theme")]
theme: Option<String>,
2021-09-10 22:22:15 +02:00
#[argh(
switch,
short = 'u',
description = "update termscp to the latest version"
)]
update: bool,
2021-07-10 20:19:29 +02:00
#[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<String>,
}
2021-07-08 16:21:39 +02:00
struct RunOpts {
remote: Option<FileTransferParams>,
2021-07-08 16:21:39 +02:00
ticks: Duration,
log_enabled: bool,
task: Task,
}
impl Default for RunOpts {
fn default() -> Self {
Self {
remote: None,
2021-07-08 16:21:39 +02:00
ticks: Duration::from_millis(10),
log_enabled: true,
task: Task::Activity(NextActivity::Authentication),
}
}
2020-11-07 17:48:41 +01:00
}
fn main() {
2021-07-10 20:19:29 +02:00
let args: Args = argh::from_env();
2021-07-08 16:21:39 +02:00
// Parse args
2021-07-10 20:19:29 +02:00
let mut run_opts: RunOpts = match parse_args(args) {
Ok(opts) => opts,
Err(err) => {
2021-07-08 16:21:39 +02:00
eprintln!("{}", err);
2021-07-10 20:19:29 +02:00
std::process::exit(255);
2021-07-08 16:21:39 +02:00
}
2021-07-10 20:19:29 +02:00
};
2021-07-08 16:21:39 +02:00
// Setup logging
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);
2020-11-07 17:48:41 +01:00
std::process::exit(255);
}
2021-07-08 16:21:39 +02:00
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);
}
2021-07-10 20:19:29 +02:00
/// ### parse_args
2021-07-08 16:21:39 +02:00
///
2021-07-10 20:19:29 +02:00
/// Parse arguments
/// In case of success returns `RunOpts`
/// in case something is wrong returns the error message
fn parse_args(args: Args) -> Result<RunOpts, String> {
let mut run_opts: RunOpts = RunOpts::default();
2020-11-07 17:48:41 +01:00
// Version
2021-07-10 20:19:29 +02:00
if args.version {
return Err(format!(
2021-05-05 22:13:57 +02:00
"termscp - {} - Developed by {}",
2020-11-07 17:48:41 +01:00
TERMSCP_VERSION, TERMSCP_AUTHORS,
2021-07-10 20:19:29 +02:00
));
2020-11-07 17:48:41 +01:00
}
// Setup activity?
2021-07-10 20:19:29 +02:00
if args.config {
2021-07-08 16:21:39 +02:00
run_opts.task = Task::Activity(NextActivity::SetupActivity);
}
2021-05-16 15:09:17 +02:00
// Logging
2021-07-10 20:19:29 +02:00
if args.quiet {
2021-07-08 16:21:39 +02:00
run_opts.log_enabled = false;
2021-05-16 15:09:17 +02:00
}
// Match ticks
2021-07-10 20:19:29 +02:00
run_opts.ticks = Duration::from_millis(args.ticks);
// @! extra modes
2021-07-10 20:19:29 +02:00
if let Some(theme) = args.theme {
2021-07-08 16:21:39 +02:00
run_opts.task = Task::ImportTheme(PathBuf::from(theme));
}
2021-09-10 22:22:15 +02:00
if args.update {
run_opts.task = Task::InstallUpdate;
}
// @! Ordinary mode
// Remote argument
2021-07-10 20:19:29 +02:00
if let Some(remote) = args.positional.get(0) {
// Parse address
2021-07-10 20:19:29 +02:00
match utils::parser::parse_remote_opt(remote.as_str()) {
Ok(mut remote) => {
// If password is provided, set password
if let Some(passwd) = args.password {
2021-08-26 11:24:13 +02:00
if let Some(mut params) = remote.params.mut_generic_params() {
params.password = Some(passwd);
}
}
// Set params
run_opts.remote = Some(remote);
2021-07-08 16:21:39 +02:00
// In this case the first activity will be FileTransfer
run_opts.task = Task::Activity(NextActivity::FileTransfer);
2020-11-22 20:36:53 +01:00
}
Err(err) => {
2021-07-10 20:19:29 +02:00
return Err(format!("Bad address option: {}", err));
}
}
}
// Local directory
2021-07-10 20:19:29 +02:00
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()) {
2021-07-10 20:19:29 +02:00
return Err(format!("Bad working directory argument: {}", err));
2021-05-16 15:09:17 +02:00
}
}
2021-07-10 20:19:29 +02:00
Ok(run_opts)
2021-07-08 16:21:39 +02:00
}
/// ### read_password
///
/// Read password from tty if address is specified
fn read_password(run_opts: &mut RunOpts) -> Result<(), String> {
// Initialize client if necessary
if let Some(remote) = run_opts.remote.as_mut() {
2021-08-26 11:24:13 +02:00
if let Some(mut params) = remote.params.mut_generic_params() {
if params.password.is_none() {
// Ask password if unspecified
params.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)
}
2020-12-01 11:35:26 +01:00
}
2021-08-26 11:24:13 +02:00
Err(_) => {
return Err("Could not read password from prompt".to_string());
}
};
}
2020-12-01 11:35:26 +01:00
}
2020-12-09 16:17:11 +01:00
}
2021-07-08 16:21:39 +02:00
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
}
},
2021-09-10 22:22:15 +02:00
Task::InstallUpdate => match support::install_update() {
Ok(msg) => {
println!("{}", msg);
0
}
Err(err) => {
eprintln!("Could not install update: {}", err);
1
}
},
2021-07-08 16:21:39 +02:00
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);
2021-07-08 16:21:39 +02:00
}
manager.run(activity);
0
2020-12-09 16:17:11 +01:00
}
}
2020-11-07 17:48:41 +01:00
}