Merge branch '0.10.0' into main

This commit is contained in:
veeso
2022-10-15 14:01:40 +02:00
59 changed files with 1058 additions and 636 deletions

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo apt install -y libdbus-1-dev libssh2-1-dev libssl-dev
run: sudo apt update && sudo apt install -y libdbus-1-dev
- uses: actions-rs/toolchain@v1
with:
toolchain: stable

View File

@@ -1,6 +1,7 @@
# Changelog
- [Changelog](#changelog)
- [0.10.0](#0100)
- [0.9.0](#090)
- [0.8.2](#082)
- [0.8.1](#081)
@@ -25,6 +26,40 @@
---
## 0.10.0
Released on 15/10/2022
> ⭐ 500 stars update ⭐
> Thank you for supporting termscp and make it reaching 500 stars on Github
- **Changed keybindings for BACKTAB**: backtab will now change the explorer tab
- To active the LOG PANEL, use `P`
- **Yes/No dialogs** are now answerable by pressing `Y` or `N` on your keyboard ([Issue 121](https://github.com/veeso/termscp/issues/121))
- **Use ssh2 config IdentityFile** as fallback for key based authentication
- **Bugfix**
- Fixed [Issue 122](https://github.com/veeso/termscp/issues/122)
- Fixed version comparison when going above 0.9
- Dependencies:
- Bump `argh` to `0.1.9`
- Bump `chrono` to `0.4.22`
- Bump `keyring` to `1.2.0`
- Bump `notify-rust` to `4.5.10`
- Bump `open` to `3.0.3`
- Bump `rpassword` to `7.0.0`
- Changed `regex` to `lazy-regex 2.3.0`
- Bump `remotefs-ftp` to `0.1.2`
- Bump `remotefs-ssh` to `0.1.2`
- Bump `self_update` to `0.32`
- Bump `ssh2-config` to `0.1.3`
- Bump `tuirealm` to `1.8.0`
- Bump `tui-realm-stdlib` to `1.1.7`
- Bump `unicode-width` to `0.1.10`
- Added `version-compare 0.1.0`
- Bump `whoami` to `1.2.3`
- Bump `wildmatch` to `2.1.1`
- Removed libssl dependency
## 0.9.0
Released on 18/06/2022

1067
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[package]
authors = ["Christian Visintin"]
authors = ["Christian Visintin <christian.visintin1997@gmail.com>"]
categories = ["command-line-utilities"]
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3"
edition = "2021"
@@ -10,7 +10,7 @@ license = "MIT"
name = "termscp"
readme = "README.md"
repository = "https://github.com/veeso/termscp"
version = "0.9.0"
version = "0.10.0"
[package.metadata.rpm]
package = "termscp"
@@ -31,51 +31,59 @@ name = "termscp"
path = "src/main.rs"
[dependencies]
argh = "0.1.7"
argh = "0.1.9"
bitflags = "1.3.2"
bytesize = "1.1.0"
chrono = "0.4.19"
chrono = "0.4.22"
content_inspector = "0.2.4"
dirs = "4.0.0"
edit = "0.1.4"
hostname = "0.3.1"
keyring = { version = "1.1.2", optional = true }
keyring = { version = "1.2.0", optional = true }
lazy-regex = "^2.3.0"
lazy_static = "1.4.0"
log = "0.4.17"
magic-crypt = "3.1.10"
notify = "4.0.17"
notify-rust = { version = "4.5.6", default-features = false, features = [ "d" ] }
open = "2.1.3"
notify-rust = { version = "4.5.10", default-features = false, features = [ "d" ] }
open = "3.0.3"
rand = "0.8.5"
regex = "1.5.6"
remotefs = "^0.2.0"
remotefs-aws-s3 = "^0.2.0"
remotefs-ftp = { version = "^0.1.0", features = [ "secure" ] }
remotefs-ssh = { version = "^0.1.1", features = [ "ssh2-vendored" ] }
rpassword = "6.0.1"
self_update = { version = "0.30.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
serde = { version = "^1.0.0", features = [ "derive" ] }
remotefs-aws-s3 = { version = "^0.2.1", default-features = false, features = [ "find", "rustls" ] }
rpassword = "7.0.0"
self_update = { version = "0.32.0", default-features = false, features = [ "rustls", "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
serde = { version = "^1", features = [ "derive" ] }
simplelog = "0.12.0"
ssh2-config = "^0.1.3"
tempfile = "3.2.0"
thiserror = "^1.0.0"
thiserror = "^1"
toml = "0.5.0"
tui-realm-stdlib = "1.1.6"
tuirealm = "1.6.0"
unicode-width = "0.1.8"
whoami = "1.2.1"
wildmatch = "2.1.0"
tui-realm-stdlib = "1.1.7"
tuirealm = "1.8.0"
unicode-width = "0.1.10"
version-compare = "0.1.0"
whoami = "1.2.3"
wildmatch = "2.1.1"
[dev-dependencies]
pretty_assertions = "1.2.1"
serial_test = "^0.7.0"
pretty_assertions = "^1.3"
serial_test = "^0.9"
[features]
default = [ "with-keyring" ]
github-actions = [ ]
with-keyring = [ "keyring" ]
[target."cfg(target_family = \"windows\")"]
[target."cfg(target_family = \"windows\")".dependencies]
remotefs-ftp = { version = "^0.1.2", features = [ "native-tls" ] }
remotefs-ssh = "^0.1.2"
[target."cfg(target_family = \"unix\")"]
[target."cfg(target_family = \"unix\")".dependencies]
remotefs-ftp = { version = "^0.1.2", features = [ "vendored", "native-tls" ] }
remotefs-ssh = { version = "^0.1.2", features = [ "ssh2-vendored" ] }
users = "0.11.0"
[profile.dev]

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Christian Visintin
Copyright (c) 2020-2022 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

View File

@@ -63,7 +63,7 @@
</p>
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Current version: 0.9.0 (18/06/2022)</p>
<p align="center">Current version: 0.10.0 (15/10/2022)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"
@@ -224,7 +224,7 @@ The user manual can be found on the [termscp's website](https://veeso.github.io/
## Upcoming Features 🧪
For **2022** there will be two major updates during the year.
For **2023** there will be two major updates during the year.
Planned for *future updates ⏲️*:
@@ -233,9 +233,8 @@ Planned for *future updates ⏲️*:
- 🇫🇷 French
- 🇩🇪 German
- 🇮🇹 Italian
- 🇳🇱 Dutch
- 🇪🇸 Spanish
- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. (Maybe will be postponed to spring 2022).
- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration.
Along to new features, termscp developments is now focused on UX and performance improvements, so if you have any suggestion, feel free to open an issue.

View File

@@ -14,7 +14,7 @@ cargo build --release
# Make zip
$zipName = "termscp-v$version-x86_64-pc-windows-msvc.zip"
Set-Location .\target\release\
Compress-Archive termscp.exe $zipName
Compress-Archive -Force termscp.exe $zipName
# Get checksum
checksum.exe -t sha256 $zipName
Get-FileHash $zipName
Move-Item $zipName .\..\..\dist\pkgs\windows\$zipName

View File

@@ -8,8 +8,6 @@ RUN apt update && apt install -y \
git \
gcc \
pkg-config \
libssl-dev \
libssh2-1-dev \
libdbus-1-dev \
curl
# Clone repository

View File

@@ -7,11 +7,12 @@ WORKDIR /usr/src/
RUN yum -y install \
git \
gcc \
openssl \
pkgconfig \
gcc \
make \
dbus-devel \
openssl-devel \
bash
bash \
rpm-build
# Install rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \
chmod +x /tmp/rust.sh && \
@@ -20,10 +21,10 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh &&
RUN git clone --branch $branch https://github.com/veeso/termscp.git
# Set workdir to termscp
WORKDIR /usr/src/termscp/
# Install cargo arxch
RUN source $HOME/.cargo/env && cargo install cargo-rpm
# Build for x86_64
RUN source $HOME/.cargo/env && cargo build --release
# Install cargo rpm
RUN source $HOME/.cargo/env && cargo install cargo-rpm
# Build pkgs
RUN source $HOME/.cargo/env && yum -y install rpm-build && cargo rpm init && cargo rpm build
RUN source $HOME/.cargo/env && cargo rpm init && cargo rpm build
CMD ["sh"]

View File

@@ -6,8 +6,6 @@ RUN apt update && apt install -y \
git \
gcc \
pkg-config \
libssl-dev \
libssh2-1-dev \
libdbus-1-dev \
curl

View File

@@ -8,9 +8,8 @@ RUN apt update && apt install -y \
git \
gcc \
pkg-config \
libssl-dev \
libssh2-1-dev \
libdbus-1-dev \
build-essential \
bash \
curl

View File

@@ -63,7 +63,7 @@
</p>
<p align="center">Entwickelt von <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Aktuelle Version: 0.9.0 (18/06/2022)</p>
<p align="center">Aktuelle Version: 0.10.0 (09/06/2022)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@@ -204,6 +204,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
| `<M>` | Select a file | Mark |
| `<N>` | Create new file with provided name | New |
| `<O|F4>` | Edit file; see Text editor | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Quit termscp | Quit |
| `<R|F6>` | Rename file | Rename |
| `<S|F2>` | Save file as... | Save |

View File

@@ -63,7 +63,7 @@
</p>
<p align="center">Desarrollado por <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Versión actual: 0.9.0 (18/06/2022)</p>
<p align="center">Versión actual: 0.10.0 (09/06/2022)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@@ -204,6 +204,7 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
| `<M>` | Seleccione un archivo | Mark |
| `<N>` | Crear un nuevo archivo con el nombre proporcionado | New |
| `<O|F4>` | Editar archivo | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Salir de termscp | Quit |
| `<R|F6>` | Renombrar archivo | Rename |
| `<S|F2>` | Guardar archivo como... | Save |

View File

@@ -63,7 +63,7 @@
</p>
<p align="center">Développé par <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Version actuelle: 0.9.0 (18/06/2022)</p>
<p align="center">Version actuelle: 0.10.0 (09/06/2022)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@@ -202,6 +202,7 @@ Pour changer de panneau, vous devez taper `<LEFT>` pour déplacer le panneau de
| `<M>` | Sélectionner un fichier | Mark |
| `<N>` | Créer un nouveau fichier avec le nom fourni | New |
| `<O|F4>` | Modifier le fichier | Open |
| `<P>` | Ouvre le panel de journals | Panel |
| `<Q|F10>` | Quitter termscp | Quit |
| `<R|F6>` | Renommer le fichier | Rename |
| `<S|F2>` | Enregistrer le fichier sous... | Save |

View File

@@ -63,7 +63,7 @@
</p>
<p align="center">Sviluppato da <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Versione corrente: 0.9.0 (18/06/2022)</p>
<p align="center">Versione corrente: 0.10.0 (09/06/2022)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@@ -198,6 +198,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
| `<M>` | Seleziona file | Mark |
| `<N>` | Crea nuovo file con il nome fornito | New |
| `<O|F4>` | Modifica file; Vedi text editor | Open |
| `<P>` | Apri pannello log | Panel |
| `<Q|F10>` | Termina termscp | Quit |
| `<R|F6>` | Rinomina file | Rename |
| `<S|F2>` | Salva file con nome | Save |

View File

@@ -202,6 +202,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
| `<M>` | Select a file | Mark |
| `<N>` | Create new file with provided name | New |
| `<O|F4>` | Edit file; see Text editor | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Quit termscp | Quit |
| `<R|F6>` | Rename file | Rename |
| `<S|F2>` | Save file as... | Save |

View File

@@ -63,7 +63,7 @@
</p>
<p align="center"><a href="https://veeso.github.io/" target="_blank">@veeso</a> 开发</p>
<p align="center">当前版本: 0.9.0 (18/06/2022)</p>
<p align="center">当前版本: 0.10.0 (09/06/2022)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@@ -199,6 +199,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
| `<M>` | 选中文件 | Mark |
| `<N>` | 使用键入的名称新建文件 | New |
| `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open |
| `<P>` | 打开日志面板 | Panel |
| `<Q|F10>` | 退出termscp | Quit |
| `<R|F7>` | 重命名文件 | Rename |
| `<S|F2>` | 另存为... | Save |

View File

@@ -8,7 +8,7 @@
# -f, -y, --force, --yes
# Skip the confirmation prompt during installation
TERMSCP_VERSION="0.9.0"
TERMSCP_VERSION="0.10.0"
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"
@@ -296,7 +296,7 @@ install_bsd_cargo_deps() {
}
install_linux_cargo_deps() {
local debian_deps="gcc pkg-config libssl-dev libssh2-1-dev libdbus-1-dev"
local debian_deps="gcc pkg-config libdbus-1-dev"
local rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel"
local arch_deps="gcc openssl pkg-config dbus"
local deps_cmd=""

View File

@@ -19,7 +19,7 @@ pub struct UserHosts {
}
/// Bookmark describes a single bookmark entry in the user hosts storage
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)]
pub struct Bookmark {
#[serde(
deserialize_with = "deserialize_protocol",
@@ -41,7 +41,7 @@ pub struct Bookmark {
}
/// Connection parameters for Aws s3 protocol
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Default)]
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
pub struct S3Params {
pub bucket: String,
pub region: Option<String>,

View File

@@ -106,7 +106,7 @@ mod tests {
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
let cfg: UserConfig = UserConfig {
user_interface: ui,
remote: remote,
remote,
};
assert_eq!(
*cfg.remote

View File

@@ -12,7 +12,7 @@ use tuirealm::tui::style::Color;
/// ### Theme
///
/// Theme contains all the colors lookup table for termscp
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct Theme {
// -- auth
#[serde(

View File

@@ -8,7 +8,7 @@ use crate::utils::path::diff_paths;
use crate::utils::string::secure_substring;
// Ext
use bytesize::ByteSize;
use regex::Regex;
use lazy_regex::{Lazy, Regex};
use remotefs::File;
use std::path::PathBuf;
use std::time::UNIX_EPOCH;
@@ -32,17 +32,14 @@ const FMT_KEY_SYMLINK: &str = "SYMLINK";
const FMT_KEY_USER: &str = "USER";
// Default
const FMT_DEFAULT_STX: &str = "{NAME} {PEX} {USER} {SIZE} {MTIME}";
// Regex
lazy_static! {
/**
* Regex matches:
* - group 0: KEY NAME
* - group 1?: LENGTH
* - group 2?: EXTRA
*/
static ref FMT_KEY_REGEX: Regex = Regex::new(r"\{(.*?)\}").ok().unwrap();
static ref FMT_ATTR_REGEX: Regex = Regex::new(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?").ok().unwrap();
}
/**
* Regex matches:
* - group 0: KEY NAME
* - group 1?: LENGTH
* - group 2?: EXTRA
*/
static FMT_KEY_REGEX: Lazy<Regex> = lazy_regex!(r"\{(.*?)\}");
static FMT_ATTR_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?");
/// Call Chain block is a block in a chain of functions which are called in order to format the File.
/// A callChain is instantiated starting from the Formatter syntax and the regex, once the groups are found

View File

@@ -25,7 +25,7 @@ bitflags! {
}
/// FileSorting defines the criteria for sorting files
#[derive(Copy, Clone, PartialEq, std::fmt::Debug)]
#[derive(Copy, Clone, PartialEq, Eq, std::fmt::Debug)]
pub enum FileSorting {
Name,
ModifyTime,
@@ -34,7 +34,7 @@ pub enum FileSorting {
}
/// GroupDirs defines how directories should be grouped in sorting files
#[derive(PartialEq, std::fmt::Debug)]
#[derive(PartialEq, Eq, std::fmt::Debug)]
pub enum GroupDirs {
First,
Last,
@@ -318,8 +318,8 @@ mod tests {
explorer.stack_size = 2;
explorer.dirstack = VecDeque::with_capacity(2);
// Push dir
explorer.pushd(&Path::new("/tmp"));
explorer.pushd(&Path::new("/home/omar"));
explorer.pushd(Path::new("/tmp"));
explorer.pushd(Path::new("/home/omar"));
// Pop
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar"));
assert_eq!(explorer.dirstack.len(), 1);
@@ -328,9 +328,9 @@ mod tests {
// Dirstack is empty now
assert!(explorer.popd().is_none());
// Exceed limit
explorer.pushd(&Path::new("/tmp"));
explorer.pushd(&Path::new("/home/omar"));
explorer.pushd(&Path::new("/dev"));
explorer.pushd(Path::new("/tmp"));
explorer.pushd(Path::new("/home/omar"));
explorer.pushd(Path::new("/dev"));
assert_eq!(explorer.dirstack.len(), 2);
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
assert_eq!(

View File

@@ -11,7 +11,7 @@ pub use params::{FileTransferParams, ProtocolParams};
/// This enum defines the different transfer protocol available in termscp
#[derive(PartialEq, Debug, Clone, Copy)]
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum FileTransferProtocol {
Sftp,
Scp,

View File

@@ -654,7 +654,7 @@ mod tests {
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
let mut counter: usize = 0;
for _ in entries {
counter = counter + 1;
counter += 1;
}
assert_eq!(host.files.len(), counter);
}
@@ -696,7 +696,7 @@ mod tests {
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
let mut counter: usize = 0;
for _ in entries {
counter = counter + 1;
counter += 1;
}
assert_eq!(host.list_dir().len(), counter);
}
@@ -709,10 +709,10 @@ mod tests {
assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
// Verify new files
// Scan dir
let entries = std::fs::read_dir(PathBuf::from(new_dir).as_path()).unwrap();
let entries = std::fs::read_dir(new_dir.as_path()).unwrap();
let mut counter: usize = 0;
for _ in entries {
counter = counter + 1;
counter += 1;
}
assert_eq!(host.files.len(), counter);
}
@@ -793,7 +793,7 @@ mod tests {
let files: Vec<File> = host.list_dir();
// Verify files
let file_0: &File = files.get(0).unwrap();
if file_0.name() == String::from("foo.txt") {
if file_0.name() == *"foo.txt" {
assert!(file_0.metadata.symlink.is_none());
} else {
assert_eq!(
@@ -803,7 +803,7 @@ mod tests {
}
// Verify simlink
let file_1: &File = files.get(1).unwrap();
if file_1.name() == String::from("bar.txt") {
if file_1.name() == *"bar.txt" {
assert_eq!(
file_1.metadata.symlink.as_ref().unwrap(),
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))

View File

@@ -5,6 +5,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate lazy_regex;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;

View File

@@ -96,7 +96,7 @@ impl Update {
new_version,
cargo_crate_version!()
);
if new_version.as_str() > cargo_crate_version!() {
if Self::is_new_version_higher(new_version.as_str(), cargo_crate_version!()) {
Some(r) // New version is available
} else {
None // No new version
@@ -105,6 +105,12 @@ impl Update {
None => None,
}
}
/// Check wether new version is higher than new version
fn is_new_version_higher(new_version: &str, current_version: &str) -> bool {
version_compare::compare(new_version, current_version).unwrap_or(version_compare::Cmp::Lt)
== version_compare::Cmp::Gt
}
}
impl From<Status> for UpdateStatus {
fn from(s: Status) -> Self {
@@ -193,4 +199,13 @@ mod test {
assert_eq!(release.body.as_str(), "fixed everything");
assert_eq!(release.version.as_str(), "0.7.0");
}
#[test]
fn should_tell_that_version_is_higher() {
assert!(Update::is_new_version_higher("0.10.0", "0.9.0"));
assert!(Update::is_new_version_higher("0.20.0", "0.19.0"));
assert!(!Update::is_new_version_higher("0.9.0", "0.10.0"));
assert!(!Update::is_new_version_higher("0.9.9", "0.10.1"));
assert!(!Update::is_new_version_higher("0.10.9", "0.11.0"));
}
}

View File

@@ -491,7 +491,7 @@ mod tests {
// Verify client has updated parameters
assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim"));
let mut expected_key_path: PathBuf = key_path.clone();
let mut expected_key_path: PathBuf = key_path;
expected_key_path.push("pi@192.168.1.31.key");
assert_eq!(
client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(),

View File

@@ -126,7 +126,7 @@ mod tests {
#[serial]
fn test_system_environment_get_bookmarks_paths() {
assert_eq!(
get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")),
get_bookmarks_paths(Path::new("/home/omar/.config/termscp/")),
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
);
}
@@ -135,7 +135,7 @@ mod tests {
#[serial]
fn test_system_environment_get_config_paths() {
assert_eq!(
get_config_paths(&Path::new("/home/omar/.config/termscp/")),
get_config_paths(Path::new("/home/omar/.config/termscp/")),
(
PathBuf::from("/home/omar/.config/termscp/config.toml"),
PathBuf::from("/home/omar/.config/termscp/.ssh/")
@@ -147,7 +147,7 @@ mod tests {
#[serial]
fn test_system_environment_get_log_paths() {
assert_eq!(
get_log_paths(&Path::new("/home/omar/.config/termscp/")),
get_log_paths(Path::new("/home/omar/.config/termscp/")),
PathBuf::from("/home/omar/.config/termscp/termscp.log"),
);
}
@@ -156,7 +156,7 @@ mod tests {
#[serial]
fn test_system_environment_get_theme_path() {
assert_eq!(
get_theme_path(&Path::new("/home/omar/.config/termscp/")),
get_theme_path(Path::new("/home/omar/.config/termscp/")),
PathBuf::from("/home/omar/.config/termscp/theme.toml"),
);
}

View File

@@ -96,7 +96,7 @@ mod tests {
#[test]
fn test_system_keys_filestorage_make_dir() {
let storage: FileStorage = FileStorage::new(&Path::new("/tmp/"));
let storage: FileStorage = FileStorage::new(Path::new("/tmp/"));
assert_eq!(
storage.make_file_path("bookmarks").as_path(),
Path::new("/tmp/.bookmarks.key")

View File

@@ -10,7 +10,7 @@ pub mod keyringstorage;
use thiserror::Error;
/// defines the error type for the `KeyStorage`
#[derive(Debug, Error, PartialEq)]
#[derive(Debug, Error, PartialEq, Eq)]
pub enum KeyStorageError {
#[cfg(feature = "with-keyring")]
#[error("Key has a bad syntax")]

View File

@@ -5,12 +5,16 @@
// Locals
use super::config_client::ConfigClient;
// Ext
use remotefs_ssh::SshKeyStorage as SshKeyStorageT;
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
use ssh2_config::SshConfig;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub struct SshKeyStorage {
hosts: HashMap<String, PathBuf>, // Association between {user}@{host} and RSA key path
/// Association between {user}@{host} and RSA key path
hosts: HashMap<String, PathBuf>,
/// Ssh2 configuration
ssh_config: Option<SshConfig>,
}
impl SshKeyStorage {
@@ -19,6 +23,7 @@ impl SshKeyStorage {
pub fn empty() -> Self {
SshKeyStorage {
hosts: HashMap::new(),
ssh_config: None,
}
}
@@ -34,21 +39,67 @@ impl SshKeyStorage {
let key: String = Self::make_mapkey(host, username);
self.hosts.insert(key, p);
}
}
impl SshKeyStorageT for SshKeyStorage {
fn resolve(&self, host: &str, username: &str) -> Option<&Path> {
/// Parse ssh2 config
fn parse_ssh2_config(path: &str) -> Result<SshConfig, String> {
use std::fs::File;
use std::io::BufReader;
let mut reader = File::open(path)
.map_err(|e| format!("failed to open {}: {}", path, e))
.map(BufReader::new)?;
SshConfig::default()
.parse(&mut reader)
.map_err(|e| format!("Failed to parse ssh2 config: {}", e))
}
/// Resolve host via termscp ssh keys storage
fn resolve_host_in_termscp_storage(&self, host: &str, username: &str) -> Option<&Path> {
let key: String = Self::make_mapkey(host, username);
self.hosts.get(&key).map(|x| x.as_path())
}
/// Resolve host via ssh2 configuration
fn resolve_host_in_ssh2_configuration(&self, host: &str) -> Option<PathBuf> {
if let Some(config) = self.ssh_config.as_ref() {
let params = config.query(host);
params
.identity_file
.as_ref()
.and_then(|x| x.get(0).cloned())
} else {
debug!("ssh2 config is not available; no key has been found");
None
}
}
}
impl SshKeyStorageTrait for SshKeyStorage {
fn resolve(&self, host: &str, username: &str) -> Option<PathBuf> {
// search in termscp keys
if let Some(path) = self.resolve_host_in_termscp_storage(host, username) {
return Some(path.to_path_buf());
}
debug!(
"couldn't find any ssh key associated to {} at {}. Trying with ssh2 config",
username, host
);
// otherwise search in configuration
self.resolve_host_in_ssh2_configuration(host)
}
}
impl From<&ConfigClient> for SshKeyStorage {
fn from(cfg_client: &ConfigClient) -> Self {
// read ssh2 config
let ssh_config = cfg_client.get_ssh_config().and_then(|x| {
debug!("reading ssh config at {}", x);
Self::parse_ssh2_config(x).ok()
});
let mut hosts: HashMap<String, PathBuf> =
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
debug!("Setting up SSH key storage");
// Iterate over keys
// Iterate over keys in storage
for key in cfg_client.iter_ssh_keys() {
match cfg_client.get_ssh_key(key) {
Ok(host) => match host {
@@ -66,7 +117,7 @@ impl From<&ConfigClient> for SshKeyStorage {
info!("Got SSH key for {}", key);
}
// Return storage
SshKeyStorage { hosts }
SshKeyStorage { hosts, ssh_config }
}
}
@@ -75,6 +126,7 @@ mod tests {
use super::*;
use crate::system::config_client::ConfigClient;
use crate::utils::test_helpers;
use pretty_assertions::assert_eq;
use std::path::Path;
@@ -93,7 +145,7 @@ mod tests {
// Create ssh key storage
let storage: SshKeyStorage = SshKeyStorage::from(&client);
// Verify key exists
let mut exp_key_path: PathBuf = key_path.clone();
let mut exp_key_path: PathBuf = key_path;
exp_key_path.push("pi@192.168.1.31.key");
assert_eq!(
*storage.resolve("192.168.1.31", "pi").unwrap(),
@@ -103,6 +155,35 @@ mod tests {
assert!(storage.resolve("deskichup", "veeso").is_none());
}
#[test]
fn sould_resolve_key_from_ssh2_config() {
let rsa_key = test_helpers::create_sample_file_with_content("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a");
let ssh_config_file = test_helpers::create_sample_file_with_content(format!(
r#"
Host test
HostName 127.0.0.1
Port 2222
User test
IdentityFile {}
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
"#,
rsa_key.path().display()
));
// make storage
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()
.unwrap();
client.set_ssh_config(Some(ssh_config_file.path().to_string_lossy().to_string()));
let storage: SshKeyStorage = SshKeyStorage::from(&client);
assert_eq!(
storage.resolve("test", "pi").unwrap().as_path(),
rsa_key.path()
);
}
#[test]
fn test_system_sshkey_storage_empty() {
let storage: SshKeyStorage = SshKeyStorage::empty();

View File

@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
use tui_realm_stdlib::{Input, List, Radio};
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
@@ -223,6 +223,14 @@ impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Form(FormMsg::DeleteBookmark)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseDeleteBookmark)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -283,6 +291,14 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Form(FormMsg::DeleteRecent)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseDeleteRecent)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {

View File

@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
@@ -186,6 +186,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Form(FormMsg::Quit)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseQuitPopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -245,6 +253,14 @@ impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Form(FormMsg::InstallUpdate)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseInstallUpdatePopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {

View File

@@ -60,14 +60,14 @@ pub enum Id {
WindowSizeError,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, Eq, PartialEq)]
pub enum Msg {
Form(FormMsg),
Ui(UiMsg),
None,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
pub enum FormMsg {
Connect,
DeleteBookmark,
@@ -81,7 +81,7 @@ pub enum FormMsg {
SaveBookmark,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
pub enum UiMsg {
AddressBlurDown,
AddressBlurUp,

View File

@@ -148,9 +148,7 @@ impl FileTransferActivity {
}
};
// Edit file
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
return Err(err);
}
self.edit_local_file(tmpfile.as_path())?;
// Get local fs entry
let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e,

View File

@@ -192,7 +192,8 @@ impl Component<Msg, NoUserEvent> for Log {
}
// -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
code: Key::BackTab | Key::Tab | Key::Char('p'),
..
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
_ => None,
}

View File

@@ -147,6 +147,14 @@ impl Component<Msg, NoUserEvent> for DeletePopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseDeletePopup))
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Transfer(TransferMsg::DeleteFile)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseDeletePopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -203,6 +211,14 @@ impl Component<Msg, NoUserEvent> for DisconnectPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseDisconnectPopup))
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::Disconnect)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseDisconnectPopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -1195,6 +1211,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseQuitPopup))
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::Quit)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseQuitPopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -1344,6 +1368,14 @@ impl Component<Msg, NoUserEvent> for ReplacePopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups))
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::PendingAction(PendingActionMsg::TransferPendingFile)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -1790,6 +1822,16 @@ impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction(
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
)),
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::PendingAction(PendingActionMsg::MakePendingDirectory)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::PendingAction(
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -1967,6 +2009,14 @@ impl Component<Msg, NoUserEvent> for WatcherPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseWatcherPopup))
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Transfer(TransferMsg::ToggleWatch)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::CloseWatcherPopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {

View File

@@ -82,14 +82,11 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
Some(Msg::None)
}
// -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseFindExplorer))
}
Event::Keyboard(KeyEvent {
code: Key::Left | Key::Right | Key::Tab,
code: Key::Left | Key::Right | Key::Tab | Key::BackTab,
..
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
Event::Keyboard(KeyEvent {
@@ -206,14 +203,11 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
Some(Msg::None)
}
// -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
}
Event::Keyboard(KeyEvent {
code: Key::Right | Key::Tab,
code: Key::Right | Key::Tab | Key::BackTab,
..
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
Event::Keyboard(KeyEvent {
@@ -275,6 +269,10 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
code: Key::Char('o') | Key::Function(4),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
Event::Keyboard(KeyEvent {
code: Key::Char('p'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
Event::Keyboard(KeyEvent {
code: Key::Char('r') | Key::Function(6),
modifiers: KeyModifiers::NONE,
@@ -386,14 +384,11 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
Some(Msg::None)
}
// -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
}
Event::Keyboard(KeyEvent {
code: Key::Left | Key::Tab,
code: Key::Left | Key::Tab | Key::BackTab,
..
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
Event::Keyboard(KeyEvent {
@@ -455,6 +450,10 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
code: Key::Char('o') | Key::Function(4),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
Event::Keyboard(KeyEvent {
code: Key::Char('p'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
Event::Keyboard(KeyEvent {
code: Key::Char('r') | Key::Function(6),
modifiers: KeyModifiers::NONE,

View File

@@ -208,7 +208,7 @@ impl FileTransferActivity {
Ok(h) => {
let hostname: String = h.as_os_str().to_string_lossy().to_string();
let tokens: Vec<&str> = hostname.split('.').collect();
String::from(*tokens.get(0).unwrap_or(&"localhost"))
String::from(*tokens.first().unwrap_or(&"localhost"))
}
Err(_) => String::from("localhost"),
};

View File

@@ -140,7 +140,6 @@ enum UiMsg {
CloseWatchedPathsList,
CloseWatcherPopup,
Disconnect,
ExplorerBackTabbed,
LogBackTabbed,
Quit,
ReplacePopupTabbed,
@@ -153,6 +152,7 @@ enum UiMsg {
ShowFindPopup,
ShowGotoPopup,
ShowKeybindingsPopup,
ShowLogPanel,
ShowMkdirPopup,
ShowNewFilePopup,
ShowOpenWithPopup,

View File

@@ -326,11 +326,7 @@ impl FileTransferActivity {
break;
}
// Send entry; name is always None after first call
if let Err(err) =
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)
{
return Err(err);
}
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)?
}
Ok(())
}
@@ -717,13 +713,11 @@ impl FileTransferActivity {
}
// Receive entry; name is always None after first call
// Local path becomes local_dir_path
if let Err(err) = self.filetransfer_recv_recurse(
self.filetransfer_recv_recurse(
entry,
local_dir_path.as_path(),
None,
) {
return Err(err);
}
)?
}
Ok(())
}

View File

@@ -102,7 +102,7 @@ impl FileTransferActivity {
self.action_submit_local(entry);
// Update file list if sync
if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_remote_filelist();
self.update_remote_filelist();
}
self.update_local_filelist();
}
@@ -112,7 +112,7 @@ impl FileTransferActivity {
self.action_submit_remote(entry);
// Update file list if sync
if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_local_filelist();
self.update_local_filelist();
}
self.update_remote_filelist();
}
@@ -161,7 +161,7 @@ impl FileTransferActivity {
FileExplorerTab::Local => {
self.action_go_to_local_upper_dir();
if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_remote_filelist();
self.update_remote_filelist();
}
// Reload file list component
self.update_local_filelist()
@@ -169,7 +169,7 @@ impl FileTransferActivity {
FileExplorerTab::Remote => {
self.action_go_to_remote_upper_dir();
if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_local_filelist();
self.update_local_filelist();
}
// Reload file list component
self.update_remote_filelist()
@@ -182,7 +182,7 @@ impl FileTransferActivity {
FileExplorerTab::Local => {
self.action_go_to_previous_local_dir();
if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_remote_filelist();
self.update_remote_filelist();
}
// Reload file list component
self.update_local_filelist()
@@ -190,7 +190,7 @@ impl FileTransferActivity {
FileExplorerTab::Remote => {
self.action_go_to_previous_remote_dir();
if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_local_filelist();
self.update_local_filelist();
}
// Reload file list component
self.update_remote_filelist()
@@ -406,7 +406,7 @@ impl FileTransferActivity {
self.disconnect();
self.umount_disconnect();
}
UiMsg::ExplorerBackTabbed => {
UiMsg::ShowLogPanel => {
assert!(self.app.active(&Id::Log).is_ok());
}
UiMsg::LogBackTabbed => {

View File

@@ -80,8 +80,8 @@ impl FileTransferActivity {
self.refresh_local_status_bar();
self.refresh_remote_status_bar();
// Update components
let _ = self.update_local_filelist();
let _ = self.update_remote_filelist();
self.update_local_filelist();
self.update_remote_filelist();
// Global listener
self.mount_global_listener();
// Give focus to local explorer

View File

@@ -6,7 +6,7 @@ use super::{CommonMsg, Msg, ViewLayout};
use tui_realm_stdlib::{List, Paragraph, Radio, Span};
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
@@ -296,6 +296,14 @@ impl Component<Msg, NoUserEvent> for SavePopup {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Common(CommonMsg::SaveConfig)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Common(CommonMsg::CloseSavePopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {

View File

@@ -278,7 +278,7 @@ impl NotificationsThreshold {
parse_bytesize(bytes).is_some()
}
fn char_valid(_input: &str, incoming: char) -> bool {
incoming.is_digit(10) || ['B', 'K', 'M', 'G', 'T', 'P'].contains(&incoming)
incoming.is_ascii_digit() || ['B', 'K', 'M', 'G', 'T', 'P'].contains(&incoming)
}
Self {
component: Input::default()

View File

@@ -58,6 +58,14 @@ impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char('y'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ssh(SshMsg::DeleteSshKey)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ssh(SshMsg::CloseDelSshKeyPopup)),
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
@@ -143,7 +151,8 @@ impl Component<Msg, NoUserEvent> for SshKeys {
_ => Some(Msg::None),
},
Event::Keyboard(KeyEvent {
code: Key::Delete, ..
code: Key::Delete | Key::Char('e'),
..
}) => Some(Msg::Ssh(SshMsg::ShowDelSshKeyPopup)),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),

View File

@@ -99,7 +99,7 @@ pub enum IdTheme {
TransferTitle2,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Msg {
Common(CommonMsg),
Config(ConfigMsg),
@@ -108,7 +108,7 @@ pub enum Msg {
None,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommonMsg {
ChangeLayout,
CloseErrorPopup,
@@ -125,7 +125,7 @@ pub enum CommonMsg {
WindowResized,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigMsg {
CheckUpdatesBlurDown,
CheckUpdatesBlurUp,
@@ -152,7 +152,7 @@ pub enum ConfigMsg {
TextEditorBlurUp,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SshMsg {
CloseDelSshKeyPopup,
CloseNewSshKeyPopup,
@@ -165,7 +165,7 @@ pub enum SshMsg {
SshUsernameBlur,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ThemeMsg {
AuthAddressBlurDown,
AuthAddressBlurUp,
@@ -230,7 +230,7 @@ const STORE_CONFIG_CHANGED: &str = "SETUP_CONFIG_CHANGED";
/// ### ViewLayout
///
/// Current view layout
#[derive(PartialEq)]
#[derive(PartialEq, Eq)]
pub enum ViewLayout {
SetupForm,
SshKeys,

View File

@@ -20,8 +20,6 @@ impl SetupActivity {
self.new_app(ViewLayout::SshKeys);
// Load keys
self.reload_ssh_keys();
// Give focus
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
}
pub(crate) fn view_ssh_keys(&mut self) {
@@ -137,5 +135,6 @@ impl SetupActivity {
vec![]
)
.is_ok());
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
}
}

View File

@@ -32,7 +32,7 @@ mod tests {
fn test_utils_crypto_aes128() {
let key: &str = "MYSUPERSECRETKEY";
let input: &str = "Hello world!";
let secret: String = aes128_b64_crypt(&key, input);
let secret: String = aes128_b64_crypt(key, input);
assert_eq!(secret.as_str(), "z4Z6LpcpYqBW4+bkIok+5A==");
assert_eq!(
aes128_b64_decrypt(key, secret.as_str())

View File

@@ -300,7 +300,7 @@ mod tests {
#[test]
fn test_utils_fmt_time() {
let system_time: SystemTime = SystemTime::from(SystemTime::UNIX_EPOCH);
let system_time: SystemTime = SystemTime::UNIX_EPOCH;
assert_eq!(
fmt_time(system_time, "%Y-%m-%d"),
String::from("1970-01-01")
@@ -326,12 +326,12 @@ mod tests {
#[test]
#[cfg(target_family = "unix")]
fn test_utils_fmt_path_elide() {
let p: &Path = &Path::new("/develop/pippo");
let p: &Path = Path::new("/develop/pippo");
// Under max size
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/pippo"));
// Above max size, only one ancestor
assert_eq!(fmt_path_elide(p, 8), String::from("/develop/pippo"));
let p: &Path = &Path::new("/develop/pippo/foo/bar");
let p: &Path = Path::new("/develop/pippo/foo/bar");
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/…/foo/bar"));
}

View File

@@ -14,56 +14,57 @@ use crate::system::environment;
// Ext
use bytesize::ByteSize;
use regex::Regex;
use lazy_regex::{Lazy, Regex};
use std::path::PathBuf;
use std::str::FromStr;
use tuirealm::tui::style::Color;
use tuirealm::utils::parser as tuirealm_parser;
// Regex
lazy_static! {
/**
* This regex matches the protocol used as option
* Regex matches:
* - group 1: Some(protocol) | None
* - group 2: Some(other args)
*/
static ref REMOTE_OPT_PROTOCOL_REGEX: Regex = Regex::new(r"(?:([a-z0-9]+)://)?(?:(.+))").unwrap();
/**
* This regex matches the protocol used as option
* Regex matches:
* - group 1: Some(protocol) | None
* - group 2: Some(other args)
*/
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(?:(.+))");
/**
* Regex matches:
* - group 1: Some(user) | None
* - group 2: Address
* - group 3: Some(port) | None
* - group 4: Some(path) | None
*/
static ref REMOTE_GENERIC_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?").ok().unwrap();
/**
* Regex matches:
* - group 1: Some(user) | None
* - group 2: Address
* - group 3: Some(port) | None
* - group 4: Some(path) | None
*/
static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?"
);
/**
* Regex matches:
* - group 1: Bucket
* - group 2: Region
* - group 3: Some(profile) | None
* - group 4: Some(path) | None
*/
static ref REMOTE_S3_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?").unwrap();
/**
* Regex matches:
* - group 1: Bucket
* - group 2: Region
* - group 3: Some(profile) | None
* - group 4: Some(path) | None
*/
static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
lazy_regex!(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
/**
* Regex matches:
* - group 1: Version
* E.g. termscp-0.3.2 => 0.3.2
* v0.4.0 => 0.4.0
*/
static ref SEMVER_REGEX: Regex = Regex::new(r".*(:?[0-9]\.[0-9]\.[0-9])").unwrap();
/**
* Regex matches:
* - group 1: Version
* E.g. termscp-0.3.2 => 0.3.2
* v0.4.0 => 0.4.0
*/
static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r".*(:?[0-9]\.[0-9]\.[0-9])");
/**
* Regex matches:
* - group 1: amount (number)
* - group 4: unit (K, M, G, T, P)
*/
static ref BYTESIZE_REGEX: Regex = Regex::new(r"(:?([0-9])+)( )*(:?[KMGTP])?B$").unwrap();
}
/**
* Regex matches:
* - group 1: amount (number)
* - group 4: unit (K, M, G, T, P)
*/
static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
// -- remote opts

View File

@@ -94,11 +94,11 @@ mod test {
#[test]
fn absolutize_path() {
assert_eq!(
absolutize(&Path::new("/home/omar"), &Path::new("readme.txt")).as_path(),
absolutize(Path::new("/home/omar"), Path::new("readme.txt")).as_path(),
Path::new("/home/omar/readme.txt")
);
assert_eq!(
absolutize(&Path::new("/home/omar"), &Path::new("/tmp/readme.txt")).as_path(),
absolutize(Path::new("/home/omar"), Path::new("/tmp/readme.txt")).as_path(),
Path::new("/tmp/readme.txt")
);
}

View File

@@ -21,14 +21,15 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) {
)
}
/// Create sample file with default lorem ipsum content
pub fn create_sample_file() -> NamedTempFile {
// Write
create_sample_file_with_content("Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus.")
}
/// Create sample file with provided content
pub fn create_sample_file_with_content(content: impl std::fmt::Display) -> NamedTempFile {
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();
writeln!(tmpfile, "{}", content).unwrap();
tmpfile
}