From 5670c0f975d551869062eaabc9bf69ba807a6de3 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 11 Jun 2021 15:28:14 +0200 Subject: [PATCH 01/22] 0.5.1 setup --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- dist/pkgs/arch/.SRCINFO | 4 ++-- dist/pkgs/arch/PKGBUILD | 2 +- install.sh | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b351f1f..cd0480a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1313,7 +1313,7 @@ dependencies = [ [[package]] name = "termscp" -version = "0.5.0" +version = "0.5.1" dependencies = [ "bitflags", "bytesize", diff --git a/Cargo.toml b/Cargo.toml index 3c6ee2f..a7d3c86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "termscp" readme = "README.md" repository = "https://github.com/veeso/termscp" -version = "0.5.0" +version = "0.5.1" [package.metadata.rpm] package = "termscp" diff --git a/README.md b/README.md index 8c9725d..072a1bc 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@

Developed by @veeso

-

Current version: 0.5.0 (23/05/2021)

+

Current version: 0.5.1 (23/05/2021)

-[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.5.0-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) +[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.5.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) [![Build](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![Build](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Build](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp) diff --git a/dist/pkgs/arch/.SRCINFO b/dist/pkgs/arch/.SRCINFO index 6f17789..26ec3c4 100644 --- a/dist/pkgs/arch/.SRCINFO +++ b/dist/pkgs/arch/.SRCINFO @@ -1,13 +1,13 @@ pkgbase = termscp pkgdesc = termscp is a SCP/SFTP/FTPS client for command line with an integrated UI to explore the remote file system. Basically WinSCP on a terminal. - pkgver = 0.5.0 + pkgver = 0.5.1 pkgrel = 1 url = https://github.com/veeso/termscp arch = x86_64 license = MIT provides = termscp options = strip - source = https://github.com/veeso/termscp/releases/download/v0.5.0/termscp-0.5.0-x86_64.tar.gz + source = https://github.com/veeso/termscp/releases/download/v0.5.1/termscp-0.5.1-x86_64.tar.gz sha256sums = 279b4cab7da68c6db0efc054ddf72e36de85910110721b66d5cdc55833c99ccf pkgname = termscp diff --git a/dist/pkgs/arch/PKGBUILD b/dist/pkgs/arch/PKGBUILD index a928561..9da854d 100644 --- a/dist/pkgs/arch/PKGBUILD +++ b/dist/pkgs/arch/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Christian Visintin pkgname=termscp -pkgver=0.5.0 +pkgver=0.5.1 pkgrel=1 pkgdesc="termscp is a SCP/SFTP/FTPS client for command line with an integrated UI to explore the remote file system. Basically WinSCP on a terminal." url="https://github.com/veeso/termscp" diff --git a/install.sh b/install.sh index 62e434a..f8351b0 100755 --- a/install.sh +++ b/install.sh @@ -8,7 +8,7 @@ # -f, -y, --force, --yes # Skip the confirmation prompt during installation -TERMSCP_VERSION="0.5.0" +TERMSCP_VERSION="0.5.1" GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}" DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb" RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm" From 8cd91f6df38dffb2d7f63bd29fcba8e98963e8ab Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 6 Jun 2021 14:19:20 +0200 Subject: [PATCH 02/22] tui-realm 0.3.2 --- CHANGELOG.md | 10 ++++++++++ Cargo.lock | 4 ++-- Cargo.toml | 17 ++++------------- src/ui/activities/auth/mod.rs | 2 +- src/ui/activities/auth/update.rs | 8 +++++--- src/ui/activities/filetransfer/misc.rs | 1 + src/ui/activities/filetransfer/update.rs | 8 +++++--- src/ui/activities/setup/mod.rs | 2 +- src/ui/activities/setup/update.rs | 6 +++--- 9 files changed, 32 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a80f0fb..ccf2d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [0.5.1](#051) - [0.5.0](#050) - [0.4.2](#042) - [0.4.1](#041) @@ -17,6 +18,15 @@ --- +## 0.5.1 + +Released on FIXME: ?? + +- Bugfix: + - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) +- Dependencies: + - Updated `tui-realm` to `0.3.2` + ## 0.5.0 Released on 23/05/2021 diff --git a/Cargo.lock b/Cargo.lock index cd0480a..77df50e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1439,9 +1439,9 @@ dependencies = [ [[package]] name = "tuirealm" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad880656efa943543c8048a28e1fa1d0ea6b5c4bf7f53636492ef8a49ec681f" +checksum = "a7c0026d8e0c649dbd9416466777a584d06ac569283683c396f08f8a69832559" dependencies = [ "crossterm", "textwrap", diff --git a/Cargo.toml b/Cargo.toml index a7d3c86..ed3f85c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ content_inspector = "0.2.4" crossterm = "0.19.0" dirs = "3.0.1" edit = "0.1.3" +ftp4 = { version = "4.0.2", features = [ "secure" ] } getopts = "0.2.21" hostname = "0.3.1" lazy_static = "1.4.0" @@ -42,31 +43,21 @@ magic-crypt = "3.1.7" rand = "0.8.3" regex = "1.5.4" rpassword = "5.0.1" +serde = { version = "^1.0.0", features = [ "derive" ] } simplelog = "0.10.0" ssh2 = "0.9.0" tempfile = "3.1.0" textwrap = "0.13.4" thiserror = "^1.0.0" toml = "0.5.8" -tuirealm = { version = "0.3.0", features = [ "with-components" ] } +tuirealm = { version = "0.3.2", features = [ "with-components" ] } +ureq = { version = "2.1.0", features = [ "json" ] } whoami = "1.1.1" wildmatch = "2.0.0" [dev-dependencies] pretty_assertions = "0.7.2" -[dependencies.ftp4] -features = ["secure"] -version = "^4.0.2" - -[dependencies.serde] -features = ["derive"] -version = "^1.0.0" - -[dependencies.ureq] -features = ["json"] -version = "2.1.0" - [features] githubActions = [] diff --git a/src/ui/activities/auth/mod.rs b/src/ui/activities/auth/mod.rs index 0cd7653..cf400af 100644 --- a/src/ui/activities/auth/mod.rs +++ b/src/ui/activities/auth/mod.rs @@ -44,7 +44,7 @@ use crate::utils::git; // Includes use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; -use tuirealm::View; +use tuirealm::{Update, View}; // -- components const COMPONENT_TEXT_H1: &str = "TEXT_H1"; diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index ae9a153..dd8c1f2 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -36,16 +36,16 @@ use super::{ }; use crate::ui::keymap::*; use tuirealm::components::InputPropsBuilder; -use tuirealm::{Msg, Payload, PropsBuilder, Value}; +use tuirealm::{Msg, Payload, PropsBuilder, Update, Value}; // -- update -impl AuthActivity { +impl Update for AuthActivity { /// ### update /// /// Update auth activity model based on msg /// The function exits when returns None - pub(super) fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> { + fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> { let ref_msg: Option<(&str, &Msg)> = msg.as_ref().map(|(s, msg)| (s.as_str(), msg)); // Match msg match ref_msg { @@ -339,7 +339,9 @@ impl AuthActivity { }, } } +} +impl AuthActivity { fn update_input_port(&mut self, port: u16) -> Option<(String, Msg)> { match self.view.get_props(COMPONENT_INPUT_PORT) { None => None, diff --git a/src/ui/activities/filetransfer/misc.rs b/src/ui/activities/filetransfer/misc.rs index b13e47e..ca14fad 100644 --- a/src/ui/activities/filetransfer/misc.rs +++ b/src/ui/activities/filetransfer/misc.rs @@ -28,6 +28,7 @@ use crate::system::sshkey_storage::SshKeyStorage; // Ext use std::env; use std::path::{Path, PathBuf}; +use tuirealm::Update; const LOG_CAPACITY: usize = 256; diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index ecb9292..7f63f2d 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -48,17 +48,17 @@ use tuirealm::{ components::progress_bar::ProgressBarPropsBuilder, props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}, tui::style::Color, - Msg, Payload, Value, + Msg, Payload, Update, Value, }; -impl FileTransferActivity { +impl Update for FileTransferActivity { // -- update /// ### update /// /// Update auth activity model based on msg /// The function exits when returns None - pub(super) fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> { + fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> { let ref_msg: Option<(&str, &Msg)> = msg.as_ref().map(|(s, msg)| (s.as_str(), msg)); // Match msg match ref_msg { @@ -652,7 +652,9 @@ impl FileTransferActivity { }, } } +} +impl FileTransferActivity { /// ### update_local_filelist /// /// Update local file list diff --git a/src/ui/activities/setup/mod.rs b/src/ui/activities/setup/mod.rs index 15966fa..1040914 100644 --- a/src/ui/activities/setup/mod.rs +++ b/src/ui/activities/setup/mod.rs @@ -40,7 +40,7 @@ extern crate tuirealm; use super::{Activity, Context, ExitReason}; // Ext use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; -use tuirealm::View; +use tuirealm::{Update, View}; // -- components const COMPONENT_TEXT_HELP: &str = "TEXT_HELP"; diff --git a/src/ui/activities/setup/update.rs b/src/ui/activities/setup/update.rs index 053d7b5..ce4b4e2 100644 --- a/src/ui/activities/setup/update.rs +++ b/src/ui/activities/setup/update.rs @@ -37,14 +37,14 @@ use super::{ use crate::ui::keymap::*; // ext -use tuirealm::{Msg, Payload, Value}; +use tuirealm::{Msg, Payload, Update, Value}; -impl SetupActivity { +impl Update for SetupActivity { /// ### update /// /// Update auth activity model based on msg /// The function exits when returns None - pub(super) fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> { + fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> { let ref_msg: Option<(&str, &Msg)> = msg.as_ref().map(|(s, msg)| (s.as_str(), msg)); // Match msg match ref_msg { From f0782324991629635a4000de35945f20a73b15f2 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 7 Jun 2021 22:09:33 +0200 Subject: [PATCH 03/22] tui-realm 0.4.1 --- CHANGELOG.md | 3 +- Cargo.lock | 86 +++++++++++++++++++++++++++++----------------------- Cargo.toml | 4 +-- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccf2d42..639219c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,8 @@ Released on FIXME: ?? - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Dependencies: - - Updated `tui-realm` to `0.3.2` + - Updated `textwrap` to `0.14.0` + - Updated `tui-realm` to `0.4.1` ## 0.5.0 diff --git a/Cargo.lock b/Cargo.lock index 77df50e..e7f7d97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,9 +95,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" [[package]] name = "byteorder" @@ -119,9 +119,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" [[package]] name = "cfg-if" @@ -215,18 +215,18 @@ checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" [[package]] name = "cpufeatures" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "281f563b2c3a0e535ab12d81d3c5859045795256ad269afa7c19542585b68f93" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" dependencies = [ "libc", ] [[package]] name = "crc-any" -version = "2.3.9" +version = "2.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d98be01088633be44a2a82b55a96dca49b226d65297428a3c44d33de07528ff" +checksum = "b9950e91c5c444b0729f5f1b0aec76c523e01920ce828e37bccfa27803ff34e1" dependencies = [ "debug-helper", ] @@ -422,9 +422,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", @@ -517,9 +517,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.94" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] name = "libssh2-sys" @@ -655,8 +655,8 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.2.0", - "security-framework-sys 2.2.0", + "security-framework 2.3.1", + "security-framework-sys 2.3.0", "tempfile", ] @@ -886,9 +886,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -962,7 +962,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom 0.2.2", + "getrandom 0.2.3", ] [[package]] @@ -1004,7 +1004,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.2", + "getrandom 0.2.3", "redox_syscall 0.2.8", ] @@ -1135,15 +1135,15 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" dependencies = [ "bitflags", "core-foundation 0.9.1", "core-foundation-sys 0.8.2", "libc", - "security-framework-sys 2.2.0", + "security-framework-sys 2.3.0", ] [[package]] @@ -1158,9 +1158,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" +checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" dependencies = [ "core-foundation-sys 0.8.2", "libc", @@ -1223,9 +1223,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -1350,28 +1350,29 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" +checksum = "f59f5365546b8424b0cc48868ae4fbbbc29a538dcc496b53543525201034f0c2" dependencies = [ "smawk", + "unicode-linebreak", "unicode-width", ] [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" dependencies = [ "proc-macro2", "quote", @@ -1439,9 +1440,9 @@ dependencies = [ [[package]] name = "tuirealm" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c0026d8e0c649dbd9416466777a584d06ac569283683c396f08f8a69832559" +checksum = "6bee2a1c050878fac02ba3a6c2e93aa92a1de56849d5deec00d4ab4bc7928c0a" dependencies = [ "crossterm", "textwrap", @@ -1465,10 +1466,19 @@ dependencies = [ ] [[package]] -name = "unicode-normalization" -version = "0.1.17" +name = "unicode-linebreak" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "05a31f45d18a3213b918019f78fe6a73a14ab896807f0aaf5622aa0684749455" +dependencies = [ + "regex", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] @@ -1539,9 +1549,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" +checksum = "025ce40a007e1907e58d5bc1a594def78e5573bb0b1160bc389634e8f12e4faa" [[package]] name = "version_check" diff --git a/Cargo.toml b/Cargo.toml index ed3f85c..13470b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ serde = { version = "^1.0.0", features = [ "derive" ] } simplelog = "0.10.0" ssh2 = "0.9.0" tempfile = "3.1.0" -textwrap = "0.13.4" +textwrap = "0.14.0" thiserror = "^1.0.0" toml = "0.5.8" -tuirealm = { version = "0.3.2", features = [ "with-components" ] } +tuirealm = { version = "0.4.1", features = [ "with-components" ] } ureq = { version = "2.1.0", features = [ "json" ] } whoami = "1.1.1" wildmatch = "2.0.0" From 26f7c1f9d11d4f9099a50decd594ad544db8b1d9 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 12 Jun 2021 09:05:02 +0200 Subject: [PATCH 04/22] Help panels as `ScrollTable` to allow displaying entire content on small screens --- CHANGELOG.md | 3 ++- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/ui/activities/auth/view.rs | 9 ++++++--- src/ui/activities/filetransfer/view.rs | 8 ++++++-- src/ui/activities/setup/view.rs | 9 ++++++--- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 639219c..6a97b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,9 +24,10 @@ Released on FIXME: ?? - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) + - Help panels as `ScrollTable` to allow displaying entire content on small screens - Dependencies: - Updated `textwrap` to `0.14.0` - - Updated `tui-realm` to `0.4.1` + - Updated `tui-realm` to `0.4.2` ## 0.5.0 diff --git a/Cargo.lock b/Cargo.lock index e7f7d97..ff7e139 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1440,9 +1440,9 @@ dependencies = [ [[package]] name = "tuirealm" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bee2a1c050878fac02ba3a6c2e93aa92a1de56849d5deec00d4ab4bc7928c0a" +checksum = "9897335542e4a4a87ad391419c35e54b4088661e671ba53e578fbbb1154740c2" dependencies = [ "crossterm", "textwrap", diff --git a/Cargo.toml b/Cargo.toml index 13470b0..931d8dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ tempfile = "3.1.0" textwrap = "0.14.0" thiserror = "^1.0.0" toml = "0.5.8" -tuirealm = { version = "0.4.1", features = [ "with-components" ] } +tuirealm = { version = "0.4.2", features = [ "with-components" ] } ureq = { version = "2.1.0", features = [ "json" ] } whoami = "1.1.1" wildmatch = "2.0.0" diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index 363801f..af13594 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -37,8 +37,8 @@ use tuirealm::components::{ input::{Input, InputPropsBuilder}, label::{Label, LabelPropsBuilder}, radio::{Radio, RadioPropsBuilder}, + scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, - table::{Table, TablePropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -622,9 +622,12 @@ impl AuthActivity { pub(super) fn mount_help(&mut self) { self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Table::new( - TablePropsBuilder::default() + Box::new(Scrolltable::new( + ScrollTablePropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) + .with_highlighted_str(Some("?")) + .with_max_scroll_step(8) + .bold() .with_table( Some(String::from("Help")), TableBuilder::default() diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index bab6290..ce2f416 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -49,6 +49,7 @@ use tuirealm::components::{ input::{Input, InputPropsBuilder}, progress_bar::{ProgressBar, ProgressBarPropsBuilder}, radio::{Radio, RadioPropsBuilder}, + scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, table::{Table, TablePropsBuilder}, }; @@ -880,9 +881,12 @@ impl FileTransferActivity { pub(super) fn mount_help(&mut self) { self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Table::new( - TablePropsBuilder::default() + Box::new(Scrolltable::new( + ScrollTablePropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) + .with_highlighted_str(Some("?")) + .with_max_scroll_step(8) + .bold() .with_table( Some(String::from("Help")), TableBuilder::default() diff --git a/src/ui/activities/setup/view.rs b/src/ui/activities/setup/view.rs index 7c37187..36342e3 100644 --- a/src/ui/activities/setup/view.rs +++ b/src/ui/activities/setup/view.rs @@ -40,8 +40,8 @@ use std::path::PathBuf; use tuirealm::components::{ input::{Input, InputPropsBuilder}, radio::{Radio, RadioPropsBuilder}, + scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, - table::{Table, TablePropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -557,9 +557,12 @@ impl SetupActivity { pub(super) fn mount_help(&mut self) { self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Table::new( - TablePropsBuilder::default() + Box::new(Scrolltable::new( + ScrollTablePropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) + .with_highlighted_str(Some("?")) + .with_max_scroll_step(8) + .bold() .with_table( Some(String::from("Help")), TableBuilder::default() From 74482d6e2c798b7e3705de1e11d4af1a27c6cbb3 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 12 Jun 2021 14:36:01 +0200 Subject: [PATCH 05/22] Fixed progress bar not visible when editing remote files; moved edit and tricky_copy to actions; added new function recv/send one --- CHANGELOG.md | 3 +- .../activities/filetransfer/actions/copy.rs | 107 ++++++ .../activities/filetransfer/actions/edit.rs | 152 +++++++++ src/ui/activities/filetransfer/session.rs | 311 ++++-------------- 4 files changed, 319 insertions(+), 254 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a97b8d..c089cd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,8 @@ Released on FIXME: ?? - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - - Help panels as `ScrollTable` to allow displaying entire content on small screens + - Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens + - Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files - Dependencies: - Updated `textwrap` to `0.14.0` - Updated `tui-realm` to `0.4.2` diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index 5809b8e..6b1bd6e 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -139,4 +139,111 @@ impl FileTransferActivity { }, } } + + /// ### tricky_copy + /// + /// Tricky copy will be used whenever copy command is not available on remote host + fn tricky_copy(&mut self, entry: &FsEntry, dest: &Path) { + // match entry + match entry { + FsEntry::File(entry) => { + // Create tempfile + let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { + Ok(f) => f, + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: could not create temporary file: {}", err), + ); + return; + } + }; + // Download file + if let Err(err) = + self.filetransfer_recv_one(entry, tmpfile.path(), entry.name.clone()) + { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: could not download to temporary file: {}", err), + ); + return; + } + // Get local fs entry + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { + Ok(e) => e, + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Copy failed: could not stat \"{}\": {}", + tmpfile.path().display(), + err + ), + ); + return; + } + }; + let tmpfile_entry = match &tmpfile_entry { + FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), + FsEntry::File(f) => f, + }; + // Upload file to destination + let wrkdir = self.remote().wrkdir.clone(); + if let Err(err) = self.filetransfer_send_one( + tmpfile_entry, + wrkdir.as_path(), + Some(String::from(dest.to_string_lossy())), + ) { + self.log_and_alert( + LogLevel::Error, + format!( + "Copy failed: could not write file {}: {}", + entry.abs_path.display(), + err + ), + ); + return; + } + } + FsEntry::Directory(_) => { + let tempdir: tempfile::TempDir = match tempfile::TempDir::new() { + Ok(d) => d, + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: could not create temporary directory: {}", err), + ); + return; + } + }; + // Download file + self.filetransfer_recv(entry, tempdir.path(), None); + // Get path of dest + let mut tempdir_path: PathBuf = tempdir.path().to_path_buf(); + tempdir_path.push(entry.get_name()); + // Stat dir + let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) { + Ok(e) => e, + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Copy failed: could not stat \"{}\": {}", + tempdir.path().display(), + err + ), + ); + return; + } + }; + // Upload to destination + let wrkdir: PathBuf = self.remote().wrkdir.clone(); + self.filetransfer_send( + &tempdir_entry, + wrkdir.as_path(), + Some(String::from(dest.to_string_lossy())), + ); + } + } + } } diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index ec88186..e7bb49d 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -27,6 +27,13 @@ */ // locals use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; +use crate::fs::FsFile; +// ext +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use std::fs::OpenOptions; +use std::io::Read; +use std::path::Path; +use std::time::SystemTime; impl FileTransferActivity { pub(crate) fn action_edit_local_file(&mut self) { @@ -76,4 +83,149 @@ impl FileTransferActivity { // Reload entries self.reload_remote_dir(); } + + /// ### edit_local_file + /// + /// Edit a file on localhost + fn edit_local_file(&mut self, path: &Path) -> Result<(), String> { + // Read first 2048 bytes or less from file to check if it is textual + match OpenOptions::new().read(true).open(path) { + Ok(mut f) => { + // Read + let mut buff: [u8; 2048] = [0; 2048]; + match f.read(&mut buff) { + Ok(size) => { + if content_inspector::inspect(&buff[0..size]).is_binary() { + return Err("Could not open file in editor: file is binary".to_string()); + } + } + Err(err) => { + return Err(format!("Could not read file: {}", err)); + } + } + } + Err(err) => { + return Err(format!("Could not read file: {}", err)); + } + } + // Put input mode back to normal + if let Err(err) = disable_raw_mode() { + error!("Failed to disable raw mode: {}", err); + } + // Leave alternate mode + if let Some(ctx) = self.context.as_mut() { + ctx.leave_alternate_screen(); + } + // Open editor + match edit::edit_file(path) { + Ok(_) => self.log( + LogLevel::Info, + format!( + "Changes performed through editor saved to \"{}\"!", + path.display() + ), + ), + Err(err) => return Err(format!("Could not open editor: {}", err)), + } + if let Some(ctx) = self.context.as_mut() { + // Clear screen + ctx.clear_screen(); + // Enter alternate mode + ctx.enter_alternate_screen(); + } + // Re-enable raw mode + let _ = enable_raw_mode(); + Ok(()) + } + + /// ### edit_remote_file + /// + /// Edit file on remote host + fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> { + // Create temp file + let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { + Ok(f) => f, + Err(err) => { + return Err(format!("Could not create temporary file: {}", err)); + } + }; + // Download file + if let Err(err) = self.filetransfer_recv_one(file, tmpfile.path(), file.name.clone()) { + return Err(format!("Could not open file {}: {}", file.name, err)); + } + // Get current file modification time + let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) { + Ok(e) => e.get_last_change_time(), + Err(err) => { + return Err(format!( + "Could not stat \"{}\": {}", + tmpfile.path().display(), + err + )) + } + }; + // Edit file + if let Err(err) = self.edit_local_file(tmpfile.path()) { + return Err(err); + } + // Get local fs entry + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { + Ok(e) => e, + Err(err) => { + return Err(format!( + "Could not stat \"{}\": {}", + tmpfile.path().display(), + err + )) + } + }; + // Check if file has changed + match prev_mtime != tmpfile_entry.get_last_change_time() { + true => { + self.log( + LogLevel::Info, + format!( + "File \"{}\" has changed; writing changes to remote", + file.abs_path.display() + ), + ); + // Get local fs entry + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { + Ok(e) => e, + Err(err) => { + return Err(format!( + "Could not stat \"{}\": {}", + tmpfile.path().display(), + err + )) + } + }; + // Write file + let tmpfile_entry: &FsFile = match &tmpfile_entry { + FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), + FsEntry::File(f) => f, + }; + // Send file + let wrkdir = self.remote().wrkdir.clone(); + if let Err(err) = self.filetransfer_send_one( + tmpfile_entry, + wrkdir.as_path(), + Some(file.name.clone()), + ) { + return Err(format!( + "Could not write file {}: {}", + file.abs_path.display(), + err + )); + } + } + false => { + self.log( + LogLevel::Info, + format!("File \"{}\" hasn't changed", file.abs_path.display()), + ); + } + } + Ok(()) + } } diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index d9aa06d..ccaf6fa 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -40,11 +40,9 @@ use crate::utils::fmt::fmt_millis; // Ext use bytesize::ByteSize; -use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; -use std::fs::OpenOptions; use std::io::{Read, Seek, Write}; use std::path::{Path, PathBuf}; -use std::time::{Instant, SystemTime}; +use std::time::Instant; use thiserror::Error; /// ## TransferErrorReason @@ -174,6 +172,38 @@ impl FileTransferActivity { self.umount_progress_bar(); } + /// ### filetransfer_send_one + /// + /// Send one file to remote at specified path. + pub(super) fn filetransfer_send_one( + &mut self, + file: &FsFile, + curr_remote_path: &Path, + dst_name: Option, + ) -> Result<(), String> { + // Reset states + self.transfer.reset(); + // Calculate total size of transfer + let total_transfer_size: usize = file.size; + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Uploading {}...", file.abs_path.display())); + // Get remote path + let file_name: String = file.name.clone(); + let mut remote_path: PathBuf = PathBuf::from(curr_remote_path); + let remote_file_name: PathBuf = match dst_name { + Some(s) => PathBuf::from(s.as_str()), + None => PathBuf::from(file_name.as_str()), + }; + remote_path.push(remote_file_name); + // Send + let result = self.filetransfer_send_file(file, remote_path.as_path(), file_name); + // Umount progress bar + self.umount_progress_bar(); + // Return result + result.map_err(|x| x.to_string()) + } + fn filetransfer_send_recurse( &mut self, entry: &FsEntry, @@ -426,6 +456,31 @@ impl FileTransferActivity { self.umount_progress_bar(); } + /// ### filetransfer_recv_one + /// + /// Receive a single file from remote. + /// Use this function instead of `filetransfer_recv_file` from external files + pub(super) fn filetransfer_recv_one( + &mut self, + entry: &FsFile, + local_path: &Path, + dst_name: String, + ) -> Result<(), String> { + // Reset states + self.transfer.reset(); + // Calculate total transfer size + let total_transfer_size: usize = entry.size; + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Downloading {}...", entry.abs_path.display())); + // Receive + let result = self.filetransfer_recv_file(local_path, entry, dst_name); + // Umount progress bar + self.umount_progress_bar(); + // Return result + result.map_err(|x| x.to_string()) + } + fn filetransfer_recv_recurse( &mut self, entry: &FsEntry, @@ -785,256 +840,6 @@ impl FileTransferActivity { } } - /// ### edit_local_file - /// - /// Edit a file on localhost - pub(super) fn edit_local_file(&mut self, path: &Path) -> Result<(), String> { - // Read first 2048 bytes or less from file to check if it is textual - match OpenOptions::new().read(true).open(path) { - Ok(mut f) => { - // Read - let mut buff: [u8; 2048] = [0; 2048]; - match f.read(&mut buff) { - Ok(size) => { - if content_inspector::inspect(&buff[0..size]).is_binary() { - return Err("Could not open file in editor: file is binary".to_string()); - } - } - Err(err) => { - return Err(format!("Could not read file: {}", err)); - } - } - } - Err(err) => { - return Err(format!("Could not read file: {}", err)); - } - } - // Put input mode back to normal - if let Err(err) = disable_raw_mode() { - error!("Failed to disable raw mode: {}", err); - } - // Leave alternate mode - if let Some(ctx) = self.context.as_mut() { - ctx.leave_alternate_screen(); - } - // Open editor - match edit::edit_file(path) { - Ok(_) => self.log( - LogLevel::Info, - format!( - "Changes performed through editor saved to \"{}\"!", - path.display() - ), - ), - Err(err) => return Err(format!("Could not open editor: {}", err)), - } - if let Some(ctx) = self.context.as_mut() { - // Clear screen - ctx.clear_screen(); - // Enter alternate mode - ctx.enter_alternate_screen(); - } - // Re-enable raw mode - let _ = enable_raw_mode(); - Ok(()) - } - - /// ### edit_remote_file - /// - /// Edit file on remote host - pub(super) fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> { - // Create temp file - let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { - Ok(f) => f, - Err(err) => { - return Err(format!("Could not create temporary file: {}", err)); - } - }; - // Download file - if let Err(err) = self.filetransfer_recv_file(tmpfile.path(), file, file.name.clone()) { - return Err(format!("Could not open file {}: {}", file.name, err)); - } - // Get current file modification time - let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) { - Ok(e) => e.get_last_change_time(), - Err(err) => { - return Err(format!( - "Could not stat \"{}\": {}", - tmpfile.path().display(), - err - )) - } - }; - // Edit file - if let Err(err) = self.edit_local_file(tmpfile.path()) { - return Err(err); - } - // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { - Ok(e) => e, - Err(err) => { - return Err(format!( - "Could not stat \"{}\": {}", - tmpfile.path().display(), - err - )) - } - }; - // Check if file has changed - match prev_mtime != tmpfile_entry.get_last_change_time() { - true => { - self.log( - LogLevel::Info, - format!( - "File \"{}\" has changed; writing changes to remote", - file.abs_path.display() - ), - ); - // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { - Ok(e) => e, - Err(err) => { - return Err(format!( - "Could not stat \"{}\": {}", - tmpfile.path().display(), - err - )) - } - }; - // Write file - let tmpfile_entry: &FsFile = match &tmpfile_entry { - FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), - FsEntry::File(f) => f, - }; - // Send file - if let Err(err) = self.filetransfer_send_file( - tmpfile_entry, - file.abs_path.as_path(), - file.name.clone(), - ) { - return Err(format!( - "Could not write file {}: {}", - file.abs_path.display(), - err - )); - } - } - false => { - self.log( - LogLevel::Info, - format!("File \"{}\" hasn't changed", file.abs_path.display()), - ); - } - } - Ok(()) - } - - /// ### tricky_copy - /// - /// Tricky copy will be used whenever copy command is not available on remote host - pub(super) fn tricky_copy(&mut self, entry: &FsEntry, dest: &Path) { - // match entry - match entry { - FsEntry::File(entry) => { - // Create tempfile - let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { - Ok(f) => f, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Copy failed: could not create temporary file: {}", err), - ); - return; - } - }; - // Download file - if let Err(err) = - self.filetransfer_recv_file(tmpfile.path(), entry, entry.name.clone()) - { - self.log_and_alert( - LogLevel::Error, - format!("Copy failed: could not download to temporary file: {}", err), - ); - return; - } - // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { - Ok(e) => e, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Copy failed: could not stat \"{}\": {}", - tmpfile.path().display(), - err - ), - ); - return; - } - }; - let tmpfile_entry = match &tmpfile_entry { - FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), - FsEntry::File(f) => f, - }; - // Upload file to destination - if let Err(err) = self.filetransfer_send_file( - tmpfile_entry, - dest, - String::from(dest.to_string_lossy()), - ) { - self.log_and_alert( - LogLevel::Error, - format!( - "Copy failed: could not write file {}: {}", - entry.abs_path.display(), - err - ), - ); - return; - } - } - FsEntry::Directory(_) => { - let tempdir: tempfile::TempDir = match tempfile::TempDir::new() { - Ok(d) => d, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Copy failed: could not create temporary directory: {}", err), - ); - return; - } - }; - // Download file - self.filetransfer_recv(entry, tempdir.path(), None); - // Get path of dest - let mut tempdir_path: PathBuf = tempdir.path().to_path_buf(); - tempdir_path.push(entry.get_name()); - // Stat dir - let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) { - Ok(e) => e, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Copy failed: could not stat \"{}\": {}", - tempdir.path().display(), - err - ), - ); - return; - } - }; - // Upload to destination - let wrkdir: PathBuf = self.remote().wrkdir.clone(); - self.filetransfer_send( - &tempdir_entry, - wrkdir.as_path(), - Some(String::from(dest.to_string_lossy())), - ); - } - } - } - // -- transfer sizes /// ### get_total_transfer_size_local From 542123ce044d894e2dba1e67ed7705b7eb0ebbe5 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 13 Jun 2021 09:47:17 +0200 Subject: [PATCH 06/22] fixed recursive remove FTP --- CHANGELOG.md | 1 + src/filetransfer/ftp_transfer.rs | 50 +++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c089cd0..0e4d6f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Released on FIXME: ?? - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) + - Fixed [Issue 43](https://github.com/veeso/termscp/issues/43): Could not remove non-empty directories in FTP - Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens - Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files - Dependencies: diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 4b69880..3249fbd 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -629,23 +629,36 @@ impl FileTransfer for FtpFileTransfer { )); } info!("Removing entry {}", fsentry.get_abs_path().display()); + let wrkdir: PathBuf = self.pwd()?; match fsentry { // Match fs entry... FsEntry::File(file) => { - debug!("entry is a file; removing file"); + // Go to parent directory + if let Some(parent_dir) = file.abs_path.parent() { + debug!("Changing wrkdir to {}", parent_dir.display()); + self.change_dir(parent_dir)?; + } + debug!("entry is a file; removing file {}", file.abs_path.display()); // Remove file directly - match self.stream.as_mut().unwrap().rm(file.name.as_ref()) { - Ok(_) => Ok(()), - Err(err) => Err(FileTransferError::new_ex( - FileTransferErrorType::PexError, - err.to_string(), - )), + let result = self + .stream + .as_mut() + .unwrap() + .rm(file.name.as_ref()) + .map(|_| ()) + .map_err(|e| { + FileTransferError::new_ex(FileTransferErrorType::PexError, e.to_string()) + }); + // Go to source directory + match self.change_dir(wrkdir.as_path()) { + Err(err) => Err(err), + Ok(_) => result, } } FsEntry::Directory(dir) => { // Get directory files debug!("Entry is a directory; iterating directory entries"); - match self.list_dir(dir.abs_path.as_path()) { + let result = match self.list_dir(dir.abs_path.as_path()) { Ok(files) => { // Remove recursively files debug!("Removing {} entries from directory...", files.len()); @@ -658,9 +671,21 @@ impl FileTransfer for FtpFileTransfer { } } // Once all files in directory have been deleted, remove directory - debug!("Finally removing directory {}", dir.name); + debug!("Finally removing directory {}...", dir.name); + // Enter parent directory + if let Some(parent_dir) = dir.abs_path.parent() { + debug!( + "Changing wrkdir to {} to delete directory {}", + parent_dir.display(), + dir.name + ); + self.change_dir(parent_dir)?; + } match self.stream.as_mut().unwrap().rmdir(dir.name.as_str()) { - Ok(_) => Ok(()), + Ok(_) => { + debug!("Removed {}", dir.abs_path.display()); + Ok(()) + } Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::PexError, err.to_string(), @@ -671,6 +696,11 @@ impl FileTransfer for FtpFileTransfer { FileTransferErrorType::DirStatFailed, err.to_string(), )), + }; + // Restore directory + match self.change_dir(wrkdir.as_path()) { + Err(err) => Err(err), + Ok(_) => result, } } } From 2b006457c7e4eb3c56f414c16ac629b692533a7a Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 13 Jun 2021 10:10:04 +0200 Subject: [PATCH 07/22] fixed rename --- src/filetransfer/ftp_transfer.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 3249fbd..6738a1e 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -723,17 +723,8 @@ impl FileTransfer for FtpFileTransfer { FsEntry::Directory(dir) => dir.name.clone(), FsEntry::File(file) => file.name.clone(), }; - let dst_name: PathBuf = match dst.file_name() { - Some(p) => PathBuf::from(p), - None => { - return Err(FileTransferError::new_ex( - FileTransferErrorType::FileCreateDenied, - String::from("Invalid destination name"), - )) - } - }; // Only names are supported - match stream.rename(src_name.as_str(), &dst_name.as_path().to_string_lossy()) { + match stream.rename(src_name.as_str(), &dst.as_path().to_string_lossy()) { Ok(_) => Ok(()), Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::FileCreateDenied, From 6cdd56e22f5fe55310ce70ea5c0043b2af41e1dd Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 13 Jun 2021 10:20:16 +0200 Subject: [PATCH 08/22] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4d6f8..4161e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Released on FIXME: ?? - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) + - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP - Fixed [Issue 43](https://github.com/veeso/termscp/issues/43): Could not remove non-empty directories in FTP - Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens - Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files From 1c58f1d62355ae2280c324ef67bac6ce338c5b95 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 12 Jun 2021 16:17:47 +0200 Subject: [PATCH 09/22] Use containers to test file transfers Use containers to test file transfers Container setup Container setup tests with docker-compose these tests won't work with containers ftp tests with containers; removed crap servers; tests only lib hostname for github booooooh fixed recursive remove FTP Use containers to test file transfers Container setup Container setup tests with docker-compose these tests won't work with containers ftp tests with containers; removed crap servers; tests only lib hostname for github booooooh fixed recursive remove FTP fixed rename changelog Desperate attempt Fixed ftp tests; migrated sftp tests to containers; use env for services github actions are just broken imo github actions are just broken imo Don't use services, since they just don't fuckin work... docker compose not supported yet? deprecated typo Now explain this: github actions have services (which don't work) and then you find out docker-compose is already installed *BLOWMIND* Fixed hostname for tests scp tests maybe tests wtf Restored host tests Changelog Improving coverage Improving coverage Restored coverage task More tests for file transfers; test ssh keys too typo tests; code improvements Use tests helpers fixed tempdir fixed tempdir --- .github/workflows/coverage.yml | 17 +- .github/workflows/linux.yml | 7 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 2 +- CHANGELOG.md | 6 + Cargo.toml | 3 +- docs/developer.md | 19 +- src/config/mod.rs | 6 + src/config/serializer.rs | 19 + src/filetransfer/ftp_transfer.rs | 696 +++++++++++++++--------------- src/filetransfer/mod.rs | 5 +- src/filetransfer/scp_transfer.rs | 536 ++++++++++++----------- src/filetransfer/sftp_transfer.rs | 552 ++++++++++-------------- src/fs/mod.rs | 64 +++ src/host/mod.rs | 94 ++-- src/system/bookmarks_client.rs | 29 +- src/system/config_client.rs | 30 +- src/system/sshkey_storage.rs | 29 +- src/ui/context.rs | 2 +- src/utils/git.rs | 2 +- src/utils/mod.rs | 4 + src/utils/parser.rs | 7 +- src/utils/test_helpers.rs | 248 +++++++++++ tests/docker-compose.yml | 35 ++ tests/test.sh | 29 ++ 25 files changed, 1393 insertions(+), 1050 deletions(-) create mode 100644 src/utils/test_helpers.rs create mode 100644 tests/docker-compose.yml create mode 100755 tests/test.sh diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f491d62..49fff47 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,4 +1,4 @@ -name: coverage +name: Coverage on: [push, pull_request] @@ -6,22 +6,23 @@ env: CARGO_TERM_COLOR: always jobs: - coverage: - name: Generate coverage + build: runs-on: ubuntu-latest + steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup rust toolchain + - uses: actions/checkout@v2 + - name: Setup containers + run: docker-compose -f "tests/docker-compose.yml" up -d --build + - name: Setup nightly toolchain uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true - - name: Run tests + - name: Run tests (nightly) uses: actions-rs/cargo@v1 with: command: test - args: --all-features --no-fail-fast + args: --all-features --lib --no-fail-fast env: CARGO_INCREMENTAL: "0" RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 374007b..9ac9f46 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -11,15 +11,18 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Setup containers + run: docker-compose -f "tests/docker-compose.yml" up -d --build - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true components: rustfmt, clippy - - uses: actions-rs/cargo@v1 + - name: Run tests + uses: actions-rs/cargo@v1 with: command: test - args: --all-features --no-fail-fast + args: --all-features --lib --no-fail-fast - name: Format run: cargo fmt --all -- --check - name: Clippy diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dbe3b63..f77e5ad 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -14,6 +14,6 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose --features githubActions -- --test-threads 1 + run: cargo test --verbose --lib --features github-actions -- --test-threads 1 - name: Clippy run: cargo clippy diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 7e685ec..80a9fc8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -14,6 +14,6 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose --features githubActions -- --test-threads 1 + run: cargo test --verbose --lib --features github-actions -- --test-threads 1 - name: Clippy run: cargo clippy diff --git a/CHANGELOG.md b/CHANGELOG.md index 4161e31..3cab7cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ Released on FIXME: ?? +- Enhancements: + - **CI now uses containers to test file transfers (SSH/FTP)** + - Improved coverage + - Found many bugs which has now been fixed + - Build in CI won't fail due to test servers not responding + - We're now able to test all the functionalities of the file transfers - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP diff --git a/Cargo.toml b/Cargo.toml index 931d8dc..c504784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,8 @@ wildmatch = "2.0.0" pretty_assertions = "0.7.2" [features] -githubActions = [] +github-actions = [] +with-containers = [] [target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))"] [target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))".dependencies] diff --git a/docs/developer.md b/docs/developer.md index e342956..a382fca 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -3,15 +3,26 @@ Document audience: developers - [Developer Manual](#developer-manual) + - [How to test](#how-to-test) - [How termscp works](#how-termscp-works) - [Activities](#activities) - [The Context](#the-context) - - [Tests fails due to receivers](#tests-fails-due-to-receivers) - [Implementing File Transfers](#implementing-file-transfers) Welcome to the developer manual for termscp. This chapter DOESN'T contain the documentation for termscp modules, which can instead be found on Rust Docs at This chapter describes how termscp works and the guide lines to implement stuff such as file transfers and add features to the user interface. +## How to test + +First an introduction to tests. + +Usually it's enough to run `cargo test`, but please note that whenever you're working on file transfer you'll need one more step. +In order to run tests with file transfers, you need to start the file transfer server containers, which can be started via `docker`. + +To run all tests with file transfers just run: `./tests/test.sh` + +--- + ## How termscp works termscp is basically made up of 4 components: @@ -62,12 +73,6 @@ The context basically holds the following data: --- -## Tests fails due to receivers - -Yes. This happens quite often and is related to the fact that I'm using public SSH/SFTP/FTP server to test file receivers and sometimes this server go down for even a day or more. If your tests don't pass due to this, don't worry, submit the pull request and I'll take care of testing them by myself. - ---- - ## Implementing File Transfers This chapter describes how to implement a file transfer in termscp. A file transfer is a module which implements the `FileTransfer` trait. The file transfer provides different modules to interact with a remote server, which in addition to the most obvious methods, used to download and upload files, provides also methods to list files, delete files, create directories etc. diff --git a/src/config/mod.rs b/src/config/mod.rs index 4153bc2..14c866e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -183,6 +183,12 @@ mod tests { file_fmt: Some(String::from("{NAME}")), remote_file_fmt: Some(String::from("{USER}")), }; + assert_eq!(ui.default_protocol, String::from("SFTP")); + assert_eq!(ui.text_editor, PathBuf::from("nano")); + assert_eq!(ui.show_hidden_files, true); + assert_eq!(ui.check_for_updates, Some(true)); + assert_eq!(ui.group_dirs, Some(String::from("first"))); + assert_eq!(ui.file_fmt, Some(String::from("{NAME}"))); let cfg: UserConfig = UserConfig { user_interface: ui, remote: remote, diff --git a/src/config/serializer.rs b/src/config/serializer.rs index 2375243..5ec89f4 100644 --- a/src/config/serializer.rs +++ b/src/config/serializer.rs @@ -208,6 +208,25 @@ mod tests { assert!(serializer.deserialize(Box::new(toml_file)).is_ok()); } + #[test] + fn test_config_serializer_fail_write() { + let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap(); + let writer: Box = Box::new(std::fs::File::open(toml_file.path()).unwrap()); + // Try to write unexisting file + let serializer: ConfigSerializer = ConfigSerializer {}; + let cfg: UserConfig = UserConfig::default(); + assert!(serializer.serialize(writer, &cfg).is_err()); + } + + #[test] + fn test_config_serializer_fail_read() { + let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap(); + let reader: Box = Box::new(std::fs::File::open(toml_file.path()).unwrap()); + // Try to write unexisting file + let serializer: ConfigSerializer = ConfigSerializer {}; + assert!(serializer.deserialize(reader).is_err()); + } + fn create_good_toml() -> tempfile::NamedTempFile { // Write let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 6738a1e..919a9a0 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -491,7 +491,10 @@ impl FileTransfer for FtpFileTransfer { info!("Disconnecting from FTP server..."); match &mut self.stream { Some(stream) => match stream.quit() { - Ok(_) => Ok(()), + Ok(_) => { + self.stream = None; + Ok(()) + } Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::ConnectionError, err.to_string(), @@ -859,9 +862,14 @@ impl FileTransfer for FtpFileTransfer { mod tests { use super::*; + use crate::utils::file::open_file; use crate::utils::fmt::fmt_time; + #[cfg(feature = "with-containers")] + use crate::utils::test_helpers::write_file; + use crate::utils::test_helpers::{create_sample_file_entry, make_fsentry}; use pretty_assertions::assert_eq; + use std::io::{Read, Write}; use std::time::Duration; #[test] @@ -875,120 +883,281 @@ mod tests { assert!(ftp.stream.is_none()); } + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_ftp_server() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Sample file + let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + // Connect + #[cfg(not(feature = "github-actions"))] + let hostname: String = String::from("127.0.0.1"); + #[cfg(feature = "github-actions")] + let hostname: String = String::from("127.0.0.1"); + assert!(ftp + .connect( + hostname, + 10021, + Some(String::from("test")), + Some(String::from("test")), + ) + .is_ok()); + assert_eq!(ftp.is_connected(), true); + // Get pwd + assert_eq!(ftp.pwd().unwrap(), PathBuf::from("/")); + // List dir (dir is empty) + assert_eq!(ftp.list_dir(&Path::new("/")).unwrap().len(), 0); + // Make directory + assert!(ftp.mkdir(PathBuf::from("/home").as_path()).is_ok()); + // Make directory (err) + assert!(ftp.mkdir(PathBuf::from("/root/pommlar").as_path()).is_err()); + // Change directory + assert!(ftp.change_dir(PathBuf::from("/home").as_path()).is_ok()); + // Change directory (err) + assert!(ftp + .change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path()) + .is_err()); + // Copy (not supported) + assert!(ftp + .copy(&FsEntry::File(entry.clone()), PathBuf::from("/").as_path()) + .is_err()); + // Exec (not supported) + assert!(ftp.exec("echo 1;").is_err()); + // Upload 2 files + let mut writable = ftp + .send_file(&entry, PathBuf::from("omar.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(ftp.on_sent(writable).is_ok()); + let mut writable = ftp + .send_file(&entry, PathBuf::from("README.md").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(ftp.on_sent(writable).is_ok()); + // Upload file (err) + assert!(ftp + .send_file(&entry, PathBuf::from("/ommlar/omarone").as_path()) + .is_err()); + // List dir + let list: Vec = ftp.list_dir(PathBuf::from("/home").as_path()).ok().unwrap(); + assert_eq!(list.len(), 2); + // Find + assert!(ftp.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert_eq!(ftp.find("*.txt").ok().unwrap().len(), 1); + assert_eq!(ftp.find("*.md").ok().unwrap().len(), 1); + assert_eq!(ftp.find("*.jpeg").ok().unwrap().len(), 0); + assert!(ftp.change_dir(PathBuf::from("/home").as_path()).is_ok()); + // Rename + assert!(ftp.mkdir(PathBuf::from("/uploads").as_path()).is_ok()); + assert!(ftp + .rename( + list.get(0).unwrap(), + PathBuf::from("/uploads/README.txt").as_path() + ) + .is_ok()); + // Rename (err) + assert!(ftp + .rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path()) + .is_err()); + let dummy: FsEntry = FsEntry::File(FsFile { + name: String::from("cucumber.txt"), + abs_path: PathBuf::from("/cucumber.txt"), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 0, + ftype: Some(String::from("txt")), // File type + readonly: true, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + assert!(ftp + .rename(&dummy, PathBuf::from("/a/b/c").as_path()) + .is_err()); + // Remove + assert!(ftp.remove(list.get(1).unwrap()).is_ok()); + assert!(ftp.remove(list.get(1).unwrap()).is_err()); + // Receive file + let mut writable = ftp + .send_file(&entry, PathBuf::from("/uploads/README.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(ftp.on_sent(writable).is_ok()); + let file: FsFile = ftp + .list_dir(PathBuf::from("/uploads").as_path()) + .ok() + .unwrap() + .get(0) + .unwrap() + .clone() + .unwrap_file(); + let mut readable = ftp.recv_file(&file).ok().unwrap(); + let mut data: Vec = vec![0; 1024]; + assert!(readable.read(&mut data).is_ok()); + assert!(ftp.on_recv(readable).is_ok()); + // Receive file (err) + assert!(ftp.recv_file(&entry).is_err()); + // Cleanup + assert!(ftp.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert!(ftp + .remove(&make_fsentry(PathBuf::from("/home"), true)) + .is_ok()); + assert!(ftp + .remove(&make_fsentry(PathBuf::from("/uploads"), true)) + .is_ok()); + // Disconnect + assert!(ftp.disconnect().is_ok()); + assert_eq!(ftp.is_connected(), false); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_ftp_server_bad_auth() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Connect + assert!(ftp + .connect( + String::from("127.0.0.1"), + 10021, + Some(String::from("omar")), + Some(String::from("ommlar")), + ) + .is_err()); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_ftp_no_credentials() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + assert!(ftp + .connect(String::from("127.0.0.1"), 10021, None, None) + .is_err()); + } + + #[test] + fn test_filetransfer_ftp_server_bad_server() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Connect + assert!(ftp + .connect( + String::from("mybadserver.veryverybad.awful"), + 21, + Some(String::from("omar")), + Some(String::from("ommlar")), + ) + .is_err()); + } + #[test] fn test_filetransfer_ftp_parse_list_line_unix() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); // Simple file - let fs_entry: FsEntry = ftp + let file: FsFile = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "-rw-rw-r-- 1 root dialout 8192 Nov 5 2018 omar.txt", ) .ok() - .unwrap(); - if let FsEntry::File(file) = fs_entry { - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 8192); - assert!(file.symlink.is_none()); - assert_eq!(file.user, None); - assert_eq!(file.group, None); - assert_eq!(file.unix_pex.unwrap(), (6, 6, 4)); - assert_eq!( - file.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - file.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - file.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - } else { - panic!("Expected file, got directory"); - } + .unwrap() + .unwrap_file(); + assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); + assert_eq!(file.name, String::from("omar.txt")); + assert_eq!(file.size, 8192); + assert!(file.symlink.is_none()); + assert_eq!(file.user, None); + assert_eq!(file.group, None); + assert_eq!(file.unix_pex.unwrap(), (6, 6, 4)); + assert_eq!( + file.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + file.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + file.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); // Simple file with number as gid, uid - let fs_entry: FsEntry = ftp + let file: FsFile = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "-rwxr-xr-x 1 0 9 4096 Nov 5 16:32 omar.txt", ) .ok() - .unwrap(); - if let FsEntry::File(file) = fs_entry { - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 4096); - assert!(file.symlink.is_none()); - assert_eq!(file.user, Some(0)); - assert_eq!(file.group, Some(9)); - assert_eq!(file.unix_pex.unwrap(), (7, 5, 5)); - assert_eq!( - fmt_time(file.last_access_time, "%m %d %M").as_str(), - "11 05 32" - ); - assert_eq!( - fmt_time(file.last_change_time, "%m %d %M").as_str(), - "11 05 32" - ); - assert_eq!( - fmt_time(file.creation_time, "%m %d %M").as_str(), - "11 05 32" - ); - } else { - panic!("Expected file, got directory"); - } + .unwrap() + .unwrap_file(); + assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); + assert_eq!(file.name, String::from("omar.txt")); + assert_eq!(file.size, 4096); + assert!(file.symlink.is_none()); + assert_eq!(file.user, Some(0)); + assert_eq!(file.group, Some(9)); + assert_eq!(file.unix_pex.unwrap(), (7, 5, 5)); + assert_eq!( + fmt_time(file.last_access_time, "%m %d %M").as_str(), + "11 05 32" + ); + assert_eq!( + fmt_time(file.last_change_time, "%m %d %M").as_str(), + "11 05 32" + ); + assert_eq!( + fmt_time(file.creation_time, "%m %d %M").as_str(), + "11 05 32" + ); // Directory - let fs_entry: FsEntry = ftp + let dir: FsDirectory = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "drwxrwxr-x 1 0 9 4096 Nov 5 2018 docs", ) .ok() - .unwrap(); - if let FsEntry::Directory(dir) = fs_entry { - assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(dir.name, String::from("docs")); - assert!(dir.symlink.is_none()); - assert_eq!(dir.user, Some(0)); - assert_eq!(dir.group, Some(9)); - assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5)); - assert_eq!( - dir.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - dir.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - dir.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!(dir.readonly, false); - } else { - panic!("Expected directory, got directory"); - } + .unwrap() + .unwrap_dir(); + assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); + assert_eq!(dir.name, String::from("docs")); + assert!(dir.symlink.is_none()); + assert_eq!(dir.user, Some(0)); + assert_eq!(dir.group, Some(9)); + assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5)); + assert_eq!( + dir.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + dir.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + dir.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!(dir.readonly, false); // Error assert!(ftp .parse_list_line( @@ -1002,186 +1171,85 @@ mod tests { fn test_filetransfer_ftp_parse_list_line_dos() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); // Simple file - let fs_entry: FsEntry = ftp + let file: FsFile = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "04-08-14 03:09PM 8192 omar.txt", ) .ok() - .unwrap(); - if let FsEntry::File(file) = fs_entry { - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 8192); - assert!(file.symlink.is_none()); - assert_eq!(file.user, None); - assert_eq!(file.group, None); - assert_eq!(file.unix_pex, None); - assert_eq!( - file.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - file.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - file.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - } else { - panic!("Expected file, got directory"); - } + .unwrap() + .unwrap_file(); + assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); + assert_eq!(file.name, String::from("omar.txt")); + assert_eq!(file.size, 8192); + assert!(file.symlink.is_none()); + assert_eq!(file.user, None); + assert_eq!(file.group, None); + assert_eq!(file.unix_pex, None); + assert_eq!( + file.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + file.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + file.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); // Directory - let fs_entry: FsEntry = ftp + let dir: FsDirectory = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "04-08-14 03:09PM docs", ) .ok() - .unwrap(); - if let FsEntry::Directory(dir) = fs_entry { - assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(dir.name, String::from("docs")); - assert!(dir.symlink.is_none()); - assert_eq!(dir.user, None); - assert_eq!(dir.group, None); - assert_eq!(dir.unix_pex, None); - assert_eq!( - dir.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - dir.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - dir.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!(dir.readonly, false); - } else { - panic!("Expected directory, got directory"); - } + .unwrap() + .unwrap_dir(); + assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); + assert_eq!(dir.name, String::from("docs")); + assert!(dir.symlink.is_none()); + assert_eq!(dir.user, None); + assert_eq!(dir.group, None); + assert_eq!(dir.unix_pex, None); + assert_eq!( + dir.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + dir.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + dir.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!(dir.readonly, false); // Error assert!(ftp .parse_list_line(PathBuf::from("/").as_path(), "04-08-14 omar.txt") .is_err()); } - #[test] - fn test_filetransfer_ftp_connect_unsecure_anonymous() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_connect_unsecure_username() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect( - String::from("test.rebex.net"), - 21, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_connect_secure() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(true); - // Connect - assert!(ftp - .connect( - String::from("test.rebex.net"), - 21, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_change_dir() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Cwd - assert!(ftp.change_dir(PathBuf::from("upload/").as_path()).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/upload")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_copy() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Copy - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(ftp - .copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt")) - .is_err()); - } - #[test] fn test_filetransfer_ftp_list_dir_dos_syntax() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); @@ -1205,94 +1273,16 @@ mod tests { } #[test] - #[cfg(not(target_os = "macos"))] - fn test_filetransfer_ftp_list_dir_unix_syntax() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // List dir - let files: Vec = ftp.list_dir(PathBuf::from("/").as_path()).ok().unwrap(); - // There should be at least 1 file - assert!(files.len() > 0); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - /* NOTE: they don't work - #[test] - fn test_filetransfer_ftp_recv() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp.connect(String::from("test.rebex.net"), 21, Some(String::from("demo")), Some(String::from("password"))).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Recv 100KB - assert!(ftp.recv_file(PathBuf::from("readme.txt").as_path()).is_ok()); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_send() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp.connect(String::from("speedtest.tele2.net"), 21, None, None).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Cwd - assert!(ftp.change_dir(PathBuf::from("upload/").as_path()).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/upload")); - // Send a sample file 100KB - assert!(ftp.send_file(PathBuf::from("test.txt").as_path()).is_ok()); - // Disconnect - assert!(ftp.disconnect().is_ok()); - }*/ - - #[test] - fn test_filetransfer_ftp_exec() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert!(ftp.exec("echo 1;").is_err()); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_find() { - let mut client: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(client - .connect( - String::from("test.rebex.net"), - 21, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Pwd - assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/")); - // Search for file (let's search for pop3-*.png); there should be 2 - let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); - assert_eq!(search_res.len(), 2); - // verify names - assert_eq!(search_res[0].get_name(), "pop3-browser.png"); - assert_eq!(search_res[1].get_name(), "pop3-console-client.png"); - // Search directory - let search_res: Vec = client.find("pub").ok().unwrap(); - assert_eq!(search_res.len(), 1); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.find("pippo").is_err()); + fn test_filetransfer_ftp_get_name_and_link() { + let client: FtpFileTransfer = FtpFileTransfer::new(false); + assert_eq!( + client.get_name_and_link("Cargo.toml"), + (String::from("Cargo.toml"), None) + ); + assert_eq!( + client.get_name_and_link("Cargo -> Cargo.toml"), + (String::from("Cargo"), Some(PathBuf::from("Cargo.toml"))) + ); } #[test] @@ -1316,9 +1306,25 @@ mod tests { assert!(ftp.disconnect().is_err()); assert!(ftp.list_dir(Path::new("/tmp")).is_err()); assert!(ftp.mkdir(Path::new("/tmp")).is_err()); + assert!(ftp + .remove(&make_fsentry(PathBuf::from("/nowhere"), false)) + .is_err()); + assert!(ftp + .rename( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); assert!(ftp.pwd().is_err()); assert!(ftp.stat(Path::new("/tmp")).is_err()); assert!(ftp.recv_file(&file).is_err()); assert!(ftp.send_file(&file, Path::new("/tmp/omar.txt")).is_err()); + let (_, temp): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + let readable: Box = Box::new(std::fs::File::open(temp.path()).unwrap()); + assert!(ftp.on_recv(readable).is_err()); + let (_, temp): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + let writable: Box = + Box::new(open_file(temp.path(), true, true, true).ok().unwrap()); + assert!(ftp.on_sent(writable).is_err()); } } diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index fff23c4..0158e10 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -284,10 +284,7 @@ pub trait FileTransfer { if filter.matches(dir.name.as_str()) { drained.push(FsEntry::Directory(dir.clone())); } - match self.iter_search(dir.abs_path.as_path(), filter) { - Ok(mut filtered) => drained.append(&mut filtered), - Err(err) => return Err(err), - } + drained.append(&mut self.iter_search(dir.abs_path.as_path(), filter)?); } FsEntry::File(file) => { if filter.matches(file.name.as_str()) { diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index df17b47..ee994cf 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -445,10 +445,9 @@ impl FileTransfer for ScpFileTransfer { self.session = Some(session); // Get working directory debug!("Getting working directory..."); - match self.perform_shell_cmd("pwd") { - Ok(output) => self.wrkdir = PathBuf::from(output.as_str().trim()), - Err(err) => return Err(err), - } + self.wrkdir = self + .perform_shell_cmd("pwd") + .map(|x| PathBuf::from(x.as_str().trim()))?; info!( "Connection established; working directory: {}", self.wrkdir.display() @@ -486,7 +485,7 @@ impl FileTransfer for ScpFileTransfer { /// /// Indicates whether the client is connected to remote fn is_connected(&self) -> bool { - self.session.as_ref().is_some() + self.session.is_some() } /// ### pwd @@ -853,7 +852,15 @@ impl FileTransfer for ScpFileTransfer { ) -> Result, FileTransferError> { match self.session.as_ref() { Some(session) => { - let file_name: PathBuf = Self::resolve(file_name); + let file_name: PathBuf = match file_name.is_absolute() { + true => PathBuf::from(file_name), + false => { + let mut p: PathBuf = self.wrkdir.clone(); + p.push(file_name); + Self::resolve(p.as_path()) + } + }; + let file_name: PathBuf = Self::resolve(file_name.as_path()); info!( "Sending file {} to {}", local.abs_path.display(), @@ -963,8 +970,12 @@ impl FileTransfer for ScpFileTransfer { mod tests { use super::*; + use crate::utils::test_helpers::make_fsentry; use pretty_assertions::assert_eq; + #[cfg(feature = "with-containers")] + use crate::utils::test_helpers::{create_sample_file_entry, write_file, write_ssh_key}; + #[test] fn test_filetransfer_scp_new() { let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); @@ -973,31 +984,210 @@ mod tests { } #[test] - fn test_filetransfer_scp_connect() { + #[cfg(feature = "with-containers")] + fn test_filetransfer_scp_server() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert_eq!(client.is_connected(), false); + // Sample file + let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + // Connect assert!(client .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), + String::from("127.0.0.1"), + 10222, + Some(String::from("sftp")), Some(String::from("password")) ) .is_ok()); - // Check session and scp + // Check session and sftp assert!(client.session.is_some()); + assert_eq!(client.wrkdir, PathBuf::from("/config")); assert_eq!(client.is_connected(), true); + // Pwd + assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); + // Stat + let stat: FsFile = client + .stat(PathBuf::from("sshd.pid").as_path()) + .ok() + .unwrap() + .unwrap_file(); + assert_eq!(stat.abs_path, PathBuf::from("/config/sshd.pid")); + let stat: FsDirectory = client + .stat(PathBuf::from("/config/").as_path()) + .ok() + .unwrap() + .unwrap_dir(); + assert_eq!(stat.abs_path, PathBuf::from("/config/")); + // Stat (err) + assert!(client + .stat(PathBuf::from("/config/5t0ca220.log").as_path()) + .is_err()); + // List dir (dir has 4 (one is hidden :D) entries) + assert_eq!(client.list_dir(&Path::new("/config")).unwrap().len(), 4); + // Make directory + assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); + // Make directory (err) + assert!(client + .mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path()) + .is_err()); + // Change directory + assert!(client + .change_dir(PathBuf::from("/tmp/omar").as_path()) + .is_ok()); + // Change directory (err) + assert!(client + .change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path()) + .is_err()); + // Copy file + assert!(client + .copy( + &make_fsentry(PathBuf::from("/config/sshd.pid"), false), + PathBuf::from("/tmp/sshd.pid").as_path() + ) + .is_ok()); + // Copy dir + assert!(client + .copy( + &make_fsentry(PathBuf::from("/tmp/omar"), true), + PathBuf::from("/tmp/ommlar").as_path() + ) + .is_ok()); + // Copy (err) + assert!(client + .copy( + &make_fsentry(PathBuf::from("/tmp/zattera"), false), + PathBuf::from("/").as_path() + ) + .is_err()); + // Exec + assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); + // Change dir to ommlar + assert!(client + .change_dir(PathBuf::from("/tmp/ommlar/").as_path()) + .is_ok()); + // Upload 2 files + let mut writable = client + .send_file(&entry, PathBuf::from("omar.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let mut writable = client + .send_file(&entry, PathBuf::from("README.md").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + // Upload file (err) + assert!(client + .send_file(&entry, PathBuf::from("/ommlar/omarone").as_path()) + .is_err()); + // List dir + let list: Vec = client + .list_dir(PathBuf::from("/tmp/ommlar").as_path()) + .ok() + .unwrap(); + assert_eq!(list.len(), 2); + // Find + assert_eq!(client.find("*.txt").ok().unwrap().len(), 1); + assert_eq!(client.find("*.md").ok().unwrap().len(), 1); + assert_eq!(client.find("*.jpeg").ok().unwrap().len(), 0); + // Rename + assert!(client + .mkdir(PathBuf::from("/tmp/uploads").as_path()) + .is_ok()); + assert!(client + .rename( + list.get(0).unwrap(), + PathBuf::from("/tmp/uploads/README.txt").as_path() + ) + .is_ok()); + // Rename (err) + assert!(client + .rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path()) + .is_err()); + let dummy: FsEntry = FsEntry::File(FsFile { + name: String::from("cucumber.txt"), + abs_path: PathBuf::from("/cucumber.txt"), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 0, + ftype: Some(String::from("txt")), // File type + readonly: true, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + assert!(client + .rename(&dummy, PathBuf::from("/a/b/c").as_path()) + .is_err()); + // Remove + assert!(client.remove(list.get(1).unwrap()).is_ok()); + // Receive file + let mut writable = client + .send_file(&entry, PathBuf::from("/tmp/uploads/README.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let file: FsFile = client + .list_dir(PathBuf::from("/tmp/uploads").as_path()) + .ok() + .unwrap() + .get(0) + .unwrap() + .clone() + .unwrap_file(); + let mut readable = client.recv_file(&file).ok().unwrap(); + let mut data: Vec = vec![0; 1024]; + assert!(readable.read(&mut data).is_ok()); + assert!(client.on_recv(readable).is_ok()); + // Receive file (err) + assert!(client.recv_file(&entry).is_err()); + // Cleanup + assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/ommlar"), true)) + .is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/omar"), true)) + .is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/uploads"), true)) + .is_ok()); // Disconnect assert!(client.disconnect().is_ok()); assert_eq!(client.is_connected(), false); } + + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_scp_ssh_storage() { + let mut storage: SshKeyStorage = SshKeyStorage::empty(); + let key_file: tempfile::NamedTempFile = write_ssh_key(); + storage.add_key("127.0.0.1", "sftp", key_file.path().to_path_buf()); + let mut client: ScpFileTransfer = ScpFileTransfer::new(storage); + // Connect + assert!(client + .connect( + String::from("127.0.0.1"), + 10222, + Some(String::from("sftp")), + None, + ) + .is_ok()); + assert_eq!(client.is_connected(), true); + assert!(client.disconnect().is_ok()); + } + #[test] fn test_filetransfer_scp_bad_auth() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( - String::from("test.rebex.net"), - 22, + String::from("127.0.0.1"), + 10222, Some(String::from("demo")), Some(String::from("badpassword")) ) @@ -1005,10 +1195,11 @@ mod tests { } #[test] + #[cfg(feature = "with-containers")] fn test_filetransfer_scp_no_credentials() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client - .connect(String::from("test.rebex.net"), 22, None, None) + .connect(String::from("127.0.0.1"), 10222, None, None) .is_err()); } @@ -1024,245 +1215,92 @@ mod tests { ) .is_err()); } - #[test] - fn test_filetransfer_scp_pwd() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Pwd - assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(client.disconnect().is_ok()); - } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn test_filetransfer_scp_cwd() { + fn test_filetransfer_scp_parse_ls() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) + // File + let entry: FsFile = client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "-rw-r--r-- 1 root root 2056 giu 13 21:11 Cargo.toml", ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Cwd (relative) - assert!(client.change_dir(PathBuf::from("pub/").as_path()).is_ok()); - // Cwd (absolute) - assert!(client.change_dir(PathBuf::from("/pub").as_path()).is_ok()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_scp_cwd_error() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Cwd (abs) - assert!(client - .change_dir(PathBuf::from("/omar/gabber").as_path()) - .is_err()); - // Cwd (rel) - assert!(client - .change_dir(PathBuf::from("gomar/pett").as_path()) - .is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_scp_ls() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // List dir - let pwd: PathBuf = client.pwd().ok().unwrap(); - let files: Vec = client.list_dir(pwd.as_path()).ok().unwrap(); - assert_eq!(files.len(), 3); // There are 3 files - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_scp_stat() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - let file: FsEntry = client - .stat(PathBuf::from("readme.txt").as_path()) .ok() - .unwrap(); - if let FsEntry::File(file) = file { - assert_eq!(file.abs_path, PathBuf::from("/readme.txt")); - } else { - panic!("Expected readme.txt to be a file"); - } + .unwrap() + .unwrap_file(); + assert_eq!(entry.name.as_str(), "Cargo.toml"); + assert_eq!(entry.abs_path, PathBuf::from("/tmp/Cargo.toml")); + assert_eq!(entry.unix_pex.unwrap(), (6, 4, 4)); + assert_eq!(entry.size, 2056); + assert_eq!(entry.readonly, false); + assert_eq!(entry.ftype.unwrap().as_str(), "toml"); + assert!(entry.symlink.is_none()); + // File (year) + let entry: FsFile = client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "-rw-rw-rw- 1 root root 3368 nov 7 2020 CODE_OF_CONDUCT.md", + ) + .ok() + .unwrap() + .unwrap_file(); + assert_eq!(entry.name.as_str(), "CODE_OF_CONDUCT.md"); + assert_eq!(entry.abs_path, PathBuf::from("/tmp/CODE_OF_CONDUCT.md")); + assert_eq!(entry.unix_pex.unwrap(), (6, 6, 6)); + assert_eq!(entry.size, 3368); + assert_eq!(entry.readonly, false); + assert_eq!(entry.ftype.unwrap().as_str(), "md"); + assert!(entry.symlink.is_none()); + // Directory + let entry: FsDirectory = client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "drwxr-xr-x 1 root root 512 giu 13 21:11 docs", + ) + .ok() + .unwrap() + .unwrap_dir(); + assert_eq!(entry.name.as_str(), "docs"); + assert_eq!(entry.abs_path, PathBuf::from("/tmp/docs")); + assert_eq!(entry.unix_pex.unwrap(), (7, 5, 5)); + assert_eq!(entry.readonly, false); + assert!(entry.symlink.is_none()); + // Short metadata + assert!(client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "drwxr-xr-x 1 root root 512 giu 13 21:11", + ) + .is_err()); + // Special file + assert!(client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "crwxr-xr-x 1 root root 512 giu 13 21:11 ttyS1", + ) + .is_err()); + // Bad pex + assert!(client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "-rwxr-xr 1 root root 512 giu 13 21:11 ttyS1", + ) + .is_err()); } #[test] - fn test_filetransfer_scp_exec() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Exec - assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); - // Disconnect - assert!(client.disconnect().is_ok()); + fn test_filetransfer_scp_get_name_and_link() { + let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); + assert_eq!( + client.get_name_and_link("Cargo.toml"), + (String::from("Cargo.toml"), None) + ); + assert_eq!( + client.get_name_and_link("Cargo -> Cargo.toml"), + (String::from("Cargo"), Some(PathBuf::from("Cargo.toml"))) + ); } - #[test] - //#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn test_filetransfer_scp_find() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Search for file (let's search for pop3-*.png); there should be 2 - let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); - assert_eq!(search_res.len(), 2); - // verify names - assert_eq!(search_res[0].get_name(), "pop3-browser.png"); - assert_eq!(search_res[1].get_name(), "pop3-console-client.png"); - // Search directory - let search_res: Vec = client.find("pub").ok().unwrap(); - assert_eq!(search_res.len(), 1); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.find("pippo").is_err()); - } - - #[test] - fn test_filetransfer_scp_recv() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - // Receive file - assert!(client.recv_file(&file).is_ok()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - #[test] - fn test_filetransfer_scp_recv_failed_nosuchfile() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Receive file - let file: FsFile = FsFile { - name: String::from("omar.txt"), - abs_path: PathBuf::from("/omar.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(client.recv_file(&file).is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - // NOTE: other functions doesn't work with this test scp server - - /* NOTE: the server doesn't allow you to create directories - #[test] - fn test_filetransfer_scp_mkdir() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok()); - let dir: String = String::from("foo"); - // Mkdir - assert!(client.mkdir(dir).is_ok()); - // cwd - assert!(client.change_dir(PathBuf::from("foo/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/foo")); - // Disconnect - assert!(client.disconnect().is_ok()); - } - */ - #[test] fn test_filetransfer_scp_uninitialized() { let file: FsFile = FsFile { @@ -1282,9 +1320,19 @@ mod tests { let mut scp: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(scp.change_dir(Path::new("/tmp")).is_err()); assert!(scp.disconnect().is_err()); + assert!(scp.exec("echo 5").is_err()); assert!(scp.list_dir(Path::new("/tmp")).is_err()); assert!(scp.mkdir(Path::new("/tmp")).is_err()); assert!(scp.pwd().is_err()); + assert!(scp + .remove(&make_fsentry(PathBuf::from("/nowhere"), false)) + .is_err()); + assert!(scp + .rename( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); assert!(scp.stat(Path::new("/tmp")).is_err()); assert!(scp.recv_file(&file).is_err()); assert!(scp.send_file(&file, Path::new("/tmp/omar.txt")).is_err()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index d63f1c4..0712661 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -466,10 +466,7 @@ impl FileTransfer for SftpFileTransfer { match self.sftp.as_ref() { Some(_) => { // Change working directory - self.wrkdir = match self.get_remote_path(dir) { - Ok(p) => p, - Err(err) => return Err(err), - }; + self.wrkdir = self.get_remote_path(dir)?; info!("Changed working directory to {}", self.wrkdir.display()); Ok(self.wrkdir.clone()) } @@ -532,10 +529,7 @@ impl FileTransfer for SftpFileTransfer { match self.sftp.as_ref() { Some(sftp) => { // Get path - let dir: PathBuf = match self.get_remote_path(path) { - Ok(p) => p, - Err(err) => return Err(err), - }; + let dir: PathBuf = self.get_remote_path(path)?; info!("Getting file entries in {}", path.display()); // Get files match sftp.readdir(dir.as_path()) { @@ -609,10 +603,7 @@ impl FileTransfer for SftpFileTransfer { // Remove recursively debug!("{} is a directory; removing all directory entries", d.name); // Get directory files - let directory_content: Vec = match self.list_dir(d.abs_path.as_path()) { - Ok(entries) => entries, - Err(err) => return Err(err), - }; + let directory_content: Vec = self.list_dir(d.abs_path.as_path())?; for entry in directory_content.iter() { if let Err(err) = self.remove(&entry) { return Err(err); @@ -666,10 +657,7 @@ impl FileTransfer for SftpFileTransfer { match self.sftp.as_ref() { Some(sftp) => { // Get path - let dir: PathBuf = match self.get_remote_path(path) { - Ok(p) => p, - Err(err) => return Err(err), - }; + let dir: PathBuf = self.get_remote_path(path)?; info!("Stat file {}", dir.display()); // Get file match sftp.stat(dir.as_path()) { @@ -758,10 +746,7 @@ impl FileTransfer for SftpFileTransfer { )), Some(sftp) => { // Get remote file name - let remote_path: PathBuf = match self.get_remote_path(file.abs_path.as_path()) { - Ok(p) => p, - Err(err) => return Err(err), - }; + let remote_path: PathBuf = self.get_remote_path(file.abs_path.as_path())?; info!("Receiving file {}", remote_path.display()); // Open remote file match sftp.open(remote_path.as_path()) { @@ -800,7 +785,9 @@ impl FileTransfer for SftpFileTransfer { mod tests { use super::*; - + use crate::utils::test_helpers::make_fsentry; + #[cfg(feature = "with-containers")] + use crate::utils::test_helpers::{create_sample_file_entry, write_file, write_ssh_key}; use pretty_assertions::assert_eq; #[test] @@ -813,34 +800,188 @@ mod tests { } #[test] - fn test_filetransfer_sftp_connect() { + #[cfg(feature = "with-containers")] + fn test_filetransfer_sftp_server() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert_eq!(client.is_connected(), false); + // Sample file + let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + // Connect assert!(client .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), + String::from("127.0.0.1"), + 10022, + Some(String::from("sftp")), Some(String::from("password")) ) .is_ok()); // Check session and sftp assert!(client.session.is_some()); assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); + assert_eq!(client.wrkdir, PathBuf::from("/config")); assert_eq!(client.is_connected(), true); + // Pwd + assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); + // Stat + let stat: FsFile = client + .stat(PathBuf::from("/config/sshd.pid").as_path()) + .ok() + .unwrap() + .unwrap_file(); + assert_eq!(stat.name.as_str(), "sshd.pid"); + let stat: FsDirectory = client + .stat(PathBuf::from("/config").as_path()) + .ok() + .unwrap() + .unwrap_dir(); + assert_eq!(stat.name.as_str(), "config"); + // Stat (err) + assert!(client + .stat(PathBuf::from("/config/5t0ca220.log").as_path()) + .is_err()); + // List dir (dir has 4 (one is hidden :D) entries) + assert_eq!(client.list_dir(&Path::new("/config")).unwrap().len(), 4); + // Make directory + assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); + // Make directory (err) + assert!(client + .mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path()) + .is_err()); + // Change directory + assert!(client + .change_dir(PathBuf::from("/tmp/omar").as_path()) + .is_ok()); + // Change directory (err) + assert!(client + .change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path()) + .is_err()); + // Copy (not supported) + assert!(client + .copy(&FsEntry::File(entry.clone()), PathBuf::from("/").as_path()) + .is_err()); + // Exec + assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); + // Upload 2 files + let mut writable = client + .send_file(&entry, PathBuf::from("omar.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let mut writable = client + .send_file(&entry, PathBuf::from("README.md").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + // Upload file (err) + assert!(client + .send_file(&entry, PathBuf::from("/ommlar/omarone").as_path()) + .is_err()); + // List dir + let list: Vec = client + .list_dir(PathBuf::from("/tmp/omar").as_path()) + .ok() + .unwrap(); + assert_eq!(list.len(), 2); + // Find + assert_eq!(client.find("*.txt").ok().unwrap().len(), 1); + assert_eq!(client.find("*.md").ok().unwrap().len(), 1); + assert_eq!(client.find("*.jpeg").ok().unwrap().len(), 0); + // Rename + assert!(client + .mkdir(PathBuf::from("/tmp/uploads").as_path()) + .is_ok()); + assert!(client + .rename( + list.get(0).unwrap(), + PathBuf::from("/tmp/uploads/README.txt").as_path() + ) + .is_ok()); + // Rename (err) + assert!(client + .rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path()) + .is_err()); + let dummy: FsEntry = FsEntry::File(FsFile { + name: String::from("cucumber.txt"), + abs_path: PathBuf::from("/cucumber.txt"), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 0, + ftype: Some(String::from("txt")), // File type + readonly: true, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + assert!(client + .rename(&dummy, PathBuf::from("/a/b/c").as_path()) + .is_err()); + // Remove + assert!(client.remove(list.get(1).unwrap()).is_ok()); + assert!(client.remove(list.get(1).unwrap()).is_err()); + // Receive file + let mut writable = client + .send_file(&entry, PathBuf::from("/tmp/uploads/README.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let file: FsFile = client + .list_dir(PathBuf::from("/tmp/uploads").as_path()) + .ok() + .unwrap() + .get(0) + .unwrap() + .clone() + .unwrap_file(); + let mut readable = client.recv_file(&file).ok().unwrap(); + let mut data: Vec = vec![0; 1024]; + assert!(readable.read(&mut data).is_ok()); + assert!(client.on_recv(readable).is_ok()); + // Receive file (err) + assert!(client.recv_file(&entry).is_err()); + // Cleanup + assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/omar"), true)) + .is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/uploads"), true)) + .is_ok()); // Disconnect assert!(client.disconnect().is_ok()); assert_eq!(client.is_connected(), false); } + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_sftp_ssh_storage() { + let mut storage: SshKeyStorage = SshKeyStorage::empty(); + let key_file: tempfile::NamedTempFile = write_ssh_key(); + storage.add_key("127.0.0.1", "sftp", key_file.path().to_path_buf()); + let mut client: SftpFileTransfer = SftpFileTransfer::new(storage); + // Connect + assert!(client + .connect( + String::from("127.0.0.1"), + 10022, + Some(String::from("sftp")), + None, + ) + .is_ok()); + assert_eq!(client.is_connected(), true); + assert!(client.disconnect().is_ok()); + } + #[test] fn test_filetransfer_sftp_bad_auth() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( - String::from("test.rebex.net"), - 22, + String::from("127.0.0.1"), + 10022, Some(String::from("demo")), Some(String::from("badpassword")) ) @@ -848,13 +989,52 @@ mod tests { } #[test] + #[cfg(feature = "with-containers")] fn test_filetransfer_sftp_no_credentials() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client - .connect(String::from("test.rebex.net"), 22, None, None) + .connect(String::from("127.0.0.1"), 10022, None, None) .is_err()); } + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_sftp_get_remote_path() { + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); + // Connect + assert!(client + .connect( + String::from("127.0.0.1"), + 10022, + Some(String::from("sftp")), + Some(String::from("password")) + ) + .is_ok()); + // get realpath + assert!(client + .change_dir(PathBuf::from("/config").as_path()) + .is_ok()); + assert_eq!( + client + .get_remote_path(PathBuf::from("sshd.pid").as_path()) + .ok() + .unwrap(), + PathBuf::from("/config/sshd.pid") + ); + // No such file + assert!(client + .get_remote_path(PathBuf::from("omarone.txt").as_path()) + .is_err()); + // Ok abs path + assert_eq!( + client + .get_remote_path(PathBuf::from("/config/sshd.pid").as_path()) + .ok() + .unwrap(), + PathBuf::from("/config/sshd.pid") + ); + } + #[test] fn test_filetransfer_sftp_bad_server() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); @@ -868,302 +1048,6 @@ mod tests { .is_err()); } - #[test] - fn test_filetransfer_sftp_pwd() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Pwd - assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_sftp_cwd() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Pwd - assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); - // Cwd (relative) - assert!(client.change_dir(PathBuf::from("pub/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/pub")); - // Cwd (absolute) - assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_sftp_copy() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Copy - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(client - .copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt")) - .is_err()); - } - - #[test] - fn test_filetransfer_sftp_cwd_error() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Cwd (abs) - assert!(client - .change_dir(PathBuf::from("/omar/gabber").as_path()) - .is_err()); - // Cwd (rel) - assert!(client - .change_dir(PathBuf::from("gomar/pett").as_path()) - .is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - assert!(client - .change_dir(PathBuf::from("gomar/pett").as_path()) - .is_err()); - } - - #[test] - fn test_filetransfer_sftp_ls() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // List dir - let pwd: PathBuf = client.pwd().ok().unwrap(); - let files: Vec = client.list_dir(pwd.as_path()).ok().unwrap(); - assert_eq!(files.len(), 3); // There are 3 files - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.list_dir(pwd.as_path()).is_err()); - } - - #[test] - fn test_filetransfer_sftp_stat() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - let file: FsEntry = client - .stat(PathBuf::from("readme.txt").as_path()) - .ok() - .unwrap(); - if let FsEntry::File(file) = file { - assert_eq!(file.abs_path, PathBuf::from("/readme.txt")); - } else { - panic!("Expected readme.txt to be a file"); - } - } - - #[test] - fn test_filetransfer_sftp_exec() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Exec - assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.exec("echo 1").is_err()); - } - - #[test] - fn test_filetransfer_sftp_find() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Search for file (let's search for pop3-*.png); there should be 2 - let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); - assert_eq!(search_res.len(), 2); - // verify names - assert_eq!(search_res[0].get_name(), "pop3-browser.png"); - assert_eq!(search_res[1].get_name(), "pop3-console-client.png"); - // Search directory - let search_res: Vec = client.find("pub").ok().unwrap(); - assert_eq!(search_res.len(), 1); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.find("pippo").is_err()); - } - - #[test] - fn test_filetransfer_sftp_recv() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - // Receive file - assert!(client.recv_file(&file).is_ok()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - #[test] - fn test_filetransfer_sftp_recv_failed_nosuchfile() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Receive file - let file: FsFile = FsFile { - name: String::from("omar.txt"), - abs_path: PathBuf::from("/omar.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(client.recv_file(&file).is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - // NOTE: other functions doesn't work with this test SFTP server - - /* NOTE: the server doesn't allow you to create directories - #[test] - fn test_filetransfer_sftp_mkdir() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok()); - let dir: String = String::from("foo"); - // Mkdir - assert!(client.mkdir(dir).is_ok()); - // cwd - assert!(client.change_dir(PathBuf::from("foo/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/foo")); - // Disconnect - assert!(client.disconnect().is_ok()); - } - */ - #[test] fn test_filetransfer_sftp_uninitialized() { let file: FsFile = FsFile { @@ -1182,10 +1066,26 @@ mod tests { }; let mut sftp: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(sftp.change_dir(Path::new("/tmp")).is_err()); + assert!(sftp + .copy( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); + assert!(sftp.exec("echo 5").is_err()); assert!(sftp.disconnect().is_err()); assert!(sftp.list_dir(Path::new("/tmp")).is_err()); assert!(sftp.mkdir(Path::new("/tmp")).is_err()); assert!(sftp.pwd().is_err()); + assert!(sftp + .remove(&make_fsentry(PathBuf::from("/nowhere"), false)) + .is_err()); + assert!(sftp + .rename( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); assert!(sftp.stat(Path::new("/tmp")).is_err()); assert!(sftp.recv_file(&file).is_err()); assert!(sftp.send_file(&file, Path::new("/tmp/omar.txt")).is_err()); diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 84ca480..447d8fc 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -226,6 +226,28 @@ impl FsEntry { }, } } + + #[cfg(test)] + /// ### unwrap_file + /// + /// Unwrap FsEntry as FsFile + pub fn unwrap_file(self) -> FsFile { + match self { + FsEntry::File(file) => file, + _ => panic!("unwrap_file: not a file"), + } + } + + #[cfg(test)] + /// ### unwrap_dir + /// + /// Unwrap FsEntry as FsDirectory + pub fn unwrap_dir(self) -> FsDirectory { + match self { + FsEntry::Directory(dir) => dir, + _ => panic!("unwrap_dir: not a directory"), + } + } } #[cfg(test)] @@ -262,6 +284,7 @@ mod tests { assert_eq!(entry.is_dir(), true); assert_eq!(entry.is_file(), false); assert_eq!(entry.get_unix_pex(), Some((7, 5, 5))); + assert_eq!(entry.unwrap_dir().abs_path, PathBuf::from("/foo")); } #[test] @@ -294,6 +317,47 @@ mod tests { assert_eq!(entry.is_symlink(), false); assert_eq!(entry.is_dir(), false); assert_eq!(entry.is_file(), true); + assert_eq!(entry.unwrap_file().abs_path, PathBuf::from("/bar.txt")); + } + + #[test] + #[should_panic] + fn test_fs_fsentry_file_unwrap_bad() { + let t_now: SystemTime = SystemTime::now(); + let entry: FsEntry = FsEntry::File(FsFile { + name: String::from("bar.txt"), + abs_path: PathBuf::from("/bar.txt"), + last_change_time: t_now, + last_access_time: t_now, + creation_time: t_now, + size: 8192, + readonly: false, + ftype: Some(String::from("txt")), + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + entry.unwrap_dir(); + } + + #[test] + #[should_panic] + fn test_fs_fsentry_dir_unwrap_bad() { + let t_now: SystemTime = SystemTime::now(); + let entry: FsEntry = FsEntry::Directory(FsDirectory { + name: String::from("foo"), + abs_path: PathBuf::from("/foo"), + last_change_time: t_now, + last_access_time: t_now, + creation_time: t_now, + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((7, 5, 5)), // UNIX only + }); + entry.unwrap_file(); } #[test] diff --git a/src/host/mod.rs b/src/host/mod.rs index f3215bc..77d6bf6 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -773,10 +773,7 @@ impl Localhost { if filter.matches(dir.name.as_str()) { drained.push(FsEntry::Directory(dir.clone())); } - match self.iter_search(dir.abs_path.as_path(), filter) { - Ok(mut filtered) => drained.append(&mut filtered), - Err(err) => return Err(err), - } + drained.append(&mut self.iter_search(dir.abs_path.as_path(), filter)?); } FsEntry::File(file) => { if filter.matches(file.name.as_str()) { @@ -829,6 +826,9 @@ impl Localhost { mod tests { use super::*; + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + use crate::utils::test_helpers::{create_sample_file, make_fsentry}; + use crate::utils::test_helpers::{make_dir_at, make_file_at}; use pretty_assertions::assert_eq; use std::fs::File; @@ -975,6 +975,7 @@ mod tests { //fs::set_permissions(file.path(), perms)?; assert!(host.open_file_write(file.path()).is_err()); } + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_localhost_symlinks() { @@ -1038,6 +1039,13 @@ mod tests { assert!(host .mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true) .is_ok()); + // Fail + assert!(host + .mkdir_ex( + PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(), + true + ) + .is_err()); } #[test] @@ -1060,6 +1068,13 @@ mod tests { let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now assert!(host.remove(files.get(0).unwrap()).is_ok()); + // Remove unexisting directory + assert!(host + .remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) + .is_err()); + assert!(host + .remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false)) + .is_err()); } #[test] @@ -1073,7 +1088,7 @@ mod tests { let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now - assert_eq!(get_filename(files.get(0).unwrap()), String::from("foo.txt")); + assert_eq!(files.get(0).unwrap().get_name(), "foo.txt"); // Rename file let dst_path: PathBuf = PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); @@ -1083,7 +1098,7 @@ mod tests { // There should be still 1 file now, but named bar.txt let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 0 files now - assert_eq!(get_filename(files.get(0).unwrap()), String::from("bar.txt")); + assert_eq!(files.get(0).unwrap().get_name(), "bar.txt"); // Fail let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); assert!(host @@ -1131,6 +1146,13 @@ mod tests { assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); // Verify host has two files assert_eq!(host.files.len(), 2); + // Fail copy + assert!(host + .copy( + &make_fsentry(PathBuf::from("/a/a7/a/a7a"), false), + PathBuf::from("571k422i").as_path() + ) + .is_err()); } #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] @@ -1232,16 +1254,16 @@ mod tests { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let dir_path: &Path = tmpdir.path(); // Make files - assert!(make_sample_file(dir_path, "pippo.txt").is_ok()); - assert!(make_sample_file(dir_path, "foo.jpg").is_ok()); + assert!(make_file_at(dir_path, "pippo.txt").is_ok()); + assert!(make_file_at(dir_path, "foo.jpg").is_ok()); // Make nested struct - assert!(make_dir(dir_path, "examples").is_ok()); + assert!(make_dir_at(dir_path, "examples").is_ok()); let mut subdir: PathBuf = PathBuf::from(dir_path); subdir.push("examples/"); - assert!(make_sample_file(subdir.as_path(), "omar.txt").is_ok()); - assert!(make_sample_file(subdir.as_path(), "errors.txt").is_ok()); - assert!(make_sample_file(subdir.as_path(), "screenshot.png").is_ok()); - assert!(make_sample_file(subdir.as_path(), "examples.csv").is_ok()); + assert!(make_file_at(subdir.as_path(), "omar.txt").is_ok()); + assert!(make_file_at(subdir.as_path(), "errors.txt").is_ok()); + assert!(make_file_at(subdir.as_path(), "screenshot.png").is_ok()); + assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok()); let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); // Find txt files let mut result: Vec = host.find("*.txt").ok().unwrap(); @@ -1300,50 +1322,4 @@ mod tests { String::from("File already exists") ); } - - /// ### make_sample_file - /// - /// Make a file with `name` in the current directory - fn make_sample_file(dir: &Path, filename: &str) -> std::io::Result<()> { - let mut p: PathBuf = PathBuf::from(dir); - p.push(filename); - let mut file: File = File::create(p.as_path())?; - write!( - file, - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n" - )?; - Ok(()) - } - - /// ### make_dir - /// - /// Make a directory in `dir` - fn make_dir(dir: &Path, dirname: &str) -> std::io::Result<()> { - let mut p: PathBuf = PathBuf::from(dir); - p.push(dirname); - std::fs::create_dir(p.as_path()) - } - - /// ### create_sample_file - /// - /// Create a sample file - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn create_sample_file() -> tempfile::NamedTempFile { - // Write - let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); - write!( - tmpfile, - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n" - ) - .unwrap(); - tmpfile - } - - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn get_filename(entry: &FsEntry) -> String { - match entry { - FsEntry::Directory(d) => d.name.clone(), - FsEntry::File(f) => f.name.clone(), - } - } } diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index fdff0c9..4c7e36b 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -427,11 +427,12 @@ mod tests { use pretty_assertions::assert_eq; use std::thread::sleep; use std::time::Duration; + use tempfile::TempDir; #[test] fn test_system_bookmarks_new() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let client: BookmarksClient = @@ -454,7 +455,7 @@ mod tests { ) .is_err()); - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); assert!( BookmarksClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar"), 16).is_err() @@ -464,7 +465,7 @@ mod tests { #[test] fn test_system_bookmarks_new_from_existing() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -510,7 +511,7 @@ mod tests { #[test] fn test_system_bookmarks_manipulate_bookmarks() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -556,7 +557,7 @@ mod tests { #[should_panic] fn test_system_bookmarks_bad_bookmark_name() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -575,7 +576,7 @@ mod tests { #[test] fn test_system_bookmarks_manipulate_recents() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -610,7 +611,7 @@ mod tests { #[test] fn test_system_bookmarks_dup_recent() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -635,7 +636,7 @@ mod tests { #[test] fn test_system_bookmarks_recents_more_than_limit() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -681,9 +682,8 @@ mod tests { #[test] #[should_panic] - fn test_system_bookmarks_add_bookmark_empty() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -702,19 +702,10 @@ mod tests { /// ### get_paths /// /// Get paths for configuration and key for bookmarks - fn get_paths(dir: &Path) -> (PathBuf, PathBuf) { let k: PathBuf = PathBuf::from(dir); let mut c: PathBuf = k.clone(); c.push("bookmarks.toml"); (c, k) } - - /// ### create_tmp_dir - /// - /// Create temporary directory - - fn create_tmp_dir() -> tempfile::TempDir { - tempfile::TempDir::new().ok().unwrap() - } } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index b0919b8..9caaa0b 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -408,10 +408,11 @@ mod tests { use pretty_assertions::assert_eq; use std::io::Read; + use tempfile::TempDir; #[test] fn test_system_config_new() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, ssh_keys_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), ssh_keys_path.as_path()) .ok() @@ -437,14 +438,14 @@ mod tests { ConfigClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar"),) .is_err() ); - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); assert!(ConfigClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar")).is_err()); } #[test] fn test_system_config_from_existing() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -477,7 +478,7 @@ mod tests { #[test] fn test_system_config_text_editor() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -488,7 +489,7 @@ mod tests { #[test] fn test_system_config_default_protocol() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -502,7 +503,7 @@ mod tests { #[test] fn test_system_config_show_hidden_files() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -513,7 +514,7 @@ mod tests { #[test] fn test_system_config_check_for_updates() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -527,7 +528,7 @@ mod tests { #[test] fn test_system_config_group_dirs() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -540,7 +541,7 @@ mod tests { #[test] fn test_system_config_local_file_fmt() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -555,7 +556,7 @@ mod tests { #[test] fn test_system_config_remote_file_fmt() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -573,7 +574,7 @@ mod tests { #[test] fn test_system_config_ssh_keys() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -637,13 +638,6 @@ mod tests { (c, k) } - /// ### create_tmp_dir - /// - /// Create temporary directory - fn create_tmp_dir() -> tempfile::TempDir { - tempfile::TempDir::new().ok().unwrap() - } - fn get_sample_rsa_key() -> String { format!( "-----BEGIN OPENSSH PRIVATE KEY-----\n{}\n-----END OPENSSH PRIVATE KEY-----", diff --git a/src/system/sshkey_storage.rs b/src/system/sshkey_storage.rs index ea6cfb4..fd5418a 100644 --- a/src/system/sshkey_storage.rs +++ b/src/system/sshkey_storage.rs @@ -87,6 +87,16 @@ impl SshKeyStorage { fn make_mapkey(host: &str, username: &str) -> String { format!("{}@{}", username, host) } + + #[cfg(test)] + /// ### add_key + /// + /// Add a key to storage + /// NOTE: available only for tests + pub fn add_key(&mut self, host: &str, username: &str, p: PathBuf) { + let key: String = Self::make_mapkey(host, username); + self.hosts.insert(key, p); + } } #[cfg(test)] @@ -100,7 +110,7 @@ mod tests { #[test] fn test_system_sshkey_storage_new() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -128,6 +138,16 @@ mod tests { assert_eq!(storage.hosts.len(), 0); } + #[test] + fn test_system_sshkey_storage_add() { + let mut storage: SshKeyStorage = SshKeyStorage::empty(); + storage.add_key("deskichup", "veeso", PathBuf::from("/tmp/omar")); + assert_eq!( + *storage.resolve("deskichup", "veeso").unwrap(), + PathBuf::from("/tmp/omar") + ); + } + /// ### get_paths /// /// Get paths for configuration and keys directory @@ -138,11 +158,4 @@ mod tests { c.push("config.toml"); (c, k) } - - /// ### create_tmp_dir - /// - /// Create temporary directory - fn create_tmp_dir() -> tempfile::TempDir { - tempfile::TempDir::new().ok().unwrap() - } } diff --git a/src/ui/context.rs b/src/ui/context.rs index e3d44ef..dd84e0c 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -177,7 +177,7 @@ mod tests { } #[test] - #[cfg(not(feature = "githubActions"))] + #[cfg(not(feature = "github-actions"))] fn test_ui_context() { // Prepare stuff let mut ctx: Context = Context::new(None, Some(String::from("alles kaput"))); diff --git a/src/utils/git.rs b/src/utils/git.rs index c0ef365..cab9070 100644 --- a/src/utils/git.rs +++ b/src/utils/git.rs @@ -80,7 +80,7 @@ mod tests { use super::*; #[test] - #[cfg(not(all(target_os = "macos", feature = "githubActions")))] + #[cfg(not(all(target_os = "macos", feature = "github-actions")))] fn test_utils_git_check_for_updates() { assert!(check_for_updates("100.0.0").ok().unwrap().is_none()); assert!(check_for_updates("0.0.1").ok().unwrap().is_some()); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e11bd67..f956857 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -33,3 +33,7 @@ pub mod git; pub mod parser; pub mod random; pub mod ui; + +#[cfg(test)] +#[allow(dead_code)] +pub mod test_helpers; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index b5a8f41..9ac603d 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -186,13 +186,10 @@ pub fn parse_lstime(tm: &str, fmt_year: &str, fmt_hours: &str) -> Result dt, - Err(err) => return Err(err), - } + )? } }; // Convert datetime to system time diff --git a/src/utils/test_helpers.rs b/src/utils/test_helpers.rs new file mode 100644 index 0000000..9e6c9fe --- /dev/null +++ b/src/utils/test_helpers.rs @@ -0,0 +1,248 @@ +//! ## TestHelpers +//! +//! contains helper functions for tests + +/** + * 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. + */ +use crate::fs::{FsDirectory, FsEntry, FsFile}; +// ext +use std::fs::File; +#[cfg(feature = "with-containers")] +use std::fs::OpenOptions; +#[cfg(feature = "with-containers")] +use std::io::Read; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::time::SystemTime; +use tempfile::NamedTempFile; + +pub fn create_sample_file_entry() -> (FsFile, NamedTempFile) { + // Write + let tmpfile = create_sample_file(); + ( + FsFile { + name: tmpfile + .path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + abs_path: tmpfile.path().to_path_buf(), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 127, + ftype: None, // File type + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }, + tmpfile, + ) +} + +pub fn create_sample_file() -> NamedTempFile { + // Write + let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); + writeln!( + tmpfile, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus." + ) + .unwrap(); + tmpfile +} + +/// ### make_file_at +/// +/// Make a file with `name` at specified path +pub fn make_file_at(dir: &Path, filename: &str) -> std::io::Result<()> { + let mut p: PathBuf = PathBuf::from(dir); + p.push(filename); + let mut file: File = File::create(p.as_path())?; + writeln!( + file, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus." + )?; + Ok(()) +} + +/// ### make_dir_at +/// +/// Make a directory in `dir` +pub fn make_dir_at(dir: &Path, dirname: &str) -> std::io::Result<()> { + let mut p: PathBuf = PathBuf::from(dir); + p.push(dirname); + std::fs::create_dir(p.as_path()) +} + +#[cfg(feature = "with-containers")] +pub fn write_file(file: &NamedTempFile, writable: &mut Box) { + let mut fhnd = OpenOptions::new() + .create(false) + .read(true) + .write(false) + .open(file.path()) + .ok() + .unwrap(); + // Read file + let mut buffer: [u8; 65536] = [0; 65536]; + assert!(fhnd.read(&mut buffer).is_ok()); + // Write file + assert!(writable.write(&buffer).is_ok()); +} + +#[cfg(feature = "with-containers")] +pub fn write_ssh_key() -> NamedTempFile { + let mut tmpfile: NamedTempFile = NamedTempFile::new().unwrap(); + writeln!( + tmpfile, + r"-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAxKyYUMRCNPlb4ZV1VMofrzApu2l3wgP4Ot9wBvHsw/+RMpcHIbQK +9iQqAVp8Z+M1fJyPXTKjoJtIzuCLF6Sjo0KI7/tFTh+yPnA5QYNLZOIRZb8skumL4gwHww +5Z942FDPuUDQ30C2mZR9lr3Cd5pA8S1ZSPTAV9QQHkpgoS8cAL8QC6dp3CJjUC8wzvXh3I +oN3bTKxCpM10KMEVuWO3lM4Nvr71auB9gzo1sFJ3bwebCZIRH01FROyA/GXRiaOtJFG/9N +nWWI/iG5AJzArKpLZNHIP+FxV/NoRH0WBXm9Wq5MrBYrD1NQzm+kInpS/2sXk3m1aZWqLm +HF2NKRXSbQAAA8iI+KSniPikpwAAAAdzc2gtcnNhAAABAQDErJhQxEI0+VvhlXVUyh+vMC +m7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VO +H7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAe +SmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndv +B5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkys +FisPU1DOb6QielL/axeTebVplaouYcXY0pFdJtAAAAAwEAAQAAAP8u3PFuTVV5SfGazwIm +MgNaux82iOsAT/HWFWecQAkqqrruUw5f+YajH/riV61NE9aq2qNOkcJrgpTWtqpt980GGd +SHWlgpRWQzfIooEiDk6Pk8RVFZsEykkDlJQSIu2onZjhi5A5ojHgZoGGabDsztSqoyOjPq +6WPvGYRiDAR3leBMyp1WufBCJqAsC4L8CjPJSmnZhc5a0zXkC9Syz74Fa08tdM7bGhtvP1 +GmzuYxkgxHH2IFeoumUSBHRiTZayGuRUDel6jgEiUMxenaDKXe7FpYzMm9tQZA10Mm4LhK +5rP9nd2/KRTFRnfZMnKvtIRC9vtlSLBe14qw+4ZCl60AAACAf1kghlO3+HIWplOmk/lCL0 +w75Zz+RdvueL9UuoyNN1QrUEY420LsixgWSeRPby+Rb/hW+XSAZJQHowQ8acFJhU85So7f +4O4wcDuE4f6hpsW9tTfkCEUdLCQJ7EKLCrod6jIV7hvI6rvXiVucRpeAzdOaq4uzj2cwDd +tOdYVsnmQAAACBAOVxBsvO/Sr3rZUbNtA6KewZh/09HNGoKNaCeiD7vaSn2UJbbPRByF/o +Oo5zv8ee8r3882NnmG808XfSn7pPZAzbbTmOaJt0fmyZhivCghSNzV6njW3o0PdnC0fGZQ +ruVXgkd7RJFbsIiD4dDcF4VCjwWHfTK21EOgJUA5pN6TNvAAAAgQDbcJWRx8Uyhkj2+srb +3n2Rt6CR7kEl9cw17ItFjMn+pO81/5U2aGw0iLlX7E06TAMQC+dyW/WaxQRey8RRdtbJ1e +TNKCN34QCWkyuYRHGhcNc0quEDayPw5QWGXlP4BzjfRUcPxY9cCXLe5wDLYsX33HwOAc59 +RorU9FCmS/654wAAABFyb290QDhjNTBmZDRjMzQ1YQECAw== +-----END OPENSSH PRIVATE KEY-----" + ) + .unwrap(); + tmpfile +} + +/// ### make_fsentry +/// +/// Create a FsEntry at specified path +pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry { + match is_dir { + true => FsEntry::Directory(FsDirectory { + name: path.file_name().unwrap().to_string_lossy().to_string(), + abs_path: path, + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }), + false => FsEntry::File(FsFile { + name: path.file_name().unwrap().to_string_lossy().to_string(), + abs_path: path, + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 127, + ftype: None, // File type + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }), + } +} + +mod test { + use super::*; + + use pretty_assertions::assert_eq; + + #[test] + fn test_utils_test_helpers_sample_file() { + let (file, _) = create_sample_file_entry(); + assert_eq!(file.readonly, false); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_utils_test_helpers_write_file() { + let (_, temp) = create_sample_file_entry(); + let tempdest = NamedTempFile::new().unwrap(); + let mut dest: Box = Box::new( + OpenOptions::new() + .create(true) + .read(false) + .write(true) + .open(tempdest.path()) + .ok() + .unwrap(), + ); + write_file(&temp, &mut dest); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_utils_test_helpers_write_ssh_key() { + let _ = write_ssh_key(); + } + + #[test] + fn test_utils_test_helpers_make_fsentry() { + assert_eq!( + make_fsentry(PathBuf::from("/tmp/omar.txt"), false) + .unwrap_file() + .name + .as_str(), + "omar.txt" + ); + assert_eq!( + make_fsentry(PathBuf::from("/tmp/cards"), true) + .unwrap_dir() + .name + .as_str(), + "cards" + ); + } + + #[test] + fn test_utils_test_helpers_make_samples() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + assert!(make_file_at(tmpdir.path(), "omaroni.txt").is_ok()); + assert!(make_file_at(PathBuf::from("/aaaaa/bbbbb/cccc").as_path(), "readme.txt").is_err()); + assert!(make_dir_at(tmpdir.path(), "docs").is_ok()); + assert!(make_dir_at(PathBuf::from("/aaaaa/bbbbb/cccc").as_path(), "docs").is_err()); + } +} diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 0000000..d68fed6 --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3" +services: + openssh-server: + image: ghcr.io/linuxserver/openssh-server + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + - SUDO_ACCESS=false + - PUBLIC_KEY=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a + - PASSWORD_ACCESS=true + - USER_PASSWORD=password + - USER_NAME=sftp + ports: + - "10022:2222" + openssh-server-scp: + image: ghcr.io/linuxserver/openssh-server + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + - SUDO_ACCESS=false + - PASSWORD_ACCESS=true + - PUBLIC_KEY=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a + - USER_PASSWORD=password + - USER_NAME=sftp + ports: + - "10222:2222" + ftp-server: + image: afharo/pure-ftp + ports: + - "10021:21" + - "30000-30009:30000-30009" + environment: + - PUBLICHOST=localhost diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..daecb00 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env sh + +if [ ! -f docker-compose.yml ]; then + set -e + cd tests/ + set +e +fi + +echo "Prepare volume..." +rm -rf /tmp/termscp-test-ftp +mkdir -p /tmp/termscp-test-ftp +echo "Building docker image..." +docker compose build +set -e +docker compose up -d +set +e + +# Go back to src root +cd .. +# Run tests +echo "Running tests" +cargo test --features with-containers -- --test-threads 1 +TEST_RESULT=$? +# Stop container +cd tests/ +echo "Stopping container..." +docker compose stop + +exit $TEST_RESULT From efad2b96dba633eb67818a7f8504a64777d4d6cf Mon Sep 17 00:00:00 2001 From: veeso Date: Wed, 16 Jun 2021 13:57:11 +0200 Subject: [PATCH 10/22] Status bar improvements: 'Show hidden files' in status bar; Status bar is has now been splitted into two, one for each explorer tab --- CHANGELOG.md | 3 + src/fs/explorer/mod.rs | 9 ++ src/ui/activities/filetransfer/mod.rs | 3 +- src/ui/activities/filetransfer/update.rs | 12 ++- src/ui/activities/filetransfer/view.rs | 112 +++++++++++++++++------ 5 files changed, 110 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cab7cb..40852bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ Released on FIXME: ?? - Found many bugs which has now been fixed - Build in CI won't fail due to test servers not responding - We're now able to test all the functionalities of the file transfers + - **Status bar improvements** + - "Show hidden files" in status bar + - Status bar is has now been splitted into two, one for each explorer tab - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index ec90def..66b78cd 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -306,6 +306,13 @@ impl FileExplorer { pub fn toggle_hidden_files(&mut self) { self.opts.toggle(ExplorerOpts::SHOW_HIDDEN_FILES); } + + /// ### hidden_files_visible + /// + /// Returns whether hidden files are visible + pub fn hidden_files_visible(&self) -> bool { + self.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES) + } } // Traits @@ -411,6 +418,7 @@ mod tests { let mut explorer: FileExplorer = FileExplorer::default(); // Don't show hidden files explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES); + assert_eq!(explorer.hidden_files_visible(), false); // Create files explorer.set_files(vec![ make_fs_entry("README.md", false), @@ -434,6 +442,7 @@ mod tests { assert_eq!(explorer.iter_files().count(), 4); // Toggle hidden explorer.toggle_hidden_files(); + assert_eq!(explorer.hidden_files_visible(), true); assert_eq!(explorer.iter_files().count(), 6); // All files are returned now } diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 5cedb60..ee71995 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -88,7 +88,8 @@ const COMPONENT_RADIO_DELETE: &str = "RADIO_DELETE"; const COMPONENT_RADIO_DISCONNECT: &str = "RADIO_DISCONNECT"; const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT"; const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING"; -const COMPONENT_SPAN_STATUS_BAR: &str = "STATUS_BAR"; +const COMPONENT_SPAN_STATUS_BAR_LOCAL: &str = "STATUS_BAR_LOCAL"; +const COMPONENT_SPAN_STATUS_BAR_REMOTE: &str = "STATUS_BAR_REMOTE"; const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO"; /// ## LogLevel diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 7f63f2d..61aa72e 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -107,6 +107,8 @@ impl Update for FileTransferActivity { (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => { // Toggle hidden files self.local_mut().toggle_hidden_files(); + // Update status bar + self.refresh_local_status_bar(); // Reload file list component self.update_local_filelist() } @@ -180,6 +182,8 @@ impl Update for FileTransferActivity { (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => { // Toggle hidden files self.remote_mut().toggle_hidden_files(); + // Update status bar + self.refresh_remote_status_bar(); // Reload file list component self.update_remote_filelist() } @@ -277,7 +281,7 @@ impl Update for FileTransferActivity { // Toggle browser sync self.browser.toggle_sync_browsing(); // Update status bar - self.refresh_status_bar(); + self.refresh_remote_status_bar(); None } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC) @@ -618,7 +622,11 @@ impl Update for FileTransferActivity { _ => panic!("Found result doesn't support SORTING"), } // Update status bar - self.refresh_status_bar(); + match self.browser.tab() { + FileExplorerTab::Local => self.refresh_local_status_bar(), + FileExplorerTab::Remote => self.refresh_remote_status_bar(), + _ => panic!("Found result doesn't support SORTING"), + }; // Reload files match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index ce2f416..3ec366b 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -100,13 +100,18 @@ impl FileTransferActivity { .build(), )), ); - // Mount status bar + // Mount status bars self.view.mount( - super::COMPONENT_SPAN_STATUS_BAR, + super::COMPONENT_SPAN_STATUS_BAR_LOCAL, + Box::new(Span::new(SpanPropsBuilder::default().build())), + ); + self.view.mount( + super::COMPONENT_SPAN_STATUS_BAR_REMOTE, Box::new(Span::new(SpanPropsBuilder::default().build())), ); // Load process bar - self.refresh_status_bar(); + self.refresh_local_status_bar(); + self.refresh_remote_status_bar(); // Update components let _ = self.update_local_filelist(); let _ = self.update_remote_filelist(); @@ -145,6 +150,12 @@ impl FileTransferActivity { .constraints([Constraint::Length(1), Constraint::Length(10)].as_ref()) .direction(Direction::Vertical) .split(chunks[1]); + // Create status bar chunks + let status_bar_chunks = Layout::default() + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .direction(Direction::Horizontal) + .horizontal_margin(1) + .split(bottom_chunks[0]); // If width is unset in the storage, set width if !store.isset(super::STORAGE_EXPLORER_WIDTH) { store.set_unsigned(super::STORAGE_EXPLORER_WIDTH, tabs_chunks[0].width as usize); @@ -170,11 +181,20 @@ impl FileTransferActivity { .view .render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]), } - // Draw log box and status bar + // Draw log box self.view .render(super::COMPONENT_LOG_BOX, f, bottom_chunks[1]); - self.view - .render(super::COMPONENT_SPAN_STATUS_BAR, f, bottom_chunks[0]); + // Draw status bar + self.view.render( + super::COMPONENT_SPAN_STATUS_BAR_LOCAL, + f, + status_bar_chunks[0], + ); + self.view.render( + super::COMPONENT_SPAN_STATUS_BAR_REMOTE, + f, + status_bar_chunks[1], + ); // @! Draw popups if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_COPY) { if props.visible { @@ -840,9 +860,54 @@ impl FileTransferActivity { self.view.umount(super::COMPONENT_LIST_FILEINFO); } - pub(super) fn refresh_status_bar(&mut self) { - let bar_spans: Vec = vec![ - TextSpanBuilder::new("Synchronized Browsing: ") + pub(super) fn refresh_local_status_bar(&mut self) { + let local_bar_spans: Vec = vec![ + TextSpanBuilder::new("File sorting: ") + .with_foreground(Color::LightYellow) + .build(), + TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting())) + .with_foreground(Color::LightYellow) + .reversed() + .build(), + TextSpanBuilder::new(" Hidden files: ") + .with_foreground(Color::LightBlue) + .build(), + TextSpanBuilder::new(Self::get_hidden_files_str( + self.local().hidden_files_visible(), + )) + .with_foreground(Color::LightBlue) + .reversed() + .build(), + ]; + if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_LOCAL) { + self.view.update( + super::COMPONENT_SPAN_STATUS_BAR_LOCAL, + SpanPropsBuilder::from(props) + .with_spans(local_bar_spans) + .build(), + ); + } + } + + pub(super) fn refresh_remote_status_bar(&mut self) { + let remote_bar_spans: Vec = vec![ + TextSpanBuilder::new("File sorting: ") + .with_foreground(Color::LightYellow) + .build(), + TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) + .with_foreground(Color::LightYellow) + .reversed() + .build(), + TextSpanBuilder::new(" Hidden files: ") + .with_foreground(Color::LightBlue) + .build(), + TextSpanBuilder::new(Self::get_hidden_files_str( + self.remote().hidden_files_visible(), + )) + .with_foreground(Color::LightBlue) + .reversed() + .build(), + TextSpanBuilder::new(" Sync Browsing: ") .with_foreground(Color::LightGreen) .build(), TextSpanBuilder::new(match self.browser.sync_browsing { @@ -852,25 +917,13 @@ impl FileTransferActivity { .with_foreground(Color::LightGreen) .reversed() .build(), - TextSpanBuilder::new(" Localhost file sorting: ") - .with_foreground(Color::LightYellow) - .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting())) - .with_foreground(Color::LightYellow) - .reversed() - .build(), - TextSpanBuilder::new(" Remote host file sorting: ") - .with_foreground(Color::LightBlue) - .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) - .with_foreground(Color::LightBlue) - .reversed() - .build(), ]; - if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR) { + if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_REMOTE) { self.view.update( - super::COMPONENT_SPAN_STATUS_BAR, - SpanPropsBuilder::from(props).with_spans(bar_spans).build(), + super::COMPONENT_SPAN_STATUS_BAR_REMOTE, + SpanPropsBuilder::from(props) + .with_spans(remote_bar_spans) + .build(), ); } } @@ -1128,4 +1181,11 @@ impl FileTransferActivity { FileSorting::BySize => "By size", } } + + fn get_hidden_files_str(show: bool) -> &'static str { + match show { + true => "Show", + false => "Hide", + } + } } From c1bc81c6646d1e59b4487498ad9572c9b4294c90 Mon Sep 17 00:00:00 2001 From: veeso Date: Wed, 16 Jun 2021 17:48:42 +0200 Subject: [PATCH 11/22] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40852bc..da3837c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ Released on FIXME: ?? - We're now able to test all the functionalities of the file transfers - **Status bar improvements** - "Show hidden files" in status bar - - Status bar is has now been splitted into two, one for each explorer tab + - Status bar has now been splitted into two, one for each explorer tab - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP From a00c0117a234491cdd29a06222264b5a439265f7 Mon Sep 17 00:00:00 2001 From: veeso Date: Wed, 16 Jun 2021 22:00:28 +0200 Subject: [PATCH 12/22] If the terminal window has less than 24 lines, then an error message is displayed in the auth activity --- CHANGELOG.md | 3 ++ src/host/mod.rs | 2 ++ src/ui/activities/auth/misc.rs | 12 ++++++++ src/ui/activities/auth/mod.rs | 6 ++++ src/ui/activities/auth/update.rs | 4 ++- src/ui/activities/auth/view.rs | 47 ++++++++++++++++++++++++++++++-- 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da3837c..089393c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ Released on FIXME: ?? - **Status bar improvements** - "Show hidden files" in status bar - Status bar has now been splitted into two, one for each explorer tab + - **Error message if terminal window is too small** + - If the terminal window has less than 24 lines, then an error message is displayed in the auth activity + - Changed auth layout to absolute sizes - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP diff --git a/src/host/mod.rs b/src/host/mod.rs index 77d6bf6..c2a6821 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -831,7 +831,9 @@ mod tests { use crate::utils::test_helpers::{make_dir_at, make_file_at}; use pretty_assertions::assert_eq; + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] use std::fs::File; + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] use std::io::Write; #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] diff --git a/src/ui/activities/auth/misc.rs b/src/ui/activities/auth/misc.rs index acda009..1145ddc 100644 --- a/src/ui/activities/auth/misc.rs +++ b/src/ui/activities/auth/misc.rs @@ -68,4 +68,16 @@ impl AuthActivity { pub(super) fn is_port_standard(port: u16) -> bool { port < 1024 } + + /// ### check_minimum_window_size + /// + /// Check minimum window size window + pub(super) fn check_minimum_window_size(&mut self, height: u16) { + if height < 24 { + // Mount window error + self.mount_size_err(); + } else { + self.umount_size_err(); + } + } } diff --git a/src/ui/activities/auth/mod.rs b/src/ui/activities/auth/mod.rs index cf400af..76be0e0 100644 --- a/src/ui/activities/auth/mod.rs +++ b/src/ui/activities/auth/mod.rs @@ -43,6 +43,7 @@ use crate::ui::context::FileTransferParams; use crate::utils::git; // Includes +use crossterm::event::Event; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use tuirealm::{Update, View}; @@ -53,6 +54,7 @@ const COMPONENT_TEXT_NEW_VERSION: &str = "TEXT_NEW_VERSION"; const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER"; const COMPONENT_TEXT_HELP: &str = "TEXT_HELP"; const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR"; +const COMPONENT_TEXT_SIZE_ERR: &str = "TEXT_SIZE_ERR"; const COMPONENT_INPUT_ADDR: &str = "INPUT_ADDRESS"; const COMPONENT_INPUT_PORT: &str = "INPUT_PORT"; const COMPONENT_INPUT_USERNAME: &str = "INPUT_USERNAME"; @@ -199,6 +201,10 @@ impl Activity for AuthActivity { if let Ok(Some(event)) = self.context.as_ref().unwrap().input_hnd.read_event() { // Set redraw to true self.redraw = true; + // Handle on resize + if let Event::Resize(_, h) = event { + self.check_minimum_window_size(h); + } // Handle event on view and update let msg = self.view.on(event); self.update(msg); diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index dd8c1f2..23971bb 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -32,7 +32,7 @@ use super::{ COMPONENT_INPUT_PORT, COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, COMPONENT_RADIO_BOOKMARK_DEL_RECENT, COMPONENT_RADIO_BOOKMARK_SAVE_PWD, COMPONENT_RADIO_PROTOCOL, COMPONENT_RADIO_QUIT, COMPONENT_RECENTS_LIST, COMPONENT_TEXT_ERROR, - COMPONENT_TEXT_HELP, + COMPONENT_TEXT_HELP, COMPONENT_TEXT_SIZE_ERR, }; use crate::ui::keymap::*; use tuirealm::components::InputPropsBuilder; @@ -306,6 +306,8 @@ impl Update for AuthActivity { self.umount_quit(); None } + // -- text size error; block everything + (COMPONENT_TEXT_SIZE_ERR, _) => None, // On submit on any unhandled (connect) (_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => { // Match key for all other components diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index af13594..eb55543 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -234,14 +234,17 @@ impl AuthActivity { pub(super) fn view(&mut self) { let mut ctx: Context = self.context.take().unwrap(); let _ = ctx.terminal.draw(|f| { + // Check window size + let height: u16 = f.size().height; + self.check_minimum_window_size(height); // Prepare chunks let chunks = Layout::default() .direction(Direction::Vertical) .margin(1) .constraints( [ - Constraint::Percentage(70), // Auth Form - Constraint::Percentage(30), // Bookmarks + Constraint::Length(21), // Auth Form + Constraint::Min(3), // Bookmarks ] .as_ref(), ) @@ -303,6 +306,14 @@ impl AuthActivity { self.view.render(super::COMPONENT_TEXT_ERROR, f, popup); } } + if let Some(props) = self.view.get_props(super::COMPONENT_TEXT_SIZE_ERR) { + if props.visible { + let popup = draw_area_in(f.size(), 80, 20); + f.render_widget(Clear, popup); + // make popup + self.view.render(super::COMPONENT_TEXT_SIZE_ERR, f, popup); + } + } if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_QUIT) { if props.visible { // make popup @@ -478,6 +489,38 @@ impl AuthActivity { self.view.umount(super::COMPONENT_TEXT_ERROR); } + /// ### mount_size_err + /// + /// Mount size error + pub(super) fn mount_size_err(&mut self) { + // Mount + self.view.mount( + super::COMPONENT_TEXT_SIZE_ERR, + Box::new(MsgBox::new( + MsgBoxPropsBuilder::default() + .with_foreground(Color::Red) + .with_borders(Borders::ALL, BorderType::Thick, Color::Red) + .bold() + .with_texts( + None, + vec![TextSpan::from( + "termscp requires at least 24 lines of height to run", + )], + ) + .build(), + )), + ); + // Give focus to error + self.view.active(super::COMPONENT_TEXT_SIZE_ERR); + } + + /// ### umount_size_err + /// + /// Umount error size error + pub(super) fn umount_size_err(&mut self) { + self.view.umount(super::COMPONENT_TEXT_SIZE_ERR); + } + /// ### mount_quit /// /// Mount quit popup From 48483a5c99646e9a931bd0ccab643804eba8b34d Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 18 Jun 2021 13:02:04 +0200 Subject: [PATCH 13/22] Unique function to send and receive files in session.rs via `TransferPayload`. Fixed transfer size when sending multiple entries --- CHANGELOG.md | 1 + src/fs/mod.rs | 1 - .../activities/filetransfer/actions/copy.rs | 55 +++--- .../activities/filetransfer/actions/edit.rs | 41 ++--- .../activities/filetransfer/actions/find.rs | 65 +++++-- src/ui/activities/filetransfer/actions/mod.rs | 2 +- .../activities/filetransfer/actions/save.rs | 60 ++++++- src/ui/activities/filetransfer/mod.rs | 3 +- src/ui/activities/filetransfer/session.rs | 169 ++++++++++++++---- 9 files changed, 298 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 089393c..b9e17ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Released on FIXME: ?? - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP - Fixed [Issue 43](https://github.com/veeso/termscp/issues/43): Could not remove non-empty directories in FTP - Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens + - Fixed [Issue 38](https://github.com/veeso/termscp/issues/38): Transfer size was wrong when transferring "selected" files (with mark) - Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files - Dependencies: - Updated `textwrap` to `0.14.0` diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 447d8fc..07e06a9 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -227,7 +227,6 @@ impl FsEntry { } } - #[cfg(test)] /// ### unwrap_file /// /// Unwrap FsEntry as FsFile diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index 6b1bd6e..27e43a6 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -27,8 +27,9 @@ */ extern crate tempfile; // locals -use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload}; use crate::filetransfer::FileTransferErrorType; +use crate::fs::FsFile; use std::path::{Path, PathBuf}; impl FileTransferActivity { @@ -66,7 +67,7 @@ impl FileTransferActivity { match self.get_remote_selected_entries() { SelectedEntry::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); - self.remote_copy_file(&entry, dest_path.as_path()); + self.remote_copy_file(entry, dest_path.as_path()); // Reload entries self.reload_remote_dir(); } @@ -74,7 +75,7 @@ impl FileTransferActivity { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files - for entry in entries.iter() { + for entry in entries.into_iter() { let mut dest_path: PathBuf = base_path.clone(); dest_path.push(entry.get_name()); self.remote_copy_file(entry, dest_path.as_path()); @@ -110,8 +111,8 @@ impl FileTransferActivity { } } - fn remote_copy_file(&mut self, entry: &FsEntry, dest: &Path) { - match self.client.as_mut().copy(entry, dest) { + fn remote_copy_file(&mut self, entry: FsEntry, dest: &Path) { + match self.client.as_mut().copy(&entry, dest) { Ok(_) => { self.log( LogLevel::Info, @@ -143,7 +144,7 @@ impl FileTransferActivity { /// ### tricky_copy /// /// Tricky copy will be used whenever copy command is not available on remote host - fn tricky_copy(&mut self, entry: &FsEntry, dest: &Path) { + fn tricky_copy(&mut self, entry: FsEntry, dest: &Path) { // match entry match entry { FsEntry::File(entry) => { @@ -159,8 +160,10 @@ impl FileTransferActivity { } }; // Download file + let name = entry.name.clone(); + let entry_path = entry.abs_path.clone(); if let Err(err) = - self.filetransfer_recv_one(entry, tmpfile.path(), entry.name.clone()) + self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name)) { self.log_and_alert( LogLevel::Error, @@ -169,8 +172,8 @@ impl FileTransferActivity { return; } // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { - Ok(e) => e, + let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) { + Ok(e) => e.unwrap_file(), Err(err) => { self.log_and_alert( LogLevel::Error, @@ -183,14 +186,10 @@ impl FileTransferActivity { return; } }; - let tmpfile_entry = match &tmpfile_entry { - FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), - FsEntry::File(f) => f, - }; // Upload file to destination let wrkdir = self.remote().wrkdir.clone(); - if let Err(err) = self.filetransfer_send_one( - tmpfile_entry, + if let Err(err) = self.filetransfer_send( + TransferPayload::File(tmpfile_entry), wrkdir.as_path(), Some(String::from(dest.to_string_lossy())), ) { @@ -198,7 +197,7 @@ impl FileTransferActivity { LogLevel::Error, format!( "Copy failed: could not write file {}: {}", - entry.abs_path.display(), + entry_path.display(), err ), ); @@ -216,11 +215,19 @@ impl FileTransferActivity { return; } }; - // Download file - self.filetransfer_recv(entry, tempdir.path(), None); // Get path of dest let mut tempdir_path: PathBuf = tempdir.path().to_path_buf(); tempdir_path.push(entry.get_name()); + // Download file + if let Err(err) = + self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None) + { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: failed to download file: {}", err), + ); + return; + } // Stat dir let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) { Ok(e) => e, @@ -238,11 +245,17 @@ impl FileTransferActivity { }; // Upload to destination let wrkdir: PathBuf = self.remote().wrkdir.clone(); - self.filetransfer_send( - &tempdir_entry, + if let Err(err) = self.filetransfer_send( + TransferPayload::Any(tempdir_entry), wrkdir.as_path(), Some(String::from(dest.to_string_lossy())), - ); + ) { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: failed to send file: {}", err), + ); + return; + } } } } diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index e7bb49d..42de105 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -26,7 +26,7 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload}; use crate::fs::FsFile; // ext use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; @@ -67,15 +67,15 @@ impl FileTransferActivity { SelectedEntry::None => vec![], }; // Edit all entries - for entry in entries.iter() { + for entry in entries.into_iter() { // Check if file if let FsEntry::File(file) = entry { self.log( LogLevel::Info, - format!("Opening file \"{}\"...", entry.get_abs_path().display()), + format!("Opening file \"{}\"...", file.abs_path.display()), ); // Edit file - if let Err(err) = self.edit_remote_file(&file) { + if let Err(err) = self.edit_remote_file(file) { self.log_and_alert(LogLevel::Error, err); } } @@ -141,7 +141,7 @@ impl FileTransferActivity { /// ### edit_remote_file /// /// Edit file on remote host - fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> { + fn edit_remote_file(&mut self, file: FsFile) -> Result<(), String> { // Create temp file let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { Ok(f) => f, @@ -150,8 +150,14 @@ impl FileTransferActivity { } }; // Download file - if let Err(err) = self.filetransfer_recv_one(file, tmpfile.path(), file.name.clone()) { - return Err(format!("Could not open file {}: {}", file.name, err)); + let file_name = file.name.clone(); + let file_path = file.abs_path.clone(); + if let Err(err) = self.filetransfer_recv( + TransferPayload::File(file), + tmpfile.path(), + Some(file_name.clone()), + ) { + return Err(format!("Could not open file {}: {}", file_name, err)); } // Get current file modification time let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) { @@ -186,12 +192,12 @@ impl FileTransferActivity { LogLevel::Info, format!( "File \"{}\" has changed; writing changes to remote", - file.abs_path.display() + file_path.display() ), ); // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { - Ok(e) => e, + let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) { + Ok(e) => e.unwrap_file(), Err(err) => { return Err(format!( "Could not stat \"{}\": {}", @@ -200,21 +206,16 @@ impl FileTransferActivity { )) } }; - // Write file - let tmpfile_entry: &FsFile = match &tmpfile_entry { - FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), - FsEntry::File(f) => f, - }; // Send file let wrkdir = self.remote().wrkdir.clone(); - if let Err(err) = self.filetransfer_send_one( - tmpfile_entry, + if let Err(err) = self.filetransfer_send( + TransferPayload::File(tmpfile_entry), wrkdir.as_path(), - Some(file.name.clone()), + Some(file_name), ) { return Err(format!( "Could not write file {}: {}", - file.abs_path.display(), + file_path.display(), err )); } @@ -222,7 +223,7 @@ impl FileTransferActivity { false => { self.log( LogLevel::Info, - format!("File \"{}\" hasn't changed", file.abs_path.display()), + format!("File \"{}\" hasn't changed", file_path.display()), ); } } diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 7e0a97d..171c4eb 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -27,7 +27,7 @@ */ // locals use super::super::browser::FileExplorerTab; -use super::{FileTransferActivity, FsEntry, SelectedEntry}; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload}; use std::path::PathBuf; @@ -77,10 +77,30 @@ impl FileTransferActivity { match self.get_found_selected_entries() { SelectedEntry::One(entry) => match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { - self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as); + if let Err(err) = self.filetransfer_send( + TransferPayload::Any(entry.get_realfile()), + wrkdir.as_path(), + save_as, + ) { + self.log_and_alert( + LogLevel::Error, + format!("Could not upload file: {}", err), + ); + return; + } } FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as); + if let Err(err) = self.filetransfer_recv( + TransferPayload::Any(entry.get_realfile()), + wrkdir.as_path(), + save_as, + ) { + self.log_and_alert( + LogLevel::Error, + format!("Could not download file: {}", err), + ); + return; + } } }, SelectedEntry::Many(entries) => { @@ -90,21 +110,34 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - for entry in entries.iter() { - match self.browser.tab() { - FileExplorerTab::FindLocal | FileExplorerTab::Local => { - self.filetransfer_send( - &entry.get_realfile(), - dest_path.as_path(), - None, - ); + let entries = entries.iter().map(|x| x.get_realfile()).collect(); + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + if let Err(err) = self.filetransfer_send( + TransferPayload::Many(entries), + dest_path.as_path(), + None, + ) { + { + self.log_and_alert( + LogLevel::Error, + format!("Could not upload file: {}", err), + ); + return; + } } - FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - self.filetransfer_recv( - &entry.get_realfile(), - dest_path.as_path(), - None, + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + if let Err(err) = self.filetransfer_recv( + TransferPayload::Many(entries), + dest_path.as_path(), + None, + ) { + self.log_and_alert( + LogLevel::Error, + format!("Could not download file: {}", err), ); + return; } } } diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index 2e13003..dcd33d9 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -25,7 +25,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -pub(self) use super::{FileTransferActivity, FsEntry, LogLevel}; +pub(self) use super::{FileTransferActivity, FsEntry, LogLevel, TransferPayload}; use tuirealm::{Payload, Value}; // actions diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index 508d99c..4a5c238 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -26,7 +26,7 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, SelectedEntry}; +use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload}; use std::path::PathBuf; impl FileTransferActivity { @@ -50,7 +50,19 @@ impl FileTransferActivity { let wrkdir: PathBuf = self.remote().wrkdir.clone(); match self.get_local_selected_entries() { SelectedEntry::One(entry) => { - self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as); + if let Err(err) = self.filetransfer_send( + TransferPayload::Any(entry.get_realfile()), + wrkdir.as_path(), + save_as, + ) { + { + self.log_and_alert( + LogLevel::Error, + format!("Could not upload file: {}", err), + ); + return; + } + } } SelectedEntry::Many(entries) => { // In case of selection: save multiple files in wrkdir/input @@ -59,8 +71,19 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - for entry in entries.iter() { - self.filetransfer_send(&entry.get_realfile(), dest_path.as_path(), None); + let entries = entries.iter().map(|x| x.get_realfile()).collect(); + if let Err(err) = self.filetransfer_send( + TransferPayload::Many(entries), + dest_path.as_path(), + None, + ) { + { + self.log_and_alert( + LogLevel::Error, + format!("Could not upload file: {}", err), + ); + return; + } } } SelectedEntry::None => {} @@ -71,7 +94,19 @@ impl FileTransferActivity { let wrkdir: PathBuf = self.local().wrkdir.clone(); match self.get_remote_selected_entries() { SelectedEntry::One(entry) => { - self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as); + if let Err(err) = self.filetransfer_recv( + TransferPayload::Any(entry.get_realfile()), + wrkdir.as_path(), + save_as, + ) { + { + self.log_and_alert( + LogLevel::Error, + format!("Could not download file: {}", err), + ); + return; + } + } } SelectedEntry::Many(entries) => { // In case of selection: save multiple files in wrkdir/input @@ -80,8 +115,19 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - for entry in entries.iter() { - self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None); + let entries = entries.iter().map(|x| x.get_realfile()).collect(); + if let Err(err) = self.filetransfer_recv( + TransferPayload::Many(entries), + dest_path.as_path(), + None, + ) { + { + self.log_and_alert( + LogLevel::Error, + format!("Could not download file: {}", err), + ); + return; + } } } SelectedEntry::None => {} diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index ee71995..6a0f25c 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -49,9 +49,10 @@ use crate::fs::explorer::FileExplorer; use crate::fs::FsEntry; use crate::host::Localhost; use crate::system::config_client::ConfigClient; -pub(crate) use lib::browser; +pub(self) use lib::browser; use lib::browser::Browser; use lib::transfer::TransferStates; +pub(self) use session::TransferPayload; // Includes use chrono::{DateTime, Local}; diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index ccaf6fa..bb3ef26 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -64,6 +64,19 @@ enum TransferErrorReason { FileTransferError(FileTransferError), } +/// ## TransferPayload +/// +/// Represents the entity to send or receive during a transfer. +/// - File: describes an individual `FsFile` to send +/// - Any: Can be any kind of `FsEntry`, but just one +/// - Many: a list of `FsEntry` +#[derive(Debug)] +pub(super) enum TransferPayload { + File(FsFile), + Any(FsEntry), + Many(Vec), +} + impl FileTransferActivity { /// ### connect /// @@ -155,27 +168,28 @@ impl FileTransferActivity { /// If entry is a directory, this applies to directory only pub(super) fn filetransfer_send( &mut self, - entry: &FsEntry, + payload: TransferPayload, curr_remote_path: &Path, dst_name: Option, - ) { - // Reset states - self.transfer.reset(); - // Calculate total size of transfer - let total_transfer_size: usize = self.get_total_transfer_size_local(entry); - self.transfer.full.init(total_transfer_size); - // Mount progress bar - self.mount_progress_bar(format!("Uploading {}...", entry.get_abs_path().display())); - // Send recurse - self.filetransfer_send_recurse(entry, curr_remote_path, dst_name); - // Umount progress bar - self.umount_progress_bar(); + ) -> Result<(), String> { + // Use different method based on payload + match payload { + TransferPayload::Any(entry) => { + self.filetransfer_send_any(&entry, curr_remote_path, dst_name) + } + TransferPayload::File(file) => { + self.filetransfer_send_file(&file, curr_remote_path, dst_name) + } + TransferPayload::Many(entries) => { + self.filetransfer_send_many(entries, curr_remote_path) + } + } } - /// ### filetransfer_send_one + /// ### filetransfer_send_file /// /// Send one file to remote at specified path. - pub(super) fn filetransfer_send_one( + fn filetransfer_send_file( &mut self, file: &FsFile, curr_remote_path: &Path, @@ -197,13 +211,64 @@ impl FileTransferActivity { }; remote_path.push(remote_file_name); // Send - let result = self.filetransfer_send_file(file, remote_path.as_path(), file_name); + let result = self.filetransfer_send_one(file, remote_path.as_path(), file_name); // Umount progress bar self.umount_progress_bar(); // Return result result.map_err(|x| x.to_string()) } + /// ### filetransfer_send_any + /// + /// Send a `TransferPayload` of type `Any` + fn filetransfer_send_any( + &mut self, + entry: &FsEntry, + curr_remote_path: &Path, + dst_name: Option, + ) -> Result<(), String> { + // Reset states + self.transfer.reset(); + // Calculate total size of transfer + let total_transfer_size: usize = self.get_total_transfer_size_local(entry); + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Uploading {}...", entry.get_abs_path().display())); + // Send recurse + self.filetransfer_send_recurse(entry, curr_remote_path, dst_name); + // Umount progress bar + self.umount_progress_bar(); + Ok(()) + } + + /// ### filetransfer_send_many + /// + /// Send many entries to remote + fn filetransfer_send_many( + &mut self, + entries: Vec, + curr_remote_path: &Path, + ) -> Result<(), String> { + // Reset states + self.transfer.reset(); + // Calculate total size of transfer + let mut total_transfer_size: usize = 0; + for entry in entries.iter() { + total_transfer_size += self.get_total_transfer_size_local(entry); + } + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Uploading {} entries...", entries.len())); + // Send recurse + for entry in entries.iter() { + // Send + self.filetransfer_send_recurse(entry, curr_remote_path, None); + } + // Umount progress bar + self.umount_progress_bar(); + Ok(()) + } + fn filetransfer_send_recurse( &mut self, entry: &FsEntry, @@ -225,8 +290,7 @@ impl FileTransferActivity { // Match entry match entry { FsEntry::File(file) => { - if let Err(err) = - self.filetransfer_send_file(file, remote_path.as_path(), file_name) + if let Err(err) = self.filetransfer_send_one(file, remote_path.as_path(), file_name) { // Log error self.log_and_alert( @@ -329,7 +393,7 @@ impl FileTransferActivity { /// ### filetransfer_send_file /// /// Send local file and write it to remote path - fn filetransfer_send_file( + fn filetransfer_send_one( &mut self, local: &FsFile, remote: &Path, @@ -438,11 +502,29 @@ impl FileTransferActivity { /// If dst_name is Some, entry will be saved with a different name. /// If entry is a directory, this applies to directory only pub(super) fn filetransfer_recv( + &mut self, + payload: TransferPayload, + local_path: &Path, + dst_name: Option, + ) -> Result<(), String> { + match payload { + TransferPayload::Any(entry) => self.filetransfer_recv_any(&entry, local_path, dst_name), + TransferPayload::File(file) => self.filetransfer_recv_file(&file, local_path), + TransferPayload::Many(entries) => self.filetransfer_recv_many(entries, local_path), + } + } + + /// ### filetransfer_recv_any + /// + /// Recv fs entry from remote. + /// If dst_name is Some, entry will be saved with a different name. + /// If entry is a directory, this applies to directory only + fn filetransfer_recv_any( &mut self, entry: &FsEntry, local_path: &Path, dst_name: Option, - ) { + ) -> Result<(), String> { // Reset states self.transfer.reset(); // Calculate total transfer size @@ -454,18 +536,13 @@ impl FileTransferActivity { self.filetransfer_recv_recurse(entry, local_path, dst_name); // Umount progress bar self.umount_progress_bar(); + Ok(()) } - /// ### filetransfer_recv_one + /// ### filetransfer_recv_file /// /// Receive a single file from remote. - /// Use this function instead of `filetransfer_recv_file` from external files - pub(super) fn filetransfer_recv_one( - &mut self, - entry: &FsFile, - local_path: &Path, - dst_name: String, - ) -> Result<(), String> { + fn filetransfer_recv_file(&mut self, entry: &FsFile, local_path: &Path) -> Result<(), String> { // Reset states self.transfer.reset(); // Calculate total transfer size @@ -474,13 +551,41 @@ impl FileTransferActivity { // Mount progress bar self.mount_progress_bar(format!("Downloading {}...", entry.abs_path.display())); // Receive - let result = self.filetransfer_recv_file(local_path, entry, dst_name); + let result = self.filetransfer_recv_one(local_path, entry, entry.name.clone()); // Umount progress bar self.umount_progress_bar(); // Return result result.map_err(|x| x.to_string()) } + /// ### filetransfer_send_many + /// + /// Send many entries to remote + fn filetransfer_recv_many( + &mut self, + entries: Vec, + curr_remote_path: &Path, + ) -> Result<(), String> { + // Reset states + self.transfer.reset(); + // Calculate total size of transfer + let mut total_transfer_size: usize = 0; + for entry in entries.iter() { + total_transfer_size += self.get_total_transfer_size_remote(entry); + } + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Uploading {} entries...", entries.len())); + // Send recurse + for entry in entries.iter() { + // Send + self.filetransfer_recv_recurse(entry, curr_remote_path, None); + } + // Umount progress bar + self.umount_progress_bar(); + Ok(()) + } + fn filetransfer_recv_recurse( &mut self, entry: &FsEntry, @@ -504,7 +609,7 @@ impl FileTransferActivity { local_file_path.push(local_file_name.as_str()); // Download file if let Err(err) = - self.filetransfer_recv_file(local_file_path.as_path(), file, file_name) + self.filetransfer_recv_one(local_file_path.as_path(), file, file_name) { self.log_and_alert( LogLevel::Error, @@ -628,10 +733,10 @@ impl FileTransferActivity { } } - /// ### filetransfer_recv_file + /// ### filetransfer_recv_one /// /// Receive file from remote and write it to local path - fn filetransfer_recv_file( + fn filetransfer_recv_one( &mut self, local: &Path, remote: &FsFile, From 0175cfbfb6f5129f47f923de507e769dc5622f25 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 18 Jun 2021 14:22:35 +0200 Subject: [PATCH 14/22] Code enhancements --- src/ui/activities/filetransfer/actions/mod.rs | 9 ++---- src/ui/activities/filetransfer/session.rs | 32 +++++++++---------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index dcd33d9..10fab58 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -78,8 +78,7 @@ impl FileTransferActivity { let files: Vec<&FsEntry> = files .iter() .map(|x| self.local().get(*x)) // Usize to Option - .filter(|x| x.is_some()) // Get only some values - .map(|x| x.unwrap()) // Option to FsEntry + .flatten() .collect(); SelectedEntry::from(files) } @@ -97,8 +96,7 @@ impl FileTransferActivity { let files: Vec<&FsEntry> = files .iter() .map(|x| self.remote().get(*x)) // Usize to Option - .filter(|x| x.is_some()) // Get only some values - .map(|x| x.unwrap()) // Option to FsEntry + .flatten() .collect(); SelectedEntry::from(files) } @@ -118,8 +116,7 @@ impl FileTransferActivity { let files: Vec<&FsEntry> = files .iter() .map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option - .filter(|x| x.is_some()) // Get only some values - .map(|x| x.unwrap()) // Option to FsEntry + .flatten() .collect(); SelectedEntry::from(files) } diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index bb3ef26..855ca5b 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -252,18 +252,17 @@ impl FileTransferActivity { // Reset states self.transfer.reset(); // Calculate total size of transfer - let mut total_transfer_size: usize = 0; - for entry in entries.iter() { - total_transfer_size += self.get_total_transfer_size_local(entry); - } + let total_transfer_size: usize = entries + .iter() + .map(|x| self.get_total_transfer_size_local(x)) + .sum(); self.transfer.full.init(total_transfer_size); // Mount progress bar self.mount_progress_bar(format!("Uploading {} entries...", entries.len())); // Send recurse - for entry in entries.iter() { - // Send - self.filetransfer_send_recurse(entry, curr_remote_path, None); - } + entries + .iter() + .for_each(|x| self.filetransfer_send_recurse(x, curr_remote_path, None)); // Umount progress bar self.umount_progress_bar(); Ok(()) @@ -569,18 +568,17 @@ impl FileTransferActivity { // Reset states self.transfer.reset(); // Calculate total size of transfer - let mut total_transfer_size: usize = 0; - for entry in entries.iter() { - total_transfer_size += self.get_total_transfer_size_remote(entry); - } + let total_transfer_size: usize = entries + .iter() + .map(|x| self.get_total_transfer_size_remote(x)) + .sum(); self.transfer.full.init(total_transfer_size); // Mount progress bar - self.mount_progress_bar(format!("Uploading {} entries...", entries.len())); + self.mount_progress_bar(format!("Downloading {} entries...", entries.len())); // Send recurse - for entry in entries.iter() { - // Send - self.filetransfer_recv_recurse(entry, curr_remote_path, None); - } + entries + .iter() + .for_each(|x| self.filetransfer_recv_recurse(x, curr_remote_path, None)); // Umount progress bar self.umount_progress_bar(); Ok(()) From 5a49987338d6a7cbf2176fc9ab9434e7a9e1e91f Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 18 Jun 2021 14:31:11 +0200 Subject: [PATCH 15/22] acceptance tests in PR --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++++++ CONTRIBUTING.md | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a04e7e1..9163c5d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,3 +32,9 @@ Please select relevant options. - [ ] I have introduced no new *C-bindings* - [ ] The changes I've made are Windows, MacOS, UNIX, Linux compatible (or I've handled them using `cfg target_os`) - [ ] I increased or maintained the code coverage for the project, compared to the previous commit + +## Acceptance tests + +wait for a *project maintainer* to fulfill this section... + +- [ ] regression test: ... diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5db7290..80083a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,7 +115,9 @@ Let's make it simple and clear: 6. Report changes to the PR you opened, writing a report of what you changed and what you have introduced. 7. Update the `CHANGELOG.md` file with details of changes to the application. In changelog report changes under a chapter called `PR{PULL_REQUEST_NUMBER}` (e.g. PR12). 8. Assign a maintainer to the reviewers. -9. Request maintainers to merge your changes. +9. Wait for a maintainer to fullfil the acceptance tests +10. Wait for a maintainer to complete the acceptance tests +11. Request maintainers to merge your changes. ### Software guidelines From 4475e8c24ce15129253e6ae09c0a1f6eb799bca1 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 19 Jun 2021 13:44:38 +0200 Subject: [PATCH 16/22] 0.5.1 ready --- CHANGELOG.md | 2 +- README.md | 2 +- codecov.yml | 7 -- dist/build/x86_64_archlinux/Dockerfile | 2 +- docs/developer.md | 138 ------------------------- 5 files changed, 3 insertions(+), 148 deletions(-) delete mode 100644 codecov.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b9e17ad..6cec85e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ ## 0.5.1 -Released on FIXME: ?? +Released on 21/06/2021 - Enhancements: - **CI now uses containers to test file transfers (SSH/FTP)** diff --git a/README.md b/README.md index 072a1bc..c1ee647 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

Developed by @veeso

-

Current version: 0.5.1 (23/05/2021)

+

Current version: 0.5.1 (21/06/2021)

[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.5.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index a93ff15..0000000 --- a/codecov.yml +++ /dev/null @@ -1,7 +0,0 @@ -ignore: - - src/main.rs - - src/lib.rs - - src/activity_manager.rs - - src/ui/activities/ - - src/ui/context.rs - - src/ui/input.rs diff --git a/dist/build/x86_64_archlinux/Dockerfile b/dist/build/x86_64_archlinux/Dockerfile index ded6c9b..f9a1cc8 100644 --- a/dist/build/x86_64_archlinux/Dockerfile +++ b/dist/build/x86_64_archlinux/Dockerfile @@ -1,4 +1,4 @@ -FROM archlinux:base-20210120.0.13969 as builder +FROM archlinux:latest as builder WORKDIR /usr/src/ # Install dependencies diff --git a/docs/developer.md b/docs/developer.md index a382fca..81e4bb0 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -7,7 +7,6 @@ Document audience: developers - [How termscp works](#how-termscp-works) - [Activities](#activities) - [The Context](#the-context) - - [Implementing File Transfers](#implementing-file-transfers) Welcome to the developer manual for termscp. This chapter DOESN'T contain the documentation for termscp modules, which can instead be found on Rust Docs at This chapter describes how termscp works and the guide lines to implement stuff such as file transfers and add features to the user interface. @@ -72,140 +71,3 @@ The context basically holds the following data: - The **Terminal**: the terminal is used to view the tui on the terminal --- - -## Implementing File Transfers - -This chapter describes how to implement a file transfer in termscp. A file transfer is a module which implements the `FileTransfer` trait. The file transfer provides different modules to interact with a remote server, which in addition to the most obvious methods, used to download and upload files, provides also methods to list files, delete files, create directories etc. - -In the following steps I will describe how to implement a new file transfer, in this case I will be implementing the SCP file transfer (which I'm actually implementing the moment I'm writing this lines). - -1. Add the Scp protocol to the `FileTransferProtocol` enum. - - Move to `src/filetransfer/mod.rs` and add `Scp` to the `FileTransferProtocol` enum - - ```rs - /// ## FileTransferProtocol - /// - /// This enum defines the different transfer protocol available in termscp - #[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone)] - pub enum FileTransferProtocol { - Sftp, - Ftp(bool), // Bool is for secure (true => ftps) - Scp, // <-- here - } - ``` - - In this case Scp is a "plain" enum type. If you need particular options, follow the implementation of `Ftp` which uses a boolean flag for indicating if using FTPS or FTP. - -2. Implement the FileTransfer struct - - Create a file at `src/filetransfer/mytransfer.rs` - - Declare your file transfer struct - - ```rs - /// ## ScpFileTransfer - /// - /// SFTP file transfer structure - pub struct ScpFileTransfer { - session: Option, - sftp: Option, - wrkdir: PathBuf, - } - ``` - -3. Implement the `FileTransfer` trait for it - - You'll have to implement the following methods for your file transfer: - - - connect: connect to remote server - - disconnect: disconnect from remote server - - is_connected: returns whether the file transfer is connected to remote - - pwd: get working directory - - change_dir: change working directory. - - list_dir: get files and directories at a certain path - - mkdir: make a new directory. Return an error in case the directory already exists - - remove: remove a file or a directory. In case the protocol doesn't support recursive removing of directories you MUST implement this through a recursive algorithm - - rename: rename a file or a directory - - stat: returns detail for a certain path - - send_file: opens a stream to a remote path for write purposes (write a remote file) - - recv_file: opens a stream to a remote path for read purposes (write a local file) - - on_sent: finalize a stream when writing a remote file. In case it's not necessary just return `Ok(())` - - on_recv: fianlize a stream when reading a remote file. In case it's not necessary just return `Ok(())` - - In case the protocol you're working on doesn't support any of this features, just return `Err(FileTransferError::new(FileTransferErrorType::UnsupportedFeature))` - -4. Add your transfer to filetransfers: - - Move to `src/filetransfer/mod.rs` and declare your file transfer: - - ```rs - // Transfers - pub mod ftp_transfer; - pub mod scp_transfer; // <-- here - pub mod sftp_transfer; - ``` - -5. Handle FileTransfer in `FileTransferActivity::new` - - Move to `src/ui/activities/filetransfer_activity/mod.rs` and add the new protocol to the client match - - ```rs - client: match protocol { - FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new()), - FileTransferProtocol::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)), - FileTransferProtocol::Scp => Box::new(ScpFileTransfer::new()), // <--- here - }, - ``` - -6. Handle right/left input events in `AuthActivity`: - - Move to `src/ui/activities/auth_activity.rs` and handle the new protocol in `handle_input_event_mode_text` for `KeyCode::Left` and `KeyCode::Right`. - Consider that the order they "rotate" must match the way they will be drawned in the interface. - For newer protocols, please put them always at the end of the list. In this list I won't, because Scp is more important than Ftp imo. - - ```rs - KeyCode::Left => { - // If current field is Protocol handle event... (move element left) - if self.selected_field == InputField::Protocol { - self.protocol = match self.protocol { - FileTransferProtocol::Sftp => FileTransferProtocol::Ftp(true), // End of list (wrap) - FileTransferProtocol::Scp => FileTransferProtocol::Sftp, - FileTransferProtocol::Ftp(ftps) => match ftps { - false => FileTransferProtocol::Scp, - true => FileTransferProtocol::Ftp(false), - } - }; - } - } - KeyCode::Right => { - // If current field is Protocol handle event... ( move element right ) - if self.selected_field == InputField::Protocol { - self.protocol = match self.protocol { - FileTransferProtocol::Sftp => FileTransferProtocol::Scp, - FileTransferProtocol::Scp => FileTransferProtocol::Ftp(false), - FileTransferProtocol::Ftp(ftps) => match ftps { - false => FileTransferProtocol::Ftp(true), - true => FileTransferProtocol::Sftp, // End of list (wrap) - } - }; - } - } - ``` - -7. Add your new file transfer to the protocol input field - - Move to `AuthActivity::draw_protocol_select` method. - Here add your new protocol to the `Spans` vector and to the match case, which chooses which element to highlight. - - ```rs - let protocols: Vec = vec![Spans::from("SFTP"), Spans::from("SCP"), Spans::from("FTP"), Spans::from("FTPS")]; - let index: usize = match self.protocol { - FileTransferProtocol::Sftp => 0, - FileTransferProtocol::Scp => 1, - FileTransferProtocol::Ftp(ftps) => match ftps { - false => 2, - true => 3, - } - }; - ``` From 5a4a364250a5207284fca4f0fc03d46b2769cb40 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 19 Jun 2021 15:01:54 +0200 Subject: [PATCH 17/22] Fixed windows dying when opening text files --- CHANGELOG.md | 1 + src/ui/activities/filetransfer/actions/edit.rs | 2 ++ src/ui/activities/setup/actions.rs | 2 ++ src/ui/activities/setup/config.rs | 2 ++ src/ui/context.rs | 10 +++++++--- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cec85e..6717d09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Released on 21/06/2021 - If the terminal window has less than 24 lines, then an error message is displayed in the auth activity - Changed auth layout to absolute sizes - Bugfix: + - Fixed termscp on Windows dying whenever opening a file with text editor - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP - Fixed [Issue 43](https://github.com/veeso/termscp/issues/43): Could not remove non-empty directories in FTP diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index 42de105..82b02e9 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -113,6 +113,7 @@ impl FileTransferActivity { error!("Failed to disable raw mode: {}", err); } // Leave alternate mode + #[cfg(not(target_os = "windows"))] if let Some(ctx) = self.context.as_mut() { ctx.leave_alternate_screen(); } @@ -127,6 +128,7 @@ impl FileTransferActivity { ), Err(err) => return Err(format!("Could not open editor: {}", err)), } + #[cfg(not(target_os = "windows"))] if let Some(ctx) = self.context.as_mut() { // Clear screen ctx.clear_screen(); diff --git a/src/ui/activities/setup/actions.rs b/src/ui/activities/setup/actions.rs index d965d42..4ca3aeb 100644 --- a/src/ui/activities/setup/actions.rs +++ b/src/ui/activities/setup/actions.rs @@ -115,6 +115,7 @@ impl SetupActivity { error!("Failed to disable raw mode: {}", err); } // Leave alternate mode + #[cfg(not(target_os = "windows"))] if let Some(ctx) = self.context.as_mut() { ctx.leave_alternate_screen(); } @@ -149,6 +150,7 @@ impl SetupActivity { } } // Restore terminal + #[cfg(not(target_os = "windows"))] if let Some(ctx) = self.context.as_mut() { // Clear screen ctx.clear_screen(); diff --git a/src/ui/activities/setup/config.rs b/src/ui/activities/setup/config.rs index 25c1b0d..2373134 100644 --- a/src/ui/activities/setup/config.rs +++ b/src/ui/activities/setup/config.rs @@ -92,6 +92,7 @@ impl SetupActivity { error!("Failed to disable raw mode: {}", err); } // Leave alternate mode + #[cfg(not(target_os = "windows"))] ctx.leave_alternate_screen(); // Get result let result: Result<(), String> = match ctx.config_client.as_ref() { @@ -121,6 +122,7 @@ impl SetupActivity { // Clear screen ctx.clear_screen(); // Enter alternate mode + #[cfg(not(target_os = "windows"))] ctx.enter_alternate_screen(); // Re-enable raw mode if let Err(err) = enable_raw_mode() { diff --git a/src/ui/context.rs b/src/ui/context.rs index dd84e0c..d9affa6 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -103,6 +103,7 @@ impl Context { /// ### enter_alternate_screen /// /// Enter alternate screen (gui window) + #[cfg(not(target_os = "windows"))] pub fn enter_alternate_screen(&mut self) { match execute!( self.terminal.backend_mut(), @@ -190,9 +191,12 @@ mod tests { assert!(ctx.get_error().is_some()); assert!(ctx.get_error().is_none()); // Try other methods - ctx.enter_alternate_screen(); - ctx.clear_screen(); - ctx.leave_alternate_screen(); + #[cfg(not(target_os = "windows"))] + { + ctx.enter_alternate_screen(); + ctx.clear_screen(); + ctx.leave_alternate_screen(); + } drop(ctx); } } From 89d205e9469114e045d8bbbd3b63e5f9d0102021 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 20 Jun 2021 11:03:55 +0200 Subject: [PATCH 18/22] Fixed UI not showing connection error --- CHANGELOG.md | 1 + src/ui/activities/filetransfer/session.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6717d09..13a0aa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Released on 21/06/2021 - If the terminal window has less than 24 lines, then an error message is displayed in the auth activity - Changed auth layout to absolute sizes - Bugfix: + - Fixed UI not showing connection errors - Fixed termscp on Windows dying whenever opening a file with text editor - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 855ca5b..ce12513 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -117,6 +117,7 @@ impl FileTransferActivity { } Err(err) => { // Set popup fatal error + self.umount_wait(); self.mount_fatal(&err.to_string()); } } From e3d2151badced9f438c6bf778ea8c65eab5e71a0 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 20 Jun 2021 11:29:12 +0200 Subject: [PATCH 19/22] Unix Build --- .github/workflows/freebsd.yml | 22 +++++++++ .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 2 +- Cargo.toml | 4 +- README.md | 2 +- dist/pkgs/freebsd/manifest | 17 +++++++ install.sh | 17 ++++++- src/config/mod.rs | 2 +- src/filetransfer/ftp_transfer.rs | 2 +- src/filetransfer/scp_transfer.rs | 4 +- src/filetransfer/sftp_transfer.rs | 2 +- src/fs/explorer/formatter.rs | 20 ++++---- src/fs/explorer/mod.rs | 2 +- src/host/mod.rs | 60 +++++++++++------------ src/system/bookmarks_client.rs | 4 +- src/ui/activities/auth/misc.rs | 2 +- src/ui/activities/filetransfer/session.rs | 12 ++++- src/ui/activities/filetransfer/view.rs | 8 +-- src/utils/fmt.rs | 2 +- 19 files changed, 124 insertions(+), 62 deletions(-) create mode 100755 .github/workflows/freebsd.yml create mode 100755 dist/pkgs/freebsd/manifest diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml new file mode 100755 index 0000000..f23ba3e --- /dev/null +++ b/.github/workflows/freebsd.yml @@ -0,0 +1,22 @@ +name: FreeBSD + +on: [push, pull_request] + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: FreeBSD build + id: test + uses: vmactions/freebsd-vm@v0.1.4 + with: + usesh: true + prepare: pkg install -y curl wget libssh gcc vim + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh && \ + chmod +x /tmp/rustup.sh && \ + /tmp/rustup.sh -y + . $HOME/.cargo/env + cargo build + cargo test --verbose --lib --features github-actions -- --test-threads 1 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f77e5ad..18661b2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build - run: cargo build --verbose + run: cargo build - name: Run tests run: cargo test --verbose --lib --features github-actions -- --test-threads 1 - name: Clippy diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 80a9fc8..b162f67 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build - run: cargo build --verbose + run: cargo build - name: Run tests run: cargo test --verbose --lib --features github-actions -- --test-threads 1 - name: Clippy diff --git a/Cargo.toml b/Cargo.toml index c504784..2d91ae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,8 +62,8 @@ pretty_assertions = "0.7.2" github-actions = [] with-containers = [] -[target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))"] -[target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))".dependencies] +[target."cfg(any(target_family = \"unix\", target_os = \"macos\", target_os = \"linux\"))"] +[target."cfg(any(target_family = \"unix\", target_os = \"macos\", target_os = \"linux\"))".dependencies] users = "0.11.0" [target."cfg(any(target_os = \"windows\", target_os = \"macos\"))"] diff --git a/README.md b/README.md index c1ee647..691c10b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.5.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) -[![Build](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![Build](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Build](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp) +[![Linux](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![MacOs](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Windows](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![FreeBSD](https://github.com/veeso/termscp/workflows/FreeBSD/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp) --- diff --git a/dist/pkgs/freebsd/manifest b/dist/pkgs/freebsd/manifest new file mode 100755 index 0000000..83a7242 --- /dev/null +++ b/dist/pkgs/freebsd/manifest @@ -0,0 +1,17 @@ +name: "termscp" +version: 0.5.1 +origin: veeso/termscp +comment: "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP" +desc: < PathBuf { p.to_path_buf() } diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index ee994cf..24d64f9 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -77,7 +77,7 @@ impl ScpFileTransfer { PathBuf::from(path_slash::PathExt::to_slash_lossy(p).as_str()) } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn resolve(p: &Path) -> PathBuf { p.to_path_buf() } @@ -1022,7 +1022,7 @@ mod tests { .stat(PathBuf::from("/config/5t0ca220.log").as_path()) .is_err()); // List dir (dir has 4 (one is hidden :D) entries) - assert_eq!(client.list_dir(&Path::new("/config")).unwrap().len(), 4); + assert!(client.list_dir(&Path::new("/config")).unwrap().len() >= 4); // Make directory assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); // Make directory (err) diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 0712661..5f353f0 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -839,7 +839,7 @@ mod tests { .stat(PathBuf::from("/config/5t0ca220.log").as_path()) .is_err()); // List dir (dir has 4 (one is hidden :D) entries) - assert_eq!(client.list_dir(&Path::new("/config")).unwrap().len(), 4); + assert!(client.list_dir(&Path::new("/config")).unwrap().len() >= 4); // Make directory assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); // Make directory (err) diff --git a/src/fs/explorer/formatter.rs b/src/fs/explorer/formatter.rs index 68a5c0a..4eed65b 100644 --- a/src/fs/explorer/formatter.rs +++ b/src/fs/explorer/formatter.rs @@ -28,7 +28,7 @@ // Deps extern crate bytesize; extern crate regex; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] extern crate users; // Locals use super::FsEntry; @@ -36,7 +36,7 @@ use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time}; // Ext use bytesize::ByteSize; use regex::Regex; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use users::{get_group_by_gid, get_user_by_uid}; // Types // FmtCallback: Formatter, fsentry: &FsEntry, cur_str, prefix, length, extra @@ -251,7 +251,7 @@ impl Formatter { _fmt_extra: Option<&String>, ) -> String { // Get username - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] let group: String = match fsentry.get_group() { Some(gid) => match get_group_by_gid(gid) { Some(user) => user.name().to_string_lossy().to_string(), @@ -431,7 +431,7 @@ impl Formatter { _fmt_extra: Option<&String>, ) -> String { // Get username - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] let username: String = match fsentry.get_user() { Some(uid) => match get_user_by_uid(uid) { Some(user) => user.name().to_string_lossy().to_string(), @@ -605,7 +605,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( formatter.fmt(&entry), format!( @@ -636,7 +636,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( formatter.fmt(&entry), format!( @@ -667,7 +667,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: None, // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( formatter.fmt(&entry), format!( @@ -698,7 +698,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: None, // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( formatter.fmt(&entry), format!( @@ -734,7 +734,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((7, 5, 5)), // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( formatter.fmt(&entry), format!( @@ -763,7 +763,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: None, // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( formatter.fmt(&entry), format!( diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 66b78cd..694d995 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -595,7 +595,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!( explorer.fmt_file(&entry), format!( diff --git a/src/host/mod.rs b/src/host/mod.rs index c2a6821..051ad9c 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -34,9 +34,9 @@ use std::time::SystemTime; use thiserror::Error; use wildmatch::WildMatch; // Metadata ext -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use std::fs::set_permissions; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use std::os::unix::fs::{MetadataExt, PermissionsExt}; // Locals @@ -439,7 +439,7 @@ impl Localhost { /// ### stat /// /// Stat file and create a FsEntry - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] pub fn stat(&self, path: &Path) -> Result { info!("Stating file {}", path.display()); let path: PathBuf = self.to_abs_path(path); @@ -605,7 +605,7 @@ impl Localhost { /// ### chmod /// /// Change file mode to file, according to UNIX permissions - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] pub fn chmod(&self, path: &Path, pex: (u8, u8, u8)) -> Result<(), HostError> { let path: PathBuf = self.to_abs_path(path); // Get metadta @@ -790,7 +790,7 @@ impl Localhost { /// ### u32_to_mode /// /// Return string with format xxxxxx to tuple of permissions (user, group, others) - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn u32_to_mode(&self, mode: u32) -> (u8, u8, u8) { let user: u8 = ((mode >> 6) & 0x7) as u8; let group: u8 = ((mode >> 3) & 0x7) as u8; @@ -801,7 +801,7 @@ impl Localhost { /// mode_to_u32 /// /// Convert owner,group,others to u32 - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn mode_to_u32(&self, mode: (u8, u8, u8)) -> u32 { ((mode.0 as u32) << 6) + ((mode.1 as u32) << 3) + mode.2 as u32 } @@ -826,17 +826,17 @@ impl Localhost { mod tests { use super::*; - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use crate::utils::test_helpers::{create_sample_file, make_fsentry}; use crate::utils::test_helpers::{make_dir_at, make_file_at}; use pretty_assertions::assert_eq; - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use std::fs::File; - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use std::io::Write; - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use std::os::unix::fs::{symlink, PermissionsExt}; #[test] @@ -848,7 +848,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_new() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert_eq!(host.wrkdir, PathBuf::from("/dev")); @@ -884,14 +884,14 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_pwd() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert_eq!(host.pwd(), PathBuf::from("/dev")); } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_list_files() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Scan dir @@ -904,7 +904,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_change_dir() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let new_dir: PathBuf = PathBuf::from("/dev"); @@ -920,7 +920,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[should_panic] fn test_host_localhost_change_dir_failed() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); @@ -929,7 +929,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_open_read() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Create temp file @@ -938,7 +938,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[should_panic] fn test_host_localhost_open_read_err_no_such_file() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); @@ -948,7 +948,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_os = "macos", target_os = "linux"))] fn test_host_localhost_open_read_err_not_accessible() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let file: tempfile::NamedTempFile = create_sample_file(); @@ -959,7 +959,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_open_write() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Create temp file @@ -968,7 +968,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_os = "macos", target_os = "linux"))] fn test_host_localhost_open_write_err() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let file: tempfile::NamedTempFile = create_sample_file(); @@ -978,7 +978,7 @@ mod tests { assert!(host.open_file_write(file.path()).is_err()); } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_localhost_symlinks() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1026,7 +1026,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_mkdir() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); @@ -1051,7 +1051,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_remove() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file @@ -1080,7 +1080,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] fn test_host_localhost_rename() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file @@ -1108,7 +1108,7 @@ mod tests { .is_err()); } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_chmod() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1127,7 +1127,7 @@ mod tests { .is_err()); } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_copy_file_absolute() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1157,7 +1157,7 @@ mod tests { .is_err()); } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_copy_file_relative() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1179,7 +1179,7 @@ mod tests { assert_eq!(host.files.len(), 2); } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_copy_directory_absolute() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1210,7 +1210,7 @@ mod tests { assert!(host.stat(test_file_path.as_path()).is_ok()); } - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_copy_directory_relative() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1245,7 +1245,7 @@ mod tests { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); // Execute - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\n"); #[cfg(target_os = "windows")] assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\r\n"); diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index 4c7e36b..e1caea3 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -92,7 +92,7 @@ impl BookmarksClient { } }; // Make a key storage (linux / unix) - #[cfg(any(target_os = "linux", target_os = "unix"))] + #[cfg(any(target_os = "linux", target_family = "unix"))] let (key_storage, service_id): (Box, &str) = { #[cfg(not(test))] let app_name: &str = "bookmarks"; @@ -446,7 +446,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "linux"))] fn test_system_bookmarks_new_err() { assert!(BookmarksClient::new( Path::new("/tmp/oifoif/omar"), diff --git a/src/ui/activities/auth/misc.rs b/src/ui/activities/auth/misc.rs index 1145ddc..d30b877 100644 --- a/src/ui/activities/auth/misc.rs +++ b/src/ui/activities/auth/misc.rs @@ -73,7 +73,7 @@ impl AuthActivity { /// /// Check minimum window size window pub(super) fn check_minimum_window_size(&mut self, height: u16) { - if height < 24 { + if height < 25 { // Mount window error self.mount_size_err(); } else { diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index ce12513..d59546f 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -656,7 +656,11 @@ impl FileTransferActivity { match self.host.mkdir_ex(local_dir_path.as_path(), true) { Ok(_) => { // Apply file mode to directory - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any( + target_family = "unix", + target_os = "macos", + target_os = "linux" + ))] if let Some(pex) = dir.unix_pex { if let Err(err) = self.host.chmod(local_dir_path.as_path(), pex) { self.log( @@ -813,7 +817,11 @@ impl FileTransferActivity { return Err(TransferErrorReason::Abrupted); } // Apply file mode to file - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any( + target_family = "unix", + target_os = "macos", + target_os = "linux" + ))] if let Some(pex) = remote.unix_pex { if let Err(err) = self.host.chmod(local, pex) { self.log( diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index 3ec366b..48e414d 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -28,7 +28,7 @@ // Deps extern crate bytesize; extern crate hostname; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] extern crate users; // locals use super::{browser::FileExplorerTab, Context, FileTransferActivity}; @@ -59,7 +59,7 @@ use tuirealm::tui::{ style::Color, widgets::{BorderType, Borders, Clear}, }; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] use users::{get_group_by_gid, get_user_by_uid}; impl FileTransferActivity { @@ -813,7 +813,7 @@ impl FileTransferActivity { .build(), ); // User - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] let username: String = match file.get_user() { Some(uid) => match get_user_by_uid(uid) { Some(user) => user.name().to_string_lossy().to_string(), @@ -824,7 +824,7 @@ impl FileTransferActivity { #[cfg(target_os = "windows")] let username: String = format!("{}", file.get_user().unwrap_or(0)); // Group - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] let group: String = match file.get_group() { Some(gid) => match get_group_by_gid(gid) { Some(group) => group.name().to_string_lossy().to_string(), diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 8f3a210..37ed892 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -216,7 +216,7 @@ mod tests { } #[test] - #[cfg(any(target_os = "unix", target_os = "linux", target_os = "macos"))] + #[cfg(any(target_family = "unix", target_os = "linux", target_os = "macos"))] fn test_utils_fmt_path_elide() { let p: &Path = &Path::new("/develop/pippo"); // Under max size From 04cafc4181e6e152b8710f96051be96fe15cf1c7 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 20 Jun 2021 17:59:27 +0200 Subject: [PATCH 20/22] distros --- README.md | 2 +- dist/pkgs/arch/.SRCINFO | 2 +- dist/pkgs/arch/PKGBUILD | 2 +- dist/pkgs/freebsd/manifest | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 691c10b..d7312b7 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Termscp is a feature rich terminal file transfer and explorer, with support for If you're considering to install termscp I want to thank you 💜 ! I hope you will enjoy termscp! If you want to contribute to this project, don't forget to check out our contribute guide. [Read More](CONTRIBUTING.md) -If you are a Linux or a MacOS user this simple shell script will install termscp on your system with a single command: +If you are a Linux, a FreeBSD or a MacOS user this simple shell script will install termscp on your system with a single command: ```sh curl --proto '=https' --tlsv1.2 -sSf "https://raw.githubusercontent.com/veeso/termscp/main/install.sh" | sh diff --git a/dist/pkgs/arch/.SRCINFO b/dist/pkgs/arch/.SRCINFO index 26ec3c4..bd66fe2 100644 --- a/dist/pkgs/arch/.SRCINFO +++ b/dist/pkgs/arch/.SRCINFO @@ -8,7 +8,7 @@ pkgbase = termscp provides = termscp options = strip source = https://github.com/veeso/termscp/releases/download/v0.5.1/termscp-0.5.1-x86_64.tar.gz - sha256sums = 279b4cab7da68c6db0efc054ddf72e36de85910110721b66d5cdc55833c99ccf + sha256sums = f66a1d1602dc8ea336ba4a42bfbe818edc9c20722e1761b471b76109c272094c pkgname = termscp diff --git a/dist/pkgs/arch/PKGBUILD b/dist/pkgs/arch/PKGBUILD index 9da854d..b195779 100644 --- a/dist/pkgs/arch/PKGBUILD +++ b/dist/pkgs/arch/PKGBUILD @@ -9,7 +9,7 @@ arch=("x86_64") provides=("termscp") options=("strip") source=("https://github.com/veeso/termscp/releases/download/v$pkgver/termscp-$pkgver-x86_64.tar.gz") -sha256sums=("279b4cab7da68c6db0efc054ddf72e36de85910110721b66d5cdc55833c99ccf") +sha256sums=("f66a1d1602dc8ea336ba4a42bfbe818edc9c20722e1761b471b76109c272094c") package() { install -Dm755 termscp -t "$pkgdir/usr/bin/" diff --git a/dist/pkgs/freebsd/manifest b/dist/pkgs/freebsd/manifest index 83a7242..a63fcb2 100755 --- a/dist/pkgs/freebsd/manifest +++ b/dist/pkgs/freebsd/manifest @@ -13,5 +13,5 @@ deps: { libssh: {origin: security/libssh, version: 0.9.5} } files: { - /usr/local/bin/termscp: "SHA256 HERE" + /usr/local/bin/termscp: "87543d13b11b6e601ba8cdde9d704c80dc3515f1681fbf71fd0b31d7206efc09" } From c78fc583b012e91a538a3a113ff0bd15c7ef5783 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 21 Jun 2021 09:11:28 +0200 Subject: [PATCH 21/22] Fix: target_family unix means also macos and linux; use BSD target_os --- Cargo.toml | 4 +- src/config/mod.rs | 2 +- src/filetransfer/ftp_transfer.rs | 2 +- src/filetransfer/scp_transfer.rs | 2 +- src/fs/explorer/formatter.rs | 20 ++++----- src/fs/explorer/mod.rs | 2 +- src/host/mod.rs | 56 +++++++++++++------------- src/system/bookmarks_client.rs | 14 ++++++- src/ui/activities/filetransfer/view.rs | 8 ++-- src/utils/fmt.rs | 2 +- 10 files changed, 61 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d91ae6..47ed781 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,8 +62,8 @@ pretty_assertions = "0.7.2" github-actions = [] with-containers = [] -[target."cfg(any(target_family = \"unix\", target_os = \"macos\", target_os = \"linux\"))"] -[target."cfg(any(target_family = \"unix\", target_os = \"macos\", target_os = \"linux\"))".dependencies] +[target."cfg(target_family = \"unix\")"] +[target."cfg(target_family = \"unix\")".dependencies] users = "0.11.0" [target."cfg(any(target_os = \"windows\", target_os = \"macos\"))"] diff --git a/src/config/mod.rs b/src/config/mod.rs index ea57dc0..06bcb44 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -225,7 +225,7 @@ mod tests { PathBuf::from(cfg.user_interface.text_editor.file_name().unwrap()), // NOTE: since edit 0.1.3 real path is used PathBuf::from("vim.EXE") ); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( PathBuf::from(cfg.user_interface.text_editor.file_name().unwrap()), // NOTE: since edit 0.1.3 real path is used PathBuf::from("vim") diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 3829579..9cb6199 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -73,7 +73,7 @@ impl FtpFileTransfer { PathBuf::from(path_slash::PathExt::to_slash_lossy(p).as_str()) } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn resolve(p: &Path) -> PathBuf { p.to_path_buf() } diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 24d64f9..34907a0 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -77,7 +77,7 @@ impl ScpFileTransfer { PathBuf::from(path_slash::PathExt::to_slash_lossy(p).as_str()) } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn resolve(p: &Path) -> PathBuf { p.to_path_buf() } diff --git a/src/fs/explorer/formatter.rs b/src/fs/explorer/formatter.rs index 4eed65b..96bdbba 100644 --- a/src/fs/explorer/formatter.rs +++ b/src/fs/explorer/formatter.rs @@ -28,7 +28,7 @@ // Deps extern crate bytesize; extern crate regex; -#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(target_family = "unix")] extern crate users; // Locals use super::FsEntry; @@ -36,7 +36,7 @@ use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time}; // Ext use bytesize::ByteSize; use regex::Regex; -#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(target_family = "unix")] use users::{get_group_by_gid, get_user_by_uid}; // Types // FmtCallback: Formatter, fsentry: &FsEntry, cur_str, prefix, length, extra @@ -251,7 +251,7 @@ impl Formatter { _fmt_extra: Option<&String>, ) -> String { // Get username - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] let group: String = match fsentry.get_group() { Some(gid) => match get_group_by_gid(gid) { Some(user) => user.name().to_string_lossy().to_string(), @@ -431,7 +431,7 @@ impl Formatter { _fmt_extra: Option<&String>, ) -> String { // Get username - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] let username: String = match fsentry.get_user() { Some(uid) => match get_user_by_uid(uid) { Some(user) => user.name().to_string_lossy().to_string(), @@ -605,7 +605,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), format!( @@ -636,7 +636,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), format!( @@ -667,7 +667,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: None, // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), format!( @@ -698,7 +698,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: None, // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), format!( @@ -734,7 +734,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((7, 5, 5)), // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), format!( @@ -763,7 +763,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: None, // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), format!( diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 694d995..320867b 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -595,7 +595,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }); - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!( explorer.fmt_file(&entry), format!( diff --git a/src/host/mod.rs b/src/host/mod.rs index 051ad9c..f832f6e 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -34,9 +34,9 @@ use std::time::SystemTime; use thiserror::Error; use wildmatch::WildMatch; // Metadata ext -#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(target_family = "unix")] use std::fs::set_permissions; -#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(target_family = "unix")] use std::os::unix::fs::{MetadataExt, PermissionsExt}; // Locals @@ -439,7 +439,7 @@ impl Localhost { /// ### stat /// /// Stat file and create a FsEntry - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] pub fn stat(&self, path: &Path) -> Result { info!("Stating file {}", path.display()); let path: PathBuf = self.to_abs_path(path); @@ -605,7 +605,7 @@ impl Localhost { /// ### chmod /// /// Change file mode to file, according to UNIX permissions - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] pub fn chmod(&self, path: &Path, pex: (u8, u8, u8)) -> Result<(), HostError> { let path: PathBuf = self.to_abs_path(path); // Get metadta @@ -790,7 +790,7 @@ impl Localhost { /// ### u32_to_mode /// /// Return string with format xxxxxx to tuple of permissions (user, group, others) - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn u32_to_mode(&self, mode: u32) -> (u8, u8, u8) { let user: u8 = ((mode >> 6) & 0x7) as u8; let group: u8 = ((mode >> 3) & 0x7) as u8; @@ -801,7 +801,7 @@ impl Localhost { /// mode_to_u32 /// /// Convert owner,group,others to u32 - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn mode_to_u32(&self, mode: (u8, u8, u8)) -> u32 { ((mode.0 as u32) << 6) + ((mode.1 as u32) << 3) + mode.2 as u32 } @@ -826,17 +826,17 @@ impl Localhost { mod tests { use super::*; - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] use crate::utils::test_helpers::{create_sample_file, make_fsentry}; use crate::utils::test_helpers::{make_dir_at, make_file_at}; use pretty_assertions::assert_eq; - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] use std::fs::File; - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] use std::io::Write; - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] use std::os::unix::fs::{symlink, PermissionsExt}; #[test] @@ -848,7 +848,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_new() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert_eq!(host.wrkdir, PathBuf::from("/dev")); @@ -884,14 +884,14 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_pwd() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert_eq!(host.pwd(), PathBuf::from("/dev")); } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_list_files() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Scan dir @@ -904,7 +904,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_change_dir() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let new_dir: PathBuf = PathBuf::from("/dev"); @@ -920,7 +920,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[should_panic] fn test_host_localhost_change_dir_failed() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); @@ -929,7 +929,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_open_read() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Create temp file @@ -938,7 +938,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[should_panic] fn test_host_localhost_open_read_err_no_such_file() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); @@ -959,7 +959,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_open_write() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Create temp file @@ -978,7 +978,7 @@ mod tests { assert!(host.open_file_write(file.path()).is_err()); } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[test] fn test_host_localhost_symlinks() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1026,7 +1026,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_mkdir() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); @@ -1051,7 +1051,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_remove() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file @@ -1080,7 +1080,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] fn test_host_localhost_rename() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file @@ -1108,7 +1108,7 @@ mod tests { .is_err()); } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[test] fn test_host_chmod() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1127,7 +1127,7 @@ mod tests { .is_err()); } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[test] fn test_host_copy_file_absolute() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1157,7 +1157,7 @@ mod tests { .is_err()); } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[test] fn test_host_copy_file_relative() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1179,7 +1179,7 @@ mod tests { assert_eq!(host.files.len(), 2); } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[test] fn test_host_copy_directory_absolute() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1210,7 +1210,7 @@ mod tests { assert!(host.stat(test_file_path.as_path()).is_ok()); } - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] #[test] fn test_host_copy_directory_relative() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); @@ -1245,7 +1245,7 @@ mod tests { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); // Execute - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\n"); #[cfg(target_os = "windows")] assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\r\n"); diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index e1caea3..f17680f 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -92,7 +92,12 @@ impl BookmarksClient { } }; // Make a key storage (linux / unix) - #[cfg(any(target_os = "linux", target_family = "unix"))] + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "netbsd", + target_os = "netbsd" + ))] let (key_storage, service_id): (Box, &str) = { #[cfg(not(test))] let app_name: &str = "bookmarks"; @@ -446,7 +451,12 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "linux"))] + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "netbsd", + target_os = "netbsd" + ))] fn test_system_bookmarks_new_err() { assert!(BookmarksClient::new( Path::new("/tmp/oifoif/omar"), diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index 48e414d..a6aa63f 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -28,7 +28,7 @@ // Deps extern crate bytesize; extern crate hostname; -#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(target_family = "unix")] extern crate users; // locals use super::{browser::FileExplorerTab, Context, FileTransferActivity}; @@ -59,7 +59,7 @@ use tuirealm::tui::{ style::Color, widgets::{BorderType, Borders, Clear}, }; -#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] +#[cfg(target_family = "unix")] use users::{get_group_by_gid, get_user_by_uid}; impl FileTransferActivity { @@ -813,7 +813,7 @@ impl FileTransferActivity { .build(), ); // User - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] let username: String = match file.get_user() { Some(uid) => match get_user_by_uid(uid) { Some(user) => user.name().to_string_lossy().to_string(), @@ -824,7 +824,7 @@ impl FileTransferActivity { #[cfg(target_os = "windows")] let username: String = format!("{}", file.get_user().unwrap_or(0)); // Group - #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + #[cfg(target_family = "unix")] let group: String = match file.get_group() { Some(gid) => match get_group_by_gid(gid) { Some(group) => group.name().to_string_lossy().to_string(), diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 37ed892..1957ab1 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -216,7 +216,7 @@ mod tests { } #[test] - #[cfg(any(target_family = "unix", target_os = "linux", target_os = "macos"))] + #[cfg(target_family = "unix")] fn test_utils_fmt_path_elide() { let p: &Path = &Path::new("/develop/pippo"); // Under max size From 1319e0b17b4cb5e5bcf3b811bd74dc0d340d18ec Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 21 Jun 2021 20:45:20 +0200 Subject: [PATCH 22/22] Fixed install for freebsd --- .github/workflows/freebsd.yml | 0 install.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 .github/workflows/freebsd.yml diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml old mode 100755 new mode 100644 diff --git a/install.sh b/install.sh index 24d22c7..18e1bcd 100755 --- a/install.sh +++ b/install.sh @@ -185,7 +185,7 @@ install_on_bsd() { msg="Installing termscp as root, please wait…" fi info "$msg" - $sudo pkg install "${archive}" + $sudo pkg install -y "${archive}" } install_on_linux() {