mirror of
https://github.com/veeso/termscp.git
synced 2025-12-06 17:15:35 -08:00
SMB support (#184)
* feat: smb client * fix: smb connection * fix: smbclient deps * feat: SMB mentions to user manual * feat: changelog * dlib macos * fix: removed smb support from macos :( * fix: restored libsmbclient build * fix: strange lint message * fix: macos build smb * fix: macos build smb * fix: macos tests * fix: macos lint * feat: SMB windows support * fix: windows tests
This commit is contained in:
committed by
GitHub
parent
a13663e5e9
commit
b7369162d2
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -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 libsmbclient-dev libsmbclient
|
||||
- name: Setup nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y libdbus-1-dev
|
||||
run: sudo apt update && sudo apt install -y libdbus-1-dev libsmbclient-dev
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
1
.github/workflows/macos.yml
vendored
1
.github/workflows/macos.yml
vendored
@@ -8,7 +8,6 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
|
||||
@@ -37,6 +37,8 @@ Released on ??
|
||||
|
||||
- **Change file permissions**: you can now change file permissions easily with the permissions popup pressing `Z` in the explorer.
|
||||
- [Issue 172](https://github.com/veeso/termscp/issues/172)
|
||||
- **SMB protocol**: Support for SMB protocol has been added thanks to the [remotefs-smb](https://github.com/veeso/remotefs-rs-smb) library and the [pavao](https://github.com/veeso/pavao) project. You may notice that the interface is quiet different between Windows and Linux/MacOs/BSD due to the fact that SMB is natively supported on Windows systems.
|
||||
- [Issue 182](https://github.com/veeso/termscp/issues/182)
|
||||
- [Issue 153](https://github.com/veeso/termscp/issues/153): show a loading message when loading directory's content
|
||||
- [Issue 176](https://github.com/veeso/termscp/issues/176): debug log is now written to CACHE_DIR
|
||||
- [Issue 173](https://github.com/veeso/termscp/issues/173): allow unknown fields in ssh2 configuration file
|
||||
|
||||
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -375,6 +375,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.24"
|
||||
@@ -2042,6 +2048,19 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "pavao"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f789d21b9a0ae0168b6325cee036811e746e635ef90896161e1df2ee3d3b60d"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"pkg-config",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@@ -2259,6 +2278,20 @@ dependencies = [
|
||||
"users",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remotefs-smb"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27c4fb523b04b6bcd5dae95a33cbaee73cf7d6607862039b6d2bff310db6ed9f"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"log",
|
||||
"pavao",
|
||||
"remotefs",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remotefs-ssh"
|
||||
version = "0.2.0"
|
||||
@@ -2897,6 +2930,7 @@ dependencies = [
|
||||
"argh",
|
||||
"bitflags 2.2.0",
|
||||
"bytesize",
|
||||
"cfg_aliases",
|
||||
"chrono",
|
||||
"content_inspector",
|
||||
"dirs 5.0.0",
|
||||
@@ -2916,6 +2950,7 @@ dependencies = [
|
||||
"remotefs",
|
||||
"remotefs-aws-s3",
|
||||
"remotefs-ftp",
|
||||
"remotefs-smb",
|
||||
"remotefs-ssh",
|
||||
"rpassword",
|
||||
"self_update",
|
||||
|
||||
@@ -70,11 +70,18 @@ wildmatch = "^2.1"
|
||||
pretty_assertions = "^1.3"
|
||||
serial_test = "^2.0"
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.1"
|
||||
|
||||
[features]
|
||||
default = [ "with-keyring" ]
|
||||
github-actions = [ ]
|
||||
with-keyring = [ "keyring" ]
|
||||
|
||||
[target."cfg(not(target_os = \"macos\"))"]
|
||||
[target."cfg(not(target_os = \"macos\"))".dependencies]
|
||||
remotefs-smb = "^0.2"
|
||||
|
||||
[target."cfg(target_family = \"windows\")"]
|
||||
[target."cfg(target_family = \"windows\")".dependencies]
|
||||
remotefs-ftp = { version = "^0.1.2", features = [ "native-tls" ] }
|
||||
|
||||
@@ -132,6 +132,7 @@ Termscp is a feature rich terminal file transfer and explorer, with support for
|
||||
- **SCP**
|
||||
- **FTP** and **FTPS**
|
||||
- **S3**
|
||||
- **SMB**
|
||||
- 🖥 Explore and operate on the remote and on the local machine file system with a handy UI
|
||||
- Create, remove, rename, search, view and edit files
|
||||
- ⭐ Connect to your favourite hosts through built-in bookmarks and recent connections
|
||||
@@ -187,9 +188,11 @@ For more information or other platforms, please visit [termscp.veeso.dev](https:
|
||||
- **Linux** users:
|
||||
- libdbus-1
|
||||
- pkg-config
|
||||
- libsmbclient
|
||||
- **FreeBSD** or, **NetBSD** users:
|
||||
- dbus
|
||||
- pkgconf
|
||||
- libsmbclient
|
||||
|
||||
### Optional Requirements ✔️
|
||||
|
||||
@@ -261,6 +264,7 @@ termscp is powered by these awesome projects:
|
||||
- [edit](https://github.com/milkey-mouse/edit)
|
||||
- [keyring-rs](https://github.com/hwchen/keyring-rs)
|
||||
- [open-rs](https://github.com/Byron/open-rs)
|
||||
- [pavao](https://github.com/veeso/pavao)
|
||||
- [remotefs](https://github.com/veeso/remotefs-rs)
|
||||
- [rpassword](https://github.com/conradkleinespel/rpassword)
|
||||
- [self_update](https://github.com/jaemk/self_update)
|
||||
|
||||
15
build.rs
Normal file
15
build.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
fn main() {
|
||||
// Setup cfg aliases
|
||||
cfg_aliases! {
|
||||
// Platforms
|
||||
macos: { target_os = "macos" },
|
||||
linux: { target_os = "linux" },
|
||||
unix: { target_family = "unix" },
|
||||
windows: { target_family = "windows" },
|
||||
// exclusive features
|
||||
smb: { not( macos ) },
|
||||
smb_unix: { all(unix, not(macos)) }
|
||||
}
|
||||
}
|
||||
1
dist/build/aarch64_centos7/Dockerfile
vendored
1
dist/build/aarch64_centos7/Dockerfile
vendored
@@ -9,6 +9,7 @@ RUN yum -y install \
|
||||
gcc \
|
||||
make \
|
||||
dbus-devel \
|
||||
libsmbclient-devel \
|
||||
bash \
|
||||
rpm-build
|
||||
# Install rust
|
||||
|
||||
2
dist/build/aarch64_debian9/Dockerfile
vendored
2
dist/build/aarch64_debian9/Dockerfile
vendored
@@ -8,6 +8,8 @@ RUN apt update && apt install -y \
|
||||
pkg-config \
|
||||
libdbus-1-dev \
|
||||
build-essential \
|
||||
libsmbclient-dev \
|
||||
libsmbclient \
|
||||
bash \
|
||||
curl
|
||||
|
||||
|
||||
1
dist/build/x86_64_centos7/Dockerfile
vendored
1
dist/build/x86_64_centos7/Dockerfile
vendored
@@ -9,6 +9,7 @@ RUN yum -y install \
|
||||
gcc \
|
||||
make \
|
||||
dbus-devel \
|
||||
libsmbclient-devel \
|
||||
bash \
|
||||
rpm-build
|
||||
# Install rust
|
||||
|
||||
2
dist/build/x86_64_debian9/Dockerfile
vendored
2
dist/build/x86_64_debian9/Dockerfile
vendored
@@ -8,6 +8,8 @@ RUN apt update && apt install -y \
|
||||
pkg-config \
|
||||
libdbus-1-dev \
|
||||
build-essential \
|
||||
libsmbclient-dev \
|
||||
libsmbclient \
|
||||
bash \
|
||||
curl
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ Termscp ist ein funktionsreicher Terminal-Dateitransfer und Explorer mit Unterst
|
||||
- **SCP**
|
||||
- **FTP** und **FTPS**
|
||||
- **S3**
|
||||
- **SMB**
|
||||
- 🖥 Erkunden und bedienen Sie das Dateisystem der Fernbedienung und des lokalen Computers mit einer praktischen Benutzeroberfläche
|
||||
- Erstellen, Entfernen, Umbenennen, Suchen, Anzeigen und Bearbeiten von Dateien
|
||||
- ⭐ Verbinden Sie sich über integrierte Lesezeichen und aktuelle Verbindungen mit Ihren Lieblingshosts
|
||||
@@ -185,10 +186,12 @@ Für weitere Informationen oder andere Plattformen besuchen Sie bitte [termscp.v
|
||||
- libssh
|
||||
- libdbus-1
|
||||
- pkg-config
|
||||
- libsmbclient
|
||||
- **FreeBSD** Benutzer:
|
||||
- libssh
|
||||
- dbus
|
||||
- pkgconf
|
||||
- libsmbclient
|
||||
|
||||
### Optionale Softwareanforderungen ✔️
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [Usage ❓](#usage-)
|
||||
- [Address argument 🌎](#address-argument-)
|
||||
- [AWS S3 address argument](#aws-s3-address-argument)
|
||||
- [SMB address argument](#smb-address-argument)
|
||||
- [How Password can be provided 🔐](#how-password-can-be-provided-)
|
||||
- [S3 connection parameters](#s3-connection-parameters)
|
||||
- [S3 credentials 🦊](#s3-credentials-)
|
||||
@@ -105,6 +106,22 @@ e.g.
|
||||
s3://buckethead@eu-central-1:default:/assets
|
||||
```
|
||||
|
||||
#### SMB address argument
|
||||
|
||||
SMB has a different syntax for CLI address argument, which is different whether you're on Windows or other systems:
|
||||
|
||||
**Windows** syntax:
|
||||
|
||||
```txt
|
||||
\\[username@]<server-name>\<share>[\path\...]
|
||||
```
|
||||
|
||||
**Other systems** syntax:
|
||||
|
||||
```txt
|
||||
smb://[username@]<server-name>[:port]/<share>[/path/.../]
|
||||
```
|
||||
|
||||
#### How Password can be provided 🔐
|
||||
|
||||
You have probably noticed, that, when providing the address as argument, there's no way to provide the password.
|
||||
|
||||
@@ -137,6 +137,7 @@ Termscp es un explorador y transferencia de archivos de terminal rico en funcion
|
||||
- **SCP**
|
||||
- **FTP** y **FTPS**
|
||||
- **S3**
|
||||
- **SMB**
|
||||
- 🖥 Explore y opere en el sistema de archivos de la máquina local y remota con una interfaz de usuario práctica
|
||||
- Cree, elimine, cambie el nombre, busque, vea y edite archivos
|
||||
- ⭐ Conéctese a sus hosts favoritos y conexiones recientes
|
||||
@@ -181,14 +182,14 @@ Para obtener más información u otras plataformas, visite [termscp.veeso.dev](h
|
||||
|
||||
### Requisitos ❗
|
||||
|
||||
- Usuarios **Linux**:
|
||||
- libssh
|
||||
- **Linux** users:
|
||||
- libdbus-1
|
||||
- pkg-config
|
||||
- Usuarios **FreeBSD**:
|
||||
- libssh
|
||||
- libsmbclient
|
||||
- **FreeBSD** or, **NetBSD** users:
|
||||
- dbus
|
||||
- pkgconf
|
||||
- libsmbclient
|
||||
|
||||
### Requisitos opcionales ✔️
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [Uso ❓](#uso-)
|
||||
- [Argumento dirección 🌎](#argumento-dirección-)
|
||||
- [Argumento dirección por AWS S3](#argumento-dirección-por-aws-s3)
|
||||
- [Argumento dirección por SMB](#argumento-dirección-por-smb)
|
||||
- [Cómo se puede proporcionar la contraseña 🔐](#cómo-se-puede-proporcionar-la-contraseña-)
|
||||
- [S3 parámetros de conexión](#s3-parámetros-de-conexión)
|
||||
- [Credenciales de S3 🦊](#credenciales-de-s3-)
|
||||
@@ -105,6 +106,22 @@ por ejemplo
|
||||
s3://buckethead@eu-central-1:default:/assets
|
||||
```
|
||||
|
||||
#### Argumento dirección por SMB
|
||||
|
||||
SMB tiene una sintaxis diferente para el argumento de la dirección CLI, que es diferente si está en Windows u otros sistemas:
|
||||
|
||||
**Windows** sintaxis:
|
||||
|
||||
```txt
|
||||
\\[username@]<server-name>\<share>[\path\...]
|
||||
```
|
||||
|
||||
**Other systems** sintaxis:
|
||||
|
||||
```txt
|
||||
smb://[username@]<server-name>[:port]/<share>[/path/.../]
|
||||
```
|
||||
|
||||
#### Cómo se puede proporcionar la contraseña 🔐
|
||||
|
||||
Probablemente haya notado que, al proporcionar la dirección como argumento, no hay forma de proporcionar la contraseña.
|
||||
|
||||
@@ -137,6 +137,7 @@ Termscp est un file transfer et explorateur de fichiers de terminal riche en fon
|
||||
- **SCP**
|
||||
- **FTP** et **FTPS**
|
||||
- **S3**
|
||||
- **SMB**
|
||||
- 🖥 Explorer et opérer sur le système de fichiers distant et local avec une interface utilisateur pratique.
|
||||
- Créer, supprimer, renommer, rechercher, afficher et modifier des fichiers
|
||||
- ⭐ Connectez-vous à vos hôtes préférés via des signets et des connexions récentes.
|
||||
@@ -181,14 +182,14 @@ Pour plus d'informations sur les autres méthodes d'installation, veuillez visit
|
||||
|
||||
### Requis ❗
|
||||
|
||||
- utilisateurs **Linux**:
|
||||
- libssh
|
||||
- **Linux** users:
|
||||
- libdbus-1
|
||||
- pkg-config
|
||||
- utilisateurs **FreeBSD**:
|
||||
- libssh
|
||||
- libsmbclient
|
||||
- **FreeBSD** or, **NetBSD** users:
|
||||
- dbus
|
||||
- pkgconf
|
||||
- libsmbclient
|
||||
|
||||
### Requis facultatives ✔️
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [Usage ❓](#usage-)
|
||||
- [Argument d'adresse 🌎](#argument-dadresse-)
|
||||
- [Argument d'adresse AWS S3](#argument-dadresse-aws-s3)
|
||||
- [Argument d'adresse SMB](#argument-dadresse-smb)
|
||||
- [Comment le mot de passe peut être fourni 🔐](#comment-le-mot-de-passe-peut-être-fourni-)
|
||||
- [S3 paramètres de connexion](#s3-paramètres-de-connexion)
|
||||
- [Identifiants S3 🦊](#identifiants-s3-)
|
||||
@@ -103,6 +104,23 @@ e.g.
|
||||
s3://buckethead@eu-central-1:default:/assets
|
||||
```
|
||||
|
||||
#### Argument d'adresse SMB
|
||||
|
||||
SMB a une syntaxe différente pour l'argument d'adresse CLI, qui est différente que vous soyez sur Windows ou sur d'autres systèmes :
|
||||
|
||||
syntaxe **Windows**:
|
||||
|
||||
```txt
|
||||
\\[username@]<server-name>\<share>[\path\...]
|
||||
```
|
||||
|
||||
syntaxe **Other systems**:
|
||||
|
||||
```txt
|
||||
smb://[username@]<server-name>[:port]/<share>[/path/.../]
|
||||
```
|
||||
|
||||
|
||||
#### Comment le mot de passe peut être fourni 🔐
|
||||
|
||||
Vous avez probablement remarqué que, lorsque vous fournissez l'adresse comme argument, il n'y a aucun moyen de fournir le mot de passe.
|
||||
|
||||
@@ -137,6 +137,7 @@ Termscp è un file transfer ed explorer ricco di funzionalità, con supporto a S
|
||||
- **SCP**
|
||||
- **FTP** and **FTPS**
|
||||
- **S3**
|
||||
- **SMB**
|
||||
- 🖥 Esplora e opera sia sul file system locale che su quello remoto con una UI di facile utilizzo.
|
||||
- Crea, rimuove, rinomina, cerca, visualizza e modifica file
|
||||
- ⭐ Connettiti ai tuoi host preferiti tramite la funzionalità integrata dei segnalibri e delle connessioni recenti.
|
||||
@@ -181,14 +182,14 @@ Per ulteriori informazioni sui metodi di installazione su altre piattaforme, vis
|
||||
|
||||
### Requisiti ❗
|
||||
|
||||
- Utenti **Linux**:
|
||||
- libssh
|
||||
- **Linux** users:
|
||||
- libdbus-1
|
||||
- pkg-config
|
||||
- Utenti **FreeBSD**:
|
||||
- libssh
|
||||
- libsmbclient
|
||||
- **FreeBSD** or, **NetBSD** users:
|
||||
- dbus
|
||||
- pkgconf
|
||||
- libsmbclient
|
||||
|
||||
### Requisiti opzionali ✔️
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [Argomenti da linea di comando ❓](#argomenti-da-linea-di-comando-)
|
||||
- [Argomento indirizzo 🌎](#argomento-indirizzo-)
|
||||
- [Argomento indirizzo per AWS S3](#argomento-indirizzo-per-aws-s3)
|
||||
- [Indirizzo SMB](#indirizzo-smb)
|
||||
- [Come fornire la password 🔐](#come-fornire-la-password-)
|
||||
- [Parametri di connessione S3](#parametri-di-connessione-s3)
|
||||
- [Credenziali S3 🦊](#credenziali-s3-)
|
||||
@@ -101,6 +102,23 @@ e.g.
|
||||
s3://buckethead@eu-central-1:default:/assets
|
||||
```
|
||||
|
||||
#### Indirizzo SMB
|
||||
|
||||
SMB ha una sintassi differente rispetto agli altri protocolli e cambia in base al sistema operativo:
|
||||
|
||||
**Windows**:
|
||||
|
||||
```txt
|
||||
\\[username@]<server-name>\<share>[\path\...]
|
||||
```
|
||||
|
||||
**Altri sistemi**:
|
||||
|
||||
```txt
|
||||
smb://[username@]<server-name>[:port]/<share>[/path/.../]
|
||||
```
|
||||
|
||||
|
||||
#### Come fornire la password 🔐
|
||||
|
||||
Quando si usa l'argomento indirizzo non è possibile fornire la password direttamente nell'argomento, esistono però altri metodi per farlo:
|
||||
|
||||
17
docs/man.md
17
docs/man.md
@@ -4,6 +4,7 @@
|
||||
- [Usage ❓](#usage-)
|
||||
- [Address argument 🌎](#address-argument-)
|
||||
- [AWS S3 address argument](#aws-s3-address-argument)
|
||||
- [SMB address argument](#smb-address-argument)
|
||||
- [How Password can be provided 🔐](#how-password-can-be-provided-)
|
||||
- [S3 connection parameters](#s3-connection-parameters)
|
||||
- [S3 credentials 🦊](#s3-credentials-)
|
||||
@@ -103,6 +104,22 @@ e.g.
|
||||
s3://buckethead@eu-central-1:default:/assets
|
||||
```
|
||||
|
||||
#### SMB address argument
|
||||
|
||||
SMB has a different syntax for CLI address argument, which is different whether you're on Windows or other systems:
|
||||
|
||||
**Windows** syntax:
|
||||
|
||||
```txt
|
||||
\\[username@]<server-name>\<share>[\path\...]
|
||||
```
|
||||
|
||||
**Other systems** syntax:
|
||||
|
||||
```txt
|
||||
smb://[username@]<server-name>[:port]/<share>[/path/.../]
|
||||
```
|
||||
|
||||
#### How Password can be provided 🔐
|
||||
|
||||
You have probably noticed, that, when providing the address as argument, there's no way to provide the password.
|
||||
|
||||
@@ -139,6 +139,7 @@ termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/S
|
||||
- **SCP**
|
||||
- **FTP** and **FTPS**
|
||||
- **S3**
|
||||
- **SMB**
|
||||
- 🖥 使用便捷的 UI 在远程和本地文件系统上浏览和操作
|
||||
- 创建、删除、重命名、搜索、查看和编辑文件
|
||||
- ⭐ 通过“内置书签”和“最近连接”快速连接到您的主机
|
||||
@@ -188,10 +189,12 @@ choco install termscp
|
||||
- libssh
|
||||
- libdbus-1
|
||||
- pkg-config
|
||||
- libsmbclient
|
||||
- **FreeBSD** 用户:
|
||||
- libssh
|
||||
- dbus
|
||||
- pkgconf
|
||||
- libsmbclient
|
||||
|
||||
### 可选项 ✔️
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [用法](#用法)
|
||||
- [地址参数](#地址参数)
|
||||
- [AWS S3 地址参数](#aws-s3-地址参数)
|
||||
- [SMB 地址参数](#smb-地址参数)
|
||||
- [如何输入密码](#如何输入密码)
|
||||
- [S3 连接参数](#s3-连接参数)
|
||||
- [Aws S3 凭证](#aws-s3-凭证)
|
||||
@@ -102,6 +103,22 @@ s3://<bucket-name>@<region>[:profile][:/wrkdir]
|
||||
s3://buckethead@eu-central-1:default:/assets
|
||||
```
|
||||
|
||||
#### SMB 地址参数
|
||||
|
||||
SMB 对 CLI 地址参数有不同的语法,无论您是在 Windows 还是其他系统上,这都是不同的:
|
||||
|
||||
**Windows** 句法:
|
||||
|
||||
```txt
|
||||
\\[username@]<server-name>\<share>[\path\...]
|
||||
```
|
||||
|
||||
**其他系统** 句法:
|
||||
|
||||
```txt
|
||||
smb://[username@]<server-name>[:port]/<share>[/path/.../]
|
||||
```
|
||||
|
||||
#### 如何输入密码
|
||||
|
||||
你可能已经注意到,url参数中没有办法直接附加密码,你可以通过以下三种方式提供密码:
|
||||
|
||||
@@ -304,14 +304,14 @@ install_bsd_cargo_deps() {
|
||||
set -e
|
||||
confirm "${YELLOW}libssh, gcc${NO_COLOR} are required to install ${GREEN}termscp${NO_COLOR}; would you like to proceed?"
|
||||
sudo="$(elevate_priv_ex /usr/local/bin)"
|
||||
$sudo pkg install -y curl wget libssh gcc dbus pkgconf
|
||||
$sudo pkg install -y curl wget libssh gcc dbus pkgconf libsmbclient
|
||||
info "Dependencies installed successfully"
|
||||
}
|
||||
|
||||
install_linux_cargo_deps() {
|
||||
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 debian_deps="gcc pkg-config libdbus-1-dev libsmbclient-dev"
|
||||
local rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel libsmbclient-devel"
|
||||
local arch_deps="gcc openssl pkg-config dbus smbclient"
|
||||
local deps_cmd=""
|
||||
# Get pkg manager
|
||||
if has apt; then
|
||||
|
||||
@@ -28,6 +28,8 @@ Address syntax can be:
|
||||
|
||||
- `protocol://user@address:port:wrkdir` for protocols such as Sftp, Scp, Ftp
|
||||
- `s3://bucket-name@region:profile:/wrkdir` for Aws S3 protocol
|
||||
- `\\\\<server>[:port]\\<share>[\\path]` for SMB (on Windows)
|
||||
- `smb://[user@]<server>[:port]</share>[/path]` for SMB (on other systems)
|
||||
|
||||
Please, report issues to <https://github.com/veeso/termscp>
|
||||
Please, consider supporting the author <https://ko-fi.com/veeso>")]
|
||||
|
||||
@@ -9,7 +9,9 @@ use std::str::FromStr;
|
||||
use serde::de::Error as DeError;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
|
||||
use crate::filetransfer::params::{
|
||||
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams as TransferSmbParams,
|
||||
};
|
||||
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
||||
|
||||
/// UserHosts contains all the hosts saved by the user in the data storage
|
||||
@@ -40,6 +42,8 @@ pub struct Bookmark {
|
||||
pub directory: Option<PathBuf>,
|
||||
/// S3 params; optional. When used other fields are empty for sure
|
||||
pub s3: Option<S3Params>,
|
||||
/// SMB params; optional. Extra params required for SMB protocol
|
||||
pub smb: Option<SmbParams>,
|
||||
}
|
||||
|
||||
/// Connection parameters for Aws s3 protocol
|
||||
@@ -55,6 +59,13 @@ pub struct S3Params {
|
||||
pub new_path_style: Option<bool>,
|
||||
}
|
||||
|
||||
/// Extra Connection parameters for SMB protocol
|
||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
|
||||
pub struct SmbParams {
|
||||
pub share: String,
|
||||
pub workgroup: Option<String>,
|
||||
}
|
||||
|
||||
// -- impls
|
||||
|
||||
impl From<FileTransferParams> for Bookmark {
|
||||
@@ -71,6 +82,7 @@ impl From<FileTransferParams> for Bookmark {
|
||||
password: params.password,
|
||||
directory,
|
||||
s3: None,
|
||||
smb: None,
|
||||
},
|
||||
ProtocolParams::AwsS3(params) => Self {
|
||||
protocol,
|
||||
@@ -80,6 +92,20 @@ impl From<FileTransferParams> for Bookmark {
|
||||
password: None,
|
||||
directory,
|
||||
s3: Some(S3Params::from(params)),
|
||||
smb: None,
|
||||
},
|
||||
ProtocolParams::Smb(params) => Self {
|
||||
smb: Some(SmbParams::from(params.clone())),
|
||||
protocol,
|
||||
address: Some(params.address),
|
||||
#[cfg(unix)]
|
||||
port: Some(params.port),
|
||||
#[cfg(windows)]
|
||||
port: None,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
directory,
|
||||
s3: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -104,6 +130,30 @@ impl From<Bookmark> for FileTransferParams {
|
||||
.password(bookmark.password);
|
||||
Self::new(bookmark.protocol, ProtocolParams::Generic(params))
|
||||
}
|
||||
#[cfg(unix)]
|
||||
FileTransferProtocol::Smb => {
|
||||
let params = TransferSmbParams::new(
|
||||
bookmark.address.unwrap_or_default(),
|
||||
bookmark.smb.clone().map(|x| x.share).unwrap_or_default(),
|
||||
)
|
||||
.port(bookmark.port.unwrap_or(445))
|
||||
.username(bookmark.username)
|
||||
.password(bookmark.password)
|
||||
.workgroup(bookmark.smb.and_then(|x| x.workgroup));
|
||||
|
||||
Self::new(bookmark.protocol, ProtocolParams::Smb(params))
|
||||
}
|
||||
#[cfg(windows)]
|
||||
FileTransferProtocol::Smb => {
|
||||
let params = TransferSmbParams::new(
|
||||
bookmark.address.unwrap_or_default(),
|
||||
bookmark.smb.clone().map(|x| x.share).unwrap_or_default(),
|
||||
)
|
||||
.username(bookmark.username)
|
||||
.password(bookmark.password);
|
||||
|
||||
Self::new(bookmark.protocol, ProtocolParams::Smb(params))
|
||||
}
|
||||
}
|
||||
.entry_directory(bookmark.directory) // Set entry directory
|
||||
}
|
||||
@@ -133,6 +183,26 @@ impl From<S3Params> for AwsS3Params {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl From<TransferSmbParams> for SmbParams {
|
||||
fn from(params: TransferSmbParams) -> Self {
|
||||
Self {
|
||||
share: params.share,
|
||||
workgroup: params.workgroup,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl From<TransferSmbParams> for SmbParams {
|
||||
fn from(params: TransferSmbParams) -> Self {
|
||||
Self {
|
||||
share: params.share,
|
||||
workgroup: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_protocol<'de, D>(deserializer: D) -> Result<FileTransferProtocol, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
@@ -178,6 +248,7 @@ mod tests {
|
||||
password: Some(String::from("password")),
|
||||
directory: Some(PathBuf::from("/tmp")),
|
||||
s3: None,
|
||||
smb: None,
|
||||
};
|
||||
let recent: Bookmark = Bookmark {
|
||||
address: Some(String::from("192.168.1.2")),
|
||||
@@ -187,6 +258,7 @@ mod tests {
|
||||
password: Some(String::from("password")),
|
||||
directory: Some(PathBuf::from("/home")),
|
||||
s3: None,
|
||||
smb: None,
|
||||
};
|
||||
let mut bookmarks: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||
bookmarks.insert(String::from("test"), bookmark);
|
||||
@@ -275,6 +347,7 @@ mod tests {
|
||||
password: Some(String::from("password")),
|
||||
directory: Some(PathBuf::from("/tmp")),
|
||||
s3: None,
|
||||
smb: None,
|
||||
};
|
||||
let params = FileTransferParams::from(bookmark);
|
||||
assert_eq!(params.protocol, FileTransferProtocol::Sftp);
|
||||
@@ -307,6 +380,7 @@ mod tests {
|
||||
secret_access_key: Some(String::from("pluto")),
|
||||
new_path_style: Some(true),
|
||||
}),
|
||||
smb: None,
|
||||
};
|
||||
let params = FileTransferParams::from(bookmark);
|
||||
assert_eq!(params.protocol, FileTransferProtocol::AwsS3);
|
||||
@@ -323,4 +397,64 @@ mod tests {
|
||||
assert_eq!(gparams.secret_access_key.as_deref().unwrap(), "pluto");
|
||||
assert_eq!(gparams.new_path_style, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn should_get_ftparams_from_smb_bookmark() {
|
||||
let bookmark: Bookmark = Bookmark {
|
||||
protocol: FileTransferProtocol::Smb,
|
||||
address: Some("localhost".to_string()),
|
||||
port: Some(445),
|
||||
username: Some("foo".to_string()),
|
||||
password: Some("bar".to_string()),
|
||||
directory: Some(PathBuf::from("/tmp")),
|
||||
s3: None,
|
||||
smb: Some(SmbParams {
|
||||
share: "test".to_string(),
|
||||
workgroup: Some("testone".to_string()),
|
||||
}),
|
||||
};
|
||||
|
||||
let params = FileTransferParams::from(bookmark);
|
||||
assert_eq!(params.protocol, FileTransferProtocol::Smb);
|
||||
assert_eq!(
|
||||
params.entry_directory.as_deref().unwrap(),
|
||||
std::path::Path::new("/tmp")
|
||||
);
|
||||
let smb_params = params.params.smb_params().unwrap();
|
||||
assert_eq!(smb_params.address.as_str(), "localhost");
|
||||
assert_eq!(smb_params.port, 445);
|
||||
assert_eq!(smb_params.share.as_str(), "test");
|
||||
assert_eq!(smb_params.password.as_deref().unwrap(), "bar");
|
||||
assert_eq!(smb_params.username.as_deref().unwrap(), "foo");
|
||||
assert_eq!(smb_params.workgroup.as_deref().unwrap(), "testone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn should_get_ftparams_from_smb_bookmark() {
|
||||
let bookmark: Bookmark = Bookmark {
|
||||
protocol: FileTransferProtocol::Smb,
|
||||
address: Some("localhost".to_string()),
|
||||
port: Some(445),
|
||||
username: None,
|
||||
password: None,
|
||||
directory: Some(PathBuf::from("/tmp")),
|
||||
s3: None,
|
||||
smb: Some(SmbParams {
|
||||
share: "test".to_string(),
|
||||
workgroup: None,
|
||||
}),
|
||||
};
|
||||
|
||||
let params = FileTransferParams::from(bookmark);
|
||||
assert_eq!(params.protocol, FileTransferProtocol::Smb);
|
||||
assert_eq!(
|
||||
params.entry_directory.as_deref().unwrap(),
|
||||
std::path::Path::new("/tmp")
|
||||
);
|
||||
let smb_params = params.params.smb_params().unwrap();
|
||||
assert_eq!(smb_params.address.as_str(), "localhost");
|
||||
assert_eq!(smb_params.share.as_str(), "test");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ mod tests {
|
||||
use tuirealm::tui::style::Color;
|
||||
|
||||
use super::*;
|
||||
use crate::config::bookmarks::{Bookmark, S3Params, UserHosts};
|
||||
use crate::config::bookmarks::{Bookmark, S3Params, SmbParams, UserHosts};
|
||||
use crate::config::params::UserConfig;
|
||||
use crate::config::themes::Theme;
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
@@ -366,7 +366,7 @@ mod tests {
|
||||
assert_eq!(host.username.as_deref().unwrap(), "root");
|
||||
assert_eq!(host.password, None);
|
||||
// Verify bookmarks
|
||||
assert_eq!(hosts.bookmarks.len(), 4);
|
||||
assert_eq!(hosts.bookmarks.len(), 5);
|
||||
let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap();
|
||||
assert_eq!(host.address.as_deref().unwrap(), "192.168.1.31");
|
||||
assert_eq!(host.port.unwrap(), 22);
|
||||
@@ -404,6 +404,20 @@ mod tests {
|
||||
assert_eq!(s3.access_key.as_deref().unwrap(), "pippo");
|
||||
assert_eq!(s3.secret_access_key.as_deref().unwrap(), "pluto");
|
||||
assert_eq!(s3.new_path_style.unwrap(), true);
|
||||
|
||||
// smb
|
||||
let host = hosts.bookmarks.get("smb").unwrap();
|
||||
assert_eq!(host.address.as_deref().unwrap(), "localhost");
|
||||
assert_eq!(host.port.unwrap(), 445);
|
||||
#[cfg(unix)]
|
||||
assert_eq!(host.username.as_deref().unwrap(), "test");
|
||||
#[cfg(unix)]
|
||||
assert_eq!(host.password.as_deref().unwrap(), "test");
|
||||
|
||||
let smb = host.smb.as_ref().unwrap();
|
||||
assert_eq!(smb.share.as_str(), "temp");
|
||||
#[cfg(unix)]
|
||||
assert_eq!(smb.workgroup.as_deref().unwrap(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -429,6 +443,7 @@ mod tests {
|
||||
password: None,
|
||||
directory: None,
|
||||
s3: None,
|
||||
smb: None,
|
||||
},
|
||||
);
|
||||
bookmarks.insert(
|
||||
@@ -441,6 +456,7 @@ mod tests {
|
||||
password: Some(String::from("password")),
|
||||
directory: Some(PathBuf::from("/tmp")),
|
||||
s3: None,
|
||||
smb: None,
|
||||
},
|
||||
);
|
||||
bookmarks.insert(
|
||||
@@ -461,6 +477,24 @@ mod tests {
|
||||
secret_access_key: None,
|
||||
new_path_style: None,
|
||||
}),
|
||||
smb: None,
|
||||
},
|
||||
);
|
||||
let smb_params: Option<SmbParams> = Some(SmbParams {
|
||||
share: "test".to_string(),
|
||||
workgroup: None,
|
||||
});
|
||||
bookmarks.insert(
|
||||
String::from("smb"),
|
||||
Bookmark {
|
||||
address: Some("localhost".to_string()),
|
||||
port: Some(445),
|
||||
protocol: FileTransferProtocol::Smb,
|
||||
username: None,
|
||||
password: None,
|
||||
directory: None,
|
||||
s3: None,
|
||||
smb: smb_params,
|
||||
},
|
||||
);
|
||||
let mut recents: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||
@@ -474,6 +508,7 @@ mod tests {
|
||||
password: Some(String::from("aaa")),
|
||||
directory: Some(PathBuf::from("/tmp")),
|
||||
s3: None,
|
||||
smb: None,
|
||||
},
|
||||
);
|
||||
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
@@ -527,6 +562,17 @@ mod tests {
|
||||
secret_access_key = "pluto"
|
||||
new_path_style = true
|
||||
|
||||
[bookmarks.smb]
|
||||
protocol = "SMB"
|
||||
address = "localhost"
|
||||
port = 445
|
||||
username = "test"
|
||||
password = "test"
|
||||
|
||||
[bookmarks.smb.smb]
|
||||
share = "temp"
|
||||
workgroup = "test"
|
||||
|
||||
[recents]
|
||||
ISO20201215T094000Z = { address = "172.16.104.10", port = 22, protocol = "SCP", username = "root" }
|
||||
"#;
|
||||
|
||||
@@ -11,7 +11,7 @@ use bytesize::ByteSize;
|
||||
use lazy_regex::{Lazy, Regex};
|
||||
use remotefs::File;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use users::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time};
|
||||
@@ -211,7 +211,7 @@ impl Formatter {
|
||||
_fmt_extra: Option<&String>,
|
||||
) -> String {
|
||||
// Get username
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
let group: String = match fsentry.metadata().gid {
|
||||
Some(gid) => match get_group_by_gid(gid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
@@ -219,7 +219,7 @@ impl Formatter {
|
||||
},
|
||||
None => 0.to_string(),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
let group: String = match fsentry.metadata().gid {
|
||||
Some(gid) => gid.to_string(),
|
||||
None => 0.to_string(),
|
||||
@@ -420,7 +420,7 @@ impl Formatter {
|
||||
_fmt_extra: Option<&String>,
|
||||
) -> String {
|
||||
// Get username
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
let username: String = match fsentry.metadata().uid {
|
||||
Some(uid) => match get_user_by_uid(uid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
@@ -428,7 +428,7 @@ impl Formatter {
|
||||
},
|
||||
None => 0.to_string(),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
let username: String = match fsentry.metadata().uid {
|
||||
Some(uid) => uid.to_string(),
|
||||
None => 0.to_string(),
|
||||
@@ -592,7 +592,7 @@ mod tests {
|
||||
mode: Some(UnixPex::from(0o644)),
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -600,7 +600,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -623,7 +623,7 @@ mod tests {
|
||||
mode: Some(UnixPex::from(0o644)),
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -631,7 +631,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -654,7 +654,7 @@ mod tests {
|
||||
mode: None,
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -662,7 +662,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -685,7 +685,7 @@ mod tests {
|
||||
mode: None,
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -693,7 +693,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -723,7 +723,7 @@ mod tests {
|
||||
mode: Some(UnixPex::from(0o755)),
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -731,7 +731,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -754,7 +754,7 @@ mod tests {
|
||||
mode: None,
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -762,7 +762,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
@@ -864,7 +864,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn should_fmt_path() {
|
||||
let t: SystemTime = SystemTime::now();
|
||||
let entry = File {
|
||||
@@ -896,7 +896,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn should_fmt_utf8_path() {
|
||||
let t: SystemTime = SystemTime::now();
|
||||
let entry = File {
|
||||
|
||||
@@ -513,7 +513,7 @@ mod tests {
|
||||
mode: Some(UnixPex::from(0o644)),
|
||||
},
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
explorer.fmt_file(&entry),
|
||||
format!(
|
||||
@@ -521,7 +521,7 @@ mod tests {
|
||||
fmt_time(t, "%b %d %Y %H:%M")
|
||||
)
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
explorer.fmt_file(&entry),
|
||||
format!(
|
||||
|
||||
@@ -7,9 +7,16 @@ use std::path::PathBuf;
|
||||
use remotefs::RemoteFs;
|
||||
use remotefs_aws_s3::AwsS3Fs;
|
||||
use remotefs_ftp::FtpFs;
|
||||
#[cfg(smb_unix)]
|
||||
use remotefs_smb::SmbOptions;
|
||||
#[cfg(smb)]
|
||||
use remotefs_smb::{SmbCredentials, SmbFs};
|
||||
use remotefs_ssh::{ScpFs, SftpFs, SshConfigParseRule, SshOpts};
|
||||
|
||||
#[cfg(not(smb))]
|
||||
use super::params::{AwsS3Params, GenericProtocolParams};
|
||||
#[cfg(smb)]
|
||||
use super::params::{AwsS3Params, GenericProtocolParams, SmbParams};
|
||||
use super::{FileTransferProtocol, ProtocolParams};
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::sshkey_storage::SshKeyStorage;
|
||||
@@ -39,6 +46,10 @@ impl Builder {
|
||||
(FileTransferProtocol::Sftp, ProtocolParams::Generic(params)) => {
|
||||
Box::new(Self::sftp_client(params, config_client))
|
||||
}
|
||||
#[cfg(smb)]
|
||||
(FileTransferProtocol::Smb, ProtocolParams::Smb(params)) => {
|
||||
Box::new(Self::smb_client(params))
|
||||
}
|
||||
(protocol, params) => {
|
||||
error!("Invalid params for protocol '{:?}'", protocol);
|
||||
panic!("Invalid protocol '{protocol:?}' with parameters of type {params:?}")
|
||||
@@ -98,6 +109,50 @@ impl Builder {
|
||||
Self::build_ssh_opts(params, config_client).into()
|
||||
}
|
||||
|
||||
#[cfg(smb_unix)]
|
||||
fn smb_client(params: SmbParams) -> SmbFs {
|
||||
let mut credentials = SmbCredentials::default()
|
||||
.server(format!("smb://{}:{}", params.address, params.port))
|
||||
.share(params.share);
|
||||
|
||||
if let Some(username) = params.username {
|
||||
credentials = credentials.username(username);
|
||||
}
|
||||
if let Some(password) = params.password {
|
||||
credentials = credentials.password(password);
|
||||
}
|
||||
if let Some(workgroup) = params.workgroup {
|
||||
credentials = credentials.workgroup(workgroup);
|
||||
}
|
||||
|
||||
match SmbFs::try_new(
|
||||
credentials,
|
||||
SmbOptions::default()
|
||||
.one_share_per_server(true)
|
||||
.case_sensitive(false),
|
||||
) {
|
||||
Ok(fs) => fs,
|
||||
Err(e) => {
|
||||
error!("Invalid params for protocol SMB: {e}");
|
||||
panic!("Invalid params for protocol SMB: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn smb_client(params: SmbParams) -> SmbFs {
|
||||
let mut credentials = SmbCredentials::new(params.address, params.share);
|
||||
|
||||
if let Some(username) = params.username {
|
||||
credentials = credentials.username(username);
|
||||
}
|
||||
if let Some(password) = params.password {
|
||||
credentials = credentials.password(password);
|
||||
}
|
||||
|
||||
SmbFs::new(credentials)
|
||||
}
|
||||
|
||||
/// Build ssh options from generic protocol params and client configuration
|
||||
fn build_ssh_opts(params: GenericProtocolParams, config_client: &ConfigClient) -> SshOpts {
|
||||
let mut opts = SshOpts::new(params.address)
|
||||
@@ -187,6 +242,14 @@ mod test {
|
||||
let _ = Builder::build(FileTransferProtocol::Sftp, params, &config_client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(smb)]
|
||||
fn should_build_smb_fs() {
|
||||
let params = ProtocolParams::Smb(SmbParams::new("localhost", "share"));
|
||||
let config_client = get_config_client();
|
||||
let _ = Builder::build(FileTransferProtocol::Smb, params, &config_client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn should_not_build_fs() {
|
||||
|
||||
@@ -13,10 +13,11 @@ pub use params::{FileTransferParams, ProtocolParams};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum FileTransferProtocol {
|
||||
Sftp,
|
||||
Scp,
|
||||
Ftp(bool), // Bool is for secure (true => ftps)
|
||||
AwsS3,
|
||||
Ftp(bool), // Bool is for secure (true => ftps)
|
||||
Scp,
|
||||
Sftp,
|
||||
Smb,
|
||||
}
|
||||
|
||||
// Traits
|
||||
@@ -24,13 +25,14 @@ pub enum FileTransferProtocol {
|
||||
impl std::string::ToString for FileTransferProtocol {
|
||||
fn to_string(&self) -> String {
|
||||
String::from(match self {
|
||||
FileTransferProtocol::AwsS3 => "S3",
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
true => "FTPS",
|
||||
false => "FTP",
|
||||
},
|
||||
FileTransferProtocol::Scp => "SCP",
|
||||
FileTransferProtocol::Sftp => "SFTP",
|
||||
FileTransferProtocol::AwsS3 => "S3",
|
||||
FileTransferProtocol::Smb => "SMB",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -41,9 +43,10 @@ impl std::str::FromStr for FileTransferProtocol {
|
||||
match s.to_ascii_uppercase().as_str() {
|
||||
"FTP" => Ok(FileTransferProtocol::Ftp(false)),
|
||||
"FTPS" => Ok(FileTransferProtocol::Ftp(true)),
|
||||
"S3" => Ok(FileTransferProtocol::AwsS3),
|
||||
"SCP" => Ok(FileTransferProtocol::Scp),
|
||||
"SFTP" => Ok(FileTransferProtocol::Sftp),
|
||||
"S3" => Ok(FileTransferProtocol::AwsS3),
|
||||
"SMB" => Ok(FileTransferProtocol::Smb),
|
||||
_ => Err(s.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -104,6 +107,14 @@ mod tests {
|
||||
FileTransferProtocol::from_str("scp").ok().unwrap(),
|
||||
FileTransferProtocol::Scp
|
||||
);
|
||||
assert_eq!(
|
||||
FileTransferProtocol::from_str("SMB").ok().unwrap(),
|
||||
FileTransferProtocol::Smb
|
||||
);
|
||||
assert_eq!(
|
||||
FileTransferProtocol::from_str("smb").ok().unwrap(),
|
||||
FileTransferProtocol::Smb
|
||||
);
|
||||
assert_eq!(
|
||||
FileTransferProtocol::from_str("S3").ok().unwrap(),
|
||||
FileTransferProtocol::AwsS3
|
||||
@@ -126,5 +137,6 @@ mod tests {
|
||||
assert_eq!(FileTransferProtocol::Scp.to_string(), String::from("SCP"));
|
||||
assert_eq!(FileTransferProtocol::Sftp.to_string(), String::from("SFTP"));
|
||||
assert_eq!(FileTransferProtocol::AwsS3.to_string(), String::from("S3"));
|
||||
assert_eq!(FileTransferProtocol::Smb.to_string(), String::from("SMB"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ pub struct FileTransferParams {
|
||||
pub enum ProtocolParams {
|
||||
Generic(GenericProtocolParams),
|
||||
AwsS3(AwsS3Params),
|
||||
Smb(SmbParams),
|
||||
}
|
||||
|
||||
/// Protocol params used by most common protocols
|
||||
@@ -44,6 +45,19 @@ pub struct AwsS3Params {
|
||||
pub new_path_style: bool,
|
||||
}
|
||||
|
||||
/// Connection parameters for SMB protocol
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SmbParams {
|
||||
pub address: String,
|
||||
#[cfg(unix)]
|
||||
pub port: u16,
|
||||
pub share: String,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
#[cfg(unix)]
|
||||
pub workgroup: Option<String>,
|
||||
}
|
||||
|
||||
impl FileTransferParams {
|
||||
/// Instantiates a new `FileTransferParams`
|
||||
pub fn new(protocol: FileTransferProtocol, params: ProtocolParams) -> Self {
|
||||
@@ -66,6 +80,7 @@ impl FileTransferParams {
|
||||
match &self.params {
|
||||
ProtocolParams::AwsS3(params) => params.password_missing(),
|
||||
ProtocolParams::Generic(params) => params.password_missing(),
|
||||
ProtocolParams::Smb(params) => params.password_missing(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +89,7 @@ impl FileTransferParams {
|
||||
match &mut self.params {
|
||||
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
|
||||
ProtocolParams::Generic(params) => params.set_default_secret(secret),
|
||||
ProtocolParams::Smb(params) => params.set_default_secret(secret),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +133,15 @@ impl ProtocolParams {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Retrieve SMB parameters if any
|
||||
pub fn smb_params(&self) -> Option<&SmbParams> {
|
||||
match self {
|
||||
ProtocolParams::Smb(params) => Some(params),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Generic protocol params
|
||||
@@ -235,6 +260,61 @@ impl AwsS3Params {
|
||||
}
|
||||
}
|
||||
|
||||
// -- SMB params
|
||||
|
||||
impl SmbParams {
|
||||
/// Instantiates a new `AwsS3Params` struct
|
||||
pub fn new<S: AsRef<str>>(address: S, share: S) -> Self {
|
||||
Self {
|
||||
address: address.as_ref().to_string(),
|
||||
#[cfg(unix)]
|
||||
port: 445,
|
||||
share: share.as_ref().to_string(),
|
||||
username: None,
|
||||
password: None,
|
||||
#[cfg(unix)]
|
||||
workgroup: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn username(mut self, username: Option<impl ToString>) -> Self {
|
||||
self.username = username.map(|x| x.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn password(mut self, password: Option<impl ToString>) -> Self {
|
||||
self.password = password.map(|x| x.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn workgroup(mut self, workgroup: Option<impl ToString>) -> Self {
|
||||
self.workgroup = workgroup.map(|x| x.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether a password is supposed to be required for this protocol params.
|
||||
/// The result true is returned ONLY if the supposed secret is MISSING!!!
|
||||
pub fn password_missing(&self) -> bool {
|
||||
self.password.is_none()
|
||||
}
|
||||
|
||||
/// Set password
|
||||
#[cfg(unix)]
|
||||
pub fn set_default_secret(&mut self, secret: String) {
|
||||
self.password = Some(secret);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn set_default_secret(&mut self, _secret: String) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
@@ -304,6 +384,53 @@ mod test {
|
||||
assert_eq!(params.new_path_style, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_init_smb_params() {
|
||||
let params = SmbParams::new("localhost", "temp");
|
||||
assert_eq!(¶ms.address, "localhost");
|
||||
|
||||
#[cfg(unix)]
|
||||
assert_eq!(params.port, 445);
|
||||
assert_eq!(¶ms.share, "temp");
|
||||
|
||||
#[cfg(unix)]
|
||||
assert!(params.username.is_none());
|
||||
#[cfg(unix)]
|
||||
assert!(params.password.is_none());
|
||||
#[cfg(unix)]
|
||||
assert!(params.workgroup.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn should_init_smb_params_with_optionals() {
|
||||
let params = SmbParams::new("localhost", "temp")
|
||||
.port(3456)
|
||||
.username(Some("foo"))
|
||||
.password(Some("bar"))
|
||||
.workgroup(Some("baz"));
|
||||
|
||||
assert_eq!(¶ms.address, "localhost");
|
||||
assert_eq!(params.port, 3456);
|
||||
assert_eq!(¶ms.share, "temp");
|
||||
assert_eq!(params.username.as_deref().unwrap(), "foo");
|
||||
assert_eq!(params.password.as_deref().unwrap(), "bar");
|
||||
assert_eq!(params.workgroup.as_deref().unwrap(), "baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn should_init_smb_params_with_optionals() {
|
||||
let params = SmbParams::new("localhost", "temp")
|
||||
.username(Some("foo"))
|
||||
.password(Some("bar"));
|
||||
|
||||
assert_eq!(¶ms.address, "localhost");
|
||||
assert_eq!(¶ms.share, "temp");
|
||||
assert_eq!(params.username.as_deref().unwrap(), "foo");
|
||||
assert_eq!(params.password.as_deref().unwrap(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references() {
|
||||
let mut params =
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
|
||||
// ext
|
||||
// Metadata ext
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use std::fs::set_permissions;
|
||||
use std::fs::{self, File as StdFile, OpenOptions};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use filetime::{self, FileTime};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use remotefs::fs::UnixPex;
|
||||
use remotefs::fs::{File, FileType, Metadata};
|
||||
use thiserror::Error;
|
||||
@@ -420,7 +420,7 @@ impl Localhost {
|
||||
filetime::set_file_atime(path, atime)
|
||||
.map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?;
|
||||
}
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = metadata.mode {
|
||||
self.chmod(path, mode)?;
|
||||
}
|
||||
@@ -454,7 +454,7 @@ impl Localhost {
|
||||
}
|
||||
|
||||
/// Change file mode to file, according to UNIX permissions
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn chmod(&self, path: &Path, pex: UnixPex) -> Result<(), HostError> {
|
||||
let path: PathBuf = self.to_path(path);
|
||||
// Get metadta
|
||||
@@ -586,7 +586,7 @@ impl Localhost {
|
||||
}
|
||||
|
||||
/// Create a symlink at path pointing at target
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn symlink(&self, path: &Path, target: &Path) -> Result<(), HostError> {
|
||||
let path = self.to_path(path);
|
||||
std::os::unix::fs::symlink(target, path.as_path()).map_err(|e| {
|
||||
@@ -644,19 +644,19 @@ impl Localhost {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use std::fs::File as StdFile;
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use std::io::Write;
|
||||
use std::ops::AddAssign;
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{symlink, PermissionsExt};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use crate::utils::test_helpers::make_fsentry;
|
||||
use crate::utils::test_helpers::{create_sample_file, make_dir_at, make_file_at};
|
||||
|
||||
@@ -669,7 +669,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_new() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
assert_eq!(host.wrkdir, PathBuf::from("/dev"));
|
||||
@@ -683,7 +683,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
fn test_host_localhost_new() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap();
|
||||
assert_eq!(host.wrkdir, PathBuf::from("C:\\users"));
|
||||
@@ -705,14 +705,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_pwd() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
assert_eq!(host.pwd(), PathBuf::from("/dev"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_list_files() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Scan dir
|
||||
@@ -725,7 +725,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_change_dir() {
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
let new_dir: PathBuf = PathBuf::from("/dev");
|
||||
@@ -741,7 +741,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[should_panic]
|
||||
fn test_host_localhost_change_dir_failed() {
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
@@ -750,7 +750,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_open_read() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Create temp file
|
||||
@@ -759,7 +759,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[should_panic]
|
||||
fn test_host_localhost_open_read_err_no_such_file() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
@@ -780,7 +780,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_open_write() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Create temp file
|
||||
@@ -799,7 +799,7 @@ mod tests {
|
||||
assert!(host.open_file_write(file.path()).is_err());
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_host_localhost_symlinks() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
@@ -837,7 +837,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_mkdir() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
@@ -862,7 +862,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_remove() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
// Create sample file
|
||||
@@ -891,7 +891,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_host_localhost_rename() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
// Create sample file
|
||||
@@ -944,7 +944,7 @@ mod tests {
|
||||
assert_eq!(new_metadata.metadata().modified, Some(new_mtime));
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_host_chmod() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
@@ -963,7 +963,7 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_host_copy_file_absolute() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
@@ -993,7 +993,7 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_host_copy_file_relative() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
@@ -1015,7 +1015,7 @@ mod tests {
|
||||
assert_eq!(host.files.len(), 2);
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_host_copy_directory_absolute() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
@@ -1046,7 +1046,7 @@ mod tests {
|
||||
assert!(host.stat(test_file_path.as_path()).is_ok());
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_host_copy_directory_relative() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
@@ -1081,9 +1081,9 @@ mod tests {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
// Execute
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\n");
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\r\n");
|
||||
}
|
||||
|
||||
@@ -1120,7 +1120,7 @@ mod tests {
|
||||
assert_eq!(result[1].name(), "examples.csv");
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn should_create_symlink() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
||||
@@ -398,28 +398,6 @@ mod tests {
|
||||
assert_eq!(client.recents_size, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
fn test_system_bookmarks_new_err() {
|
||||
assert!(BookmarksClient::new(
|
||||
Path::new("/tmp/oifoif/omar"),
|
||||
Path::new("/tmp/efnnu/omar"),
|
||||
16
|
||||
)
|
||||
.is_err());
|
||||
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
assert!(
|
||||
BookmarksClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar"), 16).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_new_from_existing() {
|
||||
|
||||
@@ -329,7 +329,7 @@ mod test {
|
||||
|
||||
/*
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn should_poll_file_moved() {
|
||||
let mut watcher = FsWatcher::init(Duration::from_millis(100)).unwrap();
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Locals
|
||||
use super::{AuthActivity, FileTransferParams};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
|
||||
|
||||
impl AuthActivity {
|
||||
/// Delete bookmark
|
||||
@@ -157,6 +157,7 @@ impl AuthActivity {
|
||||
match bookmark.params {
|
||||
ProtocolParams::AwsS3(params) => self.load_bookmark_s3_into_gui(params),
|
||||
ProtocolParams::Generic(params) => self.load_bookmark_generic_into_gui(params),
|
||||
ProtocolParams::Smb(params) => self.load_bookmark_smb_into_gui(params),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,4 +179,15 @@ impl AuthActivity {
|
||||
self.mount_s3_session_token(params.session_token.as_deref().unwrap_or(""));
|
||||
self.mount_s3_new_path_style(params.new_path_style);
|
||||
}
|
||||
|
||||
fn load_bookmark_smb_into_gui(&mut self, params: SmbParams) {
|
||||
self.mount_address(params.address.as_str());
|
||||
#[cfg(unix)]
|
||||
self.mount_port(params.port);
|
||||
self.mount_username(params.username.as_deref().unwrap_or(""));
|
||||
self.mount_password(params.password.as_deref().unwrap_or(""));
|
||||
self.mount_smb_share(¶ms.share);
|
||||
#[cfg(unix)]
|
||||
self.mount_smb_workgroup(params.workgroup.as_deref().unwrap_or(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
use crate::ui::activities::auth::{
|
||||
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
|
||||
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
|
||||
};
|
||||
|
||||
use super::{FileTransferProtocol, FormMsg, Msg, UiMsg};
|
||||
|
||||
// -- protocol
|
||||
@@ -26,7 +31,11 @@ impl ProtocolRadio {
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3"])
|
||||
.choices(if cfg!(smb) {
|
||||
&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"]
|
||||
} else {
|
||||
&["SFTP", "SCP", "FTP", "FTPS", "S3"]
|
||||
})
|
||||
.foreground(color)
|
||||
.rewind(true)
|
||||
.title("Protocol", Alignment::Left)
|
||||
@@ -37,10 +46,11 @@ impl ProtocolRadio {
|
||||
/// Convert radio index for protocol into a `FileTransferProtocol`
|
||||
fn protocol_opt_to_enum(protocol: usize) -> FileTransferProtocol {
|
||||
match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
4 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SCP => FileTransferProtocol::Scp,
|
||||
RADIO_PROTOCOL_FTP => FileTransferProtocol::Ftp(false),
|
||||
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
|
||||
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
}
|
||||
}
|
||||
@@ -48,11 +58,12 @@ impl ProtocolRadio {
|
||||
/// Convert `FileTransferProtocol` enum into radio group index
|
||||
fn protocol_enum_to_opt(protocol: FileTransferProtocol) -> usize {
|
||||
match protocol {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
FileTransferProtocol::AwsS3 => 4,
|
||||
FileTransferProtocol::Sftp => RADIO_PROTOCOL_SFTP,
|
||||
FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP,
|
||||
FileTransferProtocol::Ftp(false) => RADIO_PROTOCOL_FTP,
|
||||
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
|
||||
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
|
||||
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -673,3 +684,72 @@ fn handle_input_ev(
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputSmbShare {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputSmbShare {
|
||||
pub fn new(host: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Share", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputSmbShare {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
Msg::Ui(UiMsg::SmbShareBlurDown),
|
||||
Msg::Ui(UiMsg::SmbShareBlurUp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputSmbWorkgroup {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl InputSmbWorkgroup {
|
||||
pub fn new(host: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Workgroup", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Component<Msg, NoUserEvent> for InputSmbWorkgroup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
Msg::Ui(UiMsg::SmbWorkgroupDown),
|
||||
Msg::Ui(UiMsg::SmbWorkgroupUp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ pub use bookmarks::{
|
||||
BookmarkName, BookmarkSavePassword, BookmarksList, DeleteBookmarkPopup, DeleteRecentPopup,
|
||||
RecentsList,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
pub use form::InputSmbWorkgroup;
|
||||
pub use form::{
|
||||
InputAddress, InputPassword, InputPort, InputRemoteDirectory, InputS3AccessKey, InputS3Bucket,
|
||||
InputS3Endpoint, InputS3Profile, InputS3Region, InputS3SecretAccessKey, InputS3SecurityToken,
|
||||
InputS3SessionToken, InputUsername, ProtocolRadio, RadioS3NewPathStyle,
|
||||
InputS3SessionToken, InputSmbShare, InputUsername, ProtocolRadio, RadioS3NewPathStyle,
|
||||
};
|
||||
pub use popup::{
|
||||
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,
|
||||
|
||||
@@ -14,6 +14,7 @@ impl AuthActivity {
|
||||
FileTransferProtocol::Sftp | FileTransferProtocol::Scp => 22,
|
||||
FileTransferProtocol::Ftp(_) => 21,
|
||||
FileTransferProtocol::AwsS3 => 22, // Doesn't matter, since not used
|
||||
FileTransferProtocol::Smb => 445,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +37,10 @@ impl AuthActivity {
|
||||
pub(super) fn collect_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||
match self.protocol {
|
||||
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(),
|
||||
protocol => self.collect_generic_host_params(protocol),
|
||||
FileTransferProtocol::Smb => self.collect_smb_host_params(),
|
||||
FileTransferProtocol::Ftp(_)
|
||||
| FileTransferProtocol::Scp
|
||||
| FileTransferProtocol::Sftp => self.collect_generic_host_params(self.protocol),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +76,25 @@ impl AuthActivity {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn collect_smb_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||
let params = self.get_smb_params_input();
|
||||
if params.address.is_empty() {
|
||||
return Err("Invalid address");
|
||||
}
|
||||
#[cfg(unix)]
|
||||
if params.port == 0 {
|
||||
return Err("Invalid port");
|
||||
}
|
||||
if params.share.is_empty() {
|
||||
return Err("Invalid share");
|
||||
}
|
||||
Ok(FileTransferParams {
|
||||
protocol: FileTransferProtocol::Smb,
|
||||
params: ProtocolParams::Smb(params),
|
||||
entry_directory: self.get_input_remote_directory(),
|
||||
})
|
||||
}
|
||||
|
||||
// -- update install
|
||||
|
||||
/// If enabled in configuration, check for updates from Github
|
||||
|
||||
@@ -23,6 +23,14 @@ use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
|
||||
// radio
|
||||
const RADIO_PROTOCOL_SFTP: usize = 0;
|
||||
const RADIO_PROTOCOL_SCP: usize = 1;
|
||||
const RADIO_PROTOCOL_FTP: usize = 2;
|
||||
const RADIO_PROTOCOL_FTPS: usize = 3;
|
||||
const RADIO_PROTOCOL_S3: usize = 4;
|
||||
const RADIO_PROTOCOL_SMB: usize = 5;
|
||||
|
||||
// -- components
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum Id {
|
||||
@@ -55,6 +63,9 @@ pub enum Id {
|
||||
S3SecretAccessKey,
|
||||
S3SecurityToken,
|
||||
S3SessionToken,
|
||||
SmbShare,
|
||||
#[cfg(unix)]
|
||||
SmbWorkgroup,
|
||||
Subtitle,
|
||||
Title,
|
||||
Username,
|
||||
@@ -125,6 +136,12 @@ pub enum UiMsg {
|
||||
S3SecurityTokenBlurUp,
|
||||
S3SessionTokenBlurDown,
|
||||
S3SessionTokenBlurUp,
|
||||
SmbShareBlurDown,
|
||||
SmbShareBlurUp,
|
||||
#[cfg(unix)]
|
||||
SmbWorkgroupDown,
|
||||
#[cfg(unix)]
|
||||
SmbWorkgroupUp,
|
||||
BookmarkNameBlur,
|
||||
SaveBookmarkPasswordBlur,
|
||||
ShowDeleteBookmarkPopup,
|
||||
@@ -143,6 +160,7 @@ pub enum UiMsg {
|
||||
enum InputMask {
|
||||
Generic,
|
||||
AwsS3,
|
||||
Smb,
|
||||
}
|
||||
|
||||
// Store keys
|
||||
@@ -218,6 +236,7 @@ impl AuthActivity {
|
||||
FileTransferProtocol::Ftp(_)
|
||||
| FileTransferProtocol::Scp
|
||||
| FileTransferProtocol::Sftp => InputMask::Generic,
|
||||
FileTransferProtocol::Smb => InputMask::Smb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::Smb => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -79,6 +80,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::Smb => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -113,7 +115,12 @@ impl AuthActivity {
|
||||
fn update_ui(&mut self, msg: UiMsg) -> Option<Msg> {
|
||||
match msg {
|
||||
UiMsg::AddressBlurDown => {
|
||||
assert!(self.app.active(&Id::Port).is_ok());
|
||||
let id = if cfg!(windows) && self.input_mask() == InputMask::Smb {
|
||||
&Id::SmbShare
|
||||
} else {
|
||||
&Id::Port
|
||||
};
|
||||
assert!(self.app.active(id).is_ok());
|
||||
}
|
||||
UiMsg::AddressBlurUp => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
@@ -155,13 +162,30 @@ impl AuthActivity {
|
||||
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||
}
|
||||
UiMsg::PasswordBlurDown => {
|
||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::RemoteDirectory,
|
||||
#[cfg(unix)]
|
||||
InputMask::Smb => &Id::SmbWorkgroup,
|
||||
#[cfg(windows)]
|
||||
InputMask::Smb => &Id::RemoteDirectory,
|
||||
InputMask::AwsS3 => panic!("this shouldn't happen (password on s3)"),
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
UiMsg::PasswordBlurUp => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
}
|
||||
UiMsg::PortBlurDown => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Username,
|
||||
InputMask::Smb => &Id::SmbShare,
|
||||
InputMask::AwsS3 => panic!("this shouldn't happen (port on s3)"),
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
UiMsg::PortBlurUp => {
|
||||
assert!(self.app.active(&Id::Address).is_ok());
|
||||
@@ -171,6 +195,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Address,
|
||||
InputMask::Smb => &Id::Address,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -189,6 +214,10 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
#[cfg(unix)]
|
||||
InputMask::Smb => &Id::SmbWorkgroup,
|
||||
#[cfg(windows)]
|
||||
InputMask::Smb => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3NewPathStyle,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -247,6 +276,25 @@ impl AuthActivity {
|
||||
UiMsg::S3NewPathStyleBlurUp => {
|
||||
assert!(self.app.active(&Id::S3SessionToken).is_ok());
|
||||
}
|
||||
UiMsg::SmbShareBlurDown => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
}
|
||||
UiMsg::SmbShareBlurUp => {
|
||||
let id = if cfg!(windows) && self.input_mask() == InputMask::Smb {
|
||||
&Id::Address
|
||||
} else {
|
||||
&Id::Port
|
||||
};
|
||||
assert!(self.app.active(id).is_ok());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
UiMsg::SmbWorkgroupDown => {
|
||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
UiMsg::SmbWorkgroupUp => {
|
||||
assert!(self.app.active(&Id::Password).is_ok());
|
||||
}
|
||||
UiMsg::SaveBookmarkPasswordBlur => {
|
||||
assert!(self.app.active(&Id::BookmarkName).is_ok());
|
||||
}
|
||||
@@ -272,7 +320,14 @@ impl AuthActivity {
|
||||
assert!(self.app.active(&Id::Password).is_ok());
|
||||
}
|
||||
UiMsg::UsernameBlurUp => {
|
||||
assert!(self.app.active(&Id::Port).is_ok());
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Port,
|
||||
InputMask::Smb => &Id::SmbShare,
|
||||
InputMask::AwsS3 => panic!("this shouldn't happen (username on s3)"),
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
UiMsg::WindowResized => {
|
||||
self.redraw = true;
|
||||
|
||||
@@ -11,7 +11,7 @@ use tuirealm::tui::widgets::Clear;
|
||||
use tuirealm::{State, StateValue, Sub, SubClause, SubEventClause};
|
||||
|
||||
use super::{components, AuthActivity, Context, FileTransferProtocol, Id, InputMask};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
|
||||
@@ -56,6 +56,9 @@ impl AuthActivity {
|
||||
self.mount_s3_security_token("");
|
||||
self.mount_s3_session_token("");
|
||||
self.mount_s3_new_path_style(false);
|
||||
self.mount_smb_share("");
|
||||
#[cfg(unix)]
|
||||
self.mount_smb_workgroup("");
|
||||
// Version notice
|
||||
if let Some(version) = self
|
||||
.context()
|
||||
@@ -150,7 +153,7 @@ impl AuthActivity {
|
||||
InputMask::Generic => Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // host
|
||||
Constraint::Length(3), // address
|
||||
Constraint::Length(3), // port
|
||||
Constraint::Length(3), // username
|
||||
Constraint::Length(3), // password
|
||||
@@ -160,6 +163,36 @@ impl AuthActivity {
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(auth_chunks[4]),
|
||||
#[cfg(unix)]
|
||||
InputMask::Smb => Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // address
|
||||
Constraint::Length(3), // port
|
||||
Constraint::Length(3), // share
|
||||
Constraint::Length(3), // username
|
||||
Constraint::Length(3), // password
|
||||
Constraint::Length(3), // workgroup
|
||||
Constraint::Length(3), // remote directory
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(auth_chunks[4]),
|
||||
#[cfg(windows)]
|
||||
InputMask::Smb => Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // address
|
||||
Constraint::Length(3), // share
|
||||
Constraint::Length(3), // username
|
||||
Constraint::Length(3), // password
|
||||
Constraint::Length(3), // remote directory
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(auth_chunks[4]),
|
||||
};
|
||||
// Create bookmark chunks
|
||||
let bookmark_chunks = Layout::default()
|
||||
@@ -188,6 +221,13 @@ impl AuthActivity {
|
||||
self.app.view(&view_ids[2], f, input_mask[2]);
|
||||
self.app.view(&view_ids[3], f, input_mask[3]);
|
||||
}
|
||||
InputMask::Smb => {
|
||||
let view_ids = self.get_smb_view();
|
||||
self.app.view(&view_ids[0], f, input_mask[0]);
|
||||
self.app.view(&view_ids[1], f, input_mask[1]);
|
||||
self.app.view(&view_ids[2], f, input_mask[2]);
|
||||
self.app.view(&view_ids[3], f, input_mask[3]);
|
||||
}
|
||||
}
|
||||
// Bookmark chunks
|
||||
self.app.view(&Id::BookmarksList, f, bookmark_chunks[0]);
|
||||
@@ -713,6 +753,31 @@ impl AuthActivity {
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
pub(crate) fn mount_smb_share(&mut self, share: &str) {
|
||||
let color = self.theme().auth_password;
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::SmbShare,
|
||||
Box::new(components::InputSmbShare::new(share, color)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn mount_smb_workgroup(&mut self, workgroup: &str) {
|
||||
let color = self.theme().auth_address;
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::SmbWorkgroup,
|
||||
Box::new(components::InputSmbWorkgroup::new(workgroup, color)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
// -- query
|
||||
|
||||
/// Collect input values from view
|
||||
@@ -748,6 +813,37 @@ impl AuthActivity {
|
||||
.new_path_style(new_path_style)
|
||||
}
|
||||
|
||||
/// Collect s3 input values from view
|
||||
#[cfg(unix)]
|
||||
pub(super) fn get_smb_params_input(&self) -> SmbParams {
|
||||
let share: String = self.get_input_smb_share();
|
||||
let workgroup: Option<String> = self.get_input_smb_workgroup();
|
||||
|
||||
let address: String = self.get_input_addr();
|
||||
let port: u16 = self.get_input_port();
|
||||
let username = self.get_input_username();
|
||||
let password = self.get_input_password();
|
||||
|
||||
SmbParams::new(address, share)
|
||||
.port(port)
|
||||
.username(username)
|
||||
.password(password)
|
||||
.workgroup(workgroup)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(super) fn get_smb_params_input(&self) -> SmbParams {
|
||||
let share: String = self.get_input_smb_share();
|
||||
|
||||
let address: String = self.get_input_addr();
|
||||
let username = self.get_input_username();
|
||||
let password = self.get_input_password();
|
||||
|
||||
SmbParams::new(address, share)
|
||||
.username(username)
|
||||
.password(password)
|
||||
}
|
||||
|
||||
pub(super) fn get_input_remote_directory(&self) -> Option<PathBuf> {
|
||||
match self.app.state(&Id::RemoteDirectory) {
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => {
|
||||
@@ -851,6 +947,21 @@ impl AuthActivity {
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn get_input_smb_share(&self) -> String {
|
||||
match self.app.state(&Id::SmbShare) {
|
||||
Ok(State::One(StateValue::String(x))) => x,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(super) fn get_input_smb_workgroup(&self) -> Option<String> {
|
||||
match self.app.state(&Id::SmbWorkgroup) {
|
||||
Ok(State::One(StateValue::String(x))) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get new bookmark params
|
||||
pub(super) fn get_new_bookmark(&self) -> (String, bool) {
|
||||
let name = match self.app.state(&Id::BookmarkName) {
|
||||
@@ -874,6 +985,7 @@ impl AuthActivity {
|
||||
match self.input_mask() {
|
||||
InputMask::AwsS3 => 12,
|
||||
InputMask::Generic => 12,
|
||||
InputMask::Smb => 12,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,6 +1025,25 @@ impl AuthActivity {
|
||||
protocol, username, params.address, params.port
|
||||
)
|
||||
}
|
||||
#[cfg(unix)]
|
||||
ProtocolParams::Smb(params) => {
|
||||
let username: String = match params.username {
|
||||
None => String::default(),
|
||||
Some(u) => format!("{u}@"),
|
||||
};
|
||||
format!(
|
||||
"\\\\{username}{}:{}\\{}",
|
||||
params.address, params.port, params.share
|
||||
)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
ProtocolParams::Smb(params) => {
|
||||
let username: String = match params.username {
|
||||
None => String::default(),
|
||||
Some(u) => format!("{u}@"),
|
||||
};
|
||||
format!("\\\\{username}{}\\{}", params.address, params.share)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,6 +1097,40 @@ impl AuthActivity {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_smb_view(&self) -> [Id; 4] {
|
||||
match self.app.focus() {
|
||||
Some(&Id::Address | &Id::Port | &Id::SmbShare | &Id::Username) => {
|
||||
[Id::Address, Id::Port, Id::SmbShare, Id::Username]
|
||||
}
|
||||
Some(&Id::Password) => [Id::Port, Id::SmbShare, Id::Username, Id::Password],
|
||||
Some(&Id::SmbWorkgroup) => [Id::SmbShare, Id::Username, Id::Password, Id::SmbWorkgroup],
|
||||
Some(&Id::RemoteDirectory) => [
|
||||
Id::Username,
|
||||
Id::Password,
|
||||
Id::SmbWorkgroup,
|
||||
Id::RemoteDirectory,
|
||||
],
|
||||
_ => [Id::Address, Id::Port, Id::SmbShare, Id::Username],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_smb_view(&self) -> [Id; 4] {
|
||||
match self.app.focus() {
|
||||
Some(&Id::Address | &Id::Password | &Id::SmbShare | &Id::Username) => {
|
||||
[Id::Address, Id::SmbShare, Id::Username, Id::Password]
|
||||
}
|
||||
Some(&Id::RemoteDirectory) => [
|
||||
Id::SmbShare,
|
||||
Id::Username,
|
||||
Id::Password,
|
||||
Id::RemoteDirectory,
|
||||
],
|
||||
_ => [Id::Address, Id::SmbShare, Id::Username, Id::Password],
|
||||
}
|
||||
}
|
||||
|
||||
fn init_global_listener(&mut self) {
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
assert!(self
|
||||
|
||||
@@ -3,7 +3,7 @@ use remotefs::fs::UnixPex;
|
||||
use super::{FileTransferActivity, LogLevel};
|
||||
|
||||
impl FileTransferActivity {
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn action_local_chmod(&mut self, mode: UnixPex) {
|
||||
let files = self.get_local_selected_entries().get_files();
|
||||
|
||||
@@ -51,7 +51,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn action_find_local_chmod(&mut self, mode: UnixPex) {
|
||||
let files = self.get_found_selected_entries().get_files();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{FileTransferActivity, LogLevel, SelectedFile};
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// Create symlink on localhost
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn action_local_symlink(&mut self, name: String) {
|
||||
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
||||
match self
|
||||
@@ -33,7 +33,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn action_local_symlink(&mut self, _name: String) {
|
||||
self.mount_error("Symlinks are not supported on Windows hosts");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use tuirealm::props::{
|
||||
Alignment, BorderSides, BorderType, Borders, Color, InputType, Style, TableBuilder, TextSpan,
|
||||
};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use users::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
use super::super::Browser;
|
||||
@@ -445,7 +445,7 @@ impl FileInfoPopup {
|
||||
.add_col(TextSpan::from("Last access time: "))
|
||||
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
|
||||
// User
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
let username: String = match file.metadata().uid {
|
||||
Some(uid) => match get_user_by_uid(uid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
@@ -453,10 +453,10 @@ impl FileInfoPopup {
|
||||
},
|
||||
None => String::from("0"),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
let username: String = format!("{}", file.metadata().uid.unwrap_or(0));
|
||||
// Group
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
let group: String = match file.metadata().gid {
|
||||
Some(gid) => match get_group_by_gid(gid) {
|
||||
Some(group) => group.name().to_string_lossy().to_string(),
|
||||
@@ -464,7 +464,7 @@ impl FileInfoPopup {
|
||||
},
|
||||
None => String::from("0"),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
let group: String = format!("{}", file.metadata().gid.unwrap_or(0));
|
||||
texts
|
||||
.add_row()
|
||||
|
||||
@@ -111,6 +111,7 @@ impl FileTransferActivity {
|
||||
match &ft_params.params {
|
||||
ProtocolParams::Generic(params) => params.address.clone(),
|
||||
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
|
||||
ProtocolParams::Smb(params) => params.address.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +134,13 @@ impl FileTransferActivity {
|
||||
);
|
||||
format!("Connecting to {}…", params.bucket_name)
|
||||
}
|
||||
ProtocolParams::Smb(params) => {
|
||||
info!(
|
||||
"Client is not connected to remote; connecting to {}:{}",
|
||||
params.address, params.share
|
||||
);
|
||||
format!("Connecting to \\\\{}\\{}…", params.address, params.share)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,8 @@ pub struct FileTransferActivity {
|
||||
cache: Option<TempDir>,
|
||||
/// Fs watcher
|
||||
fswatcher: Option<FsWatcher>,
|
||||
/// conncted once
|
||||
connected: bool,
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -252,6 +254,7 @@ impl FileTransferActivity {
|
||||
None
|
||||
}
|
||||
},
|
||||
connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,14 +375,12 @@ impl Activity for FileTransferActivity {
|
||||
return;
|
||||
}
|
||||
// Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error)
|
||||
if !self.client.is_connected() && !self.app.mounted(&Id::FatalPopup) {
|
||||
if (!self.client.is_connected() || !self.connected) && !self.app.mounted(&Id::FatalPopup) {
|
||||
let ftparams = self.context().ft_params().unwrap();
|
||||
// print params
|
||||
let msg: String = Self::get_connection_msg(&ftparams.params);
|
||||
// Set init state to connecting popup
|
||||
self.mount_wait(msg.as_str());
|
||||
// Force ui draw
|
||||
self.view();
|
||||
self.mount_blocking_wait(msg.as_str());
|
||||
// Connect to remote
|
||||
self.connect();
|
||||
// Redraw
|
||||
|
||||
@@ -57,6 +57,7 @@ impl FileTransferActivity {
|
||||
// Connect to remote
|
||||
match self.client.connect() {
|
||||
Ok(Welcome { banner, .. }) => {
|
||||
self.connected = true;
|
||||
if let Some(banner) = banner {
|
||||
// Log welcome
|
||||
self.log(
|
||||
|
||||
@@ -36,13 +36,13 @@ impl FileTransferActivity {
|
||||
self.umount_chmod();
|
||||
self.mount_blocking_wait("Applying new file mode…");
|
||||
match self.browser.tab() {
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::Local => self.action_local_chmod(mode),
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::FindLocal => self.action_find_local_chmod(mode),
|
||||
FileExplorerTab::Remote => self.action_remote_chmod(mode),
|
||||
FileExplorerTab::FindRemote => self.action_find_remote_chmod(mode),
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => {}
|
||||
}
|
||||
self.umount_wait();
|
||||
@@ -441,13 +441,13 @@ impl FileTransferActivity {
|
||||
}
|
||||
UiMsg::ShowChmodPopup => {
|
||||
let selected_file = match self.browser.tab() {
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::Local => self.get_local_selected_entries(),
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::FindLocal => self.get_found_selected_entries(),
|
||||
FileExplorerTab::Remote => self.get_remote_selected_entries(),
|
||||
FileExplorerTab::FindRemote => self.get_found_selected_entries(),
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => SelectedFile::None,
|
||||
};
|
||||
if let Some(mode) = selected_file.unix_pex() {
|
||||
|
||||
@@ -11,6 +11,10 @@ use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use super::{ConfigMsg, Msg};
|
||||
use crate::explorer::GroupDirs as GroupDirsEnum;
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::ui::activities::setup::{
|
||||
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
|
||||
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
|
||||
};
|
||||
use crate::utils::parser::parse_bytesize;
|
||||
|
||||
// -- components
|
||||
@@ -63,16 +67,17 @@ impl DefaultProtocol {
|
||||
.color(Color::Cyan)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3"])
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"])
|
||||
.foreground(Color::Cyan)
|
||||
.rewind(true)
|
||||
.title("Default protocol", Alignment::Left)
|
||||
.value(match protocol {
|
||||
FileTransferProtocol::AwsS3 => 4,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Sftp => RADIO_PROTOCOL_SFTP,
|
||||
FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP,
|
||||
FileTransferProtocol::Ftp(false) => RADIO_PROTOCOL_FTP,
|
||||
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
|
||||
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
|
||||
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ use crate::config::themes::Theme;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::theme_provider::ThemeProvider;
|
||||
|
||||
// radio
|
||||
const RADIO_PROTOCOL_SFTP: usize = 0;
|
||||
const RADIO_PROTOCOL_SCP: usize = 1;
|
||||
const RADIO_PROTOCOL_FTP: usize = 2;
|
||||
const RADIO_PROTOCOL_FTPS: usize = 3;
|
||||
const RADIO_PROTOCOL_S3: usize = 4;
|
||||
const RADIO_PROTOCOL_SMB: usize = 5;
|
||||
|
||||
// -- components
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
enum Id {
|
||||
|
||||
@@ -13,6 +13,10 @@ use tuirealm::{State, StateValue};
|
||||
use super::{components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout};
|
||||
use crate::explorer::GroupDirs;
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::ui::activities::setup::{
|
||||
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
|
||||
RADIO_PROTOCOL_SMB,
|
||||
};
|
||||
use crate::utils::fmt::fmt_bytes;
|
||||
|
||||
impl SetupActivity {
|
||||
@@ -268,10 +272,11 @@ impl SetupActivity {
|
||||
self.app.state(&Id::Config(IdConfig::DefaultProtocol))
|
||||
{
|
||||
let protocol: FileTransferProtocol = match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
4 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SCP => FileTransferProtocol::Scp,
|
||||
RADIO_PROTOCOL_FTP => FileTransferProtocol::Ftp(false),
|
||||
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
|
||||
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
};
|
||||
self.config_mut().set_default_protocol(protocol);
|
||||
|
||||
@@ -308,7 +308,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_utils_fmt_path_elide() {
|
||||
let p: &Path = Path::new("/develop/pippo");
|
||||
// Under max size
|
||||
|
||||
@@ -12,6 +12,8 @@ use lazy_regex::{Lazy, Regex};
|
||||
use tuirealm::tui::style::Color;
|
||||
use tuirealm::utils::parser as tuirealm_parser;
|
||||
|
||||
#[cfg(smb)]
|
||||
use crate::filetransfer::params::SmbParams;
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
|
||||
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
||||
#[cfg(not(test))] // NOTE: don't use configuration during tests
|
||||
@@ -25,9 +27,10 @@ use crate::system::environment;
|
||||
* This regex matches the protocol used as option
|
||||
* Regex matches:
|
||||
* - group 1: Some(protocol) | None
|
||||
* - group 2: Some(other args)
|
||||
* - group 2: SMB windows prefix
|
||||
* - group 3: Some(other args)
|
||||
*/
|
||||
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(?:(.+))");
|
||||
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(\\\\)?(?:(.+))");
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
@@ -50,6 +53,30 @@ static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
||||
static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
|
||||
lazy_regex!(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: username
|
||||
* - group 2: address
|
||||
* - group 3: port?
|
||||
* - group 4: share?
|
||||
* - group 5: remote-dir?
|
||||
*/
|
||||
#[cfg(smb_unix)]
|
||||
static REMOTE_SMB_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: username?
|
||||
* - group 2: address
|
||||
* - group 3: share
|
||||
* - group 4: remote-dir?
|
||||
*/
|
||||
#[cfg(windows)]
|
||||
static REMOTE_SMB_OPT_REGEX: Lazy<Regex> =
|
||||
lazy_regex!(r"(?:([^@]+)@)?(?:([^:\\]+))(?:\\([^\\]+))?(?:(\\.+))?");
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Version
|
||||
@@ -83,6 +110,21 @@ static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B
|
||||
/// - sftp://172.26.104.1:4022
|
||||
/// - sftp://172.26.104.1
|
||||
/// - ...
|
||||
///
|
||||
/// For s3:
|
||||
///
|
||||
/// s3://<bucket-name>@<region>[:profile][:/wrkdir]
|
||||
///
|
||||
/// For SMB:
|
||||
///
|
||||
/// on UNIX derived (macos, linux, ...)
|
||||
///
|
||||
/// smb://[username@]<address>[:port]/<share>[/path]
|
||||
///
|
||||
/// on Windows
|
||||
///
|
||||
/// \\<address>\<share>[\path]
|
||||
///
|
||||
pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||
// Set protocol to default protocol
|
||||
#[cfg(not(test))] // NOTE: don't use configuration during tests
|
||||
@@ -108,6 +150,8 @@ pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||
// Match against regex for protocol type
|
||||
match protocol {
|
||||
FileTransferProtocol::AwsS3 => parse_s3_remote_opt(s.as_str()),
|
||||
#[cfg(smb)]
|
||||
FileTransferProtocol::Smb => parse_smb_remote_opts(s.as_str()),
|
||||
protocol => parse_generic_remote_opt(s.as_str(), protocol),
|
||||
}
|
||||
}
|
||||
@@ -127,13 +171,15 @@ fn parse_remote_opt_protocol(
|
||||
let protocol = match protocol {
|
||||
Some(Ok(protocol)) => protocol,
|
||||
Some(Err(err)) => return Err(err),
|
||||
#[cfg(windows)]
|
||||
None if groups.get(2).is_some() => FileTransferProtocol::Smb,
|
||||
None => default,
|
||||
};
|
||||
// Return protocol and remaining arguments
|
||||
Ok((
|
||||
protocol,
|
||||
groups
|
||||
.get(2)
|
||||
.get(3)
|
||||
.map(|x| x.as_str().to_string())
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
@@ -220,6 +266,70 @@ fn parse_s3_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse remote options for smb protocol
|
||||
#[cfg(smb_unix)]
|
||||
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
|
||||
match REMOTE_SMB_OPT_REGEX.captures(s) {
|
||||
Some(groups) => {
|
||||
let username: Option<String> = match groups.get(1) {
|
||||
Some(group) => Some(group.as_str().to_string()),
|
||||
None => Some(whoami::username()),
|
||||
};
|
||||
let address = match groups.get(2) {
|
||||
Some(group) => group.as_str().to_string(),
|
||||
None => return Err(String::from("Missing address")),
|
||||
};
|
||||
let port = match groups.get(3) {
|
||||
Some(port) => match port.as_str().parse::<u16>() {
|
||||
// Try to parse port
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(format!("Bad port \"{}\": {}", port.as_str(), err)),
|
||||
},
|
||||
None => 445,
|
||||
};
|
||||
let share = match groups.get(4) {
|
||||
Some(group) => group.as_str().to_string(),
|
||||
None => return Err(String::from("Missing address")),
|
||||
};
|
||||
let entry_directory: Option<PathBuf> =
|
||||
groups.get(5).map(|group| PathBuf::from(group.as_str()));
|
||||
|
||||
Ok(FileTransferParams::new(
|
||||
FileTransferProtocol::Smb,
|
||||
ProtocolParams::Smb(SmbParams::new(address, share).port(port).username(username)),
|
||||
)
|
||||
.entry_directory(entry_directory))
|
||||
}
|
||||
None => Err(String::from("Bad remote host syntax!")),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
|
||||
match REMOTE_SMB_OPT_REGEX.captures(s) {
|
||||
Some(groups) => {
|
||||
let username = groups.get(1).map(|x| x.as_str().to_string());
|
||||
let address = match groups.get(2) {
|
||||
Some(group) => group.as_str().to_string(),
|
||||
None => return Err(String::from("Missing address")),
|
||||
};
|
||||
let share = match groups.get(3) {
|
||||
Some(group) => group.as_str().to_string(),
|
||||
None => return Err(String::from("Missing address")),
|
||||
};
|
||||
let entry_directory: Option<PathBuf> =
|
||||
groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
||||
|
||||
Ok(FileTransferParams::new(
|
||||
FileTransferProtocol::Smb,
|
||||
ProtocolParams::Smb(SmbParams::new(address, share).username(username)),
|
||||
)
|
||||
.entry_directory(entry_directory))
|
||||
}
|
||||
None => Err(String::from("Bad remote host syntax!")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse semver string
|
||||
pub fn parse_semver(haystack: &str) -> Option<String> {
|
||||
match SEMVER_REGEX.captures(haystack) {
|
||||
@@ -502,6 +612,71 @@ mod tests {
|
||||
assert!(parse_remote_opt(&String::from("s3://mybucket:default:/foobar")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(smb_unix)]
|
||||
fn should_parse_smb_address() {
|
||||
let result = parse_remote_opt("smb://myserver/myshare").ok().unwrap();
|
||||
let params = result.params.smb_params().unwrap();
|
||||
|
||||
assert_eq!(params.address.as_str(), "myserver");
|
||||
assert_eq!(params.port, 445);
|
||||
assert_eq!(params.share.as_str(), "myshare");
|
||||
assert!(params.username.is_some());
|
||||
assert!(params.password.is_none());
|
||||
assert!(params.workgroup.is_none());
|
||||
assert!(result.entry_directory.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(smb_unix)]
|
||||
fn should_parse_smb_address_with_opts() {
|
||||
let result = parse_remote_opt("smb://omar@myserver:4445/myshare/dir/subdir")
|
||||
.ok()
|
||||
.unwrap();
|
||||
let params = result.params.smb_params().unwrap();
|
||||
|
||||
assert_eq!(params.address.as_str(), "myserver");
|
||||
assert_eq!(params.port, 4445);
|
||||
assert_eq!(params.username.as_deref().unwrap(), "omar");
|
||||
assert!(params.password.is_none());
|
||||
assert!(params.workgroup.is_none());
|
||||
assert_eq!(params.share.as_str(), "myshare");
|
||||
assert_eq!(
|
||||
result.entry_directory.as_deref().unwrap(),
|
||||
std::path::Path::new("/dir/subdir")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn should_parse_smb_address() {
|
||||
let result = parse_remote_opt(&String::from("\\\\myserver\\myshare"))
|
||||
.ok()
|
||||
.unwrap();
|
||||
let params = result.params.smb_params().unwrap();
|
||||
|
||||
assert_eq!(params.address.as_str(), "myserver");
|
||||
assert_eq!(params.share.as_str(), "myshare");
|
||||
assert!(result.entry_directory.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn should_parse_smb_address_with_opts() {
|
||||
let result = parse_remote_opt(&String::from("\\\\omar@myserver\\myshare\\path"))
|
||||
.ok()
|
||||
.unwrap();
|
||||
let params = result.params.smb_params().unwrap();
|
||||
|
||||
assert_eq!(params.address.as_str(), "myserver");
|
||||
assert_eq!(params.share.as_str(), "myshare");
|
||||
assert_eq!(params.username.as_deref().unwrap(), "omar");
|
||||
assert_eq!(
|
||||
result.entry_directory.as_deref().unwrap(),
|
||||
std::path::Path::new("\\path")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utils_parse_semver() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user