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
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install dependencies - 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 - uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
+35
View File
@@ -1,6 +1,7 @@
# Changelog # Changelog
- [Changelog](#changelog) - [Changelog](#changelog)
- [0.10.0](#0100)
- [0.9.0](#090) - [0.9.0](#090)
- [0.8.2](#082) - [0.8.2](#082)
- [0.8.1](#081) - [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 ## 0.9.0
Released on 18/06/2022 Released on 18/06/2022
Generated
+629 -438
View File
File diff suppressed because it is too large Load Diff
+30 -22
View File
@@ -1,5 +1,5 @@
[package] [package]
authors = ["Christian Visintin"] authors = ["Christian Visintin <christian.visintin1997@gmail.com>"]
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3" description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3"
edition = "2021" edition = "2021"
@@ -10,7 +10,7 @@ license = "MIT"
name = "termscp" name = "termscp"
readme = "README.md" readme = "README.md"
repository = "https://github.com/veeso/termscp" repository = "https://github.com/veeso/termscp"
version = "0.9.0" version = "0.10.0"
[package.metadata.rpm] [package.metadata.rpm]
package = "termscp" package = "termscp"
@@ -31,51 +31,59 @@ name = "termscp"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
argh = "0.1.7" argh = "0.1.9"
bitflags = "1.3.2" bitflags = "1.3.2"
bytesize = "1.1.0" bytesize = "1.1.0"
chrono = "0.4.19" chrono = "0.4.22"
content_inspector = "0.2.4" content_inspector = "0.2.4"
dirs = "4.0.0" dirs = "4.0.0"
edit = "0.1.4" edit = "0.1.4"
hostname = "0.3.1" 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" lazy_static = "1.4.0"
log = "0.4.17" log = "0.4.17"
magic-crypt = "3.1.10" magic-crypt = "3.1.10"
notify = "4.0.17" notify = "4.0.17"
notify-rust = { version = "4.5.6", default-features = false, features = [ "d" ] } notify-rust = { version = "4.5.10", default-features = false, features = [ "d" ] }
open = "2.1.3" open = "3.0.3"
rand = "0.8.5" rand = "0.8.5"
regex = "1.5.6"
remotefs = "^0.2.0" remotefs = "^0.2.0"
remotefs-aws-s3 = "^0.2.0" remotefs-aws-s3 = { version = "^0.2.1", default-features = false, features = [ "find", "rustls" ] }
remotefs-ftp = { version = "^0.1.0", features = [ "secure" ] }
remotefs-ssh = { version = "^0.1.1", features = [ "ssh2-vendored" ] } rpassword = "7.0.0"
rpassword = "6.0.1" self_update = { version = "0.32.0", default-features = false, features = [ "rustls", "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
self_update = { version = "0.30.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] } serde = { version = "^1", features = [ "derive" ] }
serde = { version = "^1.0.0", features = [ "derive" ] }
simplelog = "0.12.0" simplelog = "0.12.0"
ssh2-config = "^0.1.3"
tempfile = "3.2.0" tempfile = "3.2.0"
thiserror = "^1.0.0" thiserror = "^1"
toml = "0.5.0" toml = "0.5.0"
tui-realm-stdlib = "1.1.6" tui-realm-stdlib = "1.1.7"
tuirealm = "1.6.0" tuirealm = "1.8.0"
unicode-width = "0.1.8" unicode-width = "0.1.10"
whoami = "1.2.1" version-compare = "0.1.0"
wildmatch = "2.1.0" whoami = "1.2.3"
wildmatch = "2.1.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.2.1" pretty_assertions = "^1.3"
serial_test = "^0.7.0" serial_test = "^0.9"
[features] [features]
default = [ "with-keyring" ] default = [ "with-keyring" ]
github-actions = [ ] github-actions = [ ]
with-keyring = [ "keyring" ] 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\")"]
[target."cfg(target_family = \"unix\")".dependencies] [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" users = "0.11.0"
[profile.dev] [profile.dev]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+3 -4
View File
@@ -63,7 +63,7 @@
</p> </p>
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></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"> <p align="center">
<a href="https://opensource.org/licenses/MIT" <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 🧪 ## 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 ⏲️*: Planned for *future updates ⏲️*:
@@ -233,9 +233,8 @@ Planned for *future updates ⏲️*:
- 🇫🇷 French - 🇫🇷 French
- 🇩🇪 German - 🇩🇪 German
- 🇮🇹 Italian - 🇮🇹 Italian
- 🇳🇱 Dutch
- 🇪🇸 Spanish - 🇪🇸 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. 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.
+2 -2
View File
@@ -14,7 +14,7 @@ cargo build --release
# Make zip # Make zip
$zipName = "termscp-v$version-x86_64-pc-windows-msvc.zip" $zipName = "termscp-v$version-x86_64-pc-windows-msvc.zip"
Set-Location .\target\release\ Set-Location .\target\release\
Compress-Archive termscp.exe $zipName Compress-Archive -Force termscp.exe $zipName
# Get checksum # Get checksum
checksum.exe -t sha256 $zipName Get-FileHash $zipName
Move-Item $zipName .\..\..\dist\pkgs\windows\$zipName Move-Item $zipName .\..\..\dist\pkgs\windows\$zipName
-2
View File
@@ -8,8 +8,6 @@ RUN apt update && apt install -y \
git \ git \
gcc \ gcc \
pkg-config \ pkg-config \
libssl-dev \
libssh2-1-dev \
libdbus-1-dev \ libdbus-1-dev \
curl curl
# Clone repository # Clone repository
+7 -6
View File
@@ -7,11 +7,12 @@ WORKDIR /usr/src/
RUN yum -y install \ RUN yum -y install \
git \ git \
gcc \ gcc \
openssl \
pkgconfig \ pkgconfig \
gcc \
make \
dbus-devel \ dbus-devel \
openssl-devel \ bash \
bash rpm-build
# Install rust # Install rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \
chmod +x /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 RUN git clone --branch $branch https://github.com/veeso/termscp.git
# Set workdir to termscp # Set workdir to termscp
WORKDIR /usr/src/termscp/ WORKDIR /usr/src/termscp/
# Install cargo arxch
RUN source $HOME/.cargo/env && cargo install cargo-rpm
# Build for x86_64 # Build for x86_64
RUN source $HOME/.cargo/env && cargo build --release RUN source $HOME/.cargo/env && cargo build --release
# Install cargo rpm
RUN source $HOME/.cargo/env && cargo install cargo-rpm
# Build pkgs # 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"] CMD ["sh"]
-2
View File
@@ -6,8 +6,6 @@ RUN apt update && apt install -y \
git \ git \
gcc \ gcc \
pkg-config \ pkg-config \
libssl-dev \
libssh2-1-dev \
libdbus-1-dev \ libdbus-1-dev \
curl curl
+1 -2
View File
@@ -8,9 +8,8 @@ RUN apt update && apt install -y \
git \ git \
gcc \ gcc \
pkg-config \ pkg-config \
libssl-dev \
libssh2-1-dev \
libdbus-1-dev \ libdbus-1-dev \
build-essential \
bash \ bash \
curl curl
+1 -1
View File
@@ -63,7 +63,7 @@
</p> </p>
<p align="center">Entwickelt von <a href="https://veeso.github.io/" target="_blank">@veeso</a></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"> <p align="center">
<a href="https://opensource.org/licenses/MIT" <a href="https://opensource.org/licenses/MIT"
+1
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 | | `<M>` | Select a file | Mark |
| `<N>` | Create new file with provided name | New | | `<N>` | Create new file with provided name | New |
| `<O|F4>` | Edit file; see Text editor | Open | | `<O|F4>` | Edit file; see Text editor | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Quit termscp | Quit | | `<Q|F10>` | Quit termscp | Quit |
| `<R|F6>` | Rename file | Rename | | `<R|F6>` | Rename file | Rename |
| `<S|F2>` | Save file as... | Save | | `<S|F2>` | Save file as... | Save |
+1 -1
View File
@@ -63,7 +63,7 @@
</p> </p>
<p align="center">Desarrollado por <a href="https://veeso.github.io/" target="_blank">@veeso</a></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"> <p align="center">
<a href="https://opensource.org/licenses/MIT" <a href="https://opensource.org/licenses/MIT"
+1
View File
@@ -204,6 +204,7 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
| `<M>` | Seleccione un archivo | Mark | | `<M>` | Seleccione un archivo | Mark |
| `<N>` | Crear un nuevo archivo con el nombre proporcionado | New | | `<N>` | Crear un nuevo archivo con el nombre proporcionado | New |
| `<O|F4>` | Editar archivo | Open | | `<O|F4>` | Editar archivo | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Salir de termscp | Quit | | `<Q|F10>` | Salir de termscp | Quit |
| `<R|F6>` | Renombrar archivo | Rename | | `<R|F6>` | Renombrar archivo | Rename |
| `<S|F2>` | Guardar archivo como... | Save | | `<S|F2>` | Guardar archivo como... | Save |
+1 -1
View File
@@ -63,7 +63,7 @@
</p> </p>
<p align="center">Développé par <a href="https://veeso.github.io/" target="_blank">@veeso</a></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"> <p align="center">
<a href="https://opensource.org/licenses/MIT" <a href="https://opensource.org/licenses/MIT"
+1
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 | | `<M>` | Sélectionner un fichier | Mark |
| `<N>` | Créer un nouveau fichier avec le nom fourni | New | | `<N>` | Créer un nouveau fichier avec le nom fourni | New |
| `<O|F4>` | Modifier le fichier | Open | | `<O|F4>` | Modifier le fichier | Open |
| `<P>` | Ouvre le panel de journals | Panel |
| `<Q|F10>` | Quitter termscp | Quit | | `<Q|F10>` | Quitter termscp | Quit |
| `<R|F6>` | Renommer le fichier | Rename | | `<R|F6>` | Renommer le fichier | Rename |
| `<S|F2>` | Enregistrer le fichier sous... | Save | | `<S|F2>` | Enregistrer le fichier sous... | Save |
+1 -1
View File
@@ -63,7 +63,7 @@
</p> </p>
<p align="center">Sviluppato da <a href="https://veeso.github.io/" target="_blank">@veeso</a></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"> <p align="center">
<a href="https://opensource.org/licenses/MIT" <a href="https://opensource.org/licenses/MIT"
+1
View File
@@ -198,6 +198,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
| `<M>` | Seleziona file | Mark | | `<M>` | Seleziona file | Mark |
| `<N>` | Crea nuovo file con il nome fornito | New | | `<N>` | Crea nuovo file con il nome fornito | New |
| `<O|F4>` | Modifica file; Vedi text editor | Open | | `<O|F4>` | Modifica file; Vedi text editor | Open |
| `<P>` | Apri pannello log | Panel |
| `<Q|F10>` | Termina termscp | Quit | | `<Q|F10>` | Termina termscp | Quit |
| `<R|F6>` | Rinomina file | Rename | | `<R|F6>` | Rinomina file | Rename |
| `<S|F2>` | Salva file con nome | Save | | `<S|F2>` | Salva file con nome | Save |
+1
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 | | `<M>` | Select a file | Mark |
| `<N>` | Create new file with provided name | New | | `<N>` | Create new file with provided name | New |
| `<O|F4>` | Edit file; see Text editor | Open | | `<O|F4>` | Edit file; see Text editor | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Quit termscp | Quit | | `<Q|F10>` | Quit termscp | Quit |
| `<R|F6>` | Rename file | Rename | | `<R|F6>` | Rename file | Rename |
| `<S|F2>` | Save file as... | Save | | `<S|F2>` | Save file as... | Save |
+1 -1
View File
@@ -63,7 +63,7 @@
</p> </p>
<p align="center">由 <a href="https://veeso.github.io/" target="_blank">@veeso</a> 开发</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"> <p align="center">
<a href="https://opensource.org/licenses/MIT" <a href="https://opensource.org/licenses/MIT"
+1
View File
@@ -199,6 +199,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
| `<M>` | 选中文件 | Mark | | `<M>` | 选中文件 | Mark |
| `<N>` | 使用键入的名称新建文件 | New | | `<N>` | 使用键入的名称新建文件 | New |
| `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open | | `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open |
| `<P>` | 打开日志面板 | Panel |
| `<Q|F10>` | 退出termscp | Quit | | `<Q|F10>` | 退出termscp | Quit |
| `<R|F7>` | 重命名文件 | Rename | | `<R|F7>` | 重命名文件 | Rename |
| `<S|F2>` | 另存为... | Save | | `<S|F2>` | 另存为... | Save |
+2 -2
View File
@@ -8,7 +8,7 @@
# -f, -y, --force, --yes # -f, -y, --force, --yes
# Skip the confirmation prompt during installation # 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}" GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb" DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm" RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm"
@@ -296,7 +296,7 @@ install_bsd_cargo_deps() {
} }
install_linux_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 rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel"
local arch_deps="gcc openssl pkg-config dbus" local arch_deps="gcc openssl pkg-config dbus"
local deps_cmd="" local deps_cmd=""
+2 -2
View File
@@ -19,7 +19,7 @@ pub struct UserHosts {
} }
/// Bookmark describes a single bookmark entry in the user hosts storage /// 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 { pub struct Bookmark {
#[serde( #[serde(
deserialize_with = "deserialize_protocol", deserialize_with = "deserialize_protocol",
@@ -41,7 +41,7 @@ pub struct Bookmark {
} }
/// Connection parameters for Aws s3 protocol /// 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 struct S3Params {
pub bucket: String, pub bucket: String,
pub region: Option<String>, pub region: Option<String>,
+1 -1
View File
@@ -106,7 +106,7 @@ mod tests {
assert_eq!(ui.file_fmt, Some(String::from("{NAME}"))); assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
let cfg: UserConfig = UserConfig { let cfg: UserConfig = UserConfig {
user_interface: ui, user_interface: ui,
remote: remote, remote,
}; };
assert_eq!( assert_eq!(
*cfg.remote *cfg.remote
+1 -1
View File
@@ -12,7 +12,7 @@ use tuirealm::tui::style::Color;
/// ### Theme /// ### Theme
/// ///
/// Theme contains all the colors lookup table for termscp /// 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 { pub struct Theme {
// -- auth // -- auth
#[serde( #[serde(
+4 -7
View File
@@ -8,7 +8,7 @@ use crate::utils::path::diff_paths;
use crate::utils::string::secure_substring; use crate::utils::string::secure_substring;
// Ext // Ext
use bytesize::ByteSize; use bytesize::ByteSize;
use regex::Regex; use lazy_regex::{Lazy, Regex};
use remotefs::File; use remotefs::File;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
@@ -32,17 +32,14 @@ const FMT_KEY_SYMLINK: &str = "SYMLINK";
const FMT_KEY_USER: &str = "USER"; const FMT_KEY_USER: &str = "USER";
// Default // Default
const FMT_DEFAULT_STX: &str = "{NAME} {PEX} {USER} {SIZE} {MTIME}"; const FMT_DEFAULT_STX: &str = "{NAME} {PEX} {USER} {SIZE} {MTIME}";
// Regex /**
lazy_static! {
/**
* Regex matches: * Regex matches:
* - group 0: KEY NAME * - group 0: KEY NAME
* - group 1?: LENGTH * - group 1?: LENGTH
* - group 2?: EXTRA * - group 2?: EXTRA
*/ */
static ref FMT_KEY_REGEX: Regex = Regex::new(r"\{(.*?)\}").ok().unwrap(); static FMT_KEY_REGEX: Lazy<Regex> = lazy_regex!(r"\{(.*?)\}");
static ref FMT_ATTR_REGEX: Regex = Regex::new(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?").ok().unwrap(); 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. /// 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 /// A callChain is instantiated starting from the Formatter syntax and the regex, once the groups are found
+7 -7
View File
@@ -25,7 +25,7 @@ bitflags! {
} }
/// FileSorting defines the criteria for sorting files /// 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 { pub enum FileSorting {
Name, Name,
ModifyTime, ModifyTime,
@@ -34,7 +34,7 @@ pub enum FileSorting {
} }
/// GroupDirs defines how directories should be grouped in sorting files /// GroupDirs defines how directories should be grouped in sorting files
#[derive(PartialEq, std::fmt::Debug)] #[derive(PartialEq, Eq, std::fmt::Debug)]
pub enum GroupDirs { pub enum GroupDirs {
First, First,
Last, Last,
@@ -318,8 +318,8 @@ mod tests {
explorer.stack_size = 2; explorer.stack_size = 2;
explorer.dirstack = VecDeque::with_capacity(2); explorer.dirstack = VecDeque::with_capacity(2);
// Push dir // Push dir
explorer.pushd(&Path::new("/tmp")); explorer.pushd(Path::new("/tmp"));
explorer.pushd(&Path::new("/home/omar")); explorer.pushd(Path::new("/home/omar"));
// Pop // Pop
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar")); assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar"));
assert_eq!(explorer.dirstack.len(), 1); assert_eq!(explorer.dirstack.len(), 1);
@@ -328,9 +328,9 @@ mod tests {
// Dirstack is empty now // Dirstack is empty now
assert!(explorer.popd().is_none()); assert!(explorer.popd().is_none());
// Exceed limit // Exceed limit
explorer.pushd(&Path::new("/tmp")); explorer.pushd(Path::new("/tmp"));
explorer.pushd(&Path::new("/home/omar")); explorer.pushd(Path::new("/home/omar"));
explorer.pushd(&Path::new("/dev")); explorer.pushd(Path::new("/dev"));
assert_eq!(explorer.dirstack.len(), 2); assert_eq!(explorer.dirstack.len(), 2);
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev")); assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
assert_eq!( assert_eq!(
+1 -1
View File
@@ -11,7 +11,7 @@ pub use params::{FileTransferParams, ProtocolParams};
/// This enum defines the different transfer protocol available in termscp /// This enum defines the different transfer protocol available in termscp
#[derive(PartialEq, Debug, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum FileTransferProtocol { pub enum FileTransferProtocol {
Sftp, Sftp,
Scp, Scp,
+6 -6
View File
@@ -654,7 +654,7 @@ mod tests {
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap(); let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
let mut counter: usize = 0; let mut counter: usize = 0;
for _ in entries { for _ in entries {
counter = counter + 1; counter += 1;
} }
assert_eq!(host.files.len(), counter); 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 entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
let mut counter: usize = 0; let mut counter: usize = 0;
for _ in entries { for _ in entries {
counter = counter + 1; counter += 1;
} }
assert_eq!(host.list_dir().len(), counter); assert_eq!(host.list_dir().len(), counter);
} }
@@ -709,10 +709,10 @@ mod tests {
assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
// Verify new files // Verify new files
// Scan dir // 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; let mut counter: usize = 0;
for _ in entries { for _ in entries {
counter = counter + 1; counter += 1;
} }
assert_eq!(host.files.len(), counter); assert_eq!(host.files.len(), counter);
} }
@@ -793,7 +793,7 @@ mod tests {
let files: Vec<File> = host.list_dir(); let files: Vec<File> = host.list_dir();
// Verify files // Verify files
let file_0: &File = files.get(0).unwrap(); 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()); assert!(file_0.metadata.symlink.is_none());
} else { } else {
assert_eq!( assert_eq!(
@@ -803,7 +803,7 @@ mod tests {
} }
// Verify simlink // Verify simlink
let file_1: &File = files.get(1).unwrap(); let file_1: &File = files.get(1).unwrap();
if file_1.name() == String::from("bar.txt") { if file_1.name() == *"bar.txt" {
assert_eq!( assert_eq!(
file_1.metadata.symlink.as_ref().unwrap(), file_1.metadata.symlink.as_ref().unwrap(),
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
+2
View File
@@ -5,6 +5,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[macro_use] #[macro_use]
extern crate lazy_regex;
#[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
+16 -1
View File
@@ -96,7 +96,7 @@ impl Update {
new_version, new_version,
cargo_crate_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 Some(r) // New version is available
} else { } else {
None // No new version None // No new version
@@ -105,6 +105,12 @@ impl Update {
None => None, 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 { impl From<Status> for UpdateStatus {
fn from(s: Status) -> Self { fn from(s: Status) -> Self {
@@ -193,4 +199,13 @@ mod test {
assert_eq!(release.body.as_str(), "fixed everything"); assert_eq!(release.body.as_str(), "fixed everything");
assert_eq!(release.version.as_str(), "0.7.0"); 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"));
}
} }
+1 -1
View File
@@ -491,7 +491,7 @@ mod tests {
// Verify client has updated parameters // Verify client has updated parameters
assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp); assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim")); 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"); expected_key_path.push("pi@192.168.1.31.key");
assert_eq!( assert_eq!(
client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(), client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(),
+4 -4
View File
@@ -126,7 +126,7 @@ mod tests {
#[serial] #[serial]
fn test_system_environment_get_bookmarks_paths() { fn test_system_environment_get_bookmarks_paths() {
assert_eq!( 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"), PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
); );
} }
@@ -135,7 +135,7 @@ mod tests {
#[serial] #[serial]
fn test_system_environment_get_config_paths() { fn test_system_environment_get_config_paths() {
assert_eq!( 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/config.toml"),
PathBuf::from("/home/omar/.config/termscp/.ssh/") PathBuf::from("/home/omar/.config/termscp/.ssh/")
@@ -147,7 +147,7 @@ mod tests {
#[serial] #[serial]
fn test_system_environment_get_log_paths() { fn test_system_environment_get_log_paths() {
assert_eq!( 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"), PathBuf::from("/home/omar/.config/termscp/termscp.log"),
); );
} }
@@ -156,7 +156,7 @@ mod tests {
#[serial] #[serial]
fn test_system_environment_get_theme_path() { fn test_system_environment_get_theme_path() {
assert_eq!( 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"), PathBuf::from("/home/omar/.config/termscp/theme.toml"),
); );
} }
+1 -1
View File
@@ -96,7 +96,7 @@ mod tests {
#[test] #[test]
fn test_system_keys_filestorage_make_dir() { 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!( assert_eq!(
storage.make_file_path("bookmarks").as_path(), storage.make_file_path("bookmarks").as_path(),
Path::new("/tmp/.bookmarks.key") Path::new("/tmp/.bookmarks.key")
+1 -1
View File
@@ -10,7 +10,7 @@ pub mod keyringstorage;
use thiserror::Error; use thiserror::Error;
/// defines the error type for the `KeyStorage` /// defines the error type for the `KeyStorage`
#[derive(Debug, Error, PartialEq)] #[derive(Debug, Error, PartialEq, Eq)]
pub enum KeyStorageError { pub enum KeyStorageError {
#[cfg(feature = "with-keyring")] #[cfg(feature = "with-keyring")]
#[error("Key has a bad syntax")] #[error("Key has a bad syntax")]
+89 -8
View File
@@ -5,12 +5,16 @@
// Locals // Locals
use super::config_client::ConfigClient; use super::config_client::ConfigClient;
// Ext // Ext
use remotefs_ssh::SshKeyStorage as SshKeyStorageT; use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
use ssh2_config::SshConfig;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub struct SshKeyStorage { 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 { impl SshKeyStorage {
@@ -19,6 +23,7 @@ impl SshKeyStorage {
pub fn empty() -> Self { pub fn empty() -> Self {
SshKeyStorage { SshKeyStorage {
hosts: HashMap::new(), hosts: HashMap::new(),
ssh_config: None,
} }
} }
@@ -34,21 +39,67 @@ impl SshKeyStorage {
let key: String = Self::make_mapkey(host, username); let key: String = Self::make_mapkey(host, username);
self.hosts.insert(key, p); self.hosts.insert(key, p);
} }
}
impl SshKeyStorageT for SshKeyStorage { /// Parse ssh2 config
fn resolve(&self, host: &str, username: &str) -> Option<&Path> { 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); let key: String = Self::make_mapkey(host, username);
self.hosts.get(&key).map(|x| x.as_path()) 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 { impl From<&ConfigClient> for SshKeyStorage {
fn from(cfg_client: &ConfigClient) -> Self { 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> = let mut hosts: HashMap<String, PathBuf> =
HashMap::with_capacity(cfg_client.iter_ssh_keys().count()); HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
debug!("Setting up SSH key storage"); debug!("Setting up SSH key storage");
// Iterate over keys // Iterate over keys in storage
for key in cfg_client.iter_ssh_keys() { for key in cfg_client.iter_ssh_keys() {
match cfg_client.get_ssh_key(key) { match cfg_client.get_ssh_key(key) {
Ok(host) => match host { Ok(host) => match host {
@@ -66,7 +117,7 @@ impl From<&ConfigClient> for SshKeyStorage {
info!("Got SSH key for {}", key); info!("Got SSH key for {}", key);
} }
// Return storage // Return storage
SshKeyStorage { hosts } SshKeyStorage { hosts, ssh_config }
} }
} }
@@ -75,6 +126,7 @@ mod tests {
use super::*; use super::*;
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
use crate::utils::test_helpers;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::path::Path; use std::path::Path;
@@ -93,7 +145,7 @@ mod tests {
// Create ssh key storage // Create ssh key storage
let storage: SshKeyStorage = SshKeyStorage::from(&client); let storage: SshKeyStorage = SshKeyStorage::from(&client);
// Verify key exists // 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"); exp_key_path.push("pi@192.168.1.31.key");
assert_eq!( assert_eq!(
*storage.resolve("192.168.1.31", "pi").unwrap(), *storage.resolve("192.168.1.31", "pi").unwrap(),
@@ -103,6 +155,35 @@ mod tests {
assert!(storage.resolve("deskichup", "veeso").is_none()); 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] #[test]
fn test_system_sshkey_storage_empty() { fn test_system_sshkey_storage_empty() {
let storage: SshKeyStorage = SshKeyStorage::empty(); let storage: SshKeyStorage = SshKeyStorage::empty();
+17 -1
View File
@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
use tui_realm_stdlib::{Input, List, Radio}; use tui_realm_stdlib::{Input, List, Radio};
use tuirealm::command::{Cmd, CmdResult, Direction, Position}; 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::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue}; use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
@@ -223,6 +223,14 @@ impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
self.perform(Cmd::Move(Direction::Right)); self.perform(Cmd::Move(Direction::Right));
Some(Msg::None) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -283,6 +291,14 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
self.perform(Cmd::Move(Direction::Right)); self.perform(Cmd::Move(Direction::Right));
Some(Msg::None) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
+17 -1
View File
@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea}; use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
use tuirealm::command::{Cmd, CmdResult, Direction, Position}; 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::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue}; use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
@@ -186,6 +186,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
self.perform(Cmd::Move(Direction::Right)); self.perform(Cmd::Move(Direction::Right));
Some(Msg::None) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -245,6 +253,14 @@ impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
self.perform(Cmd::Move(Direction::Right)); self.perform(Cmd::Move(Direction::Right));
Some(Msg::None) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
+3 -3
View File
@@ -60,14 +60,14 @@ pub enum Id {
WindowSizeError, WindowSizeError,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum Msg { pub enum Msg {
Form(FormMsg), Form(FormMsg),
Ui(UiMsg), Ui(UiMsg),
None, None,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Eq)]
pub enum FormMsg { pub enum FormMsg {
Connect, Connect,
DeleteBookmark, DeleteBookmark,
@@ -81,7 +81,7 @@ pub enum FormMsg {
SaveBookmark, SaveBookmark,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Eq)]
pub enum UiMsg { pub enum UiMsg {
AddressBlurDown, AddressBlurDown,
AddressBlurUp, AddressBlurUp,
@@ -148,9 +148,7 @@ impl FileTransferActivity {
} }
}; };
// Edit file // Edit file
if let Err(err) = self.edit_local_file(tmpfile.as_path()) { self.edit_local_file(tmpfile.as_path())?;
return Err(err);
}
// Get local fs entry // Get local fs entry
let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) { let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e, Ok(e) => e,
@@ -192,7 +192,8 @@ impl Component<Msg, NoUserEvent> for Log {
} }
// -- comp msg // -- comp msg
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::BackTab, .. code: Key::BackTab | Key::Tab | Key::Char('p'),
..
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)), }) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
_ => None, _ => None,
} }
@@ -147,6 +147,14 @@ impl Component<Msg, NoUserEvent> for DeletePopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseDeletePopup)) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -203,6 +211,14 @@ impl Component<Msg, NoUserEvent> for DisconnectPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseDisconnectPopup)) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -1195,6 +1211,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseQuitPopup)) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -1344,6 +1368,14 @@ impl Component<Msg, NoUserEvent> for ReplacePopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -1790,6 +1822,16 @@ impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction( Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction(
PendingActionMsg::CloseSyncBrowsingMkdirPopup, 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -1967,6 +2009,14 @@ impl Component<Msg, NoUserEvent> for WatcherPopup {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseWatcherPopup)) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -82,14 +82,11 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
Some(Msg::None) Some(Msg::None)
} }
// -- comp msg // -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseFindExplorer)) Some(Msg::Ui(UiMsg::CloseFindExplorer))
} }
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Left | Key::Right | Key::Tab, code: Key::Left | Key::Right | Key::Tab | Key::BackTab,
.. ..
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)), }) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
@@ -206,14 +203,11 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
Some(Msg::None) Some(Msg::None)
} }
// -- comp msg // -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::ShowDisconnectPopup)) Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
} }
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Right | Key::Tab, code: Key::Right | Key::Tab | Key::BackTab,
.. ..
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)), }) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
@@ -275,6 +269,10 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
code: Key::Char('o') | Key::Function(4), code: Key::Char('o') | Key::Function(4),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)), }) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
Event::Keyboard(KeyEvent {
code: Key::Char('p'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Char('r') | Key::Function(6), code: Key::Char('r') | Key::Function(6),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
@@ -386,14 +384,11 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
Some(Msg::None) Some(Msg::None)
} }
// -- comp msg // -- comp msg
Event::Keyboard(KeyEvent {
code: Key::BackTab, ..
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::ShowDisconnectPopup)) Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
} }
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Left | Key::Tab, code: Key::Left | Key::Tab | Key::BackTab,
.. ..
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)), }) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
@@ -455,6 +450,10 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
code: Key::Char('o') | Key::Function(4), code: Key::Char('o') | Key::Function(4),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)), }) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
Event::Keyboard(KeyEvent {
code: Key::Char('p'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Char('r') | Key::Function(6), code: Key::Char('r') | Key::Function(6),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
+1 -1
View File
@@ -208,7 +208,7 @@ impl FileTransferActivity {
Ok(h) => { Ok(h) => {
let hostname: String = h.as_os_str().to_string_lossy().to_string(); let hostname: String = h.as_os_str().to_string_lossy().to_string();
let tokens: Vec<&str> = hostname.split('.').collect(); 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"), Err(_) => String::from("localhost"),
}; };
+1 -1
View File
@@ -140,7 +140,6 @@ enum UiMsg {
CloseWatchedPathsList, CloseWatchedPathsList,
CloseWatcherPopup, CloseWatcherPopup,
Disconnect, Disconnect,
ExplorerBackTabbed,
LogBackTabbed, LogBackTabbed,
Quit, Quit,
ReplacePopupTabbed, ReplacePopupTabbed,
@@ -153,6 +152,7 @@ enum UiMsg {
ShowFindPopup, ShowFindPopup,
ShowGotoPopup, ShowGotoPopup,
ShowKeybindingsPopup, ShowKeybindingsPopup,
ShowLogPanel,
ShowMkdirPopup, ShowMkdirPopup,
ShowNewFilePopup, ShowNewFilePopup,
ShowOpenWithPopup, ShowOpenWithPopup,
+3 -9
View File
@@ -326,11 +326,7 @@ impl FileTransferActivity {
break; break;
} }
// Send entry; name is always None after first call // Send entry; name is always None after first call
if let Err(err) = self.filetransfer_send_recurse(entry, remote_path.as_path(), None)?
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)
{
return Err(err);
}
} }
Ok(()) Ok(())
} }
@@ -717,13 +713,11 @@ impl FileTransferActivity {
} }
// Receive entry; name is always None after first call // Receive entry; name is always None after first call
// Local path becomes local_dir_path // Local path becomes local_dir_path
if let Err(err) = self.filetransfer_recv_recurse( self.filetransfer_recv_recurse(
entry, entry,
local_dir_path.as_path(), local_dir_path.as_path(),
None, None,
) { )?
return Err(err);
}
} }
Ok(()) Ok(())
} }
+7 -7
View File
@@ -102,7 +102,7 @@ impl FileTransferActivity {
self.action_submit_local(entry); self.action_submit_local(entry);
// Update file list if sync // Update file list if sync
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_remote_filelist(); self.update_remote_filelist();
} }
self.update_local_filelist(); self.update_local_filelist();
} }
@@ -112,7 +112,7 @@ impl FileTransferActivity {
self.action_submit_remote(entry); self.action_submit_remote(entry);
// Update file list if sync // Update file list if sync
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_local_filelist(); self.update_local_filelist();
} }
self.update_remote_filelist(); self.update_remote_filelist();
} }
@@ -161,7 +161,7 @@ impl FileTransferActivity {
FileExplorerTab::Local => { FileExplorerTab::Local => {
self.action_go_to_local_upper_dir(); self.action_go_to_local_upper_dir();
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_remote_filelist(); self.update_remote_filelist();
} }
// Reload file list component // Reload file list component
self.update_local_filelist() self.update_local_filelist()
@@ -169,7 +169,7 @@ impl FileTransferActivity {
FileExplorerTab::Remote => { FileExplorerTab::Remote => {
self.action_go_to_remote_upper_dir(); self.action_go_to_remote_upper_dir();
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_local_filelist(); self.update_local_filelist();
} }
// Reload file list component // Reload file list component
self.update_remote_filelist() self.update_remote_filelist()
@@ -182,7 +182,7 @@ impl FileTransferActivity {
FileExplorerTab::Local => { FileExplorerTab::Local => {
self.action_go_to_previous_local_dir(); self.action_go_to_previous_local_dir();
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_remote_filelist(); self.update_remote_filelist();
} }
// Reload file list component // Reload file list component
self.update_local_filelist() self.update_local_filelist()
@@ -190,7 +190,7 @@ impl FileTransferActivity {
FileExplorerTab::Remote => { FileExplorerTab::Remote => {
self.action_go_to_previous_remote_dir(); self.action_go_to_previous_remote_dir();
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
let _ = self.update_local_filelist(); self.update_local_filelist();
} }
// Reload file list component // Reload file list component
self.update_remote_filelist() self.update_remote_filelist()
@@ -406,7 +406,7 @@ impl FileTransferActivity {
self.disconnect(); self.disconnect();
self.umount_disconnect(); self.umount_disconnect();
} }
UiMsg::ExplorerBackTabbed => { UiMsg::ShowLogPanel => {
assert!(self.app.active(&Id::Log).is_ok()); assert!(self.app.active(&Id::Log).is_ok());
} }
UiMsg::LogBackTabbed => { UiMsg::LogBackTabbed => {
+2 -2
View File
@@ -80,8 +80,8 @@ impl FileTransferActivity {
self.refresh_local_status_bar(); self.refresh_local_status_bar();
self.refresh_remote_status_bar(); self.refresh_remote_status_bar();
// Update components // Update components
let _ = self.update_local_filelist(); self.update_local_filelist();
let _ = self.update_remote_filelist(); self.update_remote_filelist();
// Global listener // Global listener
self.mount_global_listener(); self.mount_global_listener();
// Give focus to local explorer // Give focus to local explorer
@@ -6,7 +6,7 @@ use super::{CommonMsg, Msg, ViewLayout};
use tui_realm_stdlib::{List, Paragraph, Radio, Span}; use tui_realm_stdlib::{List, Paragraph, Radio, Span};
use tuirealm::command::{Cmd, CmdResult, Direction, Position}; 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::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue}; use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
@@ -296,6 +296,14 @@ impl Component<Msg, NoUserEvent> for SavePopup {
self.perform(Cmd::Move(Direction::Right)); self.perform(Cmd::Move(Direction::Right));
Some(Msg::None) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
+1 -1
View File
@@ -278,7 +278,7 @@ impl NotificationsThreshold {
parse_bytesize(bytes).is_some() parse_bytesize(bytes).is_some()
} }
fn char_valid(_input: &str, incoming: char) -> bool { 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 { Self {
component: Input::default() component: Input::default()
+10 -1
View File
@@ -58,6 +58,14 @@ impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
self.perform(Cmd::Move(Direction::Right)); self.perform(Cmd::Move(Direction::Right));
Some(Msg::None) 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 { Event::Keyboard(KeyEvent {
code: Key::Enter, .. code: Key::Enter, ..
}) => { }) => {
@@ -143,7 +151,8 @@ impl Component<Msg, NoUserEvent> for SshKeys {
_ => Some(Msg::None), _ => Some(Msg::None),
}, },
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Delete, .. code: Key::Delete | Key::Char('e'),
..
}) => Some(Msg::Ssh(SshMsg::ShowDelSshKeyPopup)), }) => Some(Msg::Ssh(SshMsg::ShowDelSshKeyPopup)),
Event::Keyboard(KeyEvent { Event::Keyboard(KeyEvent {
code: Key::Char('n'), code: Key::Char('n'),
+6 -6
View File
@@ -99,7 +99,7 @@ pub enum IdTheme {
TransferTitle2, TransferTitle2,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Msg { pub enum Msg {
Common(CommonMsg), Common(CommonMsg),
Config(ConfigMsg), Config(ConfigMsg),
@@ -108,7 +108,7 @@ pub enum Msg {
None, None,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommonMsg { pub enum CommonMsg {
ChangeLayout, ChangeLayout,
CloseErrorPopup, CloseErrorPopup,
@@ -125,7 +125,7 @@ pub enum CommonMsg {
WindowResized, WindowResized,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigMsg { pub enum ConfigMsg {
CheckUpdatesBlurDown, CheckUpdatesBlurDown,
CheckUpdatesBlurUp, CheckUpdatesBlurUp,
@@ -152,7 +152,7 @@ pub enum ConfigMsg {
TextEditorBlurUp, TextEditorBlurUp,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum SshMsg { pub enum SshMsg {
CloseDelSshKeyPopup, CloseDelSshKeyPopup,
CloseNewSshKeyPopup, CloseNewSshKeyPopup,
@@ -165,7 +165,7 @@ pub enum SshMsg {
SshUsernameBlur, SshUsernameBlur,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ThemeMsg { pub enum ThemeMsg {
AuthAddressBlurDown, AuthAddressBlurDown,
AuthAddressBlurUp, AuthAddressBlurUp,
@@ -230,7 +230,7 @@ const STORE_CONFIG_CHANGED: &str = "SETUP_CONFIG_CHANGED";
/// ### ViewLayout /// ### ViewLayout
/// ///
/// Current view layout /// Current view layout
#[derive(PartialEq)] #[derive(PartialEq, Eq)]
pub enum ViewLayout { pub enum ViewLayout {
SetupForm, SetupForm,
SshKeys, SshKeys,
+1 -2
View File
@@ -20,8 +20,6 @@ impl SetupActivity {
self.new_app(ViewLayout::SshKeys); self.new_app(ViewLayout::SshKeys);
// Load keys // Load keys
self.reload_ssh_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) { pub(crate) fn view_ssh_keys(&mut self) {
@@ -137,5 +135,6 @@ impl SetupActivity {
vec![] vec![]
) )
.is_ok()); .is_ok());
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@ mod tests {
fn test_utils_crypto_aes128() { fn test_utils_crypto_aes128() {
let key: &str = "MYSUPERSECRETKEY"; let key: &str = "MYSUPERSECRETKEY";
let input: &str = "Hello world!"; 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!(secret.as_str(), "z4Z6LpcpYqBW4+bkIok+5A==");
assert_eq!( assert_eq!(
aes128_b64_decrypt(key, secret.as_str()) aes128_b64_decrypt(key, secret.as_str())
+3 -3
View File
@@ -300,7 +300,7 @@ mod tests {
#[test] #[test]
fn test_utils_fmt_time() { fn test_utils_fmt_time() {
let system_time: SystemTime = SystemTime::from(SystemTime::UNIX_EPOCH); let system_time: SystemTime = SystemTime::UNIX_EPOCH;
assert_eq!( assert_eq!(
fmt_time(system_time, "%Y-%m-%d"), fmt_time(system_time, "%Y-%m-%d"),
String::from("1970-01-01") String::from("1970-01-01")
@@ -326,12 +326,12 @@ mod tests {
#[test] #[test]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
fn test_utils_fmt_path_elide() { fn test_utils_fmt_path_elide() {
let p: &Path = &Path::new("/develop/pippo"); let p: &Path = Path::new("/develop/pippo");
// Under max size // Under max size
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/pippo")); assert_eq!(fmt_path_elide(p, 16), String::from("/develop/pippo"));
// Above max size, only one ancestor // Above max size, only one ancestor
assert_eq!(fmt_path_elide(p, 8), String::from("/develop/pippo")); 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")); assert_eq!(fmt_path_elide(p, 16), String::from("/develop/…/foo/bar"));
} }
+14 -13
View File
@@ -14,56 +14,57 @@ use crate::system::environment;
// Ext // Ext
use bytesize::ByteSize; use bytesize::ByteSize;
use regex::Regex; use lazy_regex::{Lazy, Regex};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use tuirealm::tui::style::Color; use tuirealm::tui::style::Color;
use tuirealm::utils::parser as tuirealm_parser; use tuirealm::utils::parser as tuirealm_parser;
// Regex // Regex
lazy_static! {
/** /**
* This regex matches the protocol used as option * This regex matches the protocol used as option
* Regex matches: * Regex matches:
* - group 1: Some(protocol) | None * - group 1: Some(protocol) | None
* - group 2: Some(other args) * - group 2: Some(other args)
*/ */
static ref REMOTE_OPT_PROTOCOL_REGEX: Regex = Regex::new(r"(?:([a-z0-9]+)://)?(?:(.+))").unwrap(); static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(?:(.+))");
/** /**
* Regex matches: * Regex matches:
* - group 1: Some(user) | None * - group 1: Some(user) | None
* - group 2: Address * - group 2: Address
* - group 3: Some(port) | None * - group 3: Some(port) | None
* - group 4: Some(path) | 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(); 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: * Regex matches:
* - group 1: Bucket * - group 1: Bucket
* - group 2: Region * - group 2: Region
* - group 3: Some(profile) | None * - group 3: Some(profile) | None
* - group 4: Some(path) | None * - group 4: Some(path) | None
*/ */
static ref REMOTE_S3_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?").unwrap(); static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
lazy_regex!(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
/** /**
* Regex matches: * Regex matches:
* - group 1: Version * - group 1: Version
* E.g. termscp-0.3.2 => 0.3.2 * E.g. termscp-0.3.2 => 0.3.2
* v0.4.0 => 0.4.0 * v0.4.0 => 0.4.0
*/ */
static ref SEMVER_REGEX: Regex = Regex::new(r".*(:?[0-9]\.[0-9]\.[0-9])").unwrap(); static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r".*(:?[0-9]\.[0-9]\.[0-9])");
/** /**
* Regex matches: * Regex matches:
* - group 1: amount (number) * - group 1: amount (number)
* - group 4: unit (K, M, G, T, P) * - group 4: unit (K, M, G, T, P)
*/ */
static ref BYTESIZE_REGEX: Regex = Regex::new(r"(:?([0-9])+)( )*(:?[KMGTP])?B$").unwrap(); static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
}
// -- remote opts // -- remote opts
+2 -2
View File
@@ -94,11 +94,11 @@ mod test {
#[test] #[test]
fn absolutize_path() { fn absolutize_path() {
assert_eq!( 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") Path::new("/home/omar/readme.txt")
); );
assert_eq!( 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") Path::new("/tmp/readme.txt")
); );
} }
+7 -6
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 { 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(); let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
writeln!( writeln!(tmpfile, "{}", content).unwrap();
tmpfile,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus."
)
.unwrap();
tmpfile tmpfile
} }