mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2efb25ad7 | ||
|
|
e45c3d5b4e | ||
|
|
69f821baef | ||
|
|
11559d0962 | ||
|
|
79c33095fc | ||
|
|
0ec6bcbcef | ||
|
|
3cde067339 | ||
|
|
c05ef32270 | ||
|
|
7eb913ec7b | ||
|
|
4e63093d25 | ||
|
|
e5d50698d2 | ||
|
|
79c31c2d84 | ||
|
|
aab266a661 | ||
|
|
a4c9acb49f |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,6 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
- [Changelog](#changelog)
|
- [Changelog](#changelog)
|
||||||
|
- [0.16.1](#0161)
|
||||||
|
- [0.16.0](#0160)
|
||||||
- [0.15.0](#0150)
|
- [0.15.0](#0150)
|
||||||
- [0.14.0](#0140)
|
- [0.14.0](#0140)
|
||||||
- [0.13.0](#0130)
|
- [0.13.0](#0130)
|
||||||
@@ -37,6 +39,31 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.16.1
|
||||||
|
|
||||||
|
Released on 12/11/2024
|
||||||
|
|
||||||
|
- Just fixed this: e45c3d5b4ef64653e5b6cc4f3703e3b67514306d
|
||||||
|
- `fix: gg rust 1.82 for introducing a nice breaking change in config which was not mentioned in changelog`
|
||||||
|
|
||||||
|
## 0.16.0
|
||||||
|
|
||||||
|
Released on 14/10/2024
|
||||||
|
|
||||||
|
- [**Multi Host support**](https://github.com/veeso/termscp/issues/285):
|
||||||
|
- Now it is possible to work on two different remotes `remote A -> remote B` instead of just `localhost -> remote`
|
||||||
|
- Cli arguments now accept an additional `remote-args` for the left panel.
|
||||||
|
- For more details read this issue <https://github.com/veeso/termscp/issues/285>.
|
||||||
|
- Change between auth forms with `<BACKTAB>`
|
||||||
|
- Bookmarks are automatically loaded into the last auth form.
|
||||||
|
- [Issue 289](https://github.com/veeso/termscp/issues/289): Use `uzers` instead of the dead package `users` which has several vulnerabilities
|
||||||
|
- [Issue 290](https://github.com/veeso/termscp/issues/290): Password prompt was broken
|
||||||
|
- [Issue 298](https://github.com/veeso/termscp/issues/298): tuirealm 2.x
|
||||||
|
- Fixed some performance issues where sometimes the app froze for a couple of seconds, thanks to this <https://github.com/veeso/tui-realm/pull/78>.
|
||||||
|
- [Issue 292](https://github.com/veeso/termscp/issues/292): New version alert was not displayed due to a semver regex issue.
|
||||||
|
- [Issue 291](https://github.com/veeso/termscp/issues/291): Show `..` directory before all the others in the explorer. If you click on it you'll go the parent directory (same as pressing `<U>`). No, you can't select it for transfers and it's actually been implemented in the worse way possible, because this little change would require a huge refactoring of the explorer component. I promise I will do it one day, but I dunno when.
|
||||||
|
- Logging: filter out messages not related to termscp or remotefs
|
||||||
|
|
||||||
## 0.15.0
|
## 0.15.0
|
||||||
|
|
||||||
Released on 03/10/2024
|
Released on 03/10/2024
|
||||||
|
|||||||
740
Cargo.lock
generated
740
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -10,7 +10,7 @@ license = "MIT"
|
|||||||
name = "termscp"
|
name = "termscp"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/veeso/termscp"
|
repository = "https://github.com/veeso/termscp"
|
||||||
version = "0.15.0"
|
version = "0.16.1"
|
||||||
|
|
||||||
[package.metadata.rpm]
|
[package.metadata.rpm]
|
||||||
package = "termscp"
|
package = "termscp"
|
||||||
@@ -77,8 +77,8 @@ tempfile = "^3"
|
|||||||
thiserror = "^1"
|
thiserror = "^1"
|
||||||
tokio = { version = "=1.38.1", features = ["rt"] }
|
tokio = { version = "=1.38.1", features = ["rt"] }
|
||||||
toml = "^0.8"
|
toml = "^0.8"
|
||||||
tui-realm-stdlib = "^1.3"
|
tui-realm-stdlib = "2"
|
||||||
tuirealm = "^1.9"
|
tuirealm = "2"
|
||||||
unicode-width = "^0.2"
|
unicode-width = "^0.2"
|
||||||
version-compare = "^0.2"
|
version-compare = "^0.2"
|
||||||
whoami = "^1.5"
|
whoami = "^1.5"
|
||||||
@@ -90,6 +90,8 @@ serial_test = "^3"
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cfg_aliases = "0.2"
|
cfg_aliases = "0.2"
|
||||||
|
vergen-git2 = { version = "1", features = ["build", "cargo", "rustc", "si"] }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["smb", "with-keyring"]
|
default = ["smb", "with-keyring"]
|
||||||
@@ -110,7 +112,7 @@ remotefs-ssh = "^0.4"
|
|||||||
[target."cfg(target_family = \"unix\")".dependencies]
|
[target."cfg(target_family = \"unix\")".dependencies]
|
||||||
remotefs-ftp = { version = "^0.2", features = ["vendored", "native-tls"] }
|
remotefs-ftp = { version = "^0.2", features = ["vendored", "native-tls"] }
|
||||||
remotefs-ssh = { version = "^0.4", features = ["ssh2-vendored"] }
|
remotefs-ssh = { version = "^0.4", features = ["ssh2-vendored"] }
|
||||||
users = "0.11.0"
|
uzers = "0.12"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
incremental = true
|
incremental = true
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Developed by <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
<p align="center">Developed by <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Current version: 0.15.0 (03/10/2024)</p>
|
<p align="center">Current version: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
23
build.rs
23
build.rs
@@ -1,16 +1,33 @@
|
|||||||
use cfg_aliases::cfg_aliases;
|
use cfg_aliases::cfg_aliases;
|
||||||
|
use vergen_git2::{BuildBuilder, CargoBuilder, Emitter, Git2Builder, RustcBuilder, SysinfoBuilder};
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Setup cfg aliases
|
// Setup cfg aliases
|
||||||
cfg_aliases! {
|
cfg_aliases! {
|
||||||
// Platforms
|
// Platforms
|
||||||
macos: { target_os = "macos" },
|
macos: { target_os = "macos" },
|
||||||
linux: { target_os = "linux" },
|
linux: { target_os = "linux" },
|
||||||
unix: { target_family = "unix" },
|
posix: { target_family = "unix" },
|
||||||
windows: { target_family = "windows" },
|
win: { target_family = "windows" },
|
||||||
// exclusive features
|
// exclusive features
|
||||||
smb: { all(feature = "smb", not( macos )) },
|
smb: { all(feature = "smb", not( macos )) },
|
||||||
smb_unix: { all(unix, feature = "smb", not(macos)) },
|
smb_unix: { all(unix, feature = "smb", not(macos)) },
|
||||||
smb_windows: { all(windows, feature = "smb") }
|
smb_windows: { all(windows, feature = "smb") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let build = BuildBuilder::all_build()?;
|
||||||
|
let cargo = CargoBuilder::all_cargo()?;
|
||||||
|
let git2 = Git2Builder::all_git()?;
|
||||||
|
let rustc = RustcBuilder::all_rustc()?;
|
||||||
|
let si = SysinfoBuilder::all_sysinfo()?;
|
||||||
|
|
||||||
|
Emitter::default()
|
||||||
|
.add_instructions(&build)?
|
||||||
|
.add_instructions(&cargo)?
|
||||||
|
.add_instructions(&git2)?
|
||||||
|
.add_instructions(&rustc)?
|
||||||
|
.add_instructions(&si)?
|
||||||
|
.emit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Entwickelt von <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
<p align="center">Entwickelt von <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Aktuelle Version: 0.15.0 (03/10/2024)</p>
|
<p align="center">Aktuelle Version: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -62,11 +62,11 @@
|
|||||||
|
|
||||||
termscp kann mit den folgenden Optionen gestartet werden:
|
termscp kann mit den folgenden Optionen gestartet werden:
|
||||||
|
|
||||||
`termscp [Optionen]... [protokoll://benutzer@adresse:port:arbeitsverzeichnis] [lokales-arbeitsverzeichnis]`
|
`termscp [Optionen]... [protokoll://benutzer@adresse:port:arbeitsverzeichnis] [protokoll://benutzer@adresse:port:arbeitsverzeichnis] [lokales-arbeitsverzeichnis]`
|
||||||
|
|
||||||
ODER
|
ODER
|
||||||
|
|
||||||
`termscp [Optionen]... -b [Lesezeichen-Name] [lokales-arbeitsverzeichnis]`
|
`termscp [Optionen]... -b [Lesezeichen-Name] -b [Lesezeichen-Name] [lokales-arbeitsverzeichnis]`
|
||||||
|
|
||||||
- `-P, --password <Passwort>` wenn Adresse angegeben wird, ist das Passwort dieses Argument
|
- `-P, --password <Passwort>` wenn Adresse angegeben wird, ist das Passwort dieses Argument
|
||||||
- `-b, --address-as-bookmark` löst das Adressargument als Lesezeichenname auf
|
- `-b, --address-as-bookmark` löst das Adressargument als Lesezeichenname auf
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Desarrollado por <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
<p align="center">Desarrollado por <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Versión actual: 0.15.0 (03/10/2024)</p>
|
<p align="center">Versión actual: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -39,11 +39,11 @@
|
|||||||
|
|
||||||
termscp se puede iniciar con las siguientes opciones:
|
termscp se puede iniciar con las siguientes opciones:
|
||||||
|
|
||||||
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
`termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
||||||
|
|
||||||
OR
|
OR
|
||||||
|
|
||||||
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
|
`termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
|
||||||
|
|
||||||
- `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento
|
- `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento
|
||||||
- `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador
|
- `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Développé par <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
<p align="center">Développé par <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Version actuelle: 0.15.0 (03/10/2024)</p>
|
<p align="center">Version actuelle: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -37,11 +37,11 @@
|
|||||||
|
|
||||||
termscp peut être démarré avec les options suivantes :
|
termscp peut être démarré avec les options suivantes :
|
||||||
|
|
||||||
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
`termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
||||||
|
|
||||||
ou
|
ou
|
||||||
|
|
||||||
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
|
`termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
|
||||||
|
|
||||||
- `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument
|
- `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument
|
||||||
- `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet
|
- `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Sviluppato da <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
<p align="center">Sviluppato da <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Versione corrente: 0.15.0 (03/10/2024)</p>
|
<p align="center">Versione corrente: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -37,11 +37,11 @@
|
|||||||
|
|
||||||
termscp può essere lanciato con questi argomenti:
|
termscp può essere lanciato con questi argomenti:
|
||||||
|
|
||||||
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
`termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
||||||
|
|
||||||
O
|
O
|
||||||
|
|
||||||
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
|
`termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
|
||||||
|
|
||||||
- `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi
|
- `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi
|
||||||
- `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro
|
- `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro
|
||||||
|
|||||||
@@ -40,13 +40,15 @@
|
|||||||
|
|
||||||
termscp can be started with the following options:
|
termscp can be started with the following options:
|
||||||
|
|
||||||
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
`termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
||||||
|
|
||||||
OR
|
OR
|
||||||
|
|
||||||
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
|
`termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
|
||||||
|
|
||||||
- `-P, --password <password>` if address is provided, password will be this argument
|
AND any combination of the two
|
||||||
|
|
||||||
|
- `-P, --password <password>` if address is provided, password will be this argument. A password *can* be specified for each remote provided. The order must be the same of the address argument. The use of this parameter is discouraged.
|
||||||
- `-b, --address-as-bookmark` resolve address argument as a bookmark name
|
- `-b, --address-as-bookmark` resolve address argument as a bookmark name
|
||||||
- `-q, --quiet` Disable logging
|
- `-q, --quiet` Disable logging
|
||||||
- `-v, --version` Print version info
|
- `-v, --version` Print version info
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Desenvolvido por <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
<p align="center">Desenvolvido por <a href="https://veeso.dev/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Versão atual: 0.15.0 (03/10/2024)</p>
|
<p align="center">Versão atual: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -40,11 +40,11 @@
|
|||||||
|
|
||||||
O termscp pode ser iniciado com as seguintes opções:
|
O termscp pode ser iniciado com as seguintes opções:
|
||||||
|
|
||||||
`termscp [opções]... [protocol://usuário@endereço:porta:diretório-trabalho] [diretório-trabalho-local]`
|
`termscp [opções]... [protocol://usuário@endereço:porta:diretório-trabalho] [protocol://usuário@endereço:porta:diretório-trabalho] [diretório-trabalho-local]`
|
||||||
|
|
||||||
OU
|
OU
|
||||||
|
|
||||||
`termscp [opções]... -b [nome-do-favorito] [diretório-trabalho-local]`
|
`termscp [opções]... -b [nome-do-favorito] -b [nome-do-favorito] [diretório-trabalho-local]`
|
||||||
|
|
||||||
- `-P, --password <senha>` se o endereço for fornecido, a senha será este argumento
|
- `-P, --password <senha>` se o endereço for fornecido, a senha será este argumento
|
||||||
- `-b, --address-as-bookmark` resolve o argumento do endereço como um nome de favorito
|
- `-b, --address-as-bookmark` resolve o argumento do endereço como um nome de favorito
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">由 <a href="https://veeso.dev/" target="_blank">@veeso</a> 开发</p>
|
<p align="center">由 <a href="https://veeso.dev/" target="_blank">@veeso</a> 开发</p>
|
||||||
<p align="center">当前版本: 0.15.0 (03/10/2024)</p>
|
<p align="center">当前版本: 0.16.1 12/11/2024</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -37,11 +37,11 @@
|
|||||||
|
|
||||||
termscp启动时可以使用以下选项:
|
termscp启动时可以使用以下选项:
|
||||||
|
|
||||||
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
`termscp [options]... [protocol://user@address:port:wrkdir] [protocol://user@address:port:wrkdir] [local-wrkdir]`
|
||||||
|
|
||||||
或作为
|
或作为
|
||||||
|
|
||||||
`termscp [options]... -b [bookmark-name] [local-wrkdir]`
|
`termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]`
|
||||||
|
|
||||||
- `-P, --password <password>` 登陆密码
|
- `-P, --password <password>` 登陆密码
|
||||||
- `-b, --address-as-bookmark` 将地址参数解析为书签名称
|
- `-b, --address-as-bookmark` 将地址参数解析为书签名称
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# -f, -y, --force, --yes
|
# -f, -y, --force, --yes
|
||||||
# Skip the confirmation prompt during installation
|
# Skip the confirmation prompt during installation
|
||||||
|
|
||||||
TERMSCP_VERSION="0.15.0"
|
TERMSCP_VERSION="0.16.1"
|
||||||
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
||||||
DEB_URL_AMD64="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
DEB_URL_AMD64="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
||||||
DEB_URL_AARCH64="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_arm64.deb"
|
DEB_URL_AARCH64="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_arm64.deb"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<span translate="getStarted.windows.moderation">Consider that Chocolatey moderation can take up to a few weeks
|
<span translate="getStarted.windows.moderation">Consider that Chocolatey moderation can take up to a few weeks
|
||||||
since last release, so if the latest version is not available yet,
|
since last release, so if the latest version is not available yet,
|
||||||
you can install it downloading the ZIP file from</span>
|
you can install it downloading the ZIP file from</span>
|
||||||
<a href="https://github.com/veeso/termscp/releases/latest/download/termscp.0.15.0.nupkg"
|
<a href="https://github.com/veeso/termscp/releases/latest/download/termscp.0.16.1.nupkg"
|
||||||
target="_blank">Github</a>
|
target="_blank">Github</a>
|
||||||
<span translate="getStarted.windows.then">and then, from the ZIP directory, install it via</span>
|
<span translate="getStarted.windows.then">and then, from the ZIP directory, install it via</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
On Debian based distros, you can install termscp using the Deb
|
On Debian based distros, you can install termscp using the Deb
|
||||||
package via:
|
package via:
|
||||||
</p>
|
</p>
|
||||||
<pre><span class="function">wget</span> -O termscp.deb <span class="string">https://github.com/veeso/termscp/releases/latest/download/termscp_0.15.0_amd64.deb</span>
|
<pre><span class="function">wget</span> -O termscp.deb <span class="string">https://github.com/veeso/termscp/releases/latest/download/termscp_0.16.1_amd64.deb</span>
|
||||||
sudo <span class="function">dpkg</span> -i <span class="string">termscp.deb</span></pre>
|
sudo <span class="function">dpkg</span> -i <span class="string">termscp.deb</span></pre>
|
||||||
</div>
|
</div>
|
||||||
<h3>
|
<h3>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="p-4 my-4 text-sm text-green-800 rounded-lg bg-green-50">
|
<div class="p-4 my-4 text-sm text-green-800 rounded-lg bg-green-50">
|
||||||
<p class="text-lg">
|
<p class="text-lg">
|
||||||
<span translate="intro.versionAlert">termscp 0.15.0 is NOW out! Download it from</span>
|
<span translate="intro.versionAlert">termscp 0.16.1 is NOW out! Download it from</span>
|
||||||
<a href="/get-started.html" translate="intro.here">here!</a>
|
<a href="/get-started.html" translate="intro.here">here!</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"intro": {
|
"intro": {
|
||||||
"caption": "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV",
|
"caption": "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV",
|
||||||
"getStarted": "Get started →",
|
"getStarted": "Get started →",
|
||||||
"versionAlert": "termscp 0.15.0 is NOW out! Download it from",
|
"versionAlert": "termscp 0.16.1 is NOW out! Download it from",
|
||||||
"here": "here",
|
"here": "here",
|
||||||
"features": {
|
"features": {
|
||||||
"handy": {
|
"handy": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"intro": {
|
"intro": {
|
||||||
"caption": "Un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/Kube/S3/WebDAV",
|
"caption": "Un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/Kube/S3/WebDAV",
|
||||||
"getStarted": "Para iniciar →",
|
"getStarted": "Para iniciar →",
|
||||||
"versionAlert": "termscp 0.15.0 ya está disponible! Descárgalo desde",
|
"versionAlert": "termscp 0.16.1 ya está disponible! Descárgalo desde",
|
||||||
"here": "aquì",
|
"here": "aquì",
|
||||||
"features": {
|
"features": {
|
||||||
"handy": {
|
"handy": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"intro": {
|
"intro": {
|
||||||
"caption": "Un file transfer et navigateur de terminal riche en fonctionnalités avec support pour SCP/SFTP/FTP/Kube/S3/WebDAV",
|
"caption": "Un file transfer et navigateur de terminal riche en fonctionnalités avec support pour SCP/SFTP/FTP/Kube/S3/WebDAV",
|
||||||
"getStarted": "Pour commencer →",
|
"getStarted": "Pour commencer →",
|
||||||
"versionAlert": "termscp 0.15.0 est maintenant sorti! Télécharge-le depuis",
|
"versionAlert": "termscp 0.16.1 est maintenant sorti! Télécharge-le depuis",
|
||||||
"here": "ici",
|
"here": "ici",
|
||||||
"features": {
|
"features": {
|
||||||
"handy": {
|
"handy": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"intro": {
|
"intro": {
|
||||||
"caption": "Un file transfer ed explorer ricco di funzionalità con supporto per SFTP/SCP/FTP/S3",
|
"caption": "Un file transfer ed explorer ricco di funzionalità con supporto per SFTP/SCP/FTP/S3",
|
||||||
"getStarted": "Installa termscp →",
|
"getStarted": "Installa termscp →",
|
||||||
"versionAlert": "termscp 0.15.0 è ORA disponbile! Scaricalo da",
|
"versionAlert": "termscp 0.16.1 è ORA disponbile! Scaricalo da",
|
||||||
"here": "qui",
|
"here": "qui",
|
||||||
"features": {
|
"features": {
|
||||||
"handy": {
|
"handy": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"intro": {
|
"intro": {
|
||||||
"caption": "功能丰富的终端 UI 文件传输和浏览器,支持 SCP/SFTP/FTP/Kube/S3/WebDAV",
|
"caption": "功能丰富的终端 UI 文件传输和浏览器,支持 SCP/SFTP/FTP/Kube/S3/WebDAV",
|
||||||
"getStarted": "开始 →",
|
"getStarted": "开始 →",
|
||||||
"versionAlert": "termscp 0.15.0 现已发布! 从下载",
|
"versionAlert": "termscp 0.16.1 现已发布! 从下载",
|
||||||
"here": "这里",
|
"here": "这里",
|
||||||
"features": {
|
"features": {
|
||||||
"handy": {
|
"handy": {
|
||||||
|
|||||||
@@ -2,15 +2,17 @@
|
|||||||
//!
|
//!
|
||||||
//! `activity_manager` is the module which provides run methods and handling for activities
|
//! `activity_manager` is the module which provides run methods and handling for activities
|
||||||
|
|
||||||
// Deps
|
use std::env;
|
||||||
// Namespaces
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
|
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
|
||||||
|
|
||||||
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
use crate::cli::{Remote, RemoteArgs};
|
||||||
use crate::host::{HostError, Localhost};
|
use crate::filetransfer::{
|
||||||
|
FileTransferParams, FileTransferProtocol, HostBridgeParams, ProtocolParams,
|
||||||
|
};
|
||||||
|
use crate::host::HostError;
|
||||||
use crate::system::bookmarks_client::BookmarksClient;
|
use crate::system::bookmarks_client::BookmarksClient;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use crate::system::environment;
|
use crate::system::environment;
|
||||||
@@ -30,6 +32,16 @@ pub enum NextActivity {
|
|||||||
SetupActivity,
|
SetupActivity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Host {
|
||||||
|
HostBridge,
|
||||||
|
Remote,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HostParams {
|
||||||
|
HostBridge(HostBridgeParams),
|
||||||
|
Remote(FileTransferParams),
|
||||||
|
}
|
||||||
|
|
||||||
/// The activity manager takes care of running activities and handling them until the application has ended
|
/// The activity manager takes care of running activities and handling them until the application has ended
|
||||||
pub struct ActivityManager {
|
pub struct ActivityManager {
|
||||||
context: Option<Context>,
|
context: Option<Context>,
|
||||||
@@ -62,10 +74,100 @@ impl ActivityManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure remote args
|
||||||
|
pub fn configure_remote_args(&mut self, remote_args: RemoteArgs) -> Result<(), String> {
|
||||||
|
// Set for host bridge
|
||||||
|
match remote_args.host_bridge {
|
||||||
|
Remote::Bookmark(params) => self.resolve_bookmark_name(
|
||||||
|
Host::HostBridge,
|
||||||
|
¶ms.name,
|
||||||
|
params.password.as_deref(),
|
||||||
|
),
|
||||||
|
Remote::Host(host_params) => self.set_host_params(
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Remote(
|
||||||
|
host_params.file_transfer_params.protocol,
|
||||||
|
host_params.file_transfer_params.params,
|
||||||
|
)),
|
||||||
|
host_params.password.as_deref(),
|
||||||
|
),
|
||||||
|
Remote::None => self.set_host_params(
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Localhost(
|
||||||
|
env::current_dir()
|
||||||
|
.map_err(|e| format!("Could not get current directory: {e}"))?,
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// set remote
|
||||||
|
match remote_args.remote {
|
||||||
|
Remote::Bookmark(params) => {
|
||||||
|
self.resolve_bookmark_name(Host::Remote, ¶ms.name, params.password.as_deref())
|
||||||
|
}
|
||||||
|
Remote::Host(host_params) => self.set_host_params(
|
||||||
|
HostParams::Remote(host_params.file_transfer_params),
|
||||||
|
host_params.password.as_deref(),
|
||||||
|
),
|
||||||
|
Remote::None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Set file transfer params
|
/// Set file transfer params
|
||||||
pub fn set_filetransfer_params(
|
pub fn set_host_params(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut params: FileTransferParams,
|
host: HostParams,
|
||||||
|
password: Option<&str>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let (remote_local_path, remote_remote_path) = match &host {
|
||||||
|
HostParams::Remote(params) => (params.local_path.clone(), params.remote_path.clone()),
|
||||||
|
_ => (None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut remote_params = match &host {
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Remote(protocol, protocol_params)) => {
|
||||||
|
Some((*protocol, protocol_params.clone()))
|
||||||
|
}
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Localhost(_)) => None,
|
||||||
|
HostParams::Remote(ft_params) => Some((ft_params.protocol, ft_params.params.clone())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Put params into the context
|
||||||
|
if let Some((protocol, params)) = remote_params.as_mut() {
|
||||||
|
self.resolve_password_for_protocol_params(*protocol, params, password)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match host {
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Localhost(path)) => {
|
||||||
|
self.context
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.set_host_bridge_params(HostBridgeParams::Localhost(path));
|
||||||
|
}
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Remote(_, _)) => {
|
||||||
|
let (protocol, params) = remote_params.unwrap();
|
||||||
|
self.context
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.set_host_bridge_params(HostBridgeParams::Remote(protocol, params));
|
||||||
|
}
|
||||||
|
HostParams::Remote(_) => {
|
||||||
|
let (protocol, params) = remote_params.unwrap();
|
||||||
|
let params = FileTransferParams {
|
||||||
|
local_path: remote_local_path,
|
||||||
|
remote_path: remote_remote_path,
|
||||||
|
protocol,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
self.context.as_mut().unwrap().set_remote_params(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_password_for_protocol_params(
|
||||||
|
&mut self,
|
||||||
|
protocol: FileTransferProtocol,
|
||||||
|
params: &mut ProtocolParams,
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// Set password if provided
|
// Set password if provided
|
||||||
@@ -73,13 +175,13 @@ impl ActivityManager {
|
|||||||
if let Some(password) = password {
|
if let Some(password) = password {
|
||||||
params.set_default_secret(password.to_string());
|
params.set_default_secret(password.to_string());
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
params.protocol,
|
protocol,
|
||||||
FileTransferProtocol::Scp | FileTransferProtocol::Sftp,
|
FileTransferProtocol::Scp | FileTransferProtocol::Sftp,
|
||||||
) && params.params.generic_params().is_some()
|
) && params.generic_params().is_some()
|
||||||
{
|
{
|
||||||
// * if protocol is SCP or SFTP check whether a SSH key is registered for this remote, in case not ask password
|
// * if protocol is SCP or SFTP check whether a SSH key is registered for this remote, in case not ask password
|
||||||
let storage = SshKeyStorage::from(self.context.as_ref().unwrap().config());
|
let storage = SshKeyStorage::from(self.context.as_ref().unwrap().config());
|
||||||
let generic_params = params.params.generic_params().unwrap();
|
let generic_params = params.generic_params().unwrap();
|
||||||
if storage
|
if storage
|
||||||
.resolve(
|
.resolve(
|
||||||
&generic_params.address,
|
&generic_params.address,
|
||||||
@@ -94,7 +196,7 @@ impl ActivityManager {
|
|||||||
"storage could not find any suitable key for {}... prompting for password",
|
"storage could not find any suitable key for {}... prompting for password",
|
||||||
generic_params.address
|
generic_params.address
|
||||||
);
|
);
|
||||||
self.prompt_password(&mut params)?;
|
self.prompt_password(params)?;
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
"a key is already set for {}; password is not required",
|
"a key is already set for {}; password is not required",
|
||||||
@@ -102,17 +204,19 @@ impl ActivityManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.prompt_password(&mut params)?;
|
self.prompt_password(params)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Put params into the context
|
|
||||||
self.context.as_mut().unwrap().set_ftparams(params);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prompt user for password to set into params.
|
/// Prompt user for password to set into params.
|
||||||
fn prompt_password(&self, params: &mut FileTransferParams) -> Result<(), String> {
|
fn prompt_password(&mut self, params: &mut ProtocolParams) -> Result<(), String> {
|
||||||
match tty::read_secret_from_tty("Password: ") {
|
let ctx = self.context.as_mut().unwrap();
|
||||||
|
let prompt = format!("Password for {}: ", params.host_name());
|
||||||
|
|
||||||
|
match tty::read_secret_from_tty(ctx.terminal(), prompt) {
|
||||||
Err(err) => Err(format!("Could not read password: {err}")),
|
Err(err) => Err(format!("Could not read password: {err}")),
|
||||||
Ok(Some(secret)) => {
|
Ok(Some(secret)) => {
|
||||||
debug!(
|
debug!(
|
||||||
@@ -130,16 +234,28 @@ impl ActivityManager {
|
|||||||
/// Returns error if bookmark is not found
|
/// Returns error if bookmark is not found
|
||||||
pub fn resolve_bookmark_name(
|
pub fn resolve_bookmark_name(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
host: Host,
|
||||||
bookmark_name: &str,
|
bookmark_name: &str,
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if let Some(bookmarks_client) = self.context.as_mut().unwrap().bookmarks_client_mut() {
|
if let Some(bookmarks_client) = self.context.as_mut().unwrap().bookmarks_client_mut() {
|
||||||
match bookmarks_client.get_bookmark(bookmark_name) {
|
let params = match bookmarks_client.get_bookmark(bookmark_name) {
|
||||||
None => Err(format!(
|
None => {
|
||||||
|
return Err(format!(
|
||||||
r#"Could not resolve bookmark name: "{bookmark_name}" no such bookmark"#
|
r#"Could not resolve bookmark name: "{bookmark_name}" no such bookmark"#
|
||||||
)),
|
))
|
||||||
Some(params) => self.set_filetransfer_params(params, password),
|
|
||||||
}
|
}
|
||||||
|
Some(params) => params,
|
||||||
|
};
|
||||||
|
|
||||||
|
let params = match host {
|
||||||
|
Host::Remote => HostParams::Remote(params),
|
||||||
|
Host::HostBridge => {
|
||||||
|
HostParams::HostBridge(HostBridgeParams::Remote(params.protocol, params.params))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_host_params(params, password)
|
||||||
} else {
|
} else {
|
||||||
Err(String::from(
|
Err(String::from(
|
||||||
"Could not resolve bookmark name: bookmarks client not initialized",
|
"Could not resolve bookmark name: bookmarks client not initialized",
|
||||||
@@ -226,15 +342,24 @@ impl ActivityManager {
|
|||||||
fn run_filetransfer(&mut self) -> Option<NextActivity> {
|
fn run_filetransfer(&mut self) -> Option<NextActivity> {
|
||||||
info!("Starting FileTransferActivity");
|
info!("Starting FileTransferActivity");
|
||||||
// Get context
|
// Get context
|
||||||
let mut ctx: Context = match self.context.take() {
|
let ctx: Context = match self.context.take() {
|
||||||
Some(ctx) => ctx,
|
Some(ctx) => ctx,
|
||||||
None => {
|
None => {
|
||||||
error!("Failed to start FileTransferActivity: context is None");
|
error!("Failed to start FileTransferActivity: context is None");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let host_bridge_params = match ctx.host_bridge_params() {
|
||||||
|
Some(params) => params.clone(),
|
||||||
|
None => {
|
||||||
|
error!("Failed to start FileTransferActivity: host bridge params is None");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// If ft params is None, return None
|
// If ft params is None, return None
|
||||||
let ft_params: &FileTransferParams = match ctx.ft_params() {
|
let remote_params: &FileTransferParams = match ctx.remote_params() {
|
||||||
Some(ft_params) => ft_params,
|
Some(ft_params) => ft_params,
|
||||||
None => {
|
None => {
|
||||||
error!("Failed to start FileTransferActivity: file transfer params is None");
|
error!("Failed to start FileTransferActivity: file transfer params is None");
|
||||||
@@ -242,28 +367,8 @@ impl ActivityManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// get local path:
|
|
||||||
// - if set in file transfer params, get it from there
|
|
||||||
// - otherwise is env current dir
|
|
||||||
// - otherwise is /
|
|
||||||
let local_wrkdir = ft_params
|
|
||||||
.local_path
|
|
||||||
.clone()
|
|
||||||
.or(std::env::current_dir().ok())
|
|
||||||
.unwrap_or(PathBuf::from("/"));
|
|
||||||
|
|
||||||
// Prepare activity
|
|
||||||
let host: Localhost = match Localhost::new(local_wrkdir) {
|
|
||||||
Ok(host) => host,
|
|
||||||
Err(err) => {
|
|
||||||
// Set error in context
|
|
||||||
error!("Failed to initialize localhost: {}", err);
|
|
||||||
ctx.set_error(format!("Could not initialize localhost: {err}"));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut activity: FileTransferActivity =
|
let mut activity: FileTransferActivity =
|
||||||
FileTransferActivity::new(host, ft_params, self.ticks);
|
FileTransferActivity::new(host_bridge_params, remote_params, self.ticks);
|
||||||
// Prepare result
|
// Prepare result
|
||||||
let result: Option<NextActivity>;
|
let result: Option<NextActivity>;
|
||||||
// Create activity
|
// Create activity
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
//!
|
//!
|
||||||
//! defines the types for main.rs types
|
//! defines the types for main.rs types
|
||||||
|
|
||||||
|
mod remote;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
|
pub use remote::{Remote, RemoteArgs};
|
||||||
|
|
||||||
use crate::activity_manager::NextActivity;
|
use crate::activity_manager::NextActivity;
|
||||||
use crate::filetransfer::FileTransferParams;
|
|
||||||
use crate::system::logging::LogLevel;
|
use crate::system::logging::LogLevel;
|
||||||
|
|
||||||
pub enum Task {
|
pub enum Task {
|
||||||
@@ -17,12 +19,14 @@ pub enum Task {
|
|||||||
InstallUpdate,
|
InstallUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs)]
|
#[derive(Default, FromArgs)]
|
||||||
#[argh(description = "
|
#[argh(description = "
|
||||||
where positional can be:
|
where positional can be:
|
||||||
- [address] [local-wrkdir]
|
- [address_a] [address_b] [local-wrkdir]
|
||||||
OR
|
OR
|
||||||
- [bookmark-Name] [local-wrkdir]
|
- -b [bookmark-name_1] -b [bookmark-name_2] [local-wrkdir]
|
||||||
|
|
||||||
|
and any combination of the above
|
||||||
|
|
||||||
Address syntax can be:
|
Address syntax can be:
|
||||||
|
|
||||||
@@ -37,14 +41,15 @@ pub struct Args {
|
|||||||
#[argh(subcommand)]
|
#[argh(subcommand)]
|
||||||
pub nested: Option<ArgsSubcommands>,
|
pub nested: Option<ArgsSubcommands>,
|
||||||
/// resolve address argument as a bookmark name
|
/// resolve address argument as a bookmark name
|
||||||
#[argh(switch, short = 'b')]
|
#[argh(option, short = 'b')]
|
||||||
pub address_as_bookmark: bool,
|
pub bookmark: Vec<String>,
|
||||||
/// enable TRACE log level
|
/// enable TRACE log level
|
||||||
#[argh(switch, short = 'D')]
|
#[argh(switch, short = 'D')]
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
/// provide password from CLI
|
/// provide password from CLI; if you need to provide multiple passwords, use multiple -P flags.
|
||||||
|
/// In case just respect the order of the addresses
|
||||||
#[argh(option, short = 'P')]
|
#[argh(option, short = 'P')]
|
||||||
pub password: Option<String>,
|
pub password: Vec<String>,
|
||||||
/// disable logging
|
/// disable logging
|
||||||
#[argh(switch, short = 'q')]
|
#[argh(switch, short = 'q')]
|
||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
@@ -55,10 +60,7 @@ pub struct Args {
|
|||||||
#[argh(switch, short = 'v')]
|
#[argh(switch, short = 'v')]
|
||||||
pub version: bool,
|
pub version: bool,
|
||||||
// -- positional
|
// -- positional
|
||||||
#[argh(
|
#[argh(positional, description = "address1 address2 local-wrkdir")]
|
||||||
positional,
|
|
||||||
description = "protocol://user@address:port:wrkdir local-wrkdir"
|
|
||||||
)]
|
|
||||||
pub positional: Vec<String>,
|
pub positional: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ pub struct LoadThemeArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunOpts {
|
pub struct RunOpts {
|
||||||
pub remote: Remote,
|
pub remote: RemoteArgs,
|
||||||
pub ticks: Duration,
|
pub ticks: Duration,
|
||||||
pub log_level: LogLevel,
|
pub log_level: LogLevel,
|
||||||
pub task: Task,
|
pub task: Task,
|
||||||
@@ -122,45 +124,10 @@ impl RunOpts {
|
|||||||
impl Default for RunOpts {
|
impl Default for RunOpts {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
remote: Remote::None,
|
remote: RemoteArgs::default(),
|
||||||
ticks: Duration::from_millis(10),
|
ticks: Duration::from_millis(10),
|
||||||
log_level: LogLevel::Info,
|
log_level: LogLevel::Info,
|
||||||
task: Task::Activity(NextActivity::Authentication),
|
task: Task::Activity(NextActivity::Authentication),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
pub enum Remote {
|
|
||||||
Bookmark(BookmarkParams),
|
|
||||||
Host(HostParams),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BookmarkParams {
|
|
||||||
pub name: String,
|
|
||||||
pub password: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HostParams {
|
|
||||||
pub params: FileTransferParams,
|
|
||||||
pub password: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BookmarkParams {
|
|
||||||
pub fn new<S: AsRef<str>>(name: S, password: Option<S>) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.as_ref().to_string(),
|
|
||||||
password: password.map(|x| x.as_ref().to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HostParams {
|
|
||||||
pub fn new<S: AsRef<str>>(params: FileTransferParams, password: Option<S>) -> Self {
|
|
||||||
Self {
|
|
||||||
params,
|
|
||||||
password: password.map(|x| x.as_ref().to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
271
src/cli/remote.rs
Normal file
271
src/cli/remote.rs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use super::Args;
|
||||||
|
use crate::filetransfer::FileTransferParams;
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
|
/// Address type
|
||||||
|
enum AddrType {
|
||||||
|
Address,
|
||||||
|
Bookmark,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Args for remote connection
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RemoteArgs {
|
||||||
|
pub host_bridge: Remote,
|
||||||
|
pub remote: Remote,
|
||||||
|
pub local_dir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RemoteArgs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
host_bridge: Remote::None,
|
||||||
|
remote: Remote::None,
|
||||||
|
local_dir: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Args> for RemoteArgs {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(args: &Args) -> Result<Self, Self::Error> {
|
||||||
|
let mut remote_args = RemoteArgs::default();
|
||||||
|
// validate arguments
|
||||||
|
match (args.bookmark.len(), args.positional.len()) {
|
||||||
|
(0, positional) if positional < 4 => Ok(()),
|
||||||
|
(1, positional) if positional < 3 => Ok(()),
|
||||||
|
(2, positional) if positional < 2 => Ok(()),
|
||||||
|
(_, _) => Err("Too many arguments".to_string()),
|
||||||
|
}?;
|
||||||
|
// parse bookmark first
|
||||||
|
let last_item_index = (args.bookmark.len() + args.positional.len())
|
||||||
|
.checked_sub(1)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut hosts = vec![];
|
||||||
|
|
||||||
|
for (i, (addr_type, arg)) in args
|
||||||
|
.bookmark
|
||||||
|
.iter()
|
||||||
|
.map(|x| (AddrType::Bookmark, x))
|
||||||
|
.chain(args.positional.iter().map(|x| (AddrType::Address, x)))
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
// check if has password
|
||||||
|
let password = args.password.get(i).cloned();
|
||||||
|
|
||||||
|
// check if is last item and so a possible local dir
|
||||||
|
if i == last_item_index && Path::new(arg).exists() {
|
||||||
|
remote_args.local_dir = Some(PathBuf::from(arg));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let remote = match addr_type {
|
||||||
|
AddrType::Address => Self::parse_remote_address(arg)
|
||||||
|
.map(|x| Remote::Host(HostParams::new(x, password)))?,
|
||||||
|
AddrType::Bookmark => Remote::Bookmark(BookmarkParams::new(arg, password.as_ref())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// set remote
|
||||||
|
hosts.push(remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set args based on hosts len
|
||||||
|
if hosts.len() == 1 {
|
||||||
|
remote_args.remote = hosts.pop().unwrap();
|
||||||
|
} else if hosts.len() == 2 {
|
||||||
|
remote_args.host_bridge = hosts.pop().unwrap();
|
||||||
|
remote_args.remote = hosts.pop().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(remote_args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteArgs {
|
||||||
|
/// Parse remote address
|
||||||
|
fn parse_remote_address(remote: &str) -> Result<FileTransferParams, String> {
|
||||||
|
utils::parser::parse_remote_opt(remote).map_err(|e| format!("Bad address option: {e}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remote argument type
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Remote {
|
||||||
|
/// Bookmark name argument
|
||||||
|
Bookmark(BookmarkParams),
|
||||||
|
/// Host argument
|
||||||
|
Host(HostParams),
|
||||||
|
/// Unspecified
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Remote {
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Self::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bookmark parameters
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BookmarkParams {
|
||||||
|
/// bookmark name
|
||||||
|
pub name: String,
|
||||||
|
/// bookmark password
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Host parameters
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HostParams {
|
||||||
|
/// file transfer parameters
|
||||||
|
pub file_transfer_params: FileTransferParams,
|
||||||
|
/// host password specified in arguments
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BookmarkParams {
|
||||||
|
pub fn new<S: AsRef<str>>(name: S, password: Option<S>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.as_ref().to_string(),
|
||||||
|
password: password.map(|x| x.as_ref().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostParams {
|
||||||
|
pub fn new<S: AsRef<str>>(params: FileTransferParams, password: Option<S>) -> Self {
|
||||||
|
Self {
|
||||||
|
file_transfer_params: params,
|
||||||
|
password: password.map(|x| x.as_ref().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_make_remote_args_from_args_one_remote() {
|
||||||
|
let args = Args {
|
||||||
|
positional: vec!["scp://host1".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::None));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Host(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_make_remote_args_from_args_two_remotes() {
|
||||||
|
let args = Args {
|
||||||
|
positional: vec!["scp://host1".to_string(), "scp://host2".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Host(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(posix)]
|
||||||
|
fn test_should_make_remote_args_from_two_remotes_and_local_dir() {
|
||||||
|
let args = Args {
|
||||||
|
positional: vec![
|
||||||
|
"scp://host1".to_string(),
|
||||||
|
"scp://host2".to_string(),
|
||||||
|
"/home".to_string(),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Host(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, Some(PathBuf::from("/home")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_make_remote_args_from_args_one_bookmarks() {
|
||||||
|
let args = Args {
|
||||||
|
bookmark: vec!["foo".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::None));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_make_remote_args_from_args_two_bookmarks() {
|
||||||
|
let args = Args {
|
||||||
|
bookmark: vec!["foo".to_string(), "bar".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::Bookmark(_)));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(posix)]
|
||||||
|
fn test_should_make_remote_args_from_two_bookmarks_and_local_dir() {
|
||||||
|
let args = Args {
|
||||||
|
bookmark: vec!["foo".to_string(), "bar".to_string()],
|
||||||
|
positional: vec!["/home".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::Bookmark(_)));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, Some(PathBuf::from("/home")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_make_remote_args_from_one_bookmark_and_one_remote() {
|
||||||
|
let args = Args {
|
||||||
|
bookmark: vec!["foo".to_string()],
|
||||||
|
positional: vec!["scp://host1".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(posix)]
|
||||||
|
fn test_should_make_remote_args_from_one_bookmark_and_one_remote_with_local_dir() {
|
||||||
|
let args = Args {
|
||||||
|
positional: vec!["scp://host1".to_string(), "/home".to_string()],
|
||||||
|
bookmark: vec!["foo".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote_args = RemoteArgs::try_from(&args).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(remote_args.host_bridge, Remote::Host(_)));
|
||||||
|
assert!(matches!(remote_args.remote, Remote::Bookmark(_)));
|
||||||
|
assert_eq!(remote_args.local_dir, Some(PathBuf::from("/home")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,9 +108,9 @@ impl From<FileTransferParams> for Bookmark {
|
|||||||
smb: Some(SmbParams::from(params.clone())),
|
smb: Some(SmbParams::from(params.clone())),
|
||||||
protocol,
|
protocol,
|
||||||
address: Some(params.address),
|
address: Some(params.address),
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
port: Some(params.port),
|
port: Some(params.port),
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
port: None,
|
port: None,
|
||||||
username: params.username,
|
username: params.username,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
@@ -159,7 +159,7 @@ impl From<Bookmark> for FileTransferParams {
|
|||||||
let params = KubeProtocolParams::from(params);
|
let params = KubeProtocolParams::from(params);
|
||||||
Self::new(bookmark.protocol, ProtocolParams::Kube(params))
|
Self::new(bookmark.protocol, ProtocolParams::Kube(params))
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
FileTransferProtocol::Smb => {
|
FileTransferProtocol::Smb => {
|
||||||
let params = TransferSmbParams::new(
|
let params = TransferSmbParams::new(
|
||||||
bookmark.address.unwrap_or_default(),
|
bookmark.address.unwrap_or_default(),
|
||||||
@@ -172,7 +172,7 @@ impl From<Bookmark> for FileTransferParams {
|
|||||||
|
|
||||||
Self::new(bookmark.protocol, ProtocolParams::Smb(params))
|
Self::new(bookmark.protocol, ProtocolParams::Smb(params))
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
FileTransferProtocol::Smb => {
|
FileTransferProtocol::Smb => {
|
||||||
let params = TransferSmbParams::new(
|
let params = TransferSmbParams::new(
|
||||||
bookmark.address.unwrap_or_default(),
|
bookmark.address.unwrap_or_default(),
|
||||||
@@ -521,7 +521,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn should_get_ftparams_from_smb_bookmark() {
|
fn should_get_ftparams_from_smb_bookmark() {
|
||||||
let bookmark: Bookmark = Bookmark {
|
let bookmark: Bookmark = Bookmark {
|
||||||
protocol: FileTransferProtocol::Smb,
|
protocol: FileTransferProtocol::Smb,
|
||||||
@@ -559,7 +559,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
fn should_get_ftparams_from_smb_bookmark() {
|
fn should_get_ftparams_from_smb_bookmark() {
|
||||||
let bookmark: Bookmark = Bookmark {
|
let bookmark: Bookmark = Bookmark {
|
||||||
protocol: FileTransferProtocol::Smb,
|
protocol: FileTransferProtocol::Smb,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub struct SmbParams {
|
|||||||
pub workgroup: Option<String>,
|
pub workgroup: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
impl From<TransferSmbParams> for SmbParams {
|
impl From<TransferSmbParams> for SmbParams {
|
||||||
fn from(params: TransferSmbParams) -> Self {
|
fn from(params: TransferSmbParams) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -19,7 +19,7 @@ impl From<TransferSmbParams> for SmbParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
impl From<TransferSmbParams> for SmbParams {
|
impl From<TransferSmbParams> for SmbParams {
|
||||||
fn from(params: TransferSmbParams) -> Self {
|
fn from(params: TransferSmbParams) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ mod tests {
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use tuirealm::tui::style::Color;
|
use tuirealm::ratatui::style::Color;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::bookmarks::{Bookmark, KubeParams, S3Params, SmbParams, UserHosts};
|
use crate::config::bookmarks::{Bookmark, KubeParams, S3Params, SmbParams, UserHosts};
|
||||||
@@ -422,14 +422,14 @@ mod tests {
|
|||||||
let host = hosts.bookmarks.get("smb").unwrap();
|
let host = hosts.bookmarks.get("smb").unwrap();
|
||||||
assert_eq!(host.address.as_deref().unwrap(), "localhost");
|
assert_eq!(host.address.as_deref().unwrap(), "localhost");
|
||||||
assert_eq!(host.port.unwrap(), 445);
|
assert_eq!(host.port.unwrap(), 445);
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(host.username.as_deref().unwrap(), "test");
|
assert_eq!(host.username.as_deref().unwrap(), "test");
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(host.password.as_deref().unwrap(), "test");
|
assert_eq!(host.password.as_deref().unwrap(), "test");
|
||||||
|
|
||||||
let smb = host.smb.as_ref().unwrap();
|
let smb = host.smb.as_ref().unwrap();
|
||||||
assert_eq!(smb.share.as_str(), "temp");
|
assert_eq!(smb.share.as_str(), "temp");
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(smb.workgroup.as_deref().unwrap(), "test");
|
assert_eq!(smb.workgroup.as_deref().unwrap(), "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// ext
|
// ext
|
||||||
use serde::de::Error as DeError;
|
use serde::de::Error as DeError;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use tuirealm::tui::style::Color;
|
use tuirealm::ratatui::style::Color;
|
||||||
|
|
||||||
use crate::utils::fmt::fmt_color;
|
use crate::utils::fmt::fmt_color;
|
||||||
use crate::utils::parser::parse_color;
|
use crate::utils::parser::parse_color;
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use bytesize::ByteSize;
|
|||||||
use lazy_regex::{Lazy, Regex};
|
use lazy_regex::{Lazy, Regex};
|
||||||
use remotefs::File;
|
use remotefs::File;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
use users::{get_group_by_gid, get_user_by_uid};
|
use uzers::{get_group_by_gid, get_user_by_uid};
|
||||||
|
|
||||||
use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time};
|
use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time};
|
||||||
use crate::utils::path::diff_paths;
|
use crate::utils::path::diff_paths;
|
||||||
@@ -211,7 +211,7 @@ impl Formatter {
|
|||||||
_fmt_extra: Option<&String>,
|
_fmt_extra: Option<&String>,
|
||||||
) -> String {
|
) -> String {
|
||||||
// Get username
|
// Get username
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
let group: String = match fsentry.metadata().gid {
|
let group: String = match fsentry.metadata().gid {
|
||||||
Some(gid) => match get_group_by_gid(gid) {
|
Some(gid) => match get_group_by_gid(gid) {
|
||||||
Some(user) => user.name().to_string_lossy().to_string(),
|
Some(user) => user.name().to_string_lossy().to_string(),
|
||||||
@@ -219,7 +219,7 @@ impl Formatter {
|
|||||||
},
|
},
|
||||||
None => 0.to_string(),
|
None => 0.to_string(),
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
let group: String = match fsentry.metadata().gid {
|
let group: String = match fsentry.metadata().gid {
|
||||||
Some(gid) => gid.to_string(),
|
Some(gid) => gid.to_string(),
|
||||||
None => 0.to_string(),
|
None => 0.to_string(),
|
||||||
@@ -420,7 +420,7 @@ impl Formatter {
|
|||||||
_fmt_extra: Option<&String>,
|
_fmt_extra: Option<&String>,
|
||||||
) -> String {
|
) -> String {
|
||||||
// Get username
|
// Get username
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
let username: String = match fsentry.metadata().uid {
|
let username: String = match fsentry.metadata().uid {
|
||||||
Some(uid) => match get_user_by_uid(uid) {
|
Some(uid) => match get_user_by_uid(uid) {
|
||||||
Some(user) => user.name().to_string_lossy().to_string(),
|
Some(user) => user.name().to_string_lossy().to_string(),
|
||||||
@@ -428,7 +428,7 @@ impl Formatter {
|
|||||||
},
|
},
|
||||||
None => 0.to_string(),
|
None => 0.to_string(),
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
let username: String = match fsentry.metadata().uid {
|
let username: String = match fsentry.metadata().uid {
|
||||||
Some(uid) => uid.to_string(),
|
Some(uid) => uid.to_string(),
|
||||||
None => 0.to_string(),
|
None => 0.to_string(),
|
||||||
@@ -592,7 +592,7 @@ mod tests {
|
|||||||
mode: Some(UnixPex::from(0o644)),
|
mode: Some(UnixPex::from(0o644)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -600,7 +600,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -623,7 +623,7 @@ mod tests {
|
|||||||
mode: Some(UnixPex::from(0o644)),
|
mode: Some(UnixPex::from(0o644)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -631,7 +631,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -654,7 +654,7 @@ mod tests {
|
|||||||
mode: None,
|
mode: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -662,7 +662,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -685,7 +685,7 @@ mod tests {
|
|||||||
mode: None,
|
mode: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -693,7 +693,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -723,7 +723,7 @@ mod tests {
|
|||||||
mode: Some(UnixPex::from(0o755)),
|
mode: Some(UnixPex::from(0o755)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -731,7 +731,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -754,7 +754,7 @@ mod tests {
|
|||||||
mode: None,
|
mode: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -762,7 +762,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatter.fmt(&entry),
|
formatter.fmt(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -864,7 +864,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn should_fmt_path() {
|
fn should_fmt_path() {
|
||||||
let t: SystemTime = SystemTime::now();
|
let t: SystemTime = SystemTime::now();
|
||||||
let entry = File {
|
let entry = File {
|
||||||
@@ -896,7 +896,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn should_fmt_utf8_path() {
|
fn should_fmt_utf8_path() {
|
||||||
let t: SystemTime = SystemTime::now();
|
let t: SystemTime = SystemTime::now();
|
||||||
let entry = File {
|
let entry = File {
|
||||||
|
|||||||
@@ -519,7 +519,7 @@ mod tests {
|
|||||||
mode: Some(UnixPex::from(0o644)),
|
mode: Some(UnixPex::from(0o644)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
explorer.fmt_file(&entry),
|
explorer.fmt_file(&entry),
|
||||||
format!(
|
format!(
|
||||||
@@ -527,7 +527,7 @@ mod tests {
|
|||||||
fmt_time(t, "%b %d %Y %H:%M")
|
fmt_time(t, "%b %d %Y %H:%M")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
explorer.fmt_file(&entry),
|
explorer.fmt_file(&entry),
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
21
src/filetransfer/host_bridge_builder.rs
Normal file
21
src/filetransfer/host_bridge_builder.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use super::{HostBridgeParams, RemoteFsBuilder};
|
||||||
|
use crate::host::{HostBridge, Localhost, RemoteBridged};
|
||||||
|
use crate::system::config_client::ConfigClient;
|
||||||
|
|
||||||
|
pub struct HostBridgeBuilder;
|
||||||
|
|
||||||
|
impl HostBridgeBuilder {
|
||||||
|
/// Build Host Bridge from parms
|
||||||
|
///
|
||||||
|
/// if protocol and parameters are inconsistent, the function will panic.
|
||||||
|
pub fn build(params: HostBridgeParams, config_client: &ConfigClient) -> Box<dyn HostBridge> {
|
||||||
|
match params {
|
||||||
|
HostBridgeParams::Localhost(path) => {
|
||||||
|
Box::new(Localhost::new(path).expect("Failed to create Localhost"))
|
||||||
|
}
|
||||||
|
HostBridgeParams::Remote(protocol, params) => Box::new(RemoteBridged::from(
|
||||||
|
RemoteFsBuilder::build(protocol, params, config_client),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
//!
|
//!
|
||||||
//! `filetransfer` is the module which provides the file transfer protocols and remotefs builders
|
//! `filetransfer` is the module which provides the file transfer protocols and remotefs builders
|
||||||
|
|
||||||
mod builder;
|
mod host_bridge_builder;
|
||||||
pub mod params;
|
pub mod params;
|
||||||
|
mod remotefs_builder;
|
||||||
|
|
||||||
// -- export types
|
// -- export types
|
||||||
pub use builder::Builder;
|
pub use host_bridge_builder::HostBridgeBuilder;
|
||||||
pub use params::{FileTransferParams, ProtocolParams};
|
pub use params::{FileTransferParams, HostBridgeParams, ProtocolParams};
|
||||||
|
pub use remotefs_builder::RemoteFsBuilder;
|
||||||
|
|
||||||
/// This enum defines the different transfer protocol available in termscp
|
/// This enum defines the different transfer protocol available in termscp
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,24 @@ pub use self::smb::SmbParams;
|
|||||||
pub use self::webdav::WebDAVProtocolParams;
|
pub use self::webdav::WebDAVProtocolParams;
|
||||||
use super::FileTransferProtocol;
|
use super::FileTransferProtocol;
|
||||||
|
|
||||||
|
/// Host bridge params
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum HostBridgeParams {
|
||||||
|
/// Localhost with starting working directory
|
||||||
|
Localhost(PathBuf),
|
||||||
|
/// Remote host with protocol and file transfer params
|
||||||
|
Remote(FileTransferProtocol, ProtocolParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostBridgeParams {
|
||||||
|
pub fn unwrap_protocol_params(&self) -> &ProtocolParams {
|
||||||
|
match self {
|
||||||
|
HostBridgeParams::Localhost(_) => panic!("Localhost has no protocol params"),
|
||||||
|
HostBridgeParams::Remote(_, params) => params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds connection parameters for file transfers
|
/// Holds connection parameters for file transfers
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FileTransferParams {
|
pub struct FileTransferParams {
|
||||||
@@ -34,6 +52,43 @@ pub enum ProtocolParams {
|
|||||||
WebDAV(WebDAVProtocolParams),
|
WebDAV(WebDAVProtocolParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProtocolParams {
|
||||||
|
pub fn password_missing(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ProtocolParams::AwsS3(params) => params.password_missing(),
|
||||||
|
ProtocolParams::Generic(params) => params.password_missing(),
|
||||||
|
ProtocolParams::Kube(params) => params.password_missing(),
|
||||||
|
ProtocolParams::Smb(params) => params.password_missing(),
|
||||||
|
ProtocolParams::WebDAV(params) => params.password_missing(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the secret to ft params for the default secret field for this protocol
|
||||||
|
pub fn set_default_secret(&mut self, secret: String) {
|
||||||
|
match self {
|
||||||
|
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
|
||||||
|
ProtocolParams::Generic(params) => params.set_default_secret(secret),
|
||||||
|
ProtocolParams::Kube(params) => params.set_default_secret(secret),
|
||||||
|
ProtocolParams::Smb(params) => params.set_default_secret(secret),
|
||||||
|
ProtocolParams::WebDAV(params) => params.set_default_secret(secret),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn host_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
|
||||||
|
ProtocolParams::Generic(params) => params.address.clone(),
|
||||||
|
ProtocolParams::Kube(params) => params
|
||||||
|
.namespace
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| String::from("default")),
|
||||||
|
ProtocolParams::Smb(params) => params.address.clone(),
|
||||||
|
ProtocolParams::WebDAV(params) => params.uri.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Protocol params used by most common protocols
|
/// Protocol params used by most common protocols
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GenericProtocolParams {
|
pub struct GenericProtocolParams {
|
||||||
@@ -68,25 +123,15 @@ impl FileTransferParams {
|
|||||||
|
|
||||||
/// Returns whether a password is supposed to be required for this protocol params.
|
/// 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!!!
|
/// The result true is returned ONLY if the supposed secret is MISSING!!!
|
||||||
|
#[cfg(test)]
|
||||||
pub fn password_missing(&self) -> bool {
|
pub fn password_missing(&self) -> bool {
|
||||||
match &self.params {
|
self.params.password_missing()
|
||||||
ProtocolParams::AwsS3(params) => params.password_missing(),
|
|
||||||
ProtocolParams::Generic(params) => params.password_missing(),
|
|
||||||
ProtocolParams::Kube(params) => params.password_missing(),
|
|
||||||
ProtocolParams::Smb(params) => params.password_missing(),
|
|
||||||
ProtocolParams::WebDAV(params) => params.password_missing(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the secret to ft params for the default secret field for this protocol
|
/// Set the secret to ft params for the default secret field for this protocol
|
||||||
|
#[cfg(test)]
|
||||||
pub fn set_default_secret(&mut self, secret: String) {
|
pub fn set_default_secret(&mut self, secret: String) {
|
||||||
match &mut self.params {
|
self.params.set_default_secret(secret);
|
||||||
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
|
|
||||||
ProtocolParams::Generic(params) => params.set_default_secret(secret),
|
|
||||||
ProtocolParams::Kube(params) => params.set_default_secret(secret),
|
|
||||||
ProtocolParams::Smb(params) => params.set_default_secret(secret),
|
|
||||||
ProtocolParams::WebDAV(params) => params.set_default_secret(secret),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +362,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn set_default_secret_smb() {
|
fn set_default_secret_smb() {
|
||||||
let mut params = FileTransferParams::new(
|
let mut params = FileTransferParams::new(
|
||||||
FileTransferProtocol::Scp,
|
FileTransferProtocol::Scp,
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SmbParams {
|
pub struct SmbParams {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub share: String,
|
pub share: String,
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
pub workgroup: Option<String>,
|
pub workgroup: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,17 +18,17 @@ impl SmbParams {
|
|||||||
pub fn new<S: AsRef<str>>(address: S, share: S) -> Self {
|
pub fn new<S: AsRef<str>>(address: S, share: S) -> Self {
|
||||||
Self {
|
Self {
|
||||||
address: address.as_ref().to_string(),
|
address: address.as_ref().to_string(),
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
port: 445,
|
port: 445,
|
||||||
share: share.as_ref().to_string(),
|
share: share.as_ref().to_string(),
|
||||||
username: None,
|
username: None,
|
||||||
password: None,
|
password: None,
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
workgroup: None,
|
workgroup: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
pub fn port(mut self, port: u16) -> Self {
|
pub fn port(mut self, port: u16) -> Self {
|
||||||
self.port = port;
|
self.port = port;
|
||||||
self
|
self
|
||||||
@@ -44,7 +44,7 @@ impl SmbParams {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
pub fn workgroup(mut self, workgroup: Option<impl ToString>) -> Self {
|
pub fn workgroup(mut self, workgroup: Option<impl ToString>) -> Self {
|
||||||
self.workgroup = workgroup.map(|x| x.to_string());
|
self.workgroup = workgroup.map(|x| x.to_string());
|
||||||
self
|
self
|
||||||
@@ -57,12 +57,12 @@ impl SmbParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set password
|
/// Set password
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
pub fn set_default_secret(&mut self, secret: String) {
|
pub fn set_default_secret(&mut self, secret: String) {
|
||||||
self.password = Some(secret);
|
self.password = Some(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
pub fn set_default_secret(&mut self, _secret: String) {}
|
pub fn set_default_secret(&mut self, _secret: String) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,20 +78,20 @@ mod test {
|
|||||||
let params = SmbParams::new("localhost", "temp");
|
let params = SmbParams::new("localhost", "temp");
|
||||||
assert_eq!(¶ms.address, "localhost");
|
assert_eq!(¶ms.address, "localhost");
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert_eq!(params.port, 445);
|
assert_eq!(params.port, 445);
|
||||||
assert_eq!(¶ms.share, "temp");
|
assert_eq!(¶ms.share, "temp");
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert!(params.username.is_none());
|
assert!(params.username.is_none());
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert!(params.password.is_none());
|
assert!(params.password.is_none());
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
assert!(params.workgroup.is_none());
|
assert!(params.workgroup.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn should_init_smb_params_with_optionals() {
|
fn should_init_smb_params_with_optionals() {
|
||||||
let params = SmbParams::new("localhost", "temp")
|
let params = SmbParams::new("localhost", "temp")
|
||||||
.port(3456)
|
.port(3456)
|
||||||
@@ -108,7 +108,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
fn should_init_smb_params_with_optionals() {
|
fn should_init_smb_params_with_optionals() {
|
||||||
let params = SmbParams::new("localhost", "temp")
|
let params = SmbParams::new("localhost", "temp")
|
||||||
.username(Some("foo"))
|
.username(Some("foo"))
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ use crate::system::sshkey_storage::SshKeyStorage;
|
|||||||
use crate::utils::ssh as ssh_utils;
|
use crate::utils::ssh as ssh_utils;
|
||||||
|
|
||||||
/// Remotefs builder
|
/// Remotefs builder
|
||||||
pub struct Builder;
|
pub struct RemoteFsBuilder;
|
||||||
|
|
||||||
impl Builder {
|
impl RemoteFsBuilder {
|
||||||
/// Build RemoteFs client from protocol and params.
|
/// Build RemoteFs client from protocol and params.
|
||||||
///
|
///
|
||||||
/// if protocol and parameters are inconsistent, the function will panic.
|
/// if protocol and parameters are inconsistent, the function will panic.
|
||||||
@@ -262,7 +262,7 @@ mod test {
|
|||||||
.session_token(Some("gerry-scotti")),
|
.session_token(Some("gerry-scotti")),
|
||||||
);
|
);
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::AwsS3, params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::AwsS3, params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -275,7 +275,7 @@ mod test {
|
|||||||
.password(Some("qwerty123")),
|
.password(Some("qwerty123")),
|
||||||
);
|
);
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::Ftp(true), params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::Ftp(true), params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -288,7 +288,7 @@ mod test {
|
|||||||
client_key: Some("client_key".to_string()),
|
client_key: Some("client_key".to_string()),
|
||||||
});
|
});
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::Kube, params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::Kube, params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -301,7 +301,7 @@ mod test {
|
|||||||
.password(Some("qwerty123")),
|
.password(Some("qwerty123")),
|
||||||
);
|
);
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::Scp, params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::Scp, params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -314,7 +314,7 @@ mod test {
|
|||||||
.password(Some("qwerty123")),
|
.password(Some("qwerty123")),
|
||||||
);
|
);
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::Sftp, params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::Sftp, params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -322,7 +322,7 @@ mod test {
|
|||||||
fn should_build_smb_fs() {
|
fn should_build_smb_fs() {
|
||||||
let params = ProtocolParams::Smb(SmbParams::new("localhost", "share"));
|
let params = ProtocolParams::Smb(SmbParams::new("localhost", "share"));
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::Smb, params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::Smb, params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -336,7 +336,7 @@ mod test {
|
|||||||
.password(Some("qwerty123")),
|
.password(Some("qwerty123")),
|
||||||
);
|
);
|
||||||
let config_client = get_config_client();
|
let config_client = get_config_client();
|
||||||
let _ = Builder::build(FileTransferProtocol::AwsS3, params, &config_client);
|
let _ = RemoteFsBuilder::build(FileTransferProtocol::AwsS3, params, &config_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_client() -> ConfigClient {
|
fn get_config_client() -> ConfigClient {
|
||||||
84
src/host/bridge.rs
Normal file
84
src/host/bridge.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use remotefs::fs::{Metadata, UnixPex};
|
||||||
|
use remotefs::File;
|
||||||
|
|
||||||
|
use super::HostResult;
|
||||||
|
|
||||||
|
/// Trait to bridge a remote filesystem to the host filesystem
|
||||||
|
///
|
||||||
|
/// In case of `Localhost` this should be effortless, while for remote hosts this should
|
||||||
|
/// implement a real bridge when the resource is first loaded on the local
|
||||||
|
/// filesystem and then processed on the remote.
|
||||||
|
pub trait HostBridge {
|
||||||
|
/// Connect to host
|
||||||
|
fn connect(&mut self) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Disconnect from host
|
||||||
|
fn disconnect(&mut self) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Returns whether the host is connected
|
||||||
|
fn is_connected(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Returns whether the host is localhost
|
||||||
|
fn is_localhost(&self) -> bool;
|
||||||
|
|
||||||
|
/// Print working directory
|
||||||
|
fn pwd(&mut self) -> HostResult<PathBuf>;
|
||||||
|
|
||||||
|
/// Change working directory with the new provided directory
|
||||||
|
fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult<PathBuf>;
|
||||||
|
|
||||||
|
/// Make a directory at path and update the file list (only if relative)
|
||||||
|
fn mkdir(&mut self, dir_name: &Path) -> HostResult<()> {
|
||||||
|
self.mkdir_ex(dir_name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extended option version of makedir.
|
||||||
|
/// ignex: don't report error if directory already exists
|
||||||
|
fn mkdir_ex(&mut self, dir_name: &Path, ignore_existing: bool) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Remove file entry
|
||||||
|
fn remove(&mut self, entry: &File) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Rename file or directory to new name
|
||||||
|
fn rename(&mut self, entry: &File, dst_path: &Path) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Copy file to destination path
|
||||||
|
fn copy(&mut self, entry: &File, dst: &Path) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Stat file and create a File
|
||||||
|
fn stat(&mut self, path: &Path) -> HostResult<File>;
|
||||||
|
|
||||||
|
/// Returns whether provided file path exists
|
||||||
|
fn exists(&mut self, path: &Path) -> HostResult<bool>;
|
||||||
|
|
||||||
|
/// Get content of a directory
|
||||||
|
fn list_dir(&mut self, path: &Path) -> HostResult<Vec<File>>;
|
||||||
|
|
||||||
|
/// Set file stat
|
||||||
|
fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Execute a command on localhost
|
||||||
|
fn exec(&mut self, cmd: &str) -> HostResult<String>;
|
||||||
|
|
||||||
|
/// Create a symlink from src to dst
|
||||||
|
fn symlink(&mut self, src: &Path, dst: &Path) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Change file mode to file, according to UNIX permissions
|
||||||
|
fn chmod(&mut self, path: &Path, pex: UnixPex) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Open file for reading
|
||||||
|
fn open_file(&mut self, file: &Path) -> HostResult<Box<dyn Read + Send>>;
|
||||||
|
|
||||||
|
/// Open file for writing
|
||||||
|
fn create_file(
|
||||||
|
&mut self,
|
||||||
|
file: &Path,
|
||||||
|
metadata: &Metadata,
|
||||||
|
) -> HostResult<Box<dyn Write + Send>>;
|
||||||
|
|
||||||
|
/// Finalize write operation
|
||||||
|
fn finalize_write(&mut self, writer: Box<dyn Write + Send>) -> HostResult<()>;
|
||||||
|
}
|
||||||
1042
src/host/localhost.rs
Normal file
1042
src/host/localhost.rs
Normal file
File diff suppressed because it is too large
Load Diff
986
src/host/mod.rs
986
src/host/mod.rs
File diff suppressed because it is too large
Load Diff
211
src/host/remote_bridged.rs
Normal file
211
src/host/remote_bridged.rs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
mod temp_mapped_file;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use remotefs::fs::{Metadata, UnixPex};
|
||||||
|
use remotefs::{File, RemoteError, RemoteErrorType, RemoteFs};
|
||||||
|
|
||||||
|
use self::temp_mapped_file::TempMappedFile;
|
||||||
|
use super::{HostBridge, HostError, HostResult};
|
||||||
|
|
||||||
|
struct WriteStreamOp {
|
||||||
|
path: PathBuf,
|
||||||
|
metadata: Metadata,
|
||||||
|
tempfile: TempMappedFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A remote host bridged over the local host
|
||||||
|
pub struct RemoteBridged {
|
||||||
|
/// Remote fs client
|
||||||
|
remote: Box<dyn RemoteFs>,
|
||||||
|
/// Reminder used to finalize write stream
|
||||||
|
write_stream_op: Option<WriteStreamOp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteBridged {
|
||||||
|
fn open_file_from_temp(&mut self, file: &Path) -> HostResult<Box<dyn Read + Send>> {
|
||||||
|
let mut temp_file = TempMappedFile::new()?;
|
||||||
|
|
||||||
|
self.remote
|
||||||
|
.open_file(file, Box::new(temp_file.clone()))
|
||||||
|
.map_err(HostError::from)?;
|
||||||
|
|
||||||
|
// Sync changes
|
||||||
|
temp_file.sync()?;
|
||||||
|
|
||||||
|
// now return as read
|
||||||
|
Ok(Box::new(temp_file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<dyn RemoteFs>> for RemoteBridged {
|
||||||
|
fn from(remote: Box<dyn RemoteFs>) -> Self {
|
||||||
|
RemoteBridged {
|
||||||
|
remote,
|
||||||
|
write_stream_op: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostBridge for RemoteBridged {
|
||||||
|
fn connect(&mut self) -> HostResult<()> {
|
||||||
|
self.remote.connect().map(|_| ()).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disconnect(&mut self) -> HostResult<()> {
|
||||||
|
self.remote.disconnect().map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_connected(&mut self) -> bool {
|
||||||
|
self.remote.is_connected()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_localhost(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwd(&mut self) -> HostResult<PathBuf> {
|
||||||
|
debug!("Getting working directory");
|
||||||
|
self.remote.pwd().map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult<PathBuf> {
|
||||||
|
debug!("Changing working directory to {:?}", new_dir);
|
||||||
|
self.remote.change_dir(new_dir).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkdir_ex(&mut self, dir_name: &Path, ignore_existing: bool) -> HostResult<()> {
|
||||||
|
debug!("Creating directory {:?}", dir_name);
|
||||||
|
match self.remote.create_dir(dir_name, UnixPex::from(0o755)) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(remotefs::RemoteError {
|
||||||
|
kind: RemoteErrorType::DirectoryAlreadyExists,
|
||||||
|
..
|
||||||
|
}) if ignore_existing => Ok(()),
|
||||||
|
Err(e) => Err(HostError::from(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, entry: &File) -> HostResult<()> {
|
||||||
|
debug!("Removing {:?}", entry.path());
|
||||||
|
if entry.is_dir() {
|
||||||
|
self.remote
|
||||||
|
.remove_dir_all(entry.path())
|
||||||
|
.map_err(HostError::from)
|
||||||
|
} else {
|
||||||
|
self.remote
|
||||||
|
.remove_file(entry.path())
|
||||||
|
.map_err(HostError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename(&mut self, entry: &File, dst_path: &Path) -> HostResult<()> {
|
||||||
|
debug!("Renaming {:?} to {:?}", entry.path(), dst_path);
|
||||||
|
self.remote
|
||||||
|
.mov(entry.path(), dst_path)
|
||||||
|
.map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(&mut self, entry: &File, dst: &Path) -> HostResult<()> {
|
||||||
|
debug!("Copying {:?} to {:?}", entry.path(), dst);
|
||||||
|
self.remote.copy(entry.path(), dst).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stat(&mut self, path: &Path) -> HostResult<File> {
|
||||||
|
debug!("Statting {:?}", path);
|
||||||
|
self.remote.stat(path).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&mut self, path: &Path) -> HostResult<bool> {
|
||||||
|
debug!("Checking existence of {:?}", path);
|
||||||
|
self.remote.exists(path).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_dir(&mut self, path: &Path) -> HostResult<Vec<File>> {
|
||||||
|
debug!("Listing directory {:?}", path);
|
||||||
|
self.remote.list_dir(path).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()> {
|
||||||
|
debug!("Setting metadata for {:?}", path);
|
||||||
|
self.remote
|
||||||
|
.setstat(path, metadata.clone())
|
||||||
|
.map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(&mut self, cmd: &str) -> HostResult<String> {
|
||||||
|
debug!("Executing command: {}", cmd);
|
||||||
|
self.remote
|
||||||
|
.exec(cmd)
|
||||||
|
.map(|(_, stdout)| stdout)
|
||||||
|
.map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symlink(&mut self, src: &Path, dst: &Path) -> HostResult<()> {
|
||||||
|
debug!("Creating symlink from {:?} to {:?}", src, dst);
|
||||||
|
self.remote.symlink(src, dst).map_err(HostError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chmod(&mut self, path: &Path, pex: UnixPex) -> HostResult<()> {
|
||||||
|
debug!("Changing permissions of {:?} to {:?}", path, pex);
|
||||||
|
let stat = self.remote.stat(path).map_err(HostError::from)?;
|
||||||
|
let mut metadata = stat.metadata.clone();
|
||||||
|
metadata.mode = Some(pex);
|
||||||
|
|
||||||
|
self.setstat(path, &metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_file(&mut self, file: &Path) -> HostResult<Box<dyn Read + Send>> {
|
||||||
|
// try to use stream, otherwise download to a temporary file and return a reader
|
||||||
|
match self.remote.open(file) {
|
||||||
|
Ok(stream) => Ok(Box::new(stream)),
|
||||||
|
Err(RemoteError {
|
||||||
|
kind: RemoteErrorType::UnsupportedFeature,
|
||||||
|
..
|
||||||
|
}) => self.open_file_from_temp(file),
|
||||||
|
Err(e) => Err(HostError::from(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_file(
|
||||||
|
&mut self,
|
||||||
|
file: &Path,
|
||||||
|
metadata: &Metadata,
|
||||||
|
) -> HostResult<Box<dyn Write + Send>> {
|
||||||
|
// try to use stream, otherwise download to a temporary file and return a reader
|
||||||
|
match self.remote.create(file, metadata) {
|
||||||
|
Ok(stream) => Ok(Box::new(stream)),
|
||||||
|
Err(RemoteError {
|
||||||
|
kind: RemoteErrorType::UnsupportedFeature,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let tempfile = TempMappedFile::new()?;
|
||||||
|
self.write_stream_op = Some(WriteStreamOp {
|
||||||
|
path: file.to_path_buf(),
|
||||||
|
metadata: metadata.clone(),
|
||||||
|
tempfile: tempfile.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Box::new(tempfile))
|
||||||
|
}
|
||||||
|
Err(e) => Err(HostError::from(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize_write(&mut self, _writer: Box<dyn Write + Send>) -> HostResult<()> {
|
||||||
|
if let Some(WriteStreamOp {
|
||||||
|
path,
|
||||||
|
metadata,
|
||||||
|
mut tempfile,
|
||||||
|
}) = self.write_stream_op.take()
|
||||||
|
{
|
||||||
|
// sync
|
||||||
|
tempfile.sync()?;
|
||||||
|
// write file
|
||||||
|
self.remote
|
||||||
|
.create_file(&path, &metadata, Box::new(tempfile))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/host/remote_bridged/temp_mapped_file.rs
Normal file
120
src/host/remote_bridged/temp_mapped_file.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
use crate::host::{HostError, HostErrorType, HostResult};
|
||||||
|
|
||||||
|
/// A temporary file mapped to a remote file which has been transferred to local
|
||||||
|
/// and which supports read/write operations
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TempMappedFile {
|
||||||
|
tempfile: Arc<NamedTempFile>,
|
||||||
|
handle: Arc<Mutex<Option<File>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for TempMappedFile {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let rc = self.write_hnd()?;
|
||||||
|
let mut ref_mut = rc.lock().unwrap();
|
||||||
|
ref_mut.as_mut().unwrap().write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
let rc = self.write_hnd()?;
|
||||||
|
let mut ref_mut = rc.lock().unwrap();
|
||||||
|
ref_mut.as_mut().unwrap().flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for TempMappedFile {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let rc = self.read_hnd()?;
|
||||||
|
let mut ref_mut = rc.lock().unwrap();
|
||||||
|
ref_mut.as_mut().unwrap().read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TempMappedFile {
|
||||||
|
pub fn new() -> HostResult<Self> {
|
||||||
|
NamedTempFile::new()
|
||||||
|
.map(|tempfile| TempMappedFile {
|
||||||
|
tempfile: Arc::new(tempfile),
|
||||||
|
handle: Arc::new(Mutex::new(None)),
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
HostError::new(
|
||||||
|
HostErrorType::CouldNotCreateFile,
|
||||||
|
Some(e),
|
||||||
|
std::path::Path::new(""),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Syncs the file to disk and frees the file handle.
|
||||||
|
///
|
||||||
|
/// Must be called
|
||||||
|
pub fn sync(&mut self) -> HostResult<()> {
|
||||||
|
{
|
||||||
|
let mut lock = self.handle.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(hnd) = lock.take() {
|
||||||
|
hnd.sync_all().map_err(|e| {
|
||||||
|
HostError::new(
|
||||||
|
HostErrorType::FileNotAccessible,
|
||||||
|
Some(e),
|
||||||
|
self.tempfile.path(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_hnd(&mut self) -> io::Result<Arc<Mutex<Option<File>>>> {
|
||||||
|
{
|
||||||
|
let mut lock = self.handle.lock().unwrap();
|
||||||
|
if lock.is_none() {
|
||||||
|
let hnd = File::create(self.tempfile.path())?;
|
||||||
|
lock.replace(hnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.handle.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_hnd(&mut self) -> io::Result<Arc<Mutex<Option<File>>>> {
|
||||||
|
{
|
||||||
|
let mut lock = self.handle.lock().unwrap();
|
||||||
|
if lock.is_none() {
|
||||||
|
let hnd = File::open(self.tempfile.path())?;
|
||||||
|
lock.replace(hnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.handle.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_write_and_read_file() {
|
||||||
|
let mut file = TempMappedFile::new().unwrap();
|
||||||
|
file.write_all(b"Hello, World!").unwrap();
|
||||||
|
|
||||||
|
file.sync().unwrap();
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
file.read_to_end(&mut buf).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buf, b"Hello, World!");
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/main.rs
127
src/main.rs
@@ -1,5 +1,13 @@
|
|||||||
const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
mod activity_manager;
|
||||||
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
mod cli;
|
||||||
|
mod config;
|
||||||
|
mod explorer;
|
||||||
|
mod filetransfer;
|
||||||
|
mod host;
|
||||||
|
mod support;
|
||||||
|
mod system;
|
||||||
|
mod ui;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
// Crates
|
// Crates
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -13,28 +21,27 @@ extern crate log;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate magic_crypt;
|
extern crate magic_crypt;
|
||||||
|
|
||||||
// External libs
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// Include
|
use self::activity_manager::{ActivityManager, NextActivity};
|
||||||
mod activity_manager;
|
use self::cli::{Args, ArgsSubcommands, RemoteArgs, RunOpts, Task};
|
||||||
mod cli_opts;
|
use self::system::logging::{self, LogLevel};
|
||||||
mod config;
|
|
||||||
mod explorer;
|
|
||||||
mod filetransfer;
|
|
||||||
mod host;
|
|
||||||
mod support;
|
|
||||||
mod system;
|
|
||||||
mod ui;
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
// namespaces
|
const APP_NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
use activity_manager::{ActivityManager, NextActivity};
|
const APP_BUILD_DATE: &str = env!("VERGEN_BUILD_TIMESTAMP");
|
||||||
use cli_opts::{Args, ArgsSubcommands, BookmarkParams, HostParams, Remote, RunOpts, Task};
|
const APP_GIT_BRANCH: &str = env!("VERGEN_GIT_BRANCH");
|
||||||
use filetransfer::FileTransferParams;
|
const APP_GIT_HASH: &str = env!("VERGEN_GIT_SHA");
|
||||||
use system::logging::{self, LogLevel};
|
const EXIT_CODE_SUCCESS: i32 = 0;
|
||||||
|
const EXIT_CODE_ERROR: i32 = 1;
|
||||||
|
const TERMSCP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn git_hash() -> &'static str {
|
||||||
|
APP_GIT_HASH[0..8].as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Args = argh::from_env();
|
let args: Args = argh::from_env();
|
||||||
@@ -50,7 +57,10 @@ fn main() {
|
|||||||
if let Err(err) = logging::init(run_opts.log_level) {
|
if let Err(err) = logging::init(run_opts.log_level) {
|
||||||
eprintln!("Failed to initialize logging: {err}");
|
eprintln!("Failed to initialize logging: {err}");
|
||||||
}
|
}
|
||||||
info!("termscp {} started!", TERMSCP_VERSION);
|
info!(
|
||||||
|
"{APP_NAME} v{TERMSCP_VERSION} ({APP_GIT_BRANCH}, {git_hash}, {APP_BUILD_DATE}) - Developed by {TERMSCP_AUTHORS}",
|
||||||
|
git_hash = git_hash()
|
||||||
|
);
|
||||||
// Run
|
// Run
|
||||||
info!("Starting activity manager...");
|
info!("Starting activity manager...");
|
||||||
let rc = run(run_opts);
|
let rc = run(run_opts);
|
||||||
@@ -72,7 +82,8 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
|||||||
// Version
|
// Version
|
||||||
if args.version {
|
if args.version {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"termscp - {TERMSCP_VERSION} - Developed by {TERMSCP_AUTHORS}",
|
"{APP_NAME} v{TERMSCP_VERSION} ({APP_GIT_BRANCH}, {git_hash}, {APP_BUILD_DATE}) - Developed by {TERMSCP_AUTHORS}",
|
||||||
|
git_hash = git_hash()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Logging
|
// Logging
|
||||||
@@ -84,22 +95,24 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
|||||||
// Match ticks
|
// Match ticks
|
||||||
run_opts.ticks = Duration::from_millis(args.ticks);
|
run_opts.ticks = Duration::from_millis(args.ticks);
|
||||||
// Remote argument
|
// Remote argument
|
||||||
match parse_address_arg(&args) {
|
match RemoteArgs::try_from(&args) {
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
Ok(Remote::None) => {}
|
|
||||||
Ok(remote) => {
|
Ok(remote) => {
|
||||||
// Set params
|
// Set params
|
||||||
run_opts.remote = remote;
|
run_opts.remote = remote;
|
||||||
// In this case the first activity will be FileTransfer
|
|
||||||
run_opts.task = Task::Activity(NextActivity::FileTransfer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set activity based on remote state
|
||||||
|
run_opts.task = if run_opts.remote.remote.is_none() {
|
||||||
|
Task::Activity(NextActivity::Authentication)
|
||||||
|
} else {
|
||||||
|
Task::Activity(NextActivity::FileTransfer)
|
||||||
|
};
|
||||||
|
|
||||||
// Local directory
|
// Local directory
|
||||||
if let Some(localdir) = args.positional.get(1) {
|
if let Some(localdir) = run_opts.remote.local_dir.as_deref() {
|
||||||
// Change working directory if local dir is set
|
if let Err(err) = env::set_current_dir(localdir) {
|
||||||
let localdir: PathBuf = PathBuf::from(localdir);
|
|
||||||
if let Err(err) = env::set_current_dir(localdir.as_path()) {
|
|
||||||
return Err(format!("Bad working directory argument: {err}"));
|
return Err(format!("Bad working directory argument: {err}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,29 +124,6 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
|||||||
Ok(run_opts)
|
Ok(run_opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse address argument from cli args
|
|
||||||
fn parse_address_arg(args: &Args) -> Result<Remote, String> {
|
|
||||||
if let Some(remote) = args.positional.first() {
|
|
||||||
if args.address_as_bookmark {
|
|
||||||
Ok(Remote::Bookmark(BookmarkParams::new(
|
|
||||||
remote,
|
|
||||||
args.password.as_ref(),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
// Parse address
|
|
||||||
parse_remote_address(remote.as_str())
|
|
||||||
.map(|x| Remote::Host(HostParams::new(x, args.password.as_deref())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Remote::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse remote address
|
|
||||||
fn parse_remote_address(remote: &str) -> Result<FileTransferParams, String> {
|
|
||||||
utils::parser::parse_remote_opt(remote).map_err(|e| format!("Bad address option: {e}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run task and return rc
|
/// Run task and return rc
|
||||||
fn run(run_opts: RunOpts) -> i32 {
|
fn run(run_opts: RunOpts) -> i32 {
|
||||||
match run_opts.task {
|
match run_opts.task {
|
||||||
@@ -147,11 +137,11 @@ fn run_import_theme(theme: &Path) -> i32 {
|
|||||||
match support::import_theme(theme) {
|
match support::import_theme(theme) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("Theme has been successfully imported!");
|
println!("Theme has been successfully imported!");
|
||||||
0
|
EXIT_CODE_ERROR
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
1
|
EXIT_CODE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,41 +150,32 @@ fn run_install_update() -> i32 {
|
|||||||
match support::install_update() {
|
match support::install_update() {
|
||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
println!("{msg}");
|
println!("{msg}");
|
||||||
0
|
EXIT_CODE_SUCCESS
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Could not install update: {err}");
|
eprintln!("Could not install update: {err}");
|
||||||
1
|
EXIT_CODE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_activity(activity: NextActivity, ticks: Duration, remote: Remote) -> i32 {
|
fn run_activity(activity: NextActivity, ticks: Duration, remote_args: RemoteArgs) -> i32 {
|
||||||
// Create activity manager (and context too)
|
// Create activity manager (and context too)
|
||||||
let mut manager: ActivityManager = match ActivityManager::new(ticks) {
|
let mut manager: ActivityManager = match ActivityManager::new(ticks) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Could not start activity manager: {err}");
|
eprintln!("Could not start activity manager: {err}");
|
||||||
return 1;
|
return EXIT_CODE_ERROR;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set file transfer params if set
|
// Set file transfer params if set
|
||||||
match remote {
|
if let Err(err) = manager.configure_remote_args(remote_args) {
|
||||||
Remote::Bookmark(BookmarkParams { name, password }) => {
|
|
||||||
if let Err(err) = manager.resolve_bookmark_name(&name, password.as_deref()) {
|
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
return 1;
|
return EXIT_CODE_ERROR;
|
||||||
}
|
|
||||||
}
|
|
||||||
Remote::Host(HostParams { params, password }) => {
|
|
||||||
if let Err(err) = manager.set_filetransfer_params(params, password.as_deref()) {
|
|
||||||
eprintln!("{err}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Remote::None => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.run(activity);
|
manager.run(activity);
|
||||||
|
|
||||||
0
|
EXIT_CODE_SUCCESS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ impl Update {
|
|||||||
|
|
||||||
/// In case received version is newer than current one, version as Some is returned; otherwise None
|
/// In case received version is newer than current one, version as Some is returned; otherwise None
|
||||||
fn check_version(r: Release) -> Option<Release> {
|
fn check_version(r: Release) -> Option<Release> {
|
||||||
|
debug!("got version from GitHub: {}", r.version);
|
||||||
match parse_semver(r.version.as_str()) {
|
match parse_semver(r.version.as_str()) {
|
||||||
Some(new_version) => {
|
Some(new_version) => {
|
||||||
// Check if version is different
|
// Check if version is different
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ pub fn init(level: LogLevel) -> Result<(), String> {
|
|||||||
let file = open_file(log_file_path.as_path(), true, true, false)
|
let file = open_file(log_file_path.as_path(), true, true, false)
|
||||||
.map_err(|e| format!("Failed to open file {}: {}", log_file_path.display(), e))?;
|
.map_err(|e| format!("Failed to open file {}: {}", log_file_path.display(), e))?;
|
||||||
// Prepare log config
|
// Prepare log config
|
||||||
let config = ConfigBuilder::new().set_time_format_rfc3339().build();
|
let config = ConfigBuilder::new()
|
||||||
|
.set_time_format_rfc3339()
|
||||||
|
.add_filter_allow_str("termscp")
|
||||||
|
.add_filter_allow_str("remotefs")
|
||||||
|
.build();
|
||||||
// Make logger
|
// Make logger
|
||||||
WriteLogger::init(level, config, file).map_err(|e| format!("Failed to initialize logger: {e}"))
|
WriteLogger::init(level, config, file).map_err(|e| format!("Failed to initialize logger: {e}"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ impl ThemeProvider {
|
|||||||
mod test {
|
mod test {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use tuirealm::tui::style::Color;
|
use tuirealm::ratatui::style::Color;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ impl FileToRemove {
|
|||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct FileUpdate {
|
pub struct FileUpdate {
|
||||||
/// Path to file which has changed
|
/// Path to file which has changed
|
||||||
local: PathBuf,
|
host_bridge: PathBuf,
|
||||||
/// Path to remote file to update
|
/// Path to remote file to update
|
||||||
remote: PathBuf,
|
remote: PathBuf,
|
||||||
}
|
}
|
||||||
@@ -152,13 +152,13 @@ impl FileUpdate {
|
|||||||
fn new(changed_path: PathBuf, local_watched_path: &Path, remote_synched_path: &Path) -> Self {
|
fn new(changed_path: PathBuf, local_watched_path: &Path, remote_synched_path: &Path) -> Self {
|
||||||
Self {
|
Self {
|
||||||
remote: remote_relative_path(&changed_path, local_watched_path, remote_synched_path),
|
remote: remote_relative_path(&changed_path, local_watched_path, remote_synched_path),
|
||||||
local: changed_path,
|
host_bridge: changed_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get path to local file to sync
|
/// Get path to local file to sync
|
||||||
pub fn local(&self) -> &Path {
|
pub fn host_bridge(&self) -> &Path {
|
||||||
self.local.as_path()
|
self.host_bridge.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get path to remote file to sync
|
/// Get path to remote file to sync
|
||||||
@@ -288,7 +288,7 @@ mod test {
|
|||||||
Path::new("/home/foo/bar.txt"),
|
Path::new("/home/foo/bar.txt"),
|
||||||
);
|
);
|
||||||
if let FsChange::Update(change) = change {
|
if let FsChange::Update(change) = change {
|
||||||
assert_eq!(change.local(), Path::new("/tmp/bar.txt"),);
|
assert_eq!(change.host_bridge(), Path::new("/tmp/bar.txt"),);
|
||||||
assert_eq!(change.remote(), Path::new("/home/foo/bar.txt"));
|
assert_eq!(change.remote(), Path::new("/home/foo/bar.txt"));
|
||||||
} else {
|
} else {
|
||||||
panic!("not an update");
|
panic!("not an update");
|
||||||
@@ -303,7 +303,7 @@ mod test {
|
|||||||
Path::new("/home/foo/temp"),
|
Path::new("/home/foo/temp"),
|
||||||
);
|
);
|
||||||
if let FsChange::Update(change) = change {
|
if let FsChange::Update(change) = change {
|
||||||
assert_eq!(change.local(), Path::new("/tmp/abc/foo.txt"),);
|
assert_eq!(change.host_bridge(), Path::new("/tmp/abc/foo.txt"),);
|
||||||
assert_eq!(change.remote(), Path::new("/home/foo/temp/abc/foo.txt"));
|
assert_eq!(change.remote(), Path::new("/home/foo/temp/abc/foo.txt"));
|
||||||
} else {
|
} else {
|
||||||
panic!("not an update");
|
panic!("not an update");
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ mod test {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn should_poll_file_moved() {
|
fn should_poll_file_moved() {
|
||||||
let mut watcher = FsWatcher::init(Duration::from_millis(100)).unwrap();
|
let mut watcher = FsWatcher::init(Duration::from_millis(100)).unwrap();
|
||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
//! `auth_activity` is the module which implements the authentication activity
|
//! `auth_activity` is the module which implements the authentication activity
|
||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
use super::{AuthActivity, FileTransferParams};
|
use super::{AuthActivity, FileTransferParams, FormTab, HostBridgeProtocol};
|
||||||
use crate::filetransfer::params::{
|
use crate::filetransfer::params::{
|
||||||
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, SmbParams,
|
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, SmbParams,
|
||||||
WebDAVProtocolParams,
|
WebDAVProtocolParams,
|
||||||
};
|
};
|
||||||
|
use crate::filetransfer::HostBridgeParams;
|
||||||
|
|
||||||
impl AuthActivity {
|
impl AuthActivity {
|
||||||
/// Delete bookmark
|
/// Delete bookmark
|
||||||
@@ -26,27 +27,49 @@ impl AuthActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load selected bookmark (at index) to input fields
|
/// Load selected bookmark (at index) to input fields
|
||||||
pub(super) fn load_bookmark(&mut self, idx: usize) {
|
pub(super) fn load_bookmark(&mut self, form_tab: FormTab, idx: usize) {
|
||||||
if let Some(bookmarks_cli) = self.bookmarks_client() {
|
if let Some(bookmarks_cli) = self.bookmarks_client() {
|
||||||
// Iterate over bookmarks
|
// Iterate over bookmarks
|
||||||
if let Some(key) = self.bookmarks_list.get(idx) {
|
if let Some(key) = self.bookmarks_list.get(idx) {
|
||||||
if let Some(bookmark) = bookmarks_cli.get_bookmark(key) {
|
if let Some(bookmark) = bookmarks_cli.get_bookmark(key) {
|
||||||
// Load parameters into components
|
// Load parameters into components
|
||||||
self.load_bookmark_into_gui(bookmark);
|
match form_tab {
|
||||||
|
FormTab::Remote => self.load_remote_bookmark_into_gui(bookmark),
|
||||||
|
FormTab::HostBridge => self.load_host_bridge_bookmark_into_gui(bookmark),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save current input fields as a bookmark
|
/// Save current input fields as a bookmark
|
||||||
pub(super) fn save_bookmark(&mut self, name: String, save_password: bool) {
|
pub(super) fn save_bookmark(&mut self, form_tab: FormTab, name: String, save_password: bool) {
|
||||||
let params = match self.collect_host_params() {
|
let params = match form_tab {
|
||||||
|
FormTab::Remote => match self.collect_remote_host_params() {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.mount_error(e);
|
self.mount_error(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
FormTab::HostBridge => match self.collect_host_bridge_params() {
|
||||||
|
Ok(HostBridgeParams::Remote(protocol, params)) => FileTransferParams {
|
||||||
|
protocol,
|
||||||
|
params,
|
||||||
|
remote_path: None,
|
||||||
|
local_path: None,
|
||||||
|
},
|
||||||
|
Ok(HostBridgeParams::Localhost(_)) => {
|
||||||
|
self.mount_error("You cannot save a localhost bookmark");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.mount_error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(bookmarks_cli) = self.bookmarks_client_mut() {
|
if let Some(bookmarks_cli) = self.bookmarks_client_mut() {
|
||||||
bookmarks_cli.add_bookmark(name.clone(), params, save_password);
|
bookmarks_cli.add_bookmark(name.clone(), params, save_password);
|
||||||
// Save bookmarks
|
// Save bookmarks
|
||||||
@@ -73,13 +96,16 @@ impl AuthActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load selected recent (at index) to input fields
|
/// Load selected recent (at index) to input fields
|
||||||
pub(super) fn load_recent(&mut self, idx: usize) {
|
pub(super) fn load_recent(&mut self, form_tab: FormTab, idx: usize) {
|
||||||
if let Some(client) = self.bookmarks_client() {
|
if let Some(client) = self.bookmarks_client() {
|
||||||
// Iterate over bookmarks
|
// Iterate over bookmarks
|
||||||
if let Some(key) = self.recents_list.get(idx) {
|
if let Some(key) = self.recents_list.get(idx) {
|
||||||
if let Some(bookmark) = client.get_recent(key) {
|
if let Some(bookmark) = client.get_recent(key) {
|
||||||
// Load parameters
|
// Load parameters
|
||||||
self.load_bookmark_into_gui(bookmark);
|
match form_tab {
|
||||||
|
FormTab::Remote => self.load_remote_bookmark_into_gui(bookmark),
|
||||||
|
FormTab::HostBridge => self.load_host_bridge_bookmark_into_gui(bookmark),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +113,7 @@ impl AuthActivity {
|
|||||||
|
|
||||||
/// Save current input fields as a "recent"
|
/// Save current input fields as a "recent"
|
||||||
pub(super) fn save_recent(&mut self) {
|
pub(super) fn save_recent(&mut self) {
|
||||||
let params = match self.collect_host_params() {
|
let params = match self.collect_remote_host_params() {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.mount_error(e);
|
self.mount_error(e);
|
||||||
@@ -147,73 +173,125 @@ impl AuthActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load bookmark data into the gui components
|
/// Load bookmark data into the gui components
|
||||||
fn load_bookmark_into_gui(&mut self, bookmark: FileTransferParams) {
|
fn load_host_bridge_bookmark_into_gui(&mut self, bookmark: FileTransferParams) {
|
||||||
// Load parameters into components
|
// Load parameters into components
|
||||||
self.protocol = bookmark.protocol;
|
self.host_bridge_protocol = HostBridgeProtocol::Remote(bookmark.protocol);
|
||||||
self.mount_protocol(bookmark.protocol);
|
self.mount_host_bridge_protocol(self.host_bridge_protocol);
|
||||||
self.mount_remote_directory(
|
self.mount_remote_directory(
|
||||||
|
FormTab::HostBridge,
|
||||||
bookmark
|
bookmark
|
||||||
.remote_path
|
.remote_path
|
||||||
.map(|x| x.to_string_lossy().to_string())
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
self.mount_local_directory(
|
self.mount_local_directory(
|
||||||
|
FormTab::HostBridge,
|
||||||
bookmark
|
bookmark
|
||||||
.local_path
|
.local_path
|
||||||
.map(|x| x.to_string_lossy().to_string())
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
match bookmark.params {
|
match bookmark.params {
|
||||||
ProtocolParams::AwsS3(params) => self.load_bookmark_s3_into_gui(params),
|
ProtocolParams::AwsS3(params) => {
|
||||||
ProtocolParams::Kube(params) => self.load_bookmark_kube_into_gui(params),
|
self.load_bookmark_s3_into_gui(FormTab::HostBridge, params)
|
||||||
|
}
|
||||||
|
ProtocolParams::Kube(params) => {
|
||||||
|
self.load_bookmark_kube_into_gui(FormTab::HostBridge, params)
|
||||||
|
}
|
||||||
|
|
||||||
ProtocolParams::Generic(params) => self.load_bookmark_generic_into_gui(params),
|
ProtocolParams::Generic(params) => {
|
||||||
ProtocolParams::Smb(params) => self.load_bookmark_smb_into_gui(params),
|
self.load_bookmark_generic_into_gui(FormTab::HostBridge, params)
|
||||||
ProtocolParams::WebDAV(params) => self.load_bookmark_webdav_into_gui(params),
|
}
|
||||||
|
ProtocolParams::Smb(params) => {
|
||||||
|
self.load_bookmark_smb_into_gui(FormTab::HostBridge, params)
|
||||||
|
}
|
||||||
|
ProtocolParams::WebDAV(params) => {
|
||||||
|
self.load_bookmark_webdav_into_gui(FormTab::HostBridge, params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_bookmark_generic_into_gui(&mut self, params: GenericProtocolParams) {
|
/// Load bookmark data into the gui components
|
||||||
self.mount_address(params.address.as_str());
|
fn load_remote_bookmark_into_gui(&mut self, bookmark: FileTransferParams) {
|
||||||
self.mount_port(params.port);
|
// Load parameters into components
|
||||||
self.mount_username(params.username.as_deref().unwrap_or(""));
|
self.remote_protocol = bookmark.protocol;
|
||||||
self.mount_password(params.password.as_deref().unwrap_or(""));
|
self.mount_remote_protocol(bookmark.protocol);
|
||||||
|
self.mount_remote_directory(
|
||||||
|
FormTab::Remote,
|
||||||
|
bookmark
|
||||||
|
.remote_path
|
||||||
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
self.mount_local_directory(
|
||||||
|
FormTab::Remote,
|
||||||
|
bookmark
|
||||||
|
.local_path
|
||||||
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
match bookmark.params {
|
||||||
|
ProtocolParams::AwsS3(params) => {
|
||||||
|
self.load_bookmark_s3_into_gui(FormTab::Remote, params)
|
||||||
|
}
|
||||||
|
ProtocolParams::Kube(params) => {
|
||||||
|
self.load_bookmark_kube_into_gui(FormTab::Remote, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_bookmark_s3_into_gui(&mut self, params: AwsS3Params) {
|
ProtocolParams::Generic(params) => {
|
||||||
self.mount_s3_bucket(params.bucket_name.as_str());
|
self.load_bookmark_generic_into_gui(FormTab::Remote, params)
|
||||||
self.mount_s3_region(params.region.as_deref().unwrap_or(""));
|
}
|
||||||
self.mount_s3_endpoint(params.endpoint.as_deref().unwrap_or(""));
|
ProtocolParams::Smb(params) => self.load_bookmark_smb_into_gui(FormTab::Remote, params),
|
||||||
self.mount_s3_profile(params.profile.as_deref().unwrap_or(""));
|
ProtocolParams::WebDAV(params) => {
|
||||||
self.mount_s3_access_key(params.access_key.as_deref().unwrap_or(""));
|
self.load_bookmark_webdav_into_gui(FormTab::Remote, params)
|
||||||
self.mount_s3_secret_access_key(params.secret_access_key.as_deref().unwrap_or(""));
|
}
|
||||||
self.mount_s3_security_token(params.security_token.as_deref().unwrap_or(""));
|
}
|
||||||
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_kube_into_gui(&mut self, params: KubeProtocolParams) {
|
fn load_bookmark_generic_into_gui(&mut self, form_tab: FormTab, params: GenericProtocolParams) {
|
||||||
self.mount_kube_cluster_url(params.cluster_url.as_deref().unwrap_or(""));
|
self.mount_address(form_tab, params.address.as_str());
|
||||||
self.mount_kube_namespace(params.namespace.as_deref().unwrap_or(""));
|
self.mount_port(form_tab, params.port);
|
||||||
self.mount_kube_client_cert(params.client_cert.as_deref().unwrap_or(""));
|
self.mount_username(form_tab, params.username.as_deref().unwrap_or(""));
|
||||||
self.mount_kube_client_key(params.client_key.as_deref().unwrap_or(""));
|
self.mount_password(form_tab, params.password.as_deref().unwrap_or(""));
|
||||||
self.mount_kube_username(params.username.as_deref().unwrap_or(""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_bookmark_smb_into_gui(&mut self, params: SmbParams) {
|
fn load_bookmark_s3_into_gui(&mut self, form_tab: FormTab, params: AwsS3Params) {
|
||||||
self.mount_address(params.address.as_str());
|
self.mount_s3_bucket(form_tab, params.bucket_name.as_str());
|
||||||
#[cfg(unix)]
|
self.mount_s3_region(form_tab, params.region.as_deref().unwrap_or(""));
|
||||||
self.mount_port(params.port);
|
self.mount_s3_endpoint(form_tab, params.endpoint.as_deref().unwrap_or(""));
|
||||||
self.mount_username(params.username.as_deref().unwrap_or(""));
|
self.mount_s3_profile(form_tab, params.profile.as_deref().unwrap_or(""));
|
||||||
self.mount_password(params.password.as_deref().unwrap_or(""));
|
self.mount_s3_access_key(form_tab, params.access_key.as_deref().unwrap_or(""));
|
||||||
self.mount_smb_share(¶ms.share);
|
self.mount_s3_secret_access_key(
|
||||||
#[cfg(unix)]
|
form_tab,
|
||||||
self.mount_smb_workgroup(params.workgroup.as_deref().unwrap_or(""));
|
params.secret_access_key.as_deref().unwrap_or(""),
|
||||||
|
);
|
||||||
|
self.mount_s3_security_token(form_tab, params.security_token.as_deref().unwrap_or(""));
|
||||||
|
self.mount_s3_session_token(form_tab, params.session_token.as_deref().unwrap_or(""));
|
||||||
|
self.mount_s3_new_path_style(form_tab, params.new_path_style);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_bookmark_webdav_into_gui(&mut self, params: WebDAVProtocolParams) {
|
fn load_bookmark_kube_into_gui(&mut self, form_tab: FormTab, params: KubeProtocolParams) {
|
||||||
self.mount_webdav_uri(¶ms.uri);
|
self.mount_kube_cluster_url(form_tab, params.cluster_url.as_deref().unwrap_or(""));
|
||||||
self.mount_username(¶ms.username);
|
self.mount_kube_namespace(form_tab, params.namespace.as_deref().unwrap_or(""));
|
||||||
self.mount_password(¶ms.password);
|
self.mount_kube_client_cert(form_tab, params.client_cert.as_deref().unwrap_or(""));
|
||||||
|
self.mount_kube_client_key(form_tab, params.client_key.as_deref().unwrap_or(""));
|
||||||
|
self.mount_kube_username(form_tab, params.username.as_deref().unwrap_or(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_bookmark_smb_into_gui(&mut self, form_tab: FormTab, params: SmbParams) {
|
||||||
|
self.mount_address(form_tab, params.address.as_str());
|
||||||
|
#[cfg(posix)]
|
||||||
|
self.mount_port(form_tab, params.port);
|
||||||
|
self.mount_username(form_tab, params.username.as_deref().unwrap_or(""));
|
||||||
|
self.mount_password(form_tab, params.password.as_deref().unwrap_or(""));
|
||||||
|
self.mount_smb_share(form_tab, ¶ms.share);
|
||||||
|
#[cfg(posix)]
|
||||||
|
self.mount_smb_workgroup(form_tab, params.workgroup.as_deref().unwrap_or(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_bookmark_webdav_into_gui(&mut self, form_tab: FormTab, params: WebDAVProtocolParams) {
|
||||||
|
self.mount_webdav_uri(form_tab, ¶ms.uri);
|
||||||
|
self.mount_username(form_tab, ¶ms.username);
|
||||||
|
self.mount_password(form_tab, ¶ms.password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputT
|
|||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||||
|
|
||||||
use super::{FormMsg, Msg, UiMsg};
|
use super::{FormMsg, Msg, UiMsg};
|
||||||
|
use crate::ui::activities::auth::FormTab;
|
||||||
|
|
||||||
// -- bookmark list
|
// -- bookmark list
|
||||||
|
|
||||||
@@ -323,10 +324,11 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
|||||||
#[derive(MockComponent)]
|
#[derive(MockComponent)]
|
||||||
pub struct BookmarkSavePassword {
|
pub struct BookmarkSavePassword {
|
||||||
component: Radio,
|
component: Radio,
|
||||||
|
form_tab: FormTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BookmarkSavePassword {
|
impl BookmarkSavePassword {
|
||||||
pub fn new(color: Color) -> Self {
|
pub fn new(form_tab: FormTab, color: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
component: Radio::default()
|
component: Radio::default()
|
||||||
.borders(
|
.borders(
|
||||||
@@ -340,6 +342,7 @@ impl BookmarkSavePassword {
|
|||||||
.rewind(true)
|
.rewind(true)
|
||||||
.foreground(color)
|
.foreground(color)
|
||||||
.title("Save secrets?", Alignment::Center),
|
.title("Save secrets?", Alignment::Center),
|
||||||
|
form_tab,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +367,7 @@ impl Component<Msg, NoUserEvent> for BookmarkSavePassword {
|
|||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => Some(Msg::Form(FormMsg::SaveBookmark)),
|
}) => Some(Msg::Form(FormMsg::SaveBookmark(self.form_tab))),
|
||||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::SaveBookmarkPasswordBlur))
|
Some(Msg::Ui(UiMsg::SaveBookmarkPasswordBlur))
|
||||||
}
|
}
|
||||||
@@ -378,10 +381,11 @@ impl Component<Msg, NoUserEvent> for BookmarkSavePassword {
|
|||||||
#[derive(MockComponent)]
|
#[derive(MockComponent)]
|
||||||
pub struct BookmarkName {
|
pub struct BookmarkName {
|
||||||
component: Input,
|
component: Input,
|
||||||
|
form_tab: FormTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BookmarkName {
|
impl BookmarkName {
|
||||||
pub fn new(color: Color) -> Self {
|
pub fn new(form_tab: FormTab, color: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
component: Input::default()
|
component: Input::default()
|
||||||
.borders(
|
.borders(
|
||||||
@@ -393,6 +397,7 @@ impl BookmarkName {
|
|||||||
.foreground(color)
|
.foreground(color)
|
||||||
.title("Bookmark name", Alignment::Left)
|
.title("Bookmark name", Alignment::Left)
|
||||||
.input_type(InputType::Text),
|
.input_type(InputType::Text),
|
||||||
|
form_tab,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -447,7 +452,7 @@ impl Component<Msg, NoUserEvent> for BookmarkName {
|
|||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => Some(Msg::Form(FormMsg::SaveBookmark)),
|
}) => Some(Msg::Form(FormMsg::SaveBookmark(self.form_tab))),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Down, ..
|
code: Key::Down, ..
|
||||||
}) => Some(Msg::Ui(UiMsg::BookmarkNameBlur)),
|
}) => Some(Msg::Ui(UiMsg::BookmarkNameBlur)),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,14 +13,15 @@ pub use bookmarks::{
|
|||||||
BookmarkName, BookmarkSavePassword, BookmarksList, DeleteBookmarkPopup, DeleteRecentPopup,
|
BookmarkName, BookmarkSavePassword, BookmarksList, DeleteBookmarkPopup, DeleteRecentPopup,
|
||||||
RecentsList,
|
RecentsList,
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
pub use form::InputSmbWorkgroup;
|
pub use form::InputSmbWorkgroup;
|
||||||
pub use form::{
|
pub use form::{
|
||||||
InputAddress, InputKubeClientCert, InputKubeClientKey, InputKubeClusterUrl, InputKubeNamespace,
|
HostBridgeProtocolRadio, InputAddress, InputKubeClientCert, InputKubeClientKey,
|
||||||
InputKubeUsername, InputLocalDirectory, InputPassword, InputPort, InputRemoteDirectory,
|
InputKubeClusterUrl, InputKubeNamespace, InputKubeUsername, InputLocalDirectory, InputPassword,
|
||||||
InputS3AccessKey, InputS3Bucket, InputS3Endpoint, InputS3Profile, InputS3Region,
|
InputPort, InputRemoteDirectory, InputS3AccessKey, InputS3Bucket, InputS3Endpoint,
|
||||||
InputS3SecretAccessKey, InputS3SecurityToken, InputS3SessionToken, InputSmbShare,
|
InputS3Profile, InputS3Region, InputS3SecretAccessKey, InputS3SecurityToken,
|
||||||
InputUsername, InputWebDAVUri, ProtocolRadio, RadioS3NewPathStyle,
|
InputS3SessionToken, InputSmbShare, InputUsername, InputWebDAVUri, RadioS3NewPathStyle,
|
||||||
|
RemoteProtocolRadio,
|
||||||
};
|
};
|
||||||
pub use popup::{
|
pub use popup::{
|
||||||
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,
|
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ impl HelpFooter {
|
|||||||
TextSpan::from(" Change field "),
|
TextSpan::from(" Change field "),
|
||||||
TextSpan::from("<TAB>").bold().fg(key_color),
|
TextSpan::from("<TAB>").bold().fg(key_color),
|
||||||
TextSpan::from(" Switch tab "),
|
TextSpan::from(" Switch tab "),
|
||||||
|
TextSpan::from("<BACKTAB>").bold().fg(key_color),
|
||||||
|
TextSpan::from(" Switch form "),
|
||||||
TextSpan::from("<ENTER>").bold().fg(key_color),
|
TextSpan::from("<ENTER>").bold().fg(key_color),
|
||||||
TextSpan::from(" Submit form "),
|
TextSpan::from(" Submit form "),
|
||||||
TextSpan::from("<F10|ESC>").bold().fg(key_color),
|
TextSpan::from("<F10|ESC>").bold().fg(key_color),
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
//!
|
//!
|
||||||
//! `auth_activity` is the module which implements the authentication activity
|
//! `auth_activity` is the module which implements the authentication activity
|
||||||
|
|
||||||
use super::{AuthActivity, FileTransferParams, FileTransferProtocol};
|
use std::env;
|
||||||
|
|
||||||
|
use super::{AuthActivity, FileTransferParams, FileTransferProtocol, FormTab, HostBridgeProtocol};
|
||||||
use crate::filetransfer::params::ProtocolParams;
|
use crate::filetransfer::params::ProtocolParams;
|
||||||
|
use crate::filetransfer::HostBridgeParams;
|
||||||
use crate::system::auto_update::{Release, Update, UpdateStatus};
|
use crate::system::auto_update::{Release, Update, UpdateStatus};
|
||||||
use crate::system::notifications::Notification;
|
use crate::system::notifications::Notification;
|
||||||
|
|
||||||
@@ -36,24 +39,64 @@ impl AuthActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collect host params as `FileTransferParams`
|
/// Collect host params as `FileTransferParams`
|
||||||
pub(super) fn collect_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
pub(super) fn collect_host_bridge_params(&self) -> Result<HostBridgeParams, &'static str> {
|
||||||
match self.protocol {
|
match self.host_bridge_protocol {
|
||||||
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(),
|
HostBridgeProtocol::Localhost => self.collect_localhost_host_params(),
|
||||||
FileTransferProtocol::Kube => self.collect_kube_host_params(),
|
HostBridgeProtocol::Remote(remote) => {
|
||||||
FileTransferProtocol::Smb => self.collect_smb_host_params(),
|
let transfer_params = match remote {
|
||||||
|
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(FormTab::HostBridge),
|
||||||
|
FileTransferProtocol::Kube => {
|
||||||
|
self.collect_kube_host_params(FormTab::HostBridge)
|
||||||
|
}
|
||||||
|
FileTransferProtocol::Smb => self.collect_smb_host_params(FormTab::HostBridge),
|
||||||
FileTransferProtocol::Ftp(_)
|
FileTransferProtocol::Ftp(_)
|
||||||
| FileTransferProtocol::Scp
|
| FileTransferProtocol::Scp
|
||||||
| FileTransferProtocol::Sftp => self.collect_generic_host_params(self.protocol),
|
| FileTransferProtocol::Sftp => {
|
||||||
FileTransferProtocol::WebDAV => self.collect_webdav_host_params(),
|
self.collect_generic_host_params(remote, FormTab::HostBridge)
|
||||||
}
|
}
|
||||||
|
FileTransferProtocol::WebDAV => {
|
||||||
|
self.collect_webdav_host_params(FormTab::HostBridge)
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(HostBridgeParams::Remote(
|
||||||
|
transfer_params.protocol,
|
||||||
|
transfer_params.params,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect host params as `FileTransferParams`
|
||||||
|
pub(super) fn collect_remote_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||||
|
match self.remote_protocol {
|
||||||
|
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(FormTab::Remote),
|
||||||
|
FileTransferProtocol::Kube => self.collect_kube_host_params(FormTab::Remote),
|
||||||
|
FileTransferProtocol::Smb => self.collect_smb_host_params(FormTab::Remote),
|
||||||
|
FileTransferProtocol::Ftp(_)
|
||||||
|
| FileTransferProtocol::Scp
|
||||||
|
| FileTransferProtocol::Sftp => {
|
||||||
|
self.collect_generic_host_params(self.remote_protocol, FormTab::Remote)
|
||||||
|
}
|
||||||
|
FileTransferProtocol::WebDAV => self.collect_webdav_host_params(FormTab::Remote),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_localhost_host_params(&self) -> Result<HostBridgeParams, &'static str> {
|
||||||
|
let path = self
|
||||||
|
.get_input_local_directory(FormTab::HostBridge)
|
||||||
|
.unwrap_or_else(|| env::current_dir().unwrap_or_default());
|
||||||
|
|
||||||
|
Ok(HostBridgeParams::Localhost(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get input values from fields or return an error if fields are invalid to work as generic
|
/// Get input values from fields or return an error if fields are invalid to work as generic
|
||||||
pub(super) fn collect_generic_host_params(
|
pub(super) fn collect_generic_host_params(
|
||||||
&self,
|
&self,
|
||||||
protocol: FileTransferProtocol,
|
protocol: FileTransferProtocol,
|
||||||
|
form_tab: FormTab,
|
||||||
) -> Result<FileTransferParams, &'static str> {
|
) -> Result<FileTransferParams, &'static str> {
|
||||||
let params = self.get_generic_params_input();
|
let params = self.get_generic_params_input(form_tab);
|
||||||
if params.address.is_empty() {
|
if params.address.is_empty() {
|
||||||
return Err("Invalid host");
|
return Err("Invalid host");
|
||||||
}
|
}
|
||||||
@@ -63,43 +106,52 @@ impl AuthActivity {
|
|||||||
Ok(FileTransferParams {
|
Ok(FileTransferParams {
|
||||||
protocol,
|
protocol,
|
||||||
params: ProtocolParams::Generic(params),
|
params: ProtocolParams::Generic(params),
|
||||||
local_path: self.get_input_local_directory(),
|
local_path: self.get_input_local_directory(form_tab),
|
||||||
remote_path: self.get_input_remote_directory(),
|
remote_path: self.get_input_remote_directory(form_tab),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get input values from fields or return an error if fields are invalid to work as aws s3
|
/// Get input values from fields or return an error if fields are invalid to work as aws s3
|
||||||
pub(super) fn collect_s3_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
pub(super) fn collect_s3_host_params(
|
||||||
let params = self.get_s3_params_input();
|
&self,
|
||||||
|
form_tab: FormTab,
|
||||||
|
) -> Result<FileTransferParams, &'static str> {
|
||||||
|
let params = self.get_s3_params_input(form_tab);
|
||||||
if params.bucket_name.is_empty() {
|
if params.bucket_name.is_empty() {
|
||||||
return Err("Invalid bucket");
|
return Err("Invalid bucket");
|
||||||
}
|
}
|
||||||
Ok(FileTransferParams {
|
Ok(FileTransferParams {
|
||||||
protocol: FileTransferProtocol::AwsS3,
|
protocol: FileTransferProtocol::AwsS3,
|
||||||
params: ProtocolParams::AwsS3(params),
|
params: ProtocolParams::AwsS3(params),
|
||||||
local_path: self.get_input_local_directory(),
|
local_path: self.get_input_local_directory(form_tab),
|
||||||
remote_path: self.get_input_remote_directory(),
|
remote_path: self.get_input_remote_directory(form_tab),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get input values from fields or return an error if fields are invalid to work as aws s3
|
/// Get input values from fields or return an error if fields are invalid to work as aws s3
|
||||||
pub(super) fn collect_kube_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
pub(super) fn collect_kube_host_params(
|
||||||
let params = self.get_kube_params_input();
|
&self,
|
||||||
|
form_tab: FormTab,
|
||||||
|
) -> Result<FileTransferParams, &'static str> {
|
||||||
|
let params = self.get_kube_params_input(form_tab);
|
||||||
|
|
||||||
Ok(FileTransferParams {
|
Ok(FileTransferParams {
|
||||||
protocol: FileTransferProtocol::Kube,
|
protocol: FileTransferProtocol::Kube,
|
||||||
params: ProtocolParams::Kube(params),
|
params: ProtocolParams::Kube(params),
|
||||||
local_path: self.get_input_local_directory(),
|
local_path: self.get_input_local_directory(form_tab),
|
||||||
remote_path: self.get_input_remote_directory(),
|
remote_path: self.get_input_remote_directory(form_tab),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn collect_smb_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
pub(super) fn collect_smb_host_params(
|
||||||
let params = self.get_smb_params_input();
|
&self,
|
||||||
|
form_tab: FormTab,
|
||||||
|
) -> Result<FileTransferParams, &'static str> {
|
||||||
|
let params = self.get_smb_params_input(form_tab);
|
||||||
if params.address.is_empty() {
|
if params.address.is_empty() {
|
||||||
return Err("Invalid address");
|
return Err("Invalid address");
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
if params.port == 0 {
|
if params.port == 0 {
|
||||||
return Err("Invalid port");
|
return Err("Invalid port");
|
||||||
}
|
}
|
||||||
@@ -109,21 +161,24 @@ impl AuthActivity {
|
|||||||
Ok(FileTransferParams {
|
Ok(FileTransferParams {
|
||||||
protocol: FileTransferProtocol::Smb,
|
protocol: FileTransferProtocol::Smb,
|
||||||
params: ProtocolParams::Smb(params),
|
params: ProtocolParams::Smb(params),
|
||||||
local_path: self.get_input_local_directory(),
|
local_path: self.get_input_local_directory(form_tab),
|
||||||
remote_path: self.get_input_remote_directory(),
|
remote_path: self.get_input_remote_directory(form_tab),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn collect_webdav_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
pub(super) fn collect_webdav_host_params(
|
||||||
let params = self.get_webdav_params_input();
|
&self,
|
||||||
|
form_tab: FormTab,
|
||||||
|
) -> Result<FileTransferParams, &'static str> {
|
||||||
|
let params = self.get_webdav_params_input(form_tab);
|
||||||
if params.uri.is_empty() {
|
if params.uri.is_empty() {
|
||||||
return Err("Invalid URI");
|
return Err("Invalid URI");
|
||||||
}
|
}
|
||||||
Ok(FileTransferParams {
|
Ok(FileTransferParams {
|
||||||
protocol: FileTransferProtocol::WebDAV,
|
protocol: FileTransferProtocol::WebDAV,
|
||||||
params: ProtocolParams::WebDAV(params),
|
params: ProtocolParams::WebDAV(params),
|
||||||
local_path: self.get_input_local_directory(),
|
local_path: self.get_input_local_directory(form_tab),
|
||||||
remote_path: self.get_input_remote_directory(),
|
remote_path: self.get_input_remote_directory(form_tab),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,26 +17,36 @@ use tuirealm::application::PollStrategy;
|
|||||||
use tuirealm::listener::EventListenerCfg;
|
use tuirealm::listener::EventListenerCfg;
|
||||||
use tuirealm::{Application, NoUserEvent, Update};
|
use tuirealm::{Application, NoUserEvent, Update};
|
||||||
|
|
||||||
use super::{Activity, Context, ExitReason};
|
use super::{Activity, Context, ExitReason, CROSSTERM_MAX_POLL};
|
||||||
use crate::config::themes::Theme;
|
use crate::config::themes::Theme;
|
||||||
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
||||||
use crate::system::bookmarks_client::BookmarksClient;
|
use crate::system::bookmarks_client::BookmarksClient;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
|
|
||||||
// radio
|
// host bridge protocol radio
|
||||||
const RADIO_PROTOCOL_SFTP: usize = 0;
|
const HOST_BRIDGE_RADIO_PROTOCOL_LOCALHOST: usize = 0;
|
||||||
const RADIO_PROTOCOL_SCP: usize = 1;
|
const HOST_BRIDGE_RADIO_PROTOCOL_SFTP: usize = 1;
|
||||||
const RADIO_PROTOCOL_FTP: usize = 2;
|
const HOST_BRIDGE_RADIO_PROTOCOL_SCP: usize = 2;
|
||||||
const RADIO_PROTOCOL_FTPS: usize = 3;
|
const HOST_BRIDGE_RADIO_PROTOCOL_FTP: usize = 3;
|
||||||
const RADIO_PROTOCOL_S3: usize = 4;
|
const HOST_BRIDGE_RADIO_PROTOCOL_FTPS: usize = 4;
|
||||||
const RADIO_PROTOCOL_KUBE: usize = 5;
|
const HOST_BRIDGE_RADIO_PROTOCOL_S3: usize = 5;
|
||||||
const RADIO_PROTOCOL_WEBDAV: usize = 6;
|
const HOST_BRIDGE_RADIO_PROTOCOL_KUBE: usize = 6;
|
||||||
const RADIO_PROTOCOL_SMB: usize = 7;
|
const HOST_BRIDGE_RADIO_PROTOCOL_WEBDAV: usize = 7;
|
||||||
|
const HOST_BRIDGE_RADIO_PROTOCOL_SMB: usize = 8; // Keep as last
|
||||||
|
|
||||||
|
// remote protocol radio
|
||||||
|
const REMOTE_RADIO_PROTOCOL_SFTP: usize = 0;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_SCP: usize = 1;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_FTP: usize = 2;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_FTPS: usize = 3;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_S3: usize = 4;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_KUBE: usize = 5;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_WEBDAV: usize = 6;
|
||||||
|
const REMOTE_RADIO_PROTOCOL_SMB: usize = 7; // Keep as last
|
||||||
|
|
||||||
// -- components
|
// -- components
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum Id {
|
pub enum Id {
|
||||||
Address,
|
|
||||||
BookmarkName,
|
BookmarkName,
|
||||||
BookmarkSavePassword,
|
BookmarkSavePassword,
|
||||||
BookmarksList,
|
BookmarksList,
|
||||||
@@ -45,22 +55,33 @@ pub enum Id {
|
|||||||
ErrorPopup,
|
ErrorPopup,
|
||||||
GlobalListener,
|
GlobalListener,
|
||||||
HelpFooter,
|
HelpFooter,
|
||||||
|
HostBridge(AuthFormId),
|
||||||
InfoPopup,
|
InfoPopup,
|
||||||
InstallUpdatePopup,
|
InstallUpdatePopup,
|
||||||
Keybindings,
|
Keybindings,
|
||||||
|
NewVersionChangelog,
|
||||||
|
NewVersionDisclaimer,
|
||||||
|
QuitPopup,
|
||||||
|
RecentsList,
|
||||||
|
Remote(AuthFormId),
|
||||||
|
Subtitle,
|
||||||
|
Title,
|
||||||
|
WaitPopup,
|
||||||
|
WindowSizeError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
|
pub enum AuthFormId {
|
||||||
|
Address,
|
||||||
KubeNamespace,
|
KubeNamespace,
|
||||||
KubeClusterUrl,
|
KubeClusterUrl,
|
||||||
KubeUsername,
|
KubeUsername,
|
||||||
KubeClientCert,
|
KubeClientCert,
|
||||||
KubeClientKey,
|
KubeClientKey,
|
||||||
LocalDirectory,
|
LocalDirectory,
|
||||||
NewVersionChangelog,
|
|
||||||
NewVersionDisclaimer,
|
|
||||||
Password,
|
Password,
|
||||||
Port,
|
Port,
|
||||||
Protocol,
|
Protocol,
|
||||||
QuitPopup,
|
|
||||||
RecentsList,
|
|
||||||
RemoteDirectory,
|
RemoteDirectory,
|
||||||
S3AccessKey,
|
S3AccessKey,
|
||||||
S3Bucket,
|
S3Bucket,
|
||||||
@@ -72,25 +93,21 @@ pub enum Id {
|
|||||||
S3SecurityToken,
|
S3SecurityToken,
|
||||||
S3SessionToken,
|
S3SessionToken,
|
||||||
SmbShare,
|
SmbShare,
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
SmbWorkgroup,
|
SmbWorkgroup,
|
||||||
Subtitle,
|
|
||||||
Title,
|
|
||||||
Username,
|
Username,
|
||||||
WaitPopup,
|
|
||||||
WebDAVUri,
|
WebDAVUri,
|
||||||
WindowSizeError,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum Msg {
|
enum Msg {
|
||||||
Form(FormMsg),
|
Form(FormMsg),
|
||||||
Ui(UiMsg),
|
Ui(UiMsg),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum FormMsg {
|
enum FormMsg {
|
||||||
Connect,
|
Connect,
|
||||||
DeleteBookmark,
|
DeleteBookmark,
|
||||||
DeleteRecent,
|
DeleteRecent,
|
||||||
@@ -98,15 +115,14 @@ pub enum FormMsg {
|
|||||||
InstallUpdate,
|
InstallUpdate,
|
||||||
LoadBookmark(usize),
|
LoadBookmark(usize),
|
||||||
LoadRecent(usize),
|
LoadRecent(usize),
|
||||||
ProtocolChanged(FileTransferProtocol),
|
HostBridgeProtocolChanged(HostBridgeProtocol),
|
||||||
|
RemoteProtocolChanged(FileTransferProtocol),
|
||||||
Quit,
|
Quit,
|
||||||
SaveBookmark,
|
SaveBookmark(FormTab),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum UiMsg {
|
pub enum UiMsg {
|
||||||
AddressBlurDown,
|
|
||||||
AddressBlurUp,
|
|
||||||
BookmarksListBlur,
|
BookmarksListBlur,
|
||||||
BookmarksTabBlur,
|
BookmarksTabBlur,
|
||||||
CloseDeleteBookmark,
|
CloseDeleteBookmark,
|
||||||
@@ -117,6 +133,25 @@ pub enum UiMsg {
|
|||||||
CloseKeybindingsPopup,
|
CloseKeybindingsPopup,
|
||||||
CloseQuitPopup,
|
CloseQuitPopup,
|
||||||
CloseSaveBookmark,
|
CloseSaveBookmark,
|
||||||
|
HostBridge(UiAuthFormMsg),
|
||||||
|
RececentsListBlur,
|
||||||
|
Remote(UiAuthFormMsg),
|
||||||
|
BookmarkNameBlur,
|
||||||
|
SaveBookmarkPasswordBlur,
|
||||||
|
ShowDeleteBookmarkPopup,
|
||||||
|
ShowDeleteRecentPopup,
|
||||||
|
ShowKeybindingsPopup,
|
||||||
|
ShowQuitPopup,
|
||||||
|
ShowReleaseNotes,
|
||||||
|
ShowSaveBookmarkPopup,
|
||||||
|
WindowResized,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum UiAuthFormMsg {
|
||||||
|
AddressBlurDown,
|
||||||
|
AddressBlurUp,
|
||||||
|
ChangeFormTab,
|
||||||
KubeNamespaceBlurDown,
|
KubeNamespaceBlurDown,
|
||||||
KubeNamespaceBlurUp,
|
KubeNamespaceBlurUp,
|
||||||
KubeClusterUrlBlurDown,
|
KubeClusterUrlBlurDown,
|
||||||
@@ -136,7 +171,6 @@ pub enum UiMsg {
|
|||||||
PortBlurUp,
|
PortBlurUp,
|
||||||
ProtocolBlurDown,
|
ProtocolBlurDown,
|
||||||
ProtocolBlurUp,
|
ProtocolBlurUp,
|
||||||
RececentsListBlur,
|
|
||||||
RemoteDirectoryBlurDown,
|
RemoteDirectoryBlurDown,
|
||||||
RemoteDirectoryBlurUp,
|
RemoteDirectoryBlurUp,
|
||||||
S3AccessKeyBlurDown,
|
S3AccessKeyBlurDown,
|
||||||
@@ -159,23 +193,14 @@ pub enum UiMsg {
|
|||||||
S3SessionTokenBlurUp,
|
S3SessionTokenBlurUp,
|
||||||
SmbShareBlurDown,
|
SmbShareBlurDown,
|
||||||
SmbShareBlurUp,
|
SmbShareBlurUp,
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
SmbWorkgroupDown,
|
SmbWorkgroupDown,
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
SmbWorkgroupUp,
|
SmbWorkgroupUp,
|
||||||
BookmarkNameBlur,
|
|
||||||
SaveBookmarkPasswordBlur,
|
|
||||||
ShowDeleteBookmarkPopup,
|
|
||||||
ShowDeleteRecentPopup,
|
|
||||||
ShowKeybindingsPopup,
|
|
||||||
ShowQuitPopup,
|
|
||||||
ShowReleaseNotes,
|
|
||||||
ShowSaveBookmarkPopup,
|
|
||||||
UsernameBlurDown,
|
UsernameBlurDown,
|
||||||
UsernameBlurUp,
|
UsernameBlurUp,
|
||||||
WebDAVUriBlurDown,
|
WebDAVUriBlurDown,
|
||||||
WebDAVUriBlurUp,
|
WebDAVUriBlurUp,
|
||||||
WindowResized,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auth form input mask
|
/// Auth form input mask
|
||||||
@@ -184,10 +209,23 @@ enum InputMask {
|
|||||||
Generic,
|
Generic,
|
||||||
AwsS3,
|
AwsS3,
|
||||||
Kube,
|
Kube,
|
||||||
|
Localhost,
|
||||||
Smb,
|
Smb,
|
||||||
WebDAV,
|
WebDAV,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum HostBridgeProtocol {
|
||||||
|
Localhost,
|
||||||
|
Remote(FileTransferProtocol),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum FormTab {
|
||||||
|
HostBridge,
|
||||||
|
Remote,
|
||||||
|
}
|
||||||
|
|
||||||
// Store keys
|
// Store keys
|
||||||
const STORE_KEY_LATEST_VERSION: &str = "AUTH_LATEST_VERSION";
|
const STORE_KEY_LATEST_VERSION: &str = "AUTH_LATEST_VERSION";
|
||||||
const STORE_KEY_RELEASE_NOTES: &str = "AUTH_RELEASE_NOTES";
|
const STORE_KEY_RELEASE_NOTES: &str = "AUTH_RELEASE_NOTES";
|
||||||
@@ -203,8 +241,11 @@ pub struct AuthActivity {
|
|||||||
exit_reason: Option<ExitReason>,
|
exit_reason: Option<ExitReason>,
|
||||||
/// Should redraw ui
|
/// Should redraw ui
|
||||||
redraw: bool,
|
redraw: bool,
|
||||||
/// Protocol
|
/// Host bridge protocol
|
||||||
protocol: FileTransferProtocol,
|
host_bridge_protocol: HostBridgeProtocol,
|
||||||
|
last_form_tab: FormTab,
|
||||||
|
/// Remote file transfer protocol
|
||||||
|
remote_protocol: FileTransferProtocol,
|
||||||
context: Option<Context>,
|
context: Option<Context>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,15 +255,17 @@ impl AuthActivity {
|
|||||||
AuthActivity {
|
AuthActivity {
|
||||||
app: Application::init(
|
app: Application::init(
|
||||||
EventListenerCfg::default()
|
EventListenerCfg::default()
|
||||||
.default_input_listener(ticks)
|
.crossterm_input_listener(ticks, CROSSTERM_MAX_POLL)
|
||||||
.poll_timeout(ticks),
|
.poll_timeout(ticks),
|
||||||
),
|
),
|
||||||
context: None,
|
context: None,
|
||||||
bookmarks_list: Vec::new(),
|
bookmarks_list: Vec::new(),
|
||||||
exit_reason: None,
|
exit_reason: None,
|
||||||
|
last_form_tab: FormTab::Remote,
|
||||||
recents_list: Vec::new(),
|
recents_list: Vec::new(),
|
||||||
redraw: true,
|
redraw: true,
|
||||||
protocol: FileTransferProtocol::Sftp,
|
host_bridge_protocol: HostBridgeProtocol::Localhost,
|
||||||
|
remote_protocol: FileTransferProtocol::Sftp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,8 +298,23 @@ impl AuthActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get current input mask to show
|
/// Get current input mask to show
|
||||||
fn input_mask(&self) -> InputMask {
|
fn remote_input_mask(&self) -> InputMask {
|
||||||
match self.protocol {
|
Self::file_transfer_protocol_input_mask(self.remote_protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current input mask to show
|
||||||
|
fn host_bridge_input_mask(&self) -> InputMask {
|
||||||
|
match self.host_bridge_protocol {
|
||||||
|
HostBridgeProtocol::Localhost => InputMask::Localhost,
|
||||||
|
HostBridgeProtocol::Remote(protocol) => {
|
||||||
|
Self::file_transfer_protocol_input_mask(protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get input mask for protocol
|
||||||
|
fn file_transfer_protocol_input_mask(protocol: FileTransferProtocol) -> InputMask {
|
||||||
|
match protocol {
|
||||||
FileTransferProtocol::AwsS3 => InputMask::AwsS3,
|
FileTransferProtocol::AwsS3 => InputMask::AwsS3,
|
||||||
FileTransferProtocol::Ftp(_)
|
FileTransferProtocol::Ftp(_)
|
||||||
| FileTransferProtocol::Scp
|
| FileTransferProtocol::Scp
|
||||||
@@ -275,7 +333,7 @@ impl Activity for AuthActivity {
|
|||||||
fn on_create(&mut self, mut context: Context) {
|
fn on_create(&mut self, mut context: Context) {
|
||||||
debug!("Initializing activity");
|
debug!("Initializing activity");
|
||||||
// Initialize file transfer params
|
// Initialize file transfer params
|
||||||
context.set_ftparams(FileTransferParams::default());
|
context.set_remote_params(FileTransferParams::default());
|
||||||
// Set context
|
// Set context
|
||||||
self.context = Some(context);
|
self.context = Some(context);
|
||||||
// Clear terminal
|
// Clear terminal
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
use tuirealm::{State, StateValue};
|
use tuirealm::{State, StateValue};
|
||||||
|
|
||||||
use super::{AuthActivity, ExitReason, FormMsg, Id, InputMask, Msg, UiMsg, Update};
|
use super::{
|
||||||
|
AuthActivity, AuthFormId, ExitReason, FormMsg, FormTab, HostBridgeProtocol, Id, InputMask, Msg,
|
||||||
|
UiAuthFormMsg, UiMsg, Update,
|
||||||
|
};
|
||||||
|
|
||||||
impl Update<Msg> for AuthActivity {
|
impl Update<Msg> for AuthActivity {
|
||||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||||
@@ -21,20 +24,27 @@ impl AuthActivity {
|
|||||||
fn update_form(&mut self, msg: FormMsg) -> Option<Msg> {
|
fn update_form(&mut self, msg: FormMsg) -> Option<Msg> {
|
||||||
match msg {
|
match msg {
|
||||||
FormMsg::Connect => {
|
FormMsg::Connect => {
|
||||||
match self.collect_host_params() {
|
let Ok(remote_params) = self.collect_remote_host_params() else {
|
||||||
Err(err) => {
|
|
||||||
// mount error
|
// mount error
|
||||||
self.mount_error(err);
|
self.mount_error("Invalid remote params parameters");
|
||||||
}
|
return None;
|
||||||
Ok(params) => {
|
};
|
||||||
|
|
||||||
|
let Ok(host_bridge_params) = self.collect_host_bridge_params() else {
|
||||||
|
// mount error
|
||||||
|
self.mount_error("Invalid host bridge params parameters");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
self.save_recent();
|
self.save_recent();
|
||||||
// Set file transfer params to context
|
// Set file transfer params to context
|
||||||
self.context_mut().set_ftparams(params);
|
self.context_mut().set_remote_params(remote_params);
|
||||||
|
// set host bridge params
|
||||||
|
self.context_mut()
|
||||||
|
.set_host_bridge_params(host_bridge_params);
|
||||||
// Set exit reason
|
// Set exit reason
|
||||||
self.exit_reason = Some(super::ExitReason::Connect);
|
self.exit_reason = Some(super::ExitReason::Connect);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
FormMsg::DeleteBookmark => {
|
FormMsg::DeleteBookmark => {
|
||||||
if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::BookmarksList) {
|
if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::BookmarksList) {
|
||||||
// Umount dialog
|
// Umount dialog
|
||||||
@@ -62,50 +72,86 @@ impl AuthActivity {
|
|||||||
self.install_update();
|
self.install_update();
|
||||||
}
|
}
|
||||||
FormMsg::LoadBookmark(i) => {
|
FormMsg::LoadBookmark(i) => {
|
||||||
self.load_bookmark(i);
|
self.load_bookmark(self.last_form_tab, i);
|
||||||
// Give focus to input password (or to protocol if not generic)
|
// Give focus to input password (or to protocol if not generic)
|
||||||
assert!(self
|
let focus = match self.last_form_tab {
|
||||||
.app
|
FormTab::Remote => match self.remote_input_mask() {
|
||||||
.active(match self.input_mask() {
|
InputMask::Localhost => &Id::Remote(AuthFormId::LocalDirectory),
|
||||||
InputMask::Generic => &Id::Password,
|
InputMask::Generic => &Id::Remote(AuthFormId::Password),
|
||||||
InputMask::Smb => &Id::Password,
|
InputMask::Smb => &Id::Remote(AuthFormId::Password),
|
||||||
InputMask::AwsS3 => &Id::S3Bucket,
|
InputMask::AwsS3 => &Id::Remote(AuthFormId::S3Bucket),
|
||||||
InputMask::Kube => &Id::KubeNamespace,
|
InputMask::Kube => &Id::Remote(AuthFormId::KubeNamespace),
|
||||||
InputMask::WebDAV => &Id::Password,
|
InputMask::WebDAV => &Id::Remote(AuthFormId::Password),
|
||||||
})
|
},
|
||||||
.is_ok());
|
FormTab::HostBridge => match self.host_bridge_input_mask() {
|
||||||
|
InputMask::Localhost => &Id::HostBridge(AuthFormId::LocalDirectory),
|
||||||
|
InputMask::Generic => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
InputMask::AwsS3 => &Id::HostBridge(AuthFormId::S3Bucket),
|
||||||
|
InputMask::Kube => &Id::HostBridge(AuthFormId::KubeNamespace),
|
||||||
|
InputMask::WebDAV => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(self.app.active(focus).is_ok());
|
||||||
}
|
}
|
||||||
FormMsg::LoadRecent(i) => {
|
FormMsg::LoadRecent(i) => {
|
||||||
self.load_recent(i);
|
self.load_recent(self.last_form_tab, i);
|
||||||
// Give focus to input password (or to protocol if not generic)
|
// Give focus to input password (or to protocol if not generic)
|
||||||
assert!(self
|
let focus = match self.last_form_tab {
|
||||||
.app
|
FormTab::Remote => match self.remote_input_mask() {
|
||||||
.active(match self.input_mask() {
|
InputMask::Localhost => &Id::Remote(AuthFormId::LocalDirectory),
|
||||||
InputMask::Generic => &Id::Password,
|
InputMask::Generic => &Id::Remote(AuthFormId::Password),
|
||||||
InputMask::Smb => &Id::Password,
|
InputMask::Smb => &Id::Remote(AuthFormId::Password),
|
||||||
InputMask::AwsS3 => &Id::S3Bucket,
|
InputMask::AwsS3 => &Id::Remote(AuthFormId::S3Bucket),
|
||||||
InputMask::Kube => &Id::KubeNamespace,
|
InputMask::Kube => &Id::Remote(AuthFormId::KubeNamespace),
|
||||||
InputMask::WebDAV => &Id::Password,
|
InputMask::WebDAV => &Id::Remote(AuthFormId::Password),
|
||||||
})
|
},
|
||||||
.is_ok());
|
FormTab::HostBridge => match self.host_bridge_input_mask() {
|
||||||
|
InputMask::Localhost => &Id::HostBridge(AuthFormId::LocalDirectory),
|
||||||
|
InputMask::Generic => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
InputMask::AwsS3 => &Id::HostBridge(AuthFormId::S3Bucket),
|
||||||
|
InputMask::Kube => &Id::HostBridge(AuthFormId::KubeNamespace),
|
||||||
|
InputMask::WebDAV => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(self.app.active(focus).is_ok());
|
||||||
}
|
}
|
||||||
FormMsg::ProtocolChanged(protocol) => {
|
FormMsg::HostBridgeProtocolChanged(protocol) => {
|
||||||
self.protocol = protocol;
|
self.host_bridge_protocol = protocol;
|
||||||
// Update port
|
// Update port
|
||||||
let port: u16 = self.get_input_port();
|
let port: u16 = self.get_input_port(FormTab::HostBridge);
|
||||||
|
if let HostBridgeProtocol::Remote(remote_protocol) = protocol {
|
||||||
if Self::is_port_standard(port) {
|
if Self::is_port_standard(port) {
|
||||||
self.mount_port(Self::get_default_port_for_protocol(protocol));
|
self.mount_port(
|
||||||
|
FormTab::HostBridge,
|
||||||
|
Self::get_default_port_for_protocol(remote_protocol),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormMsg::RemoteProtocolChanged(protocol) => {
|
||||||
|
self.remote_protocol = protocol;
|
||||||
|
// Update port
|
||||||
|
let port: u16 = self.get_input_port(FormTab::Remote);
|
||||||
|
if Self::is_port_standard(port) {
|
||||||
|
self.mount_port(
|
||||||
|
FormTab::Remote,
|
||||||
|
Self::get_default_port_for_protocol(protocol),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormMsg::Quit => {
|
FormMsg::Quit => {
|
||||||
self.exit_reason = Some(ExitReason::Quit);
|
self.exit_reason = Some(ExitReason::Quit);
|
||||||
}
|
}
|
||||||
FormMsg::SaveBookmark => {
|
FormMsg::SaveBookmark(form_tab) => {
|
||||||
// get bookmark name
|
// get bookmark name
|
||||||
let (name, save_password) = self.get_new_bookmark();
|
let (name, save_password) = self.get_new_bookmark();
|
||||||
// Save bookmark
|
// Save bookmark
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
self.save_bookmark(name, save_password);
|
self.save_bookmark(form_tab, name, save_password);
|
||||||
}
|
}
|
||||||
// Umount popup
|
// Umount popup
|
||||||
self.umount_bookmark_save_dialog();
|
self.umount_bookmark_save_dialog();
|
||||||
@@ -118,16 +164,30 @@ impl AuthActivity {
|
|||||||
|
|
||||||
fn update_ui(&mut self, msg: UiMsg) -> Option<Msg> {
|
fn update_ui(&mut self, msg: UiMsg) -> Option<Msg> {
|
||||||
match msg {
|
match msg {
|
||||||
UiMsg::AddressBlurDown => {
|
UiMsg::HostBridge(UiAuthFormMsg::AddressBlurDown) => {
|
||||||
let id = if cfg!(windows) && self.input_mask() == InputMask::Smb {
|
let id = if cfg!(windows) && self.host_bridge_input_mask() == InputMask::Smb {
|
||||||
&Id::SmbShare
|
&Id::HostBridge(AuthFormId::SmbShare)
|
||||||
} else {
|
} else {
|
||||||
&Id::Port
|
&Id::HostBridge(AuthFormId::Port)
|
||||||
};
|
};
|
||||||
assert!(self.app.active(id).is_ok());
|
assert!(self.app.active(id).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::AddressBlurUp => {
|
UiMsg::Remote(UiAuthFormMsg::AddressBlurDown) => {
|
||||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
let id = if cfg!(windows) && self.remote_input_mask() == InputMask::Smb {
|
||||||
|
&Id::Remote(AuthFormId::SmbShare)
|
||||||
|
} else {
|
||||||
|
&Id::Remote(AuthFormId::Port)
|
||||||
|
};
|
||||||
|
assert!(self.app.active(id).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::AddressBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::AddressBlurUp) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Protocol)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::BookmarksListBlur => {
|
UiMsg::BookmarksListBlur => {
|
||||||
assert!(self.app.active(&Id::RecentsList).is_ok());
|
assert!(self.app.active(&Id::RecentsList).is_ok());
|
||||||
@@ -136,7 +196,21 @@ impl AuthActivity {
|
|||||||
assert!(self.app.active(&Id::BookmarkSavePassword).is_ok());
|
assert!(self.app.active(&Id::BookmarkSavePassword).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::BookmarksTabBlur => {
|
UiMsg::BookmarksTabBlur => {
|
||||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::ChangeFormTab) => {
|
||||||
|
self.last_form_tab = FormTab::Remote;
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Protocol)).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::ChangeFormTab) => {
|
||||||
|
self.last_form_tab = FormTab::HostBridge;
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::CloseDeleteBookmark => {
|
UiMsg::CloseDeleteBookmark => {
|
||||||
assert!(self.app.umount(&Id::DeleteBookmarkPopup).is_ok());
|
assert!(self.app.umount(&Id::DeleteBookmarkPopup).is_ok());
|
||||||
@@ -162,185 +236,554 @@ impl AuthActivity {
|
|||||||
assert!(self.app.umount(&Id::BookmarkName).is_ok());
|
assert!(self.app.umount(&Id::BookmarkName).is_ok());
|
||||||
assert!(self.app.umount(&Id::BookmarkSavePassword).is_ok());
|
assert!(self.app.umount(&Id::BookmarkSavePassword).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::LocalDirectoryBlurDown => {
|
UiMsg::HostBridge(UiAuthFormMsg::LocalDirectoryBlurDown) => {
|
||||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::LocalDirectoryBlurUp => {
|
UiMsg::Remote(UiAuthFormMsg::LocalDirectoryBlurDown) => {
|
||||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::Protocol)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::ParamsFormBlur => {
|
UiMsg::HostBridge(UiAuthFormMsg::LocalDirectoryBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::LocalDirectoryBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::ParamsFormBlur) => {
|
||||||
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::PasswordBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::ParamsFormBlur) => {
|
||||||
|
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::PasswordBlurDown) => {
|
||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.active(match self.input_mask() {
|
.active(match self.host_bridge_input_mask() {
|
||||||
InputMask::Generic => &Id::RemoteDirectory,
|
InputMask::Localhost => unreachable!(),
|
||||||
#[cfg(unix)]
|
InputMask::Generic => &Id::HostBridge(AuthFormId::RemoteDirectory),
|
||||||
InputMask::Smb => &Id::SmbWorkgroup,
|
#[cfg(posix)]
|
||||||
#[cfg(windows)]
|
InputMask::Smb => &Id::HostBridge(AuthFormId::SmbWorkgroup),
|
||||||
InputMask::Smb => &Id::RemoteDirectory,
|
#[cfg(win)]
|
||||||
InputMask::AwsS3 => panic!("this shouldn't happen (password on s3)"),
|
InputMask::Smb => &Id::HostBridge(AuthFormId::RemoteDirectory),
|
||||||
InputMask::Kube => panic!("this shouldn't happen (password on kube)"),
|
InputMask::AwsS3 => unreachable!("this shouldn't happen (password on s3)"),
|
||||||
InputMask::WebDAV => &Id::RemoteDirectory,
|
InputMask::Kube => unreachable!("this shouldn't happen (password on kube)"),
|
||||||
|
InputMask::WebDAV => &Id::HostBridge(AuthFormId::RemoteDirectory),
|
||||||
})
|
})
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::PasswordBlurUp => {
|
UiMsg::Remote(UiAuthFormMsg::PasswordBlurDown) => {
|
||||||
assert!(self.app.active(&Id::Username).is_ok());
|
|
||||||
}
|
|
||||||
UiMsg::PortBlurDown => {
|
|
||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.active(match self.input_mask() {
|
.active(match self.remote_input_mask() {
|
||||||
InputMask::Generic => &Id::Username,
|
InputMask::Localhost => unreachable!(),
|
||||||
InputMask::Smb => &Id::SmbShare,
|
InputMask::Generic => &Id::Remote(AuthFormId::RemoteDirectory),
|
||||||
InputMask::AwsS3 | InputMask::Kube | InputMask::WebDAV =>
|
#[cfg(posix)]
|
||||||
panic!("this shouldn't happen (port on s3/kube/webdav)"),
|
InputMask::Smb => &Id::Remote(AuthFormId::SmbWorkgroup),
|
||||||
|
#[cfg(win)]
|
||||||
|
InputMask::Smb => &Id::Remote(AuthFormId::RemoteDirectory),
|
||||||
|
InputMask::AwsS3 => unreachable!("this shouldn't happen (password on s3)"),
|
||||||
|
InputMask::Kube => unreachable!("this shouldn't happen (password on kube)"),
|
||||||
|
InputMask::WebDAV => &Id::Remote(AuthFormId::RemoteDirectory),
|
||||||
})
|
})
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::PortBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::PasswordBlurUp) => {
|
||||||
assert!(self.app.active(&Id::Address).is_ok());
|
|
||||||
}
|
|
||||||
UiMsg::ProtocolBlurDown => {
|
|
||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.active(match self.input_mask() {
|
.active(&Id::HostBridge(AuthFormId::Username))
|
||||||
InputMask::Generic => &Id::Address,
|
.is_ok());
|
||||||
InputMask::Smb => &Id::Address,
|
}
|
||||||
InputMask::AwsS3 => &Id::S3Bucket,
|
UiMsg::Remote(UiAuthFormMsg::PasswordBlurUp) => {
|
||||||
InputMask::Kube => &Id::KubeNamespace,
|
assert!(self.app.active(&Id::Remote(AuthFormId::Username)).is_ok());
|
||||||
InputMask::WebDAV => &Id::WebDAVUri,
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::PortBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.host_bridge_input_mask() {
|
||||||
|
InputMask::Generic => &Id::HostBridge(AuthFormId::Username),
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::SmbShare),
|
||||||
|
InputMask::Localhost
|
||||||
|
| InputMask::AwsS3
|
||||||
|
| InputMask::Kube
|
||||||
|
| InputMask::WebDAV =>
|
||||||
|
unreachable!("this shouldn't happen (port on s3/kube/webdav)"),
|
||||||
})
|
})
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::ProtocolBlurUp => {
|
UiMsg::Remote(UiAuthFormMsg::PortBlurDown) => {
|
||||||
assert!(self.app.active(&Id::LocalDirectory).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.remote_input_mask() {
|
||||||
|
InputMask::Generic => &Id::Remote(AuthFormId::Username),
|
||||||
|
InputMask::Smb => &Id::Remote(AuthFormId::SmbShare),
|
||||||
|
InputMask::Localhost
|
||||||
|
| InputMask::AwsS3
|
||||||
|
| InputMask::Kube
|
||||||
|
| InputMask::WebDAV =>
|
||||||
|
unreachable!("this shouldn't happen (port on s3/kube/webdav)"),
|
||||||
|
})
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::PortBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Address))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::PortBlurUp) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Address)).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::ProtocolBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.host_bridge_input_mask() {
|
||||||
|
InputMask::Localhost => &Id::HostBridge(AuthFormId::LocalDirectory),
|
||||||
|
InputMask::Generic => &Id::HostBridge(AuthFormId::Address),
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::Address),
|
||||||
|
InputMask::AwsS3 => &Id::HostBridge(AuthFormId::S3Bucket),
|
||||||
|
InputMask::Kube => &Id::HostBridge(AuthFormId::KubeNamespace),
|
||||||
|
InputMask::WebDAV => &Id::HostBridge(AuthFormId::WebDAVUri),
|
||||||
|
})
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::ProtocolBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.remote_input_mask() {
|
||||||
|
InputMask::Localhost => &Id::Remote(AuthFormId::LocalDirectory),
|
||||||
|
InputMask::Generic => &Id::Remote(AuthFormId::Address),
|
||||||
|
InputMask::Smb => &Id::Remote(AuthFormId::Address),
|
||||||
|
InputMask::AwsS3 => &Id::Remote(AuthFormId::S3Bucket),
|
||||||
|
InputMask::Kube => &Id::Remote(AuthFormId::KubeNamespace),
|
||||||
|
InputMask::WebDAV => &Id::Remote(AuthFormId::WebDAVUri),
|
||||||
|
})
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::ProtocolBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::LocalDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::ProtocolBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::LocalDirectory))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::RececentsListBlur => {
|
UiMsg::RececentsListBlur => {
|
||||||
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::RemoteDirectoryBlurDown => {
|
UiMsg::HostBridge(UiAuthFormMsg::RemoteDirectoryBlurDown) => {
|
||||||
assert!(self.app.active(&Id::LocalDirectory).is_ok());
|
|
||||||
}
|
|
||||||
UiMsg::RemoteDirectoryBlurUp => {
|
|
||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.active(match self.input_mask() {
|
.active(&Id::HostBridge(AuthFormId::LocalDirectory))
|
||||||
InputMask::Generic => &Id::Password,
|
.is_ok());
|
||||||
#[cfg(unix)]
|
}
|
||||||
InputMask::Smb => &Id::SmbWorkgroup,
|
UiMsg::Remote(UiAuthFormMsg::RemoteDirectoryBlurDown) => {
|
||||||
#[cfg(windows)]
|
assert!(self
|
||||||
InputMask::Smb => &Id::Password,
|
.app
|
||||||
InputMask::Kube => &Id::KubeClientKey,
|
.active(&Id::Remote(AuthFormId::LocalDirectory))
|
||||||
InputMask::AwsS3 => &Id::S3NewPathStyle,
|
.is_ok());
|
||||||
InputMask::WebDAV => &Id::Password,
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::RemoteDirectoryBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.host_bridge_input_mask() {
|
||||||
|
InputMask::Localhost => unreachable!(),
|
||||||
|
InputMask::Generic => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
#[cfg(posix)]
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::SmbWorkgroup),
|
||||||
|
#[cfg(win)]
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::Password),
|
||||||
|
InputMask::Kube => &Id::HostBridge(AuthFormId::KubeClientKey),
|
||||||
|
InputMask::AwsS3 => &Id::HostBridge(AuthFormId::S3NewPathStyle),
|
||||||
|
InputMask::WebDAV => &Id::HostBridge(AuthFormId::Password),
|
||||||
})
|
})
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3BucketBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::RemoteDirectoryBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3Region).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.remote_input_mask() {
|
||||||
|
InputMask::Localhost => unreachable!(),
|
||||||
|
InputMask::Generic => &Id::Remote(AuthFormId::Password),
|
||||||
|
#[cfg(posix)]
|
||||||
|
InputMask::Smb => &Id::Remote(AuthFormId::SmbWorkgroup),
|
||||||
|
#[cfg(win)]
|
||||||
|
InputMask::Smb => &Id::Remote(AuthFormId::Password),
|
||||||
|
InputMask::Kube => &Id::Remote(AuthFormId::KubeClientKey),
|
||||||
|
InputMask::AwsS3 => &Id::Remote(AuthFormId::S3NewPathStyle),
|
||||||
|
InputMask::WebDAV => &Id::Remote(AuthFormId::Password),
|
||||||
|
})
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3BucketBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3BucketBlurDown) => {
|
||||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Region))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3RegionBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3BucketBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3Endpoint).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Region)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3RegionBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3BucketBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3Bucket).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3EndpointBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3BucketBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3Profile).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::Protocol)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3EndpointBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3RegionBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3Region).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Endpoint))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3ProfileBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3RegionBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3AccessKey).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Endpoint)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3ProfileBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3RegionBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3Endpoint).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Bucket))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3AccessKeyBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3RegionBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3SecretAccessKey).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Bucket)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3AccessKeyBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3EndpointBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3Profile).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Profile))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3SecretAccessKeyBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3EndpointBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3SecurityToken).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Profile)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3SecretAccessKeyBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3EndpointBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3AccessKey).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Region))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3SecurityTokenBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3EndpointBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3SessionToken).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Region)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3SecurityTokenBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3ProfileBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3SecretAccessKey).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3AccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3SessionTokenBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3ProfileBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3NewPathStyle).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3AccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3SessionTokenBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3ProfileBlurUp) => {
|
||||||
assert!(self.app.active(&Id::S3SecurityToken).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Endpoint))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3NewPathStyleBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3ProfileBlurUp) => {
|
||||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Endpoint)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::S3NewPathStyleBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3AccessKeyBlurDown) => {
|
||||||
assert!(self.app.active(&Id::S3SessionToken).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3SecretAccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeClientCertBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3AccessKeyBlurDown) => {
|
||||||
assert!(self.app.active(&Id::KubeClientKey).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3SecretAccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeClientCertBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3AccessKeyBlurUp) => {
|
||||||
assert!(self.app.active(&Id::KubeUsername).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3Profile))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeClientKeyBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3AccessKeyBlurUp) => {
|
||||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
assert!(self.app.active(&Id::Remote(AuthFormId::S3Profile)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeClientKeyBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3SecretAccessKeyBlurDown) => {
|
||||||
assert!(self.app.active(&Id::KubeClientCert).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3SecurityToken))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeNamespaceBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3SecretAccessKeyBlurDown) => {
|
||||||
assert!(self.app.active(&Id::KubeClusterUrl).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3SecurityToken))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeNamespaceBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3SecretAccessKeyBlurUp) => {
|
||||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3AccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeClusterUrlBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3SecretAccessKeyBlurUp) => {
|
||||||
assert!(self.app.active(&Id::KubeUsername).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3AccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeClusterUrlBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3SecurityTokenBlurDown) => {
|
||||||
assert!(self.app.active(&Id::KubeNamespace).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3SessionToken))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeUsernameBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3SecurityTokenBlurDown) => {
|
||||||
assert!(self.app.active(&Id::KubeClientCert).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3SessionToken))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::KubeUsernameBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3SecurityTokenBlurUp) => {
|
||||||
assert!(self.app.active(&Id::KubeClusterUrl).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3SecretAccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::SmbShareBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::S3SecurityTokenBlurUp) => {
|
||||||
assert!(self.app.active(&Id::Username).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3SecretAccessKey))
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::SmbShareBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::S3SessionTokenBlurDown) => {
|
||||||
let id = if cfg!(windows) && self.input_mask() == InputMask::Smb {
|
assert!(self
|
||||||
&Id::Address
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3NewPathStyle))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::S3SessionTokenBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3NewPathStyle))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::S3SessionTokenBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3SecurityToken))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::S3SessionTokenBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3SecurityToken))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::S3NewPathStyleBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::S3NewPathStyleBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::S3NewPathStyleBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::S3SessionToken))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::S3NewPathStyleBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::S3SessionToken))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeClientCertBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeClientKey))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeClientCertBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeClientKey))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeClientCertBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeUsername))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeClientCertBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeUsername))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeClientKeyBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeClientKeyBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeClientKeyBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeClientCert))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeClientKeyBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeClientCert))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeNamespaceBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeClusterUrl))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeNamespaceBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeClusterUrl))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeNamespaceBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeNamespaceBlurUp) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Protocol)).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeClusterUrlBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeUsername))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeClusterUrlBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeUsername))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeClusterUrlBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeNamespace))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeClusterUrlBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeNamespace))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeUsernameBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeClientCert))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeUsernameBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeClientCert))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::KubeUsernameBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::KubeClusterUrl))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::KubeUsernameBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::KubeClusterUrl))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::SmbShareBlurDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Username))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::SmbShareBlurDown) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Username)).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::SmbShareBlurUp) => {
|
||||||
|
let id = if cfg!(windows) && self.host_bridge_input_mask() == InputMask::Smb {
|
||||||
|
&Id::HostBridge(AuthFormId::Address)
|
||||||
} else {
|
} else {
|
||||||
&Id::Port
|
&Id::HostBridge(AuthFormId::Port)
|
||||||
};
|
};
|
||||||
assert!(self.app.active(id).is_ok());
|
assert!(self.app.active(id).is_ok());
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
UiMsg::Remote(UiAuthFormMsg::SmbShareBlurUp) => {
|
||||||
UiMsg::SmbWorkgroupDown => {
|
let id = if cfg!(windows) && self.remote_input_mask() == InputMask::Smb {
|
||||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
&Id::Remote(AuthFormId::Address)
|
||||||
|
} else {
|
||||||
|
&Id::Remote(AuthFormId::Port)
|
||||||
|
};
|
||||||
|
assert!(self.app.active(id).is_ok());
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
UiMsg::SmbWorkgroupUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::SmbWorkgroupDown) => {
|
||||||
assert!(self.app.active(&Id::Password).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
#[cfg(posix)]
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::SmbWorkgroupDown) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::Remote(AuthFormId::RemoteDirectory))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
#[cfg(posix)]
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::SmbWorkgroupUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Password))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
#[cfg(posix)]
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::SmbWorkgroupUp) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Password)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::SaveBookmarkPasswordBlur => {
|
UiMsg::SaveBookmarkPasswordBlur => {
|
||||||
assert!(self.app.active(&Id::BookmarkName).is_ok());
|
assert!(self.app.active(&Id::BookmarkName).is_ok());
|
||||||
@@ -361,28 +804,60 @@ impl AuthActivity {
|
|||||||
self.mount_release_notes();
|
self.mount_release_notes();
|
||||||
}
|
}
|
||||||
UiMsg::ShowSaveBookmarkPopup => {
|
UiMsg::ShowSaveBookmarkPopup => {
|
||||||
self.mount_bookmark_save_dialog();
|
self.mount_bookmark_save_dialog(self.get_current_form_tab());
|
||||||
}
|
}
|
||||||
UiMsg::UsernameBlurDown => {
|
UiMsg::HostBridge(UiAuthFormMsg::UsernameBlurDown) => {
|
||||||
assert!(self.app.active(&Id::Password).is_ok());
|
|
||||||
}
|
|
||||||
UiMsg::UsernameBlurUp => {
|
|
||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.active(match self.input_mask() {
|
.active(&Id::HostBridge(AuthFormId::Password))
|
||||||
InputMask::Generic => &Id::Port,
|
.is_ok());
|
||||||
InputMask::Smb => &Id::SmbShare,
|
}
|
||||||
InputMask::Kube => panic!("this shouldn't happen (username on kube)"),
|
UiMsg::Remote(UiAuthFormMsg::UsernameBlurDown) => {
|
||||||
InputMask::AwsS3 => panic!("this shouldn't happen (username on s3)"),
|
assert!(self.app.active(&Id::Remote(AuthFormId::Password)).is_ok());
|
||||||
InputMask::WebDAV => &Id::WebDAVUri,
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::UsernameBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.host_bridge_input_mask() {
|
||||||
|
InputMask::Localhost => unreachable!(),
|
||||||
|
InputMask::Generic => &Id::HostBridge(AuthFormId::Port),
|
||||||
|
InputMask::Smb => &Id::HostBridge(AuthFormId::SmbShare),
|
||||||
|
InputMask::Kube => unreachable!("this shouldn't happen (username on kube)"),
|
||||||
|
InputMask::AwsS3 => unreachable!("this shouldn't happen (username on s3)"),
|
||||||
|
InputMask::WebDAV => &Id::HostBridge(AuthFormId::WebDAVUri),
|
||||||
})
|
})
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::WebDAVUriBlurDown => {
|
UiMsg::Remote(UiAuthFormMsg::UsernameBlurUp) => {
|
||||||
assert!(self.app.active(&Id::Username).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(match self.remote_input_mask() {
|
||||||
|
InputMask::Localhost => unreachable!(),
|
||||||
|
InputMask::Generic => &Id::Remote(AuthFormId::Port),
|
||||||
|
InputMask::Smb => &Id::Remote(AuthFormId::SmbShare),
|
||||||
|
InputMask::Kube => unreachable!("this shouldn't happen (username on kube)"),
|
||||||
|
InputMask::AwsS3 => unreachable!("this shouldn't happen (username on s3)"),
|
||||||
|
InputMask::WebDAV => &Id::Remote(AuthFormId::WebDAVUri),
|
||||||
|
})
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::WebDAVUriBlurUp => {
|
UiMsg::HostBridge(UiAuthFormMsg::WebDAVUriBlurDown) => {
|
||||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Username))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::WebDAVUriBlurDown) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Username)).is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::HostBridge(UiAuthFormMsg::WebDAVUriBlurUp) => {
|
||||||
|
assert!(self
|
||||||
|
.app
|
||||||
|
.active(&Id::HostBridge(AuthFormId::Protocol))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
UiMsg::Remote(UiAuthFormMsg::WebDAVUriBlurUp) => {
|
||||||
|
assert!(self.app.active(&Id::Remote(AuthFormId::Protocol)).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::WindowResized => {
|
UiMsg::WindowResized => {
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ enum SyncBrowsingDestination {
|
|||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
/// Enter a directory on local host from entry
|
/// Enter a directory on local host from entry
|
||||||
pub(crate) fn action_enter_local_dir(&mut self, dir: File) {
|
pub(crate) fn action_enter_local_dir(&mut self, dir: File) {
|
||||||
self.local_changedir(dir.path(), true);
|
self.host_bridge_changedir(dir.path(), true);
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name()));
|
self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name()));
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,9 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
/// Change local directory reading value from input
|
/// Change local directory reading value from input
|
||||||
pub(crate) fn action_change_local_dir(&mut self, input: String) {
|
pub(crate) fn action_change_local_dir(&mut self, input: String) {
|
||||||
let dir_path: PathBuf = self.local_to_abs_path(PathBuf::from(input.as_str()).as_path());
|
let dir_path: PathBuf =
|
||||||
self.local_changedir(dir_path.as_path(), true);
|
self.host_bridge_to_abs_path(PathBuf::from(input.as_str()).as_path());
|
||||||
|
self.host_bridge_changedir(dir_path.as_path(), true);
|
||||||
// Check whether to sync
|
// Check whether to sync
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.synchronize_browsing(SyncBrowsingDestination::Path(input));
|
self.synchronize_browsing(SyncBrowsingDestination::Path(input));
|
||||||
@@ -55,8 +56,8 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
/// Go to previous directory from localhost
|
/// Go to previous directory from localhost
|
||||||
pub(crate) fn action_go_to_previous_local_dir(&mut self) {
|
pub(crate) fn action_go_to_previous_local_dir(&mut self) {
|
||||||
if let Some(d) = self.local_mut().popd() {
|
if let Some(d) = self.host_bridge_mut().popd() {
|
||||||
self.local_changedir(d.as_path(), false);
|
self.host_bridge_changedir(d.as_path(), false);
|
||||||
// Check whether to sync
|
// Check whether to sync
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.synchronize_browsing(SyncBrowsingDestination::PreviousDir);
|
self.synchronize_browsing(SyncBrowsingDestination::PreviousDir);
|
||||||
@@ -78,10 +79,10 @@ impl FileTransferActivity {
|
|||||||
/// Go to upper directory on local host
|
/// Go to upper directory on local host
|
||||||
pub(crate) fn action_go_to_local_upper_dir(&mut self) {
|
pub(crate) fn action_go_to_local_upper_dir(&mut self) {
|
||||||
// Get pwd
|
// Get pwd
|
||||||
let path: PathBuf = self.local().wrkdir.clone();
|
let path: PathBuf = self.host_bridge().wrkdir.clone();
|
||||||
// Go to parent directory
|
// Go to parent directory
|
||||||
if let Some(parent) = path.as_path().parent() {
|
if let Some(parent) = path.as_path().parent() {
|
||||||
self.local_changedir(parent, true);
|
self.host_bridge_changedir(parent, true);
|
||||||
// If sync is enabled update remote too
|
// If sync is enabled update remote too
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.synchronize_browsing(SyncBrowsingDestination::ParentDir);
|
self.synchronize_browsing(SyncBrowsingDestination::ParentDir);
|
||||||
@@ -118,7 +119,7 @@ impl FileTransferActivity {
|
|||||||
trace!("Synchronizing browsing to path {}", path.display());
|
trace!("Synchronizing browsing to path {}", path.display());
|
||||||
// Check whether destination exists on host
|
// Check whether destination exists on host
|
||||||
let exists = match self.browser.tab() {
|
let exists = match self.browser.tab() {
|
||||||
FileExplorerTab::Local => match self.client.exists(path.as_path()) {
|
FileExplorerTab::HostBridge => match self.client.exists(path.as_path()) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(
|
error!(
|
||||||
@@ -129,7 +130,17 @@ impl FileTransferActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FileExplorerTab::Remote => self.host.file_exists(path.as_path()),
|
FileExplorerTab::Remote => match self.host_bridge.exists(path.as_path()) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Failed to check whether {} exists on host: {}",
|
||||||
|
path.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let name = path
|
let name = path
|
||||||
@@ -150,7 +161,7 @@ impl FileTransferActivity {
|
|||||||
trace!("User wants to create the unexisting directory");
|
trace!("User wants to create the unexisting directory");
|
||||||
// Make directory
|
// Make directory
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_remote_mkdir(name.clone()),
|
FileExplorerTab::HostBridge => self.action_remote_mkdir(name.clone()),
|
||||||
FileExplorerTab::Remote => self.action_local_mkdir(name.clone()),
|
FileExplorerTab::Remote => self.action_local_mkdir(name.clone()),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -173,18 +184,18 @@ impl FileTransferActivity {
|
|||||||
// Enter directory
|
// Enter directory
|
||||||
match destination {
|
match destination {
|
||||||
SyncBrowsingDestination::ParentDir => match self.browser.tab() {
|
SyncBrowsingDestination::ParentDir => match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.remote_changedir(path.as_path(), true),
|
FileExplorerTab::HostBridge => self.remote_changedir(path.as_path(), true),
|
||||||
FileExplorerTab::Remote => self.local_changedir(path.as_path(), true),
|
FileExplorerTab::Remote => self.host_bridge_changedir(path.as_path(), true),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
SyncBrowsingDestination::Path(_) => match self.browser.tab() {
|
SyncBrowsingDestination::Path(_) => match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.remote_changedir(path.as_path(), true),
|
FileExplorerTab::HostBridge => self.remote_changedir(path.as_path(), true),
|
||||||
FileExplorerTab::Remote => self.local_changedir(path.as_path(), true),
|
FileExplorerTab::Remote => self.host_bridge_changedir(path.as_path(), true),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
SyncBrowsingDestination::PreviousDir => match self.browser.tab() {
|
SyncBrowsingDestination::PreviousDir => match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.remote_changedir(path.as_path(), false),
|
FileExplorerTab::HostBridge => self.remote_changedir(path.as_path(), false),
|
||||||
FileExplorerTab::Remote => self.local_changedir(path.as_path(), false),
|
FileExplorerTab::Remote => self.host_bridge_changedir(path.as_path(), false),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -197,13 +208,13 @@ impl FileTransferActivity {
|
|||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
match (destination, self.browser.tab()) {
|
match (destination, self.browser.tab()) {
|
||||||
// NOTE: tab and methods are switched on purpose
|
// NOTE: tab and methods are switched on purpose
|
||||||
(SyncBrowsingDestination::ParentDir, FileExplorerTab::Local) => {
|
(SyncBrowsingDestination::ParentDir, FileExplorerTab::HostBridge) => {
|
||||||
self.remote().wrkdir.parent().map(|x| x.to_path_buf())
|
self.remote().wrkdir.parent().map(|x| x.to_path_buf())
|
||||||
}
|
}
|
||||||
(SyncBrowsingDestination::ParentDir, FileExplorerTab::Remote) => {
|
(SyncBrowsingDestination::ParentDir, FileExplorerTab::Remote) => {
|
||||||
self.local().wrkdir.parent().map(|x| x.to_path_buf())
|
self.host_bridge().wrkdir.parent().map(|x| x.to_path_buf())
|
||||||
}
|
}
|
||||||
(SyncBrowsingDestination::PreviousDir, FileExplorerTab::Local) => {
|
(SyncBrowsingDestination::PreviousDir, FileExplorerTab::HostBridge) => {
|
||||||
if let Some(p) = self.remote_mut().popd() {
|
if let Some(p) = self.remote_mut().popd() {
|
||||||
Some(p)
|
Some(p)
|
||||||
} else {
|
} else {
|
||||||
@@ -212,7 +223,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(SyncBrowsingDestination::PreviousDir, FileExplorerTab::Remote) => {
|
(SyncBrowsingDestination::PreviousDir, FileExplorerTab::Remote) => {
|
||||||
if let Some(p) = self.local_mut().popd() {
|
if let Some(p) = self.host_bridge_mut().popd() {
|
||||||
Some(p)
|
Some(p)
|
||||||
} else {
|
} else {
|
||||||
warn!("Cannot synchronize browsing: local has no previous directory in stack");
|
warn!("Cannot synchronize browsing: local has no previous directory in stack");
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ use remotefs::fs::UnixPex;
|
|||||||
use super::{FileTransferActivity, LogLevel};
|
use super::{FileTransferActivity, LogLevel};
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn action_local_chmod(&mut self, mode: UnixPex) {
|
pub fn action_local_chmod(&mut self, mode: UnixPex) {
|
||||||
let files = self.get_local_selected_entries().get_files();
|
let files = self.get_local_selected_entries().get_files();
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
if let Err(err) = self.host.chmod(file.path(), mode) {
|
if let Err(err) = self.host_bridge.chmod(file.path(), mode) {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
@@ -51,12 +50,11 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn action_find_local_chmod(&mut self, mode: UnixPex) {
|
pub fn action_find_local_chmod(&mut self, mode: UnixPex) {
|
||||||
let files = self.get_found_selected_entries().get_files();
|
let files = self.get_found_selected_entries().get_files();
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
if let Err(err) = self.host.chmod(file.path(), mode) {
|
if let Err(err) = self.host_bridge.chmod(file.path(), mode) {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn local_copy_file(&mut self, entry: &File, dest: &Path) {
|
fn local_copy_file(&mut self, entry: &File, dest: &Path) {
|
||||||
match self.host.copy(entry, dest) {
|
match self.host_bridge.copy(entry, dest) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
@@ -136,7 +136,7 @@ impl FileTransferActivity {
|
|||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
// Stat dir
|
// Stat dir
|
||||||
let tempdir_entry = match self.host.stat(tempdir_path.as_path()) {
|
let tempdir_entry = match self.host_bridge.stat(tempdir_path.as_path()) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
@@ -189,7 +189,7 @@ impl FileTransferActivity {
|
|||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
// Get local fs entry
|
// Get local fs entry
|
||||||
let tmpfile_entry = match self.host.stat(tmpfile.path()) {
|
let tmpfile_entry = match self.host_bridge.stat(tmpfile.path()) {
|
||||||
Ok(e) if e.is_file() => e,
|
Ok(e) if e.is_file() => e,
|
||||||
Ok(_) => panic!("{} is not a file", tmpfile.path().display()),
|
Ok(_) => panic!("{} is not a file", tmpfile.path().display()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn local_remove_file(&mut self, entry: &File) {
|
pub(crate) fn local_remove_file(&mut self, entry: &File) {
|
||||||
match self.host.remove(entry) {
|
match self.host_bridge.remove(entry) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Log
|
// Log
|
||||||
self.log(
|
self.log(
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
//!
|
//!
|
||||||
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||||
|
|
||||||
// locals
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
// ext
|
use remotefs::fs::Metadata;
|
||||||
use remotefs::File;
|
use remotefs::File;
|
||||||
|
|
||||||
use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload};
|
use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload};
|
||||||
@@ -29,7 +28,12 @@ impl FileTransferActivity {
|
|||||||
format!("Opening file \"{}\"…", entry.path().display()),
|
format!("Opening file \"{}\"…", entry.path().display()),
|
||||||
);
|
);
|
||||||
// Edit file
|
// Edit file
|
||||||
if let Err(err) = self.edit_local_file(entry.path()) {
|
let res = match self.host_bridge.is_localhost() {
|
||||||
|
true => self.edit_local_file(entry.path()).map(|_| ()),
|
||||||
|
false => self.edit_bridged_local_file(entry),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
self.log_and_alert(LogLevel::Error, err);
|
self.log_and_alert(LogLevel::Error, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +63,83 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Edit a file on localhost
|
/// Edit a file on localhost
|
||||||
fn edit_local_file(&mut self, path: &Path) -> Result<(), String> {
|
fn edit_bridged_local_file(&mut self, entry: &File) -> Result<(), String> {
|
||||||
|
// Download file
|
||||||
|
let tmpfile: String =
|
||||||
|
match self.get_cache_tmp_name(&entry.name(), entry.extension().as_deref()) {
|
||||||
|
None => {
|
||||||
|
return Err("Could not create tempdir".to_string());
|
||||||
|
}
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
let cache: PathBuf = match self.cache.as_ref() {
|
||||||
|
None => {
|
||||||
|
return Err("Could not create tempdir".to_string());
|
||||||
|
}
|
||||||
|
Some(p) => p.path().to_path_buf(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// open from host bridge
|
||||||
|
let mut reader = match self.host_bridge.open_file(entry.path()) {
|
||||||
|
Ok(reader) => reader,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!("Failed to open bridged entry: {err}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tempfile = cache.join(tmpfile);
|
||||||
|
|
||||||
|
// write to file
|
||||||
|
let mut writer = match std::fs::File::create(tempfile.as_path()) {
|
||||||
|
Ok(writer) => writer,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!("Failed to write file: {err}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_file_size = match std::io::copy(&mut reader, &mut writer) {
|
||||||
|
Err(err) => return Err(format!("Could not write file: {err}")),
|
||||||
|
Ok(size) => size,
|
||||||
|
};
|
||||||
|
|
||||||
|
// edit file
|
||||||
|
|
||||||
|
let has_changed = self.edit_local_file(tempfile.as_path())?;
|
||||||
|
|
||||||
|
if has_changed {
|
||||||
|
// report changes to remote
|
||||||
|
let mut reader = match std::fs::File::open(tempfile.as_path()) {
|
||||||
|
Ok(reader) => reader,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!("Could not open file: {err}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut writer = match self.host_bridge.create_file(
|
||||||
|
entry.path(),
|
||||||
|
&Metadata {
|
||||||
|
size: new_file_size,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(writer) => writer,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!("Could not write file: {err}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = std::io::copy(&mut reader, &mut writer) {
|
||||||
|
return Err(format!("Could not write file: {err}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.host_bridge
|
||||||
|
.finalize_write(writer)
|
||||||
|
.map_err(|err| format!("Could not write file: {err}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_local_file(&mut self, path: &Path) -> Result<bool, String> {
|
||||||
// Read first 2048 bytes or less from file to check if it is textual
|
// Read first 2048 bytes or less from file to check if it is textual
|
||||||
match OpenOptions::new().read(true).open(path) {
|
match OpenOptions::new().read(true).open(path) {
|
||||||
Ok(mut f) => {
|
Ok(mut f) => {
|
||||||
@@ -90,6 +170,8 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
// Lock ports
|
// Lock ports
|
||||||
assert!(self.app.lock_ports().is_ok());
|
assert!(self.app.lock_ports().is_ok());
|
||||||
|
// Get current file modification time
|
||||||
|
let prev_mtime = self.get_localhost_mtime(path)?;
|
||||||
// Open editor
|
// Open editor
|
||||||
match edit::edit_file(path) {
|
match edit::edit_file(path) {
|
||||||
Ok(_) => self.log(
|
Ok(_) => self.log(
|
||||||
@@ -117,7 +199,23 @@ impl FileTransferActivity {
|
|||||||
// Unlock ports
|
// Unlock ports
|
||||||
assert!(self.app.unlock_ports().is_ok());
|
assert!(self.app.unlock_ports().is_ok());
|
||||||
}
|
}
|
||||||
Ok(())
|
let after_mtime = self.get_localhost_mtime(path)?;
|
||||||
|
|
||||||
|
// return if file has changed
|
||||||
|
Ok(prev_mtime != after_mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_localhost_mtime(&self, p: &Path) -> Result<SystemTime, String> {
|
||||||
|
let attr = match std::fs::metadata(p) {
|
||||||
|
Ok(metadata) => metadata,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!("Could not read file metadata: {}", err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Metadata::from(attr)
|
||||||
|
.modified
|
||||||
|
.unwrap_or(std::time::UNIX_EPOCH))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edit file on remote host
|
/// Edit file on remote host
|
||||||
@@ -138,7 +236,7 @@ impl FileTransferActivity {
|
|||||||
return Err(format!("Could not open file {file_name}: {err}"));
|
return Err(format!("Could not open file {file_name}: {err}"));
|
||||||
}
|
}
|
||||||
// Get current file modification time
|
// Get current file modification time
|
||||||
let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) {
|
let prev_mtime: SystemTime = match self.host_bridge.stat(tmpfile.as_path()) {
|
||||||
Ok(e) => e.metadata().modified.unwrap_or(std::time::UNIX_EPOCH),
|
Ok(e) => e.metadata().modified.unwrap_or(std::time::UNIX_EPOCH),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@@ -151,7 +249,7 @@ impl FileTransferActivity {
|
|||||||
// Edit file
|
// Edit file
|
||||||
self.edit_local_file(tmpfile.as_path())?;
|
self.edit_local_file(tmpfile.as_path())?;
|
||||||
// Get local fs entry
|
// Get local fs entry
|
||||||
let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) {
|
let tmpfile_entry: File = match self.host_bridge.stat(tmpfile.as_path()) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@@ -177,7 +275,7 @@ impl FileTransferActivity {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// Get local fs entry
|
// Get local fs entry
|
||||||
let tmpfile_entry = match self.host.stat(tmpfile.as_path()) {
|
let tmpfile_entry = match self.host_bridge.stat(tmpfile.as_path()) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use super::{FileTransferActivity, LogLevel};
|
|||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
pub(crate) fn action_local_exec(&mut self, input: String) {
|
pub(crate) fn action_local_exec(&mut self, input: String) {
|
||||||
match self.host.exec(input.as_str()) {
|
match self.host_bridge.exec(input.as_str()) {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
// Reload files
|
// Reload files
|
||||||
self.log(LogLevel::Info, format!("\"{input}\": {output}"));
|
self.log(LogLevel::Info, format!("\"{input}\": {output}"));
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ impl FileTransferActivity {
|
|||||||
let filter = Filter::from_str(filter).unwrap();
|
let filter = Filter::from_str(filter).unwrap();
|
||||||
|
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.browser.local().iter_files(),
|
FileExplorerTab::HostBridge => self.browser.host_bridge().iter_files(),
|
||||||
FileExplorerTab::Remote => self.browser.remote().iter_files(),
|
FileExplorerTab::Remote => self.browser.remote().iter_files(),
|
||||||
_ => return vec![],
|
_ => return vec![],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ impl FileTransferActivity {
|
|||||||
};
|
};
|
||||||
// Change directory
|
// Change directory
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
self.local_changedir(path.as_path(), true)
|
self.host_bridge_changedir(path.as_path(), true)
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||||
self.remote_changedir(path.as_path(), true)
|
self.remote_changedir(path.as_path(), true)
|
||||||
@@ -36,12 +36,16 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
pub(crate) fn action_find_transfer(&mut self, opts: TransferOpts) {
|
pub(crate) fn action_find_transfer(&mut self, opts: TransferOpts) {
|
||||||
let wrkdir: PathBuf = match self.browser.tab() {
|
let wrkdir: PathBuf = match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => self.remote().wrkdir.clone(),
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => self.local().wrkdir.clone(),
|
self.remote().wrkdir.clone()
|
||||||
|
}
|
||||||
|
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||||
|
self.host_bridge().wrkdir.clone()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match self.get_found_selected_entries() {
|
match self.get_found_selected_entries() {
|
||||||
SelectedFile::One(entry) => match self.browser.tab() {
|
SelectedFile::One(entry) => match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||||
if self.config().get_prompt_on_file_replace()
|
if self.config().get_prompt_on_file_replace()
|
||||||
&& self.remote_file_exists(file_to_check.as_path())
|
&& self.remote_file_exists(file_to_check.as_path())
|
||||||
@@ -66,7 +70,7 @@ impl FileTransferActivity {
|
|||||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||||
if self.config().get_prompt_on_file_replace()
|
if self.config().get_prompt_on_file_replace()
|
||||||
&& self.local_file_exists(file_to_check.as_path())
|
&& self.host_bridge_file_exists(file_to_check.as_path())
|
||||||
&& !self.should_replace_file(
|
&& !self.should_replace_file(
|
||||||
opts.save_as.clone().unwrap_or_else(|| entry.name()),
|
opts.save_as.clone().unwrap_or_else(|| entry.name()),
|
||||||
)
|
)
|
||||||
@@ -94,7 +98,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
// Iter files
|
// Iter files
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
if self.config().get_prompt_on_file_replace() {
|
if self.config().get_prompt_on_file_replace() {
|
||||||
// Check which file would be replaced
|
// Check which file would be replaced
|
||||||
let existing_files: Vec<&File> = entries
|
let existing_files: Vec<&File> = entries
|
||||||
@@ -131,7 +135,7 @@ impl FileTransferActivity {
|
|||||||
let existing_files: Vec<&File> = entries
|
let existing_files: Vec<&File> = entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
self.local_file_exists(
|
self.host_bridge_file_exists(
|
||||||
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -179,7 +183,7 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
fn remove_found_file(&mut self, entry: &File) {
|
fn remove_found_file(&mut self, entry: &File) {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
self.local_remove_file(entry);
|
self.local_remove_file(entry);
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||||
@@ -224,7 +228,7 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
fn open_found_file(&mut self, entry: &File, with: Option<&str>) {
|
fn open_found_file(&mut self, entry: &File, with: Option<&str>) {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
self.action_open_local_file(entry, with);
|
self.action_open_local_file(entry, with);
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ use super::{FileTransferActivity, LogLevel};
|
|||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
pub(crate) fn action_local_mkdir(&mut self, input: String) {
|
pub(crate) fn action_local_mkdir(&mut self, input: String) {
|
||||||
match self.host.mkdir(PathBuf::from(input.as_str()).as_path()) {
|
match self
|
||||||
|
.host_bridge
|
||||||
|
.mkdir(PathBuf::from(input.as_str()).as_path())
|
||||||
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Reload files
|
// Reload files
|
||||||
self.log(LogLevel::Info, format!("Created directory \"{input}\""));
|
self.log(LogLevel::Info, format!("Created directory \"{input}\""));
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ impl From<Vec<&File>> for SelectedFile {
|
|||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
/// Get local file entry
|
/// Get local file entry
|
||||||
pub(crate) fn get_local_selected_entries(&self) -> SelectedFile {
|
pub(crate) fn get_local_selected_entries(&self) -> SelectedFile {
|
||||||
match self.get_selected_index(&Id::ExplorerLocal) {
|
match self.get_selected_index(&Id::ExplorerHostBridge) {
|
||||||
SelectedFileIndex::One(idx) => SelectedFile::from(self.local().get(idx)),
|
SelectedFileIndex::One(idx) => SelectedFile::from(self.host_bridge().get(idx)),
|
||||||
SelectedFileIndex::Many(files) => {
|
SelectedFileIndex::Many(files) => {
|
||||||
let files: Vec<&File> = files
|
let files: Vec<&File> = files
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|x| self.local().get(*x)) // Usize to Option<File>
|
.filter_map(|x| self.host_bridge().get(*x)) // Usize to Option<File>
|
||||||
.collect();
|
.collect();
|
||||||
SelectedFile::from(files)
|
SelectedFile::from(files)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@
|
|||||||
use std::fs::File as StdFile;
|
use std::fs::File as StdFile;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use remotefs::fs::Metadata;
|
||||||
|
|
||||||
use super::{File, FileTransferActivity, LogLevel};
|
use super::{File, FileTransferActivity, LogLevel};
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
pub(crate) fn action_local_newfile(&mut self, input: String) {
|
pub(crate) fn action_local_newfile(&mut self, input: String) {
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
let mut file_exists: bool = false;
|
let mut file_exists: bool = false;
|
||||||
for file in self.local().iter_files_all() {
|
for file in self.host_bridge().iter_files_all() {
|
||||||
if input == file.name() {
|
if input == file.name() {
|
||||||
file_exists = true;
|
file_exists = true;
|
||||||
}
|
}
|
||||||
@@ -21,20 +23,36 @@ impl FileTransferActivity {
|
|||||||
self.log_and_alert(LogLevel::Warn, format!("File \"{input}\" already exists",));
|
self.log_and_alert(LogLevel::Warn, format!("File \"{input}\" already exists",));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create file
|
// Create file
|
||||||
let file_path: PathBuf = PathBuf::from(input.as_str());
|
let file_path: PathBuf = PathBuf::from(input.as_str());
|
||||||
if let Err(err) = self.host.open_file_write(file_path.as_path()) {
|
let writer = match self
|
||||||
|
.host_bridge
|
||||||
|
.create_file(file_path.as_path(), &Metadata::default())
|
||||||
|
{
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(err) => {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!("Could not create file \"{}\": {}", file_path.display(), err),
|
format!("Could not create file \"{}\": {}", file_path.display(), err),
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// finalize write
|
||||||
|
if let Err(err) = self.host_bridge.finalize_write(writer) {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Could not write file \"{}\": {}", file_path.display(), err),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!("Created file \"{}\"", file_path.display()),
|
format!("Created file \"{}\"", file_path.display()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn action_remote_newfile(&mut self, input: String) {
|
pub(crate) fn action_remote_newfile(&mut self, input: String) {
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
@@ -57,7 +75,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
Ok(tfile) => {
|
Ok(tfile) => {
|
||||||
// Stat tempfile
|
// Stat tempfile
|
||||||
let local_file: File = match self.host.stat(tfile.path()) {
|
let local_file: File = match self.host_bridge.stat(tfile.path()) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
/// Perform open lopcal file
|
/// Perform open lopcal file
|
||||||
pub(crate) fn action_open_local_file(&mut self, entry: &File, open_with: Option<&str>) {
|
pub(crate) fn action_open_local_file(&mut self, entry: &File, open_with: Option<&str>) {
|
||||||
|
if self.host_bridge.is_localhost() {
|
||||||
self.open_path_with(entry.path(), open_with);
|
self.open_path_with(entry.path(), open_with);
|
||||||
|
} else {
|
||||||
|
self.open_bridged_file(entry, open_with);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open remote file. The file is first downloaded to a temporary directory on localhost
|
/// Open remote file. The file is first downloaded to a temporary directory on localhost
|
||||||
@@ -104,6 +108,57 @@ impl FileTransferActivity {
|
|||||||
.for_each(|x| self.action_open_remote_file(x, Some(with)));
|
.for_each(|x| self.action_open_remote_file(x, Some(with)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_bridged_file(&mut self, entry: &File, open_with: Option<&str>) {
|
||||||
|
// Download file
|
||||||
|
let tmpfile: String =
|
||||||
|
match self.get_cache_tmp_name(&entry.name(), entry.extension().as_deref()) {
|
||||||
|
None => {
|
||||||
|
self.log(LogLevel::Error, String::from("Could not create tempdir"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
let cache: PathBuf = match self.cache.as_ref() {
|
||||||
|
None => {
|
||||||
|
self.log(LogLevel::Error, String::from("Could not create tempdir"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(p) => p.path().to_path_buf(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tmpfile = cache.join(tmpfile);
|
||||||
|
|
||||||
|
// open from host bridge
|
||||||
|
let mut reader = match self.host_bridge.open_file(entry.path()) {
|
||||||
|
Ok(reader) => reader,
|
||||||
|
Err(err) => {
|
||||||
|
self.log(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Failed to open bridged entry: {err}"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// write to file
|
||||||
|
let mut writer = match std::fs::File::create(tmpfile.as_path()) {
|
||||||
|
Ok(writer) => writer,
|
||||||
|
Err(err) => {
|
||||||
|
self.log(LogLevel::Error, format!("Failed to create file: {err}"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = std::io::copy(&mut reader, &mut writer) {
|
||||||
|
self.log(LogLevel::Error, format!("Failed to write file: {err}"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpfile.exists() {
|
||||||
|
self.open_path_with(tmpfile.as_path(), open_with);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Common function which opens a path with default or specified program.
|
/// Common function which opens a path with default or specified program.
|
||||||
fn open_path_with(&mut self, p: &Path, with: Option<&str>) {
|
fn open_path_with(&mut self, p: &Path, with: Option<&str>) {
|
||||||
// Open file
|
// Open file
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn local_rename_file(&mut self, entry: &File, dest: &Path) {
|
fn local_rename_file(&mut self, entry: &File, dest: &Path) {
|
||||||
match self.host.rename(entry, dest) {
|
match self.host_bridge.rename(entry, dest) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
|
|||||||
@@ -93,12 +93,12 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remote_recv_file(&mut self, opts: TransferOpts) {
|
fn remote_recv_file(&mut self, opts: TransferOpts) {
|
||||||
let wrkdir: PathBuf = self.local().wrkdir.clone();
|
let wrkdir: PathBuf = self.host_bridge().wrkdir.clone();
|
||||||
match self.get_remote_selected_entries() {
|
match self.get_remote_selected_entries() {
|
||||||
SelectedFile::One(entry) => {
|
SelectedFile::One(entry) => {
|
||||||
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
|
||||||
if self.config().get_prompt_on_file_replace()
|
if self.config().get_prompt_on_file_replace()
|
||||||
&& self.local_file_exists(file_to_check.as_path())
|
&& self.host_bridge_file_exists(file_to_check.as_path())
|
||||||
&& !self
|
&& !self
|
||||||
.should_replace_file(opts.save_as.clone().unwrap_or_else(|| entry.name()))
|
.should_replace_file(opts.save_as.clone().unwrap_or_else(|| entry.name()))
|
||||||
{
|
{
|
||||||
@@ -129,7 +129,7 @@ impl FileTransferActivity {
|
|||||||
let existing_files: Vec<&File> = entries
|
let existing_files: Vec<&File> = entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
self.local_file_exists(
|
self.host_bridge_file_exists(
|
||||||
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use crate::ui::activities::filetransfer::lib::browser::FileExplorerTab;
|
|||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
pub(crate) fn action_scan(&mut self, p: &Path) -> Result<Vec<File>, String> {
|
pub(crate) fn action_scan(&mut self, p: &Path) -> Result<Vec<File>, String> {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => self
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => self
|
||||||
.host
|
.host_bridge
|
||||||
.list_dir(p)
|
.list_dir(p)
|
||||||
.map_err(|e| format!("Failed to list directory: {}", e)),
|
.map_err(|e| format!("Failed to list directory: {}", e)),
|
||||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self
|
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ impl FileTransferActivity {
|
|||||||
} else if entry.metadata().symlink.is_some() {
|
} else if entry.metadata().symlink.is_some() {
|
||||||
// Stat file
|
// Stat file
|
||||||
let symlink = entry.metadata().symlink.as_ref().unwrap();
|
let symlink = entry.metadata().symlink.as_ref().unwrap();
|
||||||
let stat_file = match self.host.stat(symlink.as_path()) {
|
let stat_file = match self.host_bridge.stat(symlink.as_path()) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(
|
warn!(
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ use super::{FileTransferActivity, LogLevel, SelectedFile};
|
|||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
/// Create symlink on localhost
|
/// Create symlink on localhost
|
||||||
#[cfg(unix)]
|
|
||||||
pub(crate) fn action_local_symlink(&mut self, name: String) {
|
pub(crate) fn action_local_symlink(&mut self, name: String) {
|
||||||
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
||||||
match self
|
match self
|
||||||
.host
|
.host_bridge
|
||||||
.symlink(PathBuf::from(name.as_str()).as_path(), entry.path())
|
.symlink(PathBuf::from(name.as_str()).as_path(), entry.path())
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -33,11 +32,6 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub(crate) fn action_local_symlink(&mut self, _name: String) {
|
|
||||||
self.mount_error("Symlinks are not supported on Windows hosts");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy file on remote
|
/// Copy file on remote
|
||||||
pub(crate) fn action_remote_symlink(&mut self, name: String) {
|
pub(crate) fn action_remote_symlink(&mut self, name: String) {
|
||||||
if let SelectedFile::One(entry) = self.get_remote_selected_entries() {
|
if let SelectedFile::One(entry) = self.get_remote_selected_entries() {
|
||||||
|
|||||||
@@ -18,8 +18,16 @@ impl FileTransferActivity {
|
|||||||
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, WalkdirError> {
|
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, WalkdirError> {
|
||||||
let mut acc = Vec::with_capacity(32_768);
|
let mut acc = Vec::with_capacity(32_768);
|
||||||
|
|
||||||
self.walkdir(&mut acc, &self.host.pwd(), |activity, path| {
|
let pwd = self
|
||||||
activity.host.list_dir(path).map_err(|e| e.to_string())
|
.host_bridge
|
||||||
|
.pwd()
|
||||||
|
.map_err(|e| WalkdirError::Error(e.to_string()))?;
|
||||||
|
|
||||||
|
self.walkdir(&mut acc, &pwd, |activity, path| {
|
||||||
|
activity
|
||||||
|
.host_bridge
|
||||||
|
.list_dir(path)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(acc)
|
Ok(acc)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||||
use tuirealm::event::{Key, KeyEvent};
|
use tuirealm::event::{Key, KeyEvent};
|
||||||
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Style, Table};
|
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Style, Table};
|
||||||
use tuirealm::tui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
|
||||||
|
|
||||||
use super::{Msg, UiMsg};
|
use super::{Msg, UiMsg};
|
||||||
@@ -32,7 +32,7 @@ impl Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MockComponent for Log {
|
impl MockComponent for Log {
|
||||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::layout::Rect) {
|
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||||
let width: usize = area.width as usize - 4;
|
let width: usize = area.width as usize - 4;
|
||||||
let focus = self
|
let focus = self
|
||||||
.props
|
.props
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use tuirealm::props::{
|
|||||||
Alignment, BorderSides, BorderType, Borders, Color, InputType, Style, TableBuilder, TextSpan,
|
Alignment, BorderSides, BorderType, Borders, Color, InputType, Style, TableBuilder, TextSpan,
|
||||||
};
|
};
|
||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
use users::{get_group_by_gid, get_user_by_uid};
|
use uzers::{get_group_by_gid, get_user_by_uid};
|
||||||
|
|
||||||
pub use self::chmod::ChmodPopup;
|
pub use self::chmod::ChmodPopup;
|
||||||
pub use self::goto::{GotoPopup, ATTR_FILES};
|
pub use self::goto::{GotoPopup, ATTR_FILES};
|
||||||
@@ -533,7 +533,7 @@ impl FileInfoPopup {
|
|||||||
.add_col(TextSpan::from("Last access time: "))
|
.add_col(TextSpan::from("Last access time: "))
|
||||||
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
|
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
|
||||||
// User
|
// User
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
let username: String = match file.metadata().uid {
|
let username: String = match file.metadata().uid {
|
||||||
Some(uid) => match get_user_by_uid(uid) {
|
Some(uid) => match get_user_by_uid(uid) {
|
||||||
Some(user) => user.name().to_string_lossy().to_string(),
|
Some(user) => user.name().to_string_lossy().to_string(),
|
||||||
@@ -541,10 +541,10 @@ impl FileInfoPopup {
|
|||||||
},
|
},
|
||||||
None => String::from("0"),
|
None => String::from("0"),
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
let username: String = format!("{}", file.metadata().uid.unwrap_or(0));
|
let username: String = format!("{}", file.metadata().uid.unwrap_or(0));
|
||||||
// Group
|
// Group
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
let group: String = match file.metadata().gid {
|
let group: String = match file.metadata().gid {
|
||||||
Some(gid) => match get_group_by_gid(gid) {
|
Some(gid) => match get_group_by_gid(gid) {
|
||||||
Some(group) => group.name().to_string_lossy().to_string(),
|
Some(group) => group.name().to_string_lossy().to_string(),
|
||||||
@@ -552,7 +552,7 @@ impl FileInfoPopup {
|
|||||||
},
|
},
|
||||||
None => String::from("0"),
|
None => String::from("0"),
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
let group: String = format!("{}", file.metadata().gid.unwrap_or(0));
|
let group: String = format!("{}", file.metadata().gid.unwrap_or(0));
|
||||||
texts
|
texts
|
||||||
.add_row()
|
.add_row()
|
||||||
@@ -1036,7 +1036,7 @@ pub struct ProgressBarFull {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProgressBarFull {
|
impl ProgressBarFull {
|
||||||
pub fn new<S: AsRef<str>>(prog: f64, label: S, title: S, color: Color) -> Self {
|
pub fn new<S: Into<String>>(prog: f64, label: S, title: S, color: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
component: ProgressBar::default()
|
component: ProgressBar::default()
|
||||||
.borders(
|
.borders(
|
||||||
@@ -1074,7 +1074,7 @@ pub struct ProgressBarPartial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProgressBarPartial {
|
impl ProgressBarPartial {
|
||||||
pub fn new<S: AsRef<str>>(prog: f64, label: S, title: S, color: Color) -> Self {
|
pub fn new<S: Into<String>>(prog: f64, label: S, title: S, color: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
component: ProgressBar::default()
|
component: ProgressBar::default()
|
||||||
.borders(
|
.borders(
|
||||||
@@ -1551,8 +1551,8 @@ pub struct StatusBarLocal {
|
|||||||
|
|
||||||
impl StatusBarLocal {
|
impl StatusBarLocal {
|
||||||
pub fn new(browser: &Browser, sorting_color: Color, hidden_color: Color) -> Self {
|
pub fn new(browser: &Browser, sorting_color: Color, hidden_color: Color) -> Self {
|
||||||
let file_sorting = file_sorting_label(browser.local().file_sorting);
|
let file_sorting = file_sorting_label(browser.host_bridge().file_sorting);
|
||||||
let hidden_files = hidden_files_label(browser.local().hidden_files_visible());
|
let hidden_files = hidden_files_label(browser.host_bridge().hidden_files_visible());
|
||||||
Self {
|
Self {
|
||||||
component: Span::default().spans(&[
|
component: Span::default().spans(&[
|
||||||
TextSpan::new("File sorting: ").fg(sorting_color),
|
TextSpan::new("File sorting: ").fg(sorting_color),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use tui_realm_stdlib::Checkbox;
|
|||||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||||
use tuirealm::event::{Key, KeyEvent};
|
use tuirealm::event::{Key, KeyEvent};
|
||||||
use tuirealm::props::{Alignment, AttrValue, Attribute, BorderSides, Borders, Color};
|
use tuirealm::props::{Alignment, AttrValue, Attribute, BorderSides, Borders, Color};
|
||||||
use tuirealm::tui::layout::{Constraint, Direction as LayoutDirection, Layout};
|
use tuirealm::ratatui::layout::{Constraint, Direction as LayoutDirection, Layout};
|
||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
|
||||||
|
|
||||||
use super::{Msg, TransferMsg, UiMsg};
|
use super::{Msg, TransferMsg, UiMsg};
|
||||||
@@ -186,7 +186,7 @@ impl MockComponent for ChmodPopup {
|
|||||||
State::One(StateValue::U32(self.get_mode().into()))
|
State::One(StateValue::U32(self.get_mode().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::layout::Rect) {
|
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||||
if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) != AttrValue::Flag(true) {
|
if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) != AttrValue::Flag(true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ impl GotoPopup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MockComponent for GotoPopup {
|
impl MockComponent for GotoPopup {
|
||||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::prelude::Rect) {
|
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::prelude::Rect) {
|
||||||
self.input.view(frame, area);
|
self.input.view(frame, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
fn test_should_suggest_absolute_path() {
|
fn test_should_suggest_absolute_path() {
|
||||||
let mut states = OwnStates {
|
let mut states = OwnStates {
|
||||||
files: vec![
|
files: vec![
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
|
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||||
use tuirealm::props::{
|
use tuirealm::props::{
|
||||||
Alignment, AttrValue, Attribute, Borders, Color, Style, Table, TextModifiers,
|
Alignment, AttrValue, Attribute, Borders, Color, Style, Table, TextModifiers, TextSpan,
|
||||||
};
|
};
|
||||||
use tuirealm::tui::text::{Line, Span};
|
use tuirealm::ratatui::text::{Line, Span};
|
||||||
use tuirealm::tui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
||||||
use tuirealm::{MockComponent, Props, State, StateValue};
|
use tuirealm::{MockComponent, Props, State, StateValue};
|
||||||
|
|
||||||
pub const FILE_LIST_CMD_SELECT_ALL: &str = "A";
|
pub const FILE_LIST_CMD_SELECT_ALL: &str = "A";
|
||||||
pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D";
|
pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D";
|
||||||
|
const PROP_DOT_DOT: &str = "dot_dot";
|
||||||
|
|
||||||
/// OwnStates contains states for this component
|
/// OwnStates contains states for this component
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
@@ -22,8 +23,8 @@ struct OwnStates {
|
|||||||
|
|
||||||
impl OwnStates {
|
impl OwnStates {
|
||||||
/// Initialize list states
|
/// Initialize list states
|
||||||
pub fn init_list_states(&mut self, len: usize) {
|
pub fn init_list_states(&mut self, len: usize, has_dot_dot: bool) {
|
||||||
self.selected = Vec::with_capacity(len);
|
self.selected = Vec::with_capacity(len + if has_dot_dot { 1 } else { 0 });
|
||||||
self.fix_list_index();
|
self.fix_list_index();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,9 +108,9 @@ impl OwnStates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Select all files
|
/// Select all files
|
||||||
pub fn select_all(&mut self) {
|
pub fn select_all(&mut self, has_dot_dot: bool) {
|
||||||
for i in 0..self.list_len() {
|
for i in 0..self.list_len() {
|
||||||
self.select(i);
|
self.select(i + if has_dot_dot { 1 } else { 0 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +173,24 @@ impl FileList {
|
|||||||
self.attr(Attribute::Content, AttrValue::Table(rows));
|
self.attr(Attribute::Content, AttrValue::Table(rows));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If enabled, show `..` entry at the beginning of the list
|
||||||
|
pub fn dot_dot(mut self, show: bool) -> Self {
|
||||||
|
self.attr(Attribute::Custom(PROP_DOT_DOT), AttrValue::Flag(show));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the value of the `dot_dot` property
|
||||||
|
fn has_dot_dot(&self) -> bool {
|
||||||
|
self.props
|
||||||
|
.get(Attribute::Custom(PROP_DOT_DOT))
|
||||||
|
.map(|x| x.unwrap_flag())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockComponent for FileList {
|
impl MockComponent for FileList {
|
||||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::layout::Rect) {
|
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||||
let title = self
|
let title = self
|
||||||
.props
|
.props
|
||||||
.get_or(
|
.get_or(
|
||||||
@@ -193,25 +208,42 @@ impl MockComponent for FileList {
|
|||||||
.unwrap_flag();
|
.unwrap_flag();
|
||||||
let div = tui_realm_stdlib::utils::get_block(borders, Some(title), focus, None);
|
let div = tui_realm_stdlib::utils::get_block(borders, Some(title), focus, None);
|
||||||
// Make list entries
|
// Make list entries
|
||||||
|
let init_table_iter = if self.has_dot_dot() {
|
||||||
|
vec![vec![TextSpan::from("..")]]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
let list_items: Vec<ListItem> = match self
|
let list_items: Vec<ListItem> = match self
|
||||||
.props
|
.props
|
||||||
.get(Attribute::Content)
|
.get(Attribute::Content)
|
||||||
.map(|x| x.unwrap_table())
|
.map(|x| x.unwrap_table())
|
||||||
{
|
{
|
||||||
Some(table) => table
|
Some(table) => init_table_iter
|
||||||
.iter()
|
.iter()
|
||||||
|
.chain(table.iter())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(num, row)| {
|
.map(|(num, row)| {
|
||||||
|
let real_num = num;
|
||||||
|
let num = if self.has_dot_dot() {
|
||||||
|
num.checked_sub(1).unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
num
|
||||||
|
};
|
||||||
|
|
||||||
let columns: Vec<Span> = row
|
let columns: Vec<Span> = row
|
||||||
.iter()
|
.iter()
|
||||||
.map(|col| {
|
.map(|col| {
|
||||||
let (fg, bg, mut modifiers) =
|
let (fg, bg, mut modifiers) =
|
||||||
tui_realm_stdlib::utils::use_or_default_styles(&self.props, col);
|
tui_realm_stdlib::utils::use_or_default_styles(&self.props, col);
|
||||||
if self.states.is_selected(num) {
|
if !(self.has_dot_dot() && real_num == 0)
|
||||||
|
&& self.states.is_selected(num)
|
||||||
|
{
|
||||||
modifiers |= TextModifiers::REVERSED
|
modifiers |= TextModifiers::REVERSED
|
||||||
| TextModifiers::UNDERLINED
|
| TextModifiers::UNDERLINED
|
||||||
| TextModifiers::ITALIC;
|
| TextModifiers::ITALIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
col.content.clone(),
|
col.content.clone(),
|
||||||
Style::default().add_modifier(modifiers).fg(fg).bg(bg),
|
Style::default().add_modifier(modifiers).fg(fg).bg(bg),
|
||||||
@@ -255,6 +287,7 @@ impl MockComponent for FileList {
|
|||||||
Some(line) => line.len(),
|
Some(line) => line.len(),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
},
|
},
|
||||||
|
self.has_dot_dot(),
|
||||||
);
|
);
|
||||||
self.states.fix_list_index();
|
self.states.fix_list_index();
|
||||||
}
|
}
|
||||||
@@ -265,8 +298,16 @@ impl MockComponent for FileList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> State {
|
fn state(&self) -> State {
|
||||||
|
if self.has_dot_dot() && self.states.list_index == 0 {
|
||||||
|
return State::One(StateValue::String("..".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
match self.states.is_selection_empty() {
|
match self.states.is_selection_empty() {
|
||||||
true => State::One(StateValue::Usize(self.states.list_index())),
|
true => State::One(StateValue::Usize(if self.has_dot_dot() {
|
||||||
|
self.states.list_index.checked_sub(1).unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
self.states.list_index
|
||||||
|
})),
|
||||||
false => State::Vec(
|
false => State::Vec(
|
||||||
self.states
|
self.states
|
||||||
.get_selection()
|
.get_selection()
|
||||||
@@ -334,7 +375,7 @@ impl MockComponent for FileList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cmd::Custom(FILE_LIST_CMD_SELECT_ALL) => {
|
Cmd::Custom(FILE_LIST_CMD_SELECT_ALL) => {
|
||||||
self.states.select_all();
|
self.states.select_all(self.has_dot_dot());
|
||||||
CmdResult::None
|
CmdResult::None
|
||||||
}
|
}
|
||||||
Cmd::Custom(FILE_LIST_CMD_DESELECT_ALL) => {
|
Cmd::Custom(FILE_LIST_CMD_DESELECT_ALL) => {
|
||||||
@@ -342,7 +383,15 @@ impl MockComponent for FileList {
|
|||||||
CmdResult::None
|
CmdResult::None
|
||||||
}
|
}
|
||||||
Cmd::Toggle => {
|
Cmd::Toggle => {
|
||||||
self.states.toggle_file(self.states.list_index());
|
if self.has_dot_dot() && self.states.list_index() == 0 {
|
||||||
|
return CmdResult::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.states.toggle_file(if self.has_dot_dot() {
|
||||||
|
self.states.list_index().checked_sub(1).unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
self.states.list_index()
|
||||||
|
});
|
||||||
CmdResult::None
|
CmdResult::None
|
||||||
}
|
}
|
||||||
_ => CmdResult::None,
|
_ => CmdResult::None,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use tui_realm_stdlib::Input;
|
use tui_realm_stdlib::Input;
|
||||||
use tuirealm::command::{Cmd, CmdResult};
|
use tuirealm::command::{Cmd, CmdResult};
|
||||||
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Table};
|
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Table};
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||||
use tuirealm::{MockComponent, State};
|
use tuirealm::{MockComponent, State};
|
||||||
|
|
||||||
use super::file_list::FileList;
|
use super::file_list::FileList;
|
||||||
@@ -88,7 +88,7 @@ impl FileListWithSearch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MockComponent for FileListWithSearch {
|
impl MockComponent for FileListWithSearch {
|
||||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::layout::Rect) {
|
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||||
// split the area in two
|
// split the area in two
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
|
|||||||
@@ -363,7 +363,8 @@ impl ExplorerLocal {
|
|||||||
.foreground(fg)
|
.foreground(fg)
|
||||||
.highlighted_color(hg)
|
.highlighted_color(hg)
|
||||||
.title(title, Alignment::Left)
|
.title(title, Alignment::Left)
|
||||||
.rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()),
|
.rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect())
|
||||||
|
.dot_dot(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,11 +440,23 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
|||||||
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => Some(Msg::Transfer(TransferMsg::EnterDirectory)),
|
}) => {
|
||||||
|
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||||
|
Some(Msg::Transfer(TransferMsg::GoToParentDirectory))
|
||||||
|
} else {
|
||||||
|
Some(Msg::Transfer(TransferMsg::EnterDirectory))
|
||||||
|
}
|
||||||
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char(' '),
|
code: Key::Char(' '),
|
||||||
..
|
..
|
||||||
}) => Some(Msg::Transfer(TransferMsg::TransferFile)),
|
}) => {
|
||||||
|
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||||
|
Some(Msg::None)
|
||||||
|
} else {
|
||||||
|
Some(Msg::Transfer(TransferMsg::TransferFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('a'),
|
code: Key::Char('a'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
@@ -559,7 +572,8 @@ impl ExplorerRemote {
|
|||||||
.foreground(fg)
|
.foreground(fg)
|
||||||
.highlighted_color(hg)
|
.highlighted_color(hg)
|
||||||
.title(title, Alignment::Left)
|
.title(title, Alignment::Left)
|
||||||
.rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()),
|
.rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect())
|
||||||
|
.dot_dot(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -635,11 +649,23 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
|||||||
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => Some(Msg::Transfer(TransferMsg::EnterDirectory)),
|
}) => {
|
||||||
|
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||||
|
Some(Msg::Transfer(TransferMsg::GoToParentDirectory))
|
||||||
|
} else {
|
||||||
|
Some(Msg::Transfer(TransferMsg::EnterDirectory))
|
||||||
|
}
|
||||||
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char(' '),
|
code: Key::Char(' '),
|
||||||
..
|
..
|
||||||
}) => Some(Msg::Transfer(TransferMsg::TransferFile)),
|
}) => {
|
||||||
|
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||||
|
Some(Msg::None)
|
||||||
|
} else {
|
||||||
|
Some(Msg::Transfer(TransferMsg::TransferFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('a'),
|
code: Key::Char('a'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ impl FileTransferActivity {
|
|||||||
Ok(Some(FsChange::Update(update))) => {
|
Ok(Some(FsChange::Update(update))) => {
|
||||||
debug!(
|
debug!(
|
||||||
"fs watcher reported an `Update` from {} to {}",
|
"fs watcher reported an `Update` from {} to {}",
|
||||||
update.local().display(),
|
update.host_bridge().display(),
|
||||||
update.remote().display()
|
update.remote().display()
|
||||||
);
|
);
|
||||||
self.upload_watched_file(update.local(), update.remote());
|
self.upload_watched_file(update.host_bridge(), update.remote());
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log(
|
self.log(
|
||||||
@@ -87,9 +87,9 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_watched_file(&mut self, local: &Path, remote: &Path) {
|
fn upload_watched_file(&mut self, host: &Path, remote: &Path) {
|
||||||
// stat local file
|
// stat host file
|
||||||
let entry = match self.host.stat(local) {
|
let entry = match self.host_bridge.stat(host) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log(
|
self.log(
|
||||||
@@ -105,8 +105,8 @@ impl FileTransferActivity {
|
|||||||
};
|
};
|
||||||
// send
|
// send
|
||||||
trace!(
|
trace!(
|
||||||
"syncing local file {} with remote {}",
|
"syncing host file {} with remote {}",
|
||||||
local.display(),
|
host.display(),
|
||||||
remote.display()
|
remote.display()
|
||||||
);
|
);
|
||||||
let remote_path = remote.parent().unwrap_or_else(|| Path::new("/"));
|
let remote_path = remote.parent().unwrap_or_else(|| Path::new("/"));
|
||||||
@@ -116,7 +116,7 @@ impl FileTransferActivity {
|
|||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!(
|
format!(
|
||||||
"synched watched file {} with {}",
|
"synched watched file {} with {}",
|
||||||
local.display(),
|
host.display(),
|
||||||
remote.display()
|
remote.display()
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ const FUZZY_SEARCH_THRESHOLD: u16 = 50;
|
|||||||
/// File explorer tab
|
/// File explorer tab
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum FileExplorerTab {
|
pub enum FileExplorerTab {
|
||||||
Local,
|
HostBridge,
|
||||||
Remote,
|
Remote,
|
||||||
FindLocal, // Find result tab
|
FindHostBridge, // Find result tab
|
||||||
FindRemote, // Find result tab
|
FindRemote, // Find result tab
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ pub enum FoundExplorerTab {
|
|||||||
|
|
||||||
/// Browser contains the browser options
|
/// Browser contains the browser options
|
||||||
pub struct Browser {
|
pub struct Browser {
|
||||||
local: FileExplorer, // Local File explorer state
|
host_bridge: FileExplorer, // Local File explorer state
|
||||||
remote: FileExplorer, // Remote File explorer state
|
remote: FileExplorer, // Remote File explorer state
|
||||||
found: Option<Found>, // File explorer for find result
|
found: Option<Found>, // File explorer for find result
|
||||||
tab: FileExplorerTab, // Current selected tab
|
tab: FileExplorerTab, // Current selected tab
|
||||||
@@ -42,30 +42,30 @@ impl Browser {
|
|||||||
/// Build a new `Browser` struct
|
/// Build a new `Browser` struct
|
||||||
pub fn new(cli: &ConfigClient) -> Self {
|
pub fn new(cli: &ConfigClient) -> Self {
|
||||||
Self {
|
Self {
|
||||||
local: Self::build_local_explorer(cli),
|
host_bridge: Self::build_local_explorer(cli),
|
||||||
remote: Self::build_remote_explorer(cli),
|
remote: Self::build_remote_explorer(cli),
|
||||||
found: None,
|
found: None,
|
||||||
tab: FileExplorerTab::Local,
|
tab: FileExplorerTab::HostBridge,
|
||||||
sync_browsing: false,
|
sync_browsing: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn explorer(&self) -> &FileExplorer {
|
pub fn explorer(&self) -> &FileExplorer {
|
||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => &self.local,
|
FileExplorerTab::HostBridge => &self.host_bridge,
|
||||||
FileExplorerTab::Remote => &self.remote,
|
FileExplorerTab::Remote => &self.remote,
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
self.found.as_ref().map(|x| &x.explorer).unwrap()
|
self.found.as_ref().map(|x| &x.explorer).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(&self) -> &FileExplorer {
|
pub fn host_bridge(&self) -> &FileExplorer {
|
||||||
&self.local
|
&self.host_bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_mut(&mut self) -> &mut FileExplorer {
|
pub fn host_bridge_mut(&mut self) -> &mut FileExplorer {
|
||||||
&mut self.local
|
&mut self.host_bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote(&self) -> &FileExplorer {
|
pub fn remote(&self) -> &FileExplorer {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Locals
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
// Ext
|
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use tuirealm::props::{
|
use tuirealm::props::{
|
||||||
Alignment, AttrValue, Attribute, Color, PropPayload, PropValue, TableBuilder, TextSpan,
|
Alignment, AttrValue, Attribute, Color, PropPayload, PropValue, TableBuilder, TextSpan,
|
||||||
@@ -11,7 +9,7 @@ use tuirealm::{PollStrategy, Update};
|
|||||||
|
|
||||||
use super::browser::FileExplorerTab;
|
use super::browser::FileExplorerTab;
|
||||||
use super::{ConfigClient, FileTransferActivity, Id, LogLevel, LogRecord, TransferPayload};
|
use super::{ConfigClient, FileTransferActivity, Id, LogLevel, LogRecord, TransferPayload};
|
||||||
use crate::filetransfer::ProtocolParams;
|
use crate::filetransfer::{HostBridgeParams, ProtocolParams};
|
||||||
use crate::system::environment;
|
use crate::system::environment;
|
||||||
use crate::system::notifications::Notification;
|
use crate::system::notifications::Notification;
|
||||||
use crate::utils::fmt::{fmt_millis, fmt_path_elide_ex};
|
use crate::utils::fmt::{fmt_millis, fmt_path_elide_ex};
|
||||||
@@ -95,9 +93,9 @@ impl FileTransferActivity {
|
|||||||
env::set_var("EDITOR", self.config().get_text_editor());
|
env::set_var("EDITOR", self.config().get_text_editor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a path to absolute according to local explorer
|
/// Convert a path to absolute according to host explorer
|
||||||
pub(super) fn local_to_abs_path(&self, path: &Path) -> PathBuf {
|
pub(super) fn host_bridge_to_abs_path(&self, path: &Path) -> PathBuf {
|
||||||
path::absolutize(self.local().wrkdir.as_path(), path)
|
path::absolutize(self.host_bridge().wrkdir.as_path(), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a path to absolute according to remote explorer
|
/// Convert a path to absolute according to remote explorer
|
||||||
@@ -107,8 +105,28 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
/// Get remote hostname
|
/// Get remote hostname
|
||||||
pub(super) fn get_remote_hostname(&self) -> String {
|
pub(super) fn get_remote_hostname(&self) -> String {
|
||||||
let ft_params = self.context().ft_params().unwrap();
|
let ft_params = self.context().remote_params().unwrap();
|
||||||
match &ft_params.params {
|
self.get_hostname(&ft_params.params)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_hostbridge_hostname(&self) -> String {
|
||||||
|
let host_bridge_params = self.context().host_bridge_params().unwrap();
|
||||||
|
match host_bridge_params {
|
||||||
|
HostBridgeParams::Localhost(_) => {
|
||||||
|
let hostname = match hostname::get() {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(_) => return String::from("localhost"),
|
||||||
|
};
|
||||||
|
let hostname: String = hostname.as_os_str().to_string_lossy().to_string();
|
||||||
|
let tokens: Vec<&str> = hostname.split('.').collect();
|
||||||
|
String::from(*tokens.first().unwrap_or(&"localhost"))
|
||||||
|
}
|
||||||
|
HostBridgeParams::Remote(_, params) => self.get_hostname(params),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hostname(&self, params: &ProtocolParams) -> String {
|
||||||
|
match params {
|
||||||
ProtocolParams::Generic(params) => params.address.clone(),
|
ProtocolParams::Generic(params) => params.address.clone(),
|
||||||
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
|
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
|
||||||
ProtocolParams::Kube(params) => {
|
ProtocolParams::Kube(params) => {
|
||||||
@@ -217,9 +235,9 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update local file list
|
/// Update host bridge file list
|
||||||
pub(super) fn update_local_filelist(&mut self) {
|
pub(super) fn update_host_bridge_filelist(&mut self) {
|
||||||
self.reload_local_dir();
|
self.reload_host_bridge_dir();
|
||||||
// Get width
|
// Get width
|
||||||
let width = self
|
let width = self
|
||||||
.context_mut()
|
.context_mut()
|
||||||
@@ -228,29 +246,26 @@ impl FileTransferActivity {
|
|||||||
.size()
|
.size()
|
||||||
.map(|x| (x.width / 2) - 2)
|
.map(|x| (x.width / 2) - 2)
|
||||||
.unwrap_or(0) as usize;
|
.unwrap_or(0) as usize;
|
||||||
let hostname: String = match hostname::get() {
|
let hostname = self.get_hostbridge_hostname();
|
||||||
Ok(h) => {
|
|
||||||
let hostname: String = h.as_os_str().to_string_lossy().to_string();
|
|
||||||
let tokens: Vec<&str> = hostname.split('.').collect();
|
|
||||||
String::from(*tokens.first().unwrap_or(&"localhost"))
|
|
||||||
}
|
|
||||||
Err(_) => String::from("localhost"),
|
|
||||||
};
|
|
||||||
let hostname: String = format!(
|
let hostname: String = format!(
|
||||||
"{}:{} ",
|
"{hostname}:{} ",
|
||||||
hostname,
|
fmt_path_elide_ex(
|
||||||
fmt_path_elide_ex(self.local().wrkdir.as_path(), width, hostname.len() + 3) // 3 because of '/…/'
|
self.host_bridge().wrkdir.as_path(),
|
||||||
|
width,
|
||||||
|
hostname.len() + 3
|
||||||
|
) // 3 because of '/…/'
|
||||||
);
|
);
|
||||||
let files: Vec<Vec<TextSpan>> = self
|
let files: Vec<Vec<TextSpan>> = self
|
||||||
.local()
|
.host_bridge()
|
||||||
.iter_files()
|
.iter_files()
|
||||||
.map(|x| vec![TextSpan::from(self.local().fmt_file(x))])
|
.map(|x| vec![TextSpan::from(self.host_bridge().fmt_file(x))])
|
||||||
.collect();
|
.collect();
|
||||||
// Update content and title
|
// Update content and title
|
||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.attr(
|
.attr(
|
||||||
&Id::ExplorerLocal,
|
&Id::ExplorerHostBridge,
|
||||||
Attribute::Content,
|
Attribute::Content,
|
||||||
AttrValue::Table(files)
|
AttrValue::Table(files)
|
||||||
)
|
)
|
||||||
@@ -258,7 +273,7 @@ impl FileTransferActivity {
|
|||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.attr(
|
.attr(
|
||||||
&Id::ExplorerLocal,
|
&Id::ExplorerHostBridge,
|
||||||
Attribute::Title,
|
Attribute::Title,
|
||||||
AttrValue::Title((hostname, Alignment::Left))
|
AttrValue::Title((hostname, Alignment::Left))
|
||||||
)
|
)
|
||||||
@@ -409,17 +424,19 @@ impl FileTransferActivity {
|
|||||||
self.browser.del_found();
|
self.browser.del_found();
|
||||||
// Restore tab
|
// Restore tab
|
||||||
let new_tab = match self.browser.tab() {
|
let new_tab = match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal => FileExplorerTab::Local,
|
FileExplorerTab::FindHostBridge => FileExplorerTab::HostBridge,
|
||||||
FileExplorerTab::FindRemote => FileExplorerTab::Remote,
|
FileExplorerTab::FindRemote => FileExplorerTab::Remote,
|
||||||
_ => FileExplorerTab::Local,
|
_ => FileExplorerTab::HostBridge,
|
||||||
};
|
};
|
||||||
// Give focus to new tab
|
// Give focus to new tab
|
||||||
match new_tab {
|
match new_tab {
|
||||||
FileExplorerTab::Local => assert!(self.app.active(&Id::ExplorerLocal).is_ok()),
|
FileExplorerTab::HostBridge => {
|
||||||
|
assert!(self.app.active(&Id::ExplorerHostBridge).is_ok())
|
||||||
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
assert!(self.app.active(&Id::ExplorerRemote).is_ok())
|
assert!(self.app.active(&Id::ExplorerRemote).is_ok())
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
assert!(self.app.active(&Id::ExplorerFind).is_ok())
|
assert!(self.app.active(&Id::ExplorerFind).is_ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,15 +462,21 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
pub(super) fn update_browser_file_list(&mut self) {
|
pub(super) fn update_browser_file_list(&mut self) {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => self.update_local_filelist(),
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => {
|
||||||
|
self.update_host_bridge_filelist()
|
||||||
|
}
|
||||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self.update_remote_filelist(),
|
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self.update_remote_filelist(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn update_browser_file_list_swapped(&mut self) {
|
pub(super) fn update_browser_file_list_swapped(&mut self) {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => self.update_remote_filelist(),
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => {
|
||||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self.update_local_filelist(),
|
self.update_remote_filelist()
|
||||||
|
}
|
||||||
|
FileExplorerTab::Remote | FileExplorerTab::FindRemote => {
|
||||||
|
self.update_host_bridge_filelist()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ use session::TransferPayload;
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use tuirealm::{Application, EventListenerCfg, NoUserEvent};
|
use tuirealm::{Application, EventListenerCfg, NoUserEvent};
|
||||||
|
|
||||||
use super::{Activity, Context, ExitReason};
|
use super::{Activity, Context, ExitReason, CROSSTERM_MAX_POLL};
|
||||||
use crate::config::themes::Theme;
|
use crate::config::themes::Theme;
|
||||||
use crate::explorer::{FileExplorer, FileSorting};
|
use crate::explorer::{FileExplorer, FileSorting};
|
||||||
use crate::filetransfer::{Builder, FileTransferParams};
|
use crate::filetransfer::{
|
||||||
use crate::host::Localhost;
|
FileTransferParams, HostBridgeBuilder, HostBridgeParams, RemoteFsBuilder,
|
||||||
|
};
|
||||||
|
use crate::host::HostBridge;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use crate::system::watcher::FsWatcher;
|
use crate::system::watcher::FsWatcher;
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ enum Id {
|
|||||||
ErrorPopup,
|
ErrorPopup,
|
||||||
ExecPopup,
|
ExecPopup,
|
||||||
ExplorerFind,
|
ExplorerFind,
|
||||||
ExplorerLocal,
|
ExplorerHostBridge,
|
||||||
ExplorerRemote,
|
ExplorerRemote,
|
||||||
FatalPopup,
|
FatalPopup,
|
||||||
FileInfoPopup,
|
FileInfoPopup,
|
||||||
@@ -68,7 +70,7 @@ enum Id {
|
|||||||
ReplacingFilesListPopup,
|
ReplacingFilesListPopup,
|
||||||
SaveAsPopup,
|
SaveAsPopup,
|
||||||
SortingPopup,
|
SortingPopup,
|
||||||
StatusBarLocal,
|
StatusBarHostBridge,
|
||||||
StatusBarRemote,
|
StatusBarRemote,
|
||||||
SymlinkPopup,
|
SymlinkPopup,
|
||||||
SyncBrowsingMkdirPopup,
|
SyncBrowsingMkdirPopup,
|
||||||
@@ -213,8 +215,8 @@ pub struct FileTransferActivity {
|
|||||||
app: Application<Id, Msg, NoUserEvent>,
|
app: Application<Id, Msg, NoUserEvent>,
|
||||||
/// Whether should redraw UI
|
/// Whether should redraw UI
|
||||||
redraw: bool,
|
redraw: bool,
|
||||||
/// Localhost bridge
|
/// Host bridge
|
||||||
host: Localhost,
|
host_bridge: Box<dyn HostBridge>,
|
||||||
/// Remote host client
|
/// Remote host client
|
||||||
client: Box<dyn RemoteFs>,
|
client: Box<dyn RemoteFs>,
|
||||||
/// Browser
|
/// Browser
|
||||||
@@ -229,26 +231,40 @@ pub struct FileTransferActivity {
|
|||||||
cache: Option<TempDir>,
|
cache: Option<TempDir>,
|
||||||
/// Fs watcher
|
/// Fs watcher
|
||||||
fswatcher: Option<FsWatcher>,
|
fswatcher: Option<FsWatcher>,
|
||||||
/// connected once
|
/// host bridge connected
|
||||||
connected: bool,
|
host_bridge_connected: bool,
|
||||||
|
/// remote connected once
|
||||||
|
remote_connected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
/// Instantiates a new FileTransferActivity
|
/// Instantiates a new FileTransferActivity
|
||||||
pub fn new(host: Localhost, params: &FileTransferParams, ticks: Duration) -> Self {
|
pub fn new(
|
||||||
|
host_bridge_params: HostBridgeParams,
|
||||||
|
remote_params: &FileTransferParams,
|
||||||
|
ticks: Duration,
|
||||||
|
) -> Self {
|
||||||
// Get config client
|
// Get config client
|
||||||
let config_client: ConfigClient = Self::init_config_client();
|
let config_client: ConfigClient = Self::init_config_client();
|
||||||
|
// init host bridge
|
||||||
|
let host_bridge = HostBridgeBuilder::build(host_bridge_params, &config_client);
|
||||||
|
let host_bridge_connected = host_bridge.is_localhost();
|
||||||
|
let enable_fs_watcher = host_bridge.is_localhost();
|
||||||
Self {
|
Self {
|
||||||
exit_reason: None,
|
exit_reason: None,
|
||||||
context: None,
|
context: None,
|
||||||
app: Application::init(
|
app: Application::init(
|
||||||
EventListenerCfg::default()
|
EventListenerCfg::default()
|
||||||
.poll_timeout(ticks)
|
.poll_timeout(ticks)
|
||||||
.default_input_listener(ticks),
|
.crossterm_input_listener(ticks, CROSSTERM_MAX_POLL),
|
||||||
),
|
),
|
||||||
redraw: true,
|
redraw: true,
|
||||||
host,
|
host_bridge,
|
||||||
client: Builder::build(params.protocol, params.params.clone(), &config_client),
|
client: RemoteFsBuilder::build(
|
||||||
|
remote_params.protocol,
|
||||||
|
remote_params.params.clone(),
|
||||||
|
&config_client,
|
||||||
|
),
|
||||||
browser: Browser::new(&config_client),
|
browser: Browser::new(&config_client),
|
||||||
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
||||||
walkdir: WalkdirStates::default(),
|
walkdir: WalkdirStates::default(),
|
||||||
@@ -257,23 +273,22 @@ impl FileTransferActivity {
|
|||||||
Ok(d) => Some(d),
|
Ok(d) => Some(d),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
},
|
},
|
||||||
fswatcher: match FsWatcher::init(Duration::from_secs(5)) {
|
fswatcher: if enable_fs_watcher {
|
||||||
Ok(w) => Some(w),
|
FsWatcher::init(Duration::from_secs(5)).ok()
|
||||||
Err(e) => {
|
} else {
|
||||||
error!("failed to initialize fs watcher: {}", e);
|
|
||||||
None
|
None
|
||||||
}
|
|
||||||
},
|
},
|
||||||
connected: false,
|
host_bridge_connected,
|
||||||
|
remote_connected: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local(&self) -> &FileExplorer {
|
fn host_bridge(&self) -> &FileExplorer {
|
||||||
self.browser.local()
|
self.browser.host_bridge()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_mut(&mut self) -> &mut FileExplorer {
|
fn host_bridge_mut(&mut self) -> &mut FileExplorer {
|
||||||
self.browser.local_mut()
|
self.browser.host_bridge_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remote(&self) -> &FileExplorer {
|
fn remote(&self) -> &FileExplorer {
|
||||||
@@ -361,7 +376,10 @@ impl Activity for FileTransferActivity {
|
|||||||
error!("Failed to enter raw mode: {}", err);
|
error!("Failed to enter raw mode: {}", err);
|
||||||
}
|
}
|
||||||
// Get files at current pwd
|
// Get files at current pwd
|
||||||
self.reload_local_dir();
|
if self.host_bridge.is_localhost() {
|
||||||
|
debug!("Reloading host bridge directory");
|
||||||
|
self.reload_host_bridge_dir();
|
||||||
|
}
|
||||||
debug!("Read working directory");
|
debug!("Read working directory");
|
||||||
// Configure text editor
|
// Configure text editor
|
||||||
self.setup_text_editor();
|
self.setup_text_editor();
|
||||||
@@ -384,15 +402,34 @@ impl Activity for FileTransferActivity {
|
|||||||
if self.context.is_none() {
|
if self.context.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error)
|
// Check if connected to host bridge (popup must be None, otherwise would try reconnecting in loop in case of error)
|
||||||
if (!self.client.is_connected() || !self.connected) && !self.app.mounted(&Id::FatalPopup) {
|
if (!self.host_bridge.is_connected() || !self.host_bridge_connected)
|
||||||
let ftparams = self.context().ft_params().unwrap();
|
&& !self.app.mounted(&Id::FatalPopup)
|
||||||
|
&& !self.host_bridge.is_localhost()
|
||||||
|
{
|
||||||
|
let host_bridge_params = self.context().host_bridge_params().unwrap();
|
||||||
|
let ft_params = host_bridge_params.unwrap_protocol_params();
|
||||||
|
// print params
|
||||||
|
let msg: String = Self::get_connection_msg(ft_params);
|
||||||
|
// Set init state to connecting popup
|
||||||
|
self.mount_blocking_wait(msg.as_str());
|
||||||
|
// Connect to remote
|
||||||
|
self.connect_to_host_bridge();
|
||||||
|
// Redraw
|
||||||
|
self.redraw = true;
|
||||||
|
}
|
||||||
|
// Check if connected to remote (popup must be None, otherwise would try reconnecting in loop in case of error)
|
||||||
|
if (!self.client.is_connected() || !self.remote_connected)
|
||||||
|
&& !self.app.mounted(&Id::FatalPopup)
|
||||||
|
&& self.host_bridge.is_connected()
|
||||||
|
{
|
||||||
|
let ftparams = self.context().remote_params().unwrap();
|
||||||
// print params
|
// print params
|
||||||
let msg: String = Self::get_connection_msg(&ftparams.params);
|
let msg: String = Self::get_connection_msg(&ftparams.params);
|
||||||
// Set init state to connecting popup
|
// Set init state to connecting popup
|
||||||
self.mount_blocking_wait(msg.as_str());
|
self.mount_blocking_wait(msg.as_str());
|
||||||
// Connect to remote
|
// Connect to remote
|
||||||
self.connect();
|
self.connect_to_remote();
|
||||||
// Redraw
|
// Redraw
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
@@ -432,6 +469,10 @@ impl Activity for FileTransferActivity {
|
|||||||
if self.client.is_connected() {
|
if self.client.is_connected() {
|
||||||
let _ = self.client.disconnect();
|
let _ = self.client.disconnect();
|
||||||
}
|
}
|
||||||
|
// disconnect host bridge
|
||||||
|
if self.host_bridge.is_connected() {
|
||||||
|
let _ = self.host_bridge.disconnect();
|
||||||
|
}
|
||||||
self.context.take()
|
self.context.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
//!
|
//!
|
||||||
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||||
|
|
||||||
// Locals
|
use std::io::{Read, Write};
|
||||||
use std::fs::File as StdFile;
|
|
||||||
use std::io::{Read, Seek, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
// Ext
|
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use remotefs::fs::{File, Metadata, ReadStream, UnixPex, Welcome, WriteStream};
|
use remotefs::fs::{File, Metadata, ReadStream, UnixPex, Welcome, WriteStream};
|
||||||
use remotefs::{RemoteError, RemoteErrorType, RemoteResult};
|
use remotefs::{RemoteError, RemoteErrorType, RemoteResult};
|
||||||
@@ -26,10 +23,8 @@ const BUFSIZE: usize = 65535;
|
|||||||
enum TransferErrorReason {
|
enum TransferErrorReason {
|
||||||
#[error("File transfer aborted")]
|
#[error("File transfer aborted")]
|
||||||
Abrupted,
|
Abrupted,
|
||||||
#[error("Failed to seek file: {0}")]
|
#[error("I/O error on host_bridgehost: {0}")]
|
||||||
CouldNotRewind(std::io::Error),
|
HostIoError(std::io::Error),
|
||||||
#[error("I/O error on localhost: {0}")]
|
|
||||||
LocalIoError(std::io::Error),
|
|
||||||
#[error("Host error: {0}")]
|
#[error("Host error: {0}")]
|
||||||
HostError(HostError),
|
HostError(HostError),
|
||||||
#[error("I/O error on remote: {0}")]
|
#[error("I/O error on remote: {0}")]
|
||||||
@@ -50,15 +45,57 @@ pub(super) enum TransferPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
|
pub(super) fn connect_to_host_bridge(&mut self) {
|
||||||
|
let ft_params = self.context().remote_params().unwrap().clone();
|
||||||
|
let entry_dir: Option<PathBuf> = ft_params.local_path;
|
||||||
|
// Connect to host bridge
|
||||||
|
match self.host_bridge.connect() {
|
||||||
|
Ok(()) => {
|
||||||
|
self.host_bridge_connected = self.host_bridge.is_connected();
|
||||||
|
if !self.host_bridge_connected {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log welcome
|
||||||
|
self.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
format!(
|
||||||
|
"Established connection with '{}'",
|
||||||
|
self.get_hostbridge_hostname()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to change directory to entry directory
|
||||||
|
let mut remote_chdir: Option<PathBuf> = None;
|
||||||
|
if let Some(remote_path) = &entry_dir {
|
||||||
|
remote_chdir = Some(remote_path.clone());
|
||||||
|
}
|
||||||
|
if let Some(remote_path) = remote_chdir {
|
||||||
|
self.local_changedir(remote_path.as_path(), false);
|
||||||
|
}
|
||||||
|
// Set state to explorer
|
||||||
|
self.umount_wait();
|
||||||
|
self.reload_host_bridge_dir();
|
||||||
|
// Update file lists
|
||||||
|
self.update_host_bridge_filelist();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Set popup fatal error
|
||||||
|
self.umount_wait();
|
||||||
|
self.mount_fatal(err.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Connect to remote
|
/// Connect to remote
|
||||||
pub(super) fn connect(&mut self) {
|
pub(super) fn connect_to_remote(&mut self) {
|
||||||
let ft_params = self.context().ft_params().unwrap().clone();
|
let ft_params = self.context().remote_params().unwrap().clone();
|
||||||
let entry_dir: Option<PathBuf> = ft_params.remote_path;
|
let entry_dir: Option<PathBuf> = ft_params.remote_path;
|
||||||
// Connect to remote
|
// Connect to remote
|
||||||
match self.client.connect() {
|
match self.client.connect() {
|
||||||
Ok(Welcome { banner, .. }) => {
|
Ok(Welcome { banner, .. }) => {
|
||||||
self.connected = self.client.is_connected();
|
self.remote_connected = self.client.is_connected();
|
||||||
if !self.connected {
|
if !self.remote_connected {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +131,7 @@ impl FileTransferActivity {
|
|||||||
self.umount_wait();
|
self.umount_wait();
|
||||||
self.reload_remote_dir();
|
self.reload_remote_dir();
|
||||||
// Update file lists
|
// Update file lists
|
||||||
self.update_local_filelist();
|
self.update_host_bridge_filelist();
|
||||||
self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -124,7 +161,7 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
/// Reload remote directory entries and update browser
|
/// Reload remote directory entries and update browser
|
||||||
pub(super) fn reload_remote_dir(&mut self) {
|
pub(super) fn reload_remote_dir(&mut self) {
|
||||||
if !self.connected {
|
if !self.remote_connected {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Get current entries
|
// Get current entries
|
||||||
@@ -149,35 +186,48 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reload local directory entries and update browser
|
/// Reload host_bridge directory entries and update browser
|
||||||
pub(super) fn reload_local_dir(&mut self) {
|
pub(super) fn reload_host_bridge_dir(&mut self) {
|
||||||
self.mount_blocking_wait("Loading local directory...");
|
if !self.host_bridge_connected {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let wrkdir: PathBuf = self.host.pwd();
|
self.mount_blocking_wait("Loading host bridge directory...");
|
||||||
|
|
||||||
let res = self.local_scan(wrkdir.as_path());
|
let wrkdir = match self.host_bridge.pwd() {
|
||||||
|
Ok(wrkdir) => wrkdir,
|
||||||
|
Err(err) => {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Could not scan current host bridge directory: {err}"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = self.host_bridge_scan(wrkdir.as_path());
|
||||||
|
|
||||||
self.umount_wait();
|
self.umount_wait();
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.local_mut().wrkdir = wrkdir;
|
self.host_bridge_mut().wrkdir = wrkdir;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!("Could not scan current local directory: {err}"),
|
format!("Could not scan current host bridge directory: {err}"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scan current local directory
|
/// Scan current host bridge directory
|
||||||
fn local_scan(&mut self, path: &Path) -> Result<(), HostError> {
|
fn host_bridge_scan(&mut self, path: &Path) -> Result<(), HostError> {
|
||||||
match self.host.list_dir(path) {
|
match self.host_bridge.list_dir(path) {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
// Set files and sort (sorting is implicit)
|
// Set files and sort (sorting is implicit)
|
||||||
self.local_mut().set_files(files);
|
self.host_bridge_mut().set_files(files);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -270,7 +320,7 @@ impl FileTransferActivity {
|
|||||||
// Reset states
|
// Reset states
|
||||||
self.transfer.reset();
|
self.transfer.reset();
|
||||||
// Calculate total size of transfer
|
// Calculate total size of transfer
|
||||||
let total_transfer_size: usize = self.get_total_transfer_size_local(entry);
|
let total_transfer_size: usize = self.get_total_transfer_size_host(entry);
|
||||||
self.transfer.full.init(total_transfer_size);
|
self.transfer.full.init(total_transfer_size);
|
||||||
// Mount progress bar
|
// Mount progress bar
|
||||||
self.mount_progress_bar(format!("Uploading {}…", entry.path().display()));
|
self.mount_progress_bar(format!("Uploading {}…", entry.path().display()));
|
||||||
@@ -292,7 +342,7 @@ impl FileTransferActivity {
|
|||||||
// Calculate total size of transfer
|
// Calculate total size of transfer
|
||||||
let total_transfer_size: usize = entries
|
let total_transfer_size: usize = entries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| self.get_total_transfer_size_local(x))
|
.map(|x| self.get_total_transfer_size_host(x))
|
||||||
.sum();
|
.sum();
|
||||||
self.transfer.full.init(total_transfer_size);
|
self.transfer.full.init(total_transfer_size);
|
||||||
// Mount progress bar
|
// Mount progress bar
|
||||||
@@ -358,7 +408,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get files in dir
|
// Get files in dir
|
||||||
match self.host.list_dir(entry.path()) {
|
match self.host_bridge.list_dir(entry.path()) {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
// Iterate over files
|
// Iterate over files
|
||||||
for entry in entries.iter() {
|
for entry in entries.iter() {
|
||||||
@@ -433,17 +483,17 @@ impl FileTransferActivity {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send local file and write it to remote path
|
/// Send host_bridge file and write it to remote path
|
||||||
fn filetransfer_send_one(
|
fn filetransfer_send_one(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: &File,
|
host_bridge: &File,
|
||||||
remote: &Path,
|
remote: &Path,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
// Sync file size and attributes before transfer
|
// Sync file size and attributes before transfer
|
||||||
let metadata = self
|
let metadata = self
|
||||||
.host
|
.host_bridge
|
||||||
.stat(local.path.as_path())
|
.stat(host_bridge.path.as_path())
|
||||||
.map_err(TransferErrorReason::HostError)
|
.map_err(TransferErrorReason::HostError)
|
||||||
.map(|x| x.metadata().clone())?;
|
.map(|x| x.metadata().clone())?;
|
||||||
|
|
||||||
@@ -452,22 +502,30 @@ impl FileTransferActivity {
|
|||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!(
|
format!(
|
||||||
"file {} won't be transferred since hasn't changed",
|
"file {} won't be transferred since hasn't changed",
|
||||||
local.path().display()
|
host_bridge.path().display()
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.transfer.full.update_progress(metadata.size as usize);
|
self.transfer.full.update_progress(metadata.size as usize);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Upload file
|
// Upload file
|
||||||
// Try to open local file
|
// Try to open host_bridge file
|
||||||
match self.host.open_file_read(local.path.as_path()) {
|
match self.host_bridge.open_file(host_bridge.path.as_path()) {
|
||||||
Ok(fhnd) => match self.client.create(remote, &metadata) {
|
Ok(host_bridge_read) => match self.client.create(remote, &metadata) {
|
||||||
Ok(rhnd) => {
|
Ok(rhnd) => self.filetransfer_send_one_with_stream(
|
||||||
self.filetransfer_send_one_with_stream(local, remote, file_name, fhnd, rhnd)
|
host_bridge,
|
||||||
}
|
remote,
|
||||||
Err(err) if err.kind == RemoteErrorType::UnsupportedFeature => {
|
file_name,
|
||||||
self.filetransfer_send_one_wno_stream(local, remote, file_name, fhnd)
|
host_bridge_read,
|
||||||
}
|
rhnd,
|
||||||
|
),
|
||||||
|
Err(err) if err.kind == RemoteErrorType::UnsupportedFeature => self
|
||||||
|
.filetransfer_send_one_wno_stream(
|
||||||
|
host_bridge,
|
||||||
|
remote,
|
||||||
|
file_name,
|
||||||
|
host_bridge_read,
|
||||||
|
),
|
||||||
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
||||||
},
|
},
|
||||||
Err(err) => Err(TransferErrorReason::HostError(err)),
|
Err(err) => Err(TransferErrorReason::HostError(err)),
|
||||||
@@ -477,20 +535,21 @@ impl FileTransferActivity {
|
|||||||
/// Send file to remote using stream
|
/// Send file to remote using stream
|
||||||
fn filetransfer_send_one_with_stream(
|
fn filetransfer_send_one_with_stream(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: &File,
|
host: &File,
|
||||||
remote: &Path,
|
remote: &Path,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
mut reader: StdFile,
|
mut reader: Box<dyn Read + Send>,
|
||||||
mut writer: WriteStream,
|
mut writer: WriteStream,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
// Write file
|
// Write file
|
||||||
let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
|
let file_size = self
|
||||||
|
.host_bridge
|
||||||
|
.stat(host.path())
|
||||||
|
.map_err(TransferErrorReason::HostError)
|
||||||
|
.map(|x| x.metadata().size as usize)?;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
self.transfer.partial.init(file_size);
|
self.transfer.partial.init(file_size);
|
||||||
// rewind
|
|
||||||
if let Err(err) = reader.rewind() {
|
|
||||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
|
||||||
}
|
|
||||||
// Write remote file
|
// Write remote file
|
||||||
let mut total_bytes_written: usize = 0;
|
let mut total_bytes_written: usize = 0;
|
||||||
let mut last_progress_val: f64 = 0.0;
|
let mut last_progress_val: f64 = 0.0;
|
||||||
@@ -535,7 +594,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(TransferErrorReason::LocalIoError(err));
|
return Err(TransferErrorReason::HostIoError(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Increase progress
|
// Increase progress
|
||||||
@@ -561,14 +620,14 @@ impl FileTransferActivity {
|
|||||||
return Err(TransferErrorReason::Abrupted);
|
return Err(TransferErrorReason::Abrupted);
|
||||||
}
|
}
|
||||||
// set stat
|
// set stat
|
||||||
if let Err(err) = self.client.setstat(remote, local.metadata().clone()) {
|
if let Err(err) = self.client.setstat(remote, host.metadata().clone()) {
|
||||||
error!("failed to set stat for {}: {}", remote.display(), err);
|
error!("failed to set stat for {}: {}", remote.display(), err);
|
||||||
}
|
}
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!(
|
format!(
|
||||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||||
local.path.display(),
|
host.path.display(),
|
||||||
remote.display(),
|
remote.display(),
|
||||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||||
@@ -580,30 +639,31 @@ impl FileTransferActivity {
|
|||||||
/// Send an `File` to remote without using streams.
|
/// Send an `File` to remote without using streams.
|
||||||
fn filetransfer_send_one_wno_stream(
|
fn filetransfer_send_one_wno_stream(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: &File,
|
host: &File,
|
||||||
remote: &Path,
|
remote: &Path,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
mut reader: StdFile,
|
reader: Box<dyn Read + Send>,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
// Sync file size and attributes before transfer
|
// Sync file size and attributes before transfer
|
||||||
let metadata = self
|
let metadata = self
|
||||||
.host
|
.host_bridge
|
||||||
.stat(local.path.as_path())
|
.stat(host.path.as_path())
|
||||||
.map_err(TransferErrorReason::HostError)
|
.map_err(TransferErrorReason::HostError)
|
||||||
.map(|x| x.metadata().clone())?;
|
.map(|x| x.metadata().clone())?;
|
||||||
// Write file
|
// Write file
|
||||||
let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
|
let file_size = self
|
||||||
|
.host_bridge
|
||||||
|
.stat(host.path())
|
||||||
|
.map_err(TransferErrorReason::HostError)
|
||||||
|
.map(|x| x.metadata().size as usize)?;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
self.transfer.partial.init(file_size);
|
self.transfer.partial.init(file_size);
|
||||||
// rewind
|
|
||||||
if let Err(err) = reader.rewind() {
|
|
||||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
|
||||||
}
|
|
||||||
// Draw before
|
// Draw before
|
||||||
self.update_progress_bar(format!("Uploading \"{file_name}\"…"));
|
self.update_progress_bar(format!("Uploading \"{file_name}\"…"));
|
||||||
self.view();
|
self.view();
|
||||||
// Send file
|
// Send file
|
||||||
if let Err(err) = self.client.create_file(remote, &metadata, Box::new(reader)) {
|
if let Err(err) = self.client.create_file(remote, &metadata, reader) {
|
||||||
return Err(TransferErrorReason::FileTransferError(err));
|
return Err(TransferErrorReason::FileTransferError(err));
|
||||||
}
|
}
|
||||||
// set stat
|
// set stat
|
||||||
@@ -621,7 +681,7 @@ impl FileTransferActivity {
|
|||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!(
|
format!(
|
||||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||||
local.path.display(),
|
host.path.display(),
|
||||||
remote.display(),
|
remote.display(),
|
||||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||||
@@ -636,15 +696,17 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn filetransfer_recv(
|
pub(super) fn filetransfer_recv(
|
||||||
&mut self,
|
&mut self,
|
||||||
payload: TransferPayload,
|
payload: TransferPayload,
|
||||||
local_path: &Path,
|
host_bridge_path: &Path,
|
||||||
dst_name: Option<String>,
|
dst_name: Option<String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let result = match payload {
|
let result = match payload {
|
||||||
TransferPayload::Any(ref entry) => {
|
TransferPayload::Any(ref entry) => {
|
||||||
self.filetransfer_recv_any(entry, local_path, dst_name)
|
self.filetransfer_recv_any(entry, host_bridge_path, dst_name)
|
||||||
|
}
|
||||||
|
TransferPayload::File(ref file) => self.filetransfer_recv_file(file, host_bridge_path),
|
||||||
|
TransferPayload::Many(ref entries) => {
|
||||||
|
self.filetransfer_recv_many(entries, host_bridge_path)
|
||||||
}
|
}
|
||||||
TransferPayload::File(ref file) => self.filetransfer_recv_file(file, local_path),
|
|
||||||
TransferPayload::Many(ref entries) => self.filetransfer_recv_many(entries, local_path),
|
|
||||||
};
|
};
|
||||||
// Notify
|
// Notify
|
||||||
match &result {
|
match &result {
|
||||||
@@ -664,7 +726,7 @@ impl FileTransferActivity {
|
|||||||
fn filetransfer_recv_any(
|
fn filetransfer_recv_any(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry: &File,
|
entry: &File,
|
||||||
local_path: &Path,
|
host_path: &Path,
|
||||||
dst_name: Option<String>,
|
dst_name: Option<String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// Reset states
|
// Reset states
|
||||||
@@ -675,14 +737,18 @@ impl FileTransferActivity {
|
|||||||
// Mount progress bar
|
// Mount progress bar
|
||||||
self.mount_progress_bar(format!("Downloading {}…", entry.path().display()));
|
self.mount_progress_bar(format!("Downloading {}…", entry.path().display()));
|
||||||
// Receive
|
// Receive
|
||||||
let result = self.filetransfer_recv_recurse(entry, local_path, dst_name);
|
let result = self.filetransfer_recv_recurse(entry, host_path, dst_name);
|
||||||
// Umount progress bar
|
// Umount progress bar
|
||||||
self.umount_progress_bar();
|
self.umount_progress_bar();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive a single file from remote.
|
/// Receive a single file from remote.
|
||||||
fn filetransfer_recv_file(&mut self, entry: &File, local_path: &Path) -> Result<(), String> {
|
fn filetransfer_recv_file(
|
||||||
|
&mut self,
|
||||||
|
entry: &File,
|
||||||
|
host_bridge_path: &Path,
|
||||||
|
) -> Result<(), String> {
|
||||||
// Reset states
|
// Reset states
|
||||||
self.transfer.reset();
|
self.transfer.reset();
|
||||||
// Calculate total transfer size
|
// Calculate total transfer size
|
||||||
@@ -691,7 +757,7 @@ impl FileTransferActivity {
|
|||||||
// Mount progress bar
|
// Mount progress bar
|
||||||
self.mount_progress_bar(format!("Downloading {}…", entry.path.display()));
|
self.mount_progress_bar(format!("Downloading {}…", entry.path.display()));
|
||||||
// Receive
|
// Receive
|
||||||
let result = self.filetransfer_recv_one(local_path, entry, entry.name());
|
let result = self.filetransfer_recv_one(host_bridge_path, entry, entry.name());
|
||||||
// Umount progress bar
|
// Umount progress bar
|
||||||
self.umount_progress_bar();
|
self.umount_progress_bar();
|
||||||
// Return result
|
// Return result
|
||||||
@@ -728,7 +794,7 @@ impl FileTransferActivity {
|
|||||||
fn filetransfer_recv_recurse(
|
fn filetransfer_recv_recurse(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry: &File,
|
entry: &File,
|
||||||
local_path: &Path,
|
host_bridge_path: &Path,
|
||||||
dst_name: Option<String>,
|
dst_name: Option<String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// Write popup
|
// Write popup
|
||||||
@@ -736,32 +802,35 @@ impl FileTransferActivity {
|
|||||||
// Match entry
|
// Match entry
|
||||||
let result: Result<(), String> = if entry.is_dir() {
|
let result: Result<(), String> = if entry.is_dir() {
|
||||||
// Get dir name
|
// Get dir name
|
||||||
let mut local_dir_path: PathBuf = PathBuf::from(local_path);
|
let mut host_bridge_dir_path: PathBuf = PathBuf::from(host_bridge_path);
|
||||||
match dst_name {
|
match dst_name {
|
||||||
Some(name) => local_dir_path.push(name),
|
Some(name) => host_bridge_dir_path.push(name),
|
||||||
None => local_dir_path.push(entry.name()),
|
None => host_bridge_dir_path.push(entry.name()),
|
||||||
}
|
}
|
||||||
// Create directory on local
|
// Create directory on host_bridge
|
||||||
match self.host.mkdir_ex(local_dir_path.as_path(), true) {
|
match self
|
||||||
|
.host_bridge
|
||||||
|
.mkdir_ex(host_bridge_dir_path.as_path(), true)
|
||||||
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Apply file mode to directory
|
// Apply file mode to directory
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.host
|
.host_bridge
|
||||||
.setstat(local_dir_path.as_path(), entry.metadata())
|
.setstat(host_bridge_dir_path.as_path(), entry.metadata())
|
||||||
{
|
{
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
"Could not set stat to directory {:?} to \"{}\": {}",
|
"Could not set stat to directory {:?} to \"{}\": {}",
|
||||||
entry.metadata(),
|
entry.metadata(),
|
||||||
local_dir_path.display(),
|
host_bridge_dir_path.display(),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!("Created directory \"{}\"", local_dir_path.display()),
|
format!("Created directory \"{}\"", host_bridge_dir_path.display()),
|
||||||
);
|
);
|
||||||
// Get files in dir
|
// Get files in dir
|
||||||
match self.client.list_dir(entry.path()) {
|
match self.client.list_dir(entry.path()) {
|
||||||
@@ -773,10 +842,10 @@ impl FileTransferActivity {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Receive entry; name is always None after first call
|
// Receive entry; name is always None after first call
|
||||||
// Local path becomes local_dir_path
|
// Local path becomes host_bridge_dir_path
|
||||||
self.filetransfer_recv_recurse(
|
self.filetransfer_recv_recurse(
|
||||||
entry,
|
entry,
|
||||||
local_dir_path.as_path(),
|
host_bridge_dir_path.as_path(),
|
||||||
None,
|
None,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
@@ -800,7 +869,7 @@ impl FileTransferActivity {
|
|||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
"Failed to create directory \"{}\": {}",
|
"Failed to create directory \"{}\": {}",
|
||||||
local_dir_path.display(),
|
host_bridge_dir_path.display(),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -808,39 +877,39 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get local file
|
// Get host_bridge file
|
||||||
let mut local_file_path: PathBuf = PathBuf::from(local_path);
|
let mut host_bridge_file_path: PathBuf = PathBuf::from(host_bridge_path);
|
||||||
let local_file_name: String = match dst_name {
|
let host_bridge_file_name: String = match dst_name {
|
||||||
Some(n) => n,
|
Some(n) => n,
|
||||||
None => entry.name(),
|
None => entry.name(),
|
||||||
};
|
};
|
||||||
local_file_path.push(local_file_name.as_str());
|
host_bridge_file_path.push(host_bridge_file_name.as_str());
|
||||||
// Download file
|
// Download file
|
||||||
if let Err(err) =
|
if let Err(err) =
|
||||||
self.filetransfer_recv_one(local_file_path.as_path(), entry, file_name)
|
self.filetransfer_recv_one(host_bridge_file_path.as_path(), entry, file_name)
|
||||||
{
|
{
|
||||||
// If transfer was abrupted or there was an IO error on remote, remove file
|
// If transfer was abrupted or there was an IO error on remote, remove file
|
||||||
if matches!(
|
if matches!(
|
||||||
err,
|
err,
|
||||||
TransferErrorReason::Abrupted | TransferErrorReason::LocalIoError(_)
|
TransferErrorReason::Abrupted | TransferErrorReason::HostIoError(_)
|
||||||
) {
|
) {
|
||||||
// Stat file
|
// Stat file
|
||||||
match self.host.stat(local_file_path.as_path()) {
|
match self.host_bridge.stat(host_bridge_file_path.as_path()) {
|
||||||
Err(err) => self.log(
|
Err(err) => self.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
"Could not remove created file {}: {}",
|
"Could not remove created file {}: {}",
|
||||||
local_file_path.display(),
|
host_bridge_file_path.display(),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Ok(entry) => {
|
Ok(entry) => {
|
||||||
if let Err(err) = self.host.remove(&entry) {
|
if let Err(err) = self.host_bridge.remove(&entry) {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
"Could not remove created file {}: {}",
|
"Could not remove created file {}: {}",
|
||||||
local_file_path.display(),
|
host_bridge_file_path.display(),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -853,8 +922,8 @@ impl FileTransferActivity {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Reload directory on local
|
// Reload directory on host_bridge
|
||||||
self.reload_local_dir();
|
self.reload_host_bridge_dir();
|
||||||
// if aborted; show alert
|
// if aborted; show alert
|
||||||
if self.transfer.aborted() {
|
if self.transfer.aborted() {
|
||||||
// Log abort
|
// Log abort
|
||||||
@@ -866,15 +935,15 @@ impl FileTransferActivity {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive file from remote and write it to local path
|
/// Receive file from remote and write it to host_bridge path
|
||||||
fn filetransfer_recv_one(
|
fn filetransfer_recv_one(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: &Path,
|
host_bridge: &Path,
|
||||||
remote: &File,
|
remote: &File,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
// check if files are equal (in case, don't transfer)
|
// check if files are equal (in case, don't transfer)
|
||||||
if !self.has_local_file_changed(local, remote) {
|
if !self.has_host_bridge_file_changed(host_bridge, remote) {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!(
|
format!(
|
||||||
@@ -888,16 +957,20 @@ impl FileTransferActivity {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to open local file
|
// Try to open host_bridge file
|
||||||
match self.host.open_file_write(local) {
|
match self.host_bridge.create_file(host_bridge, &remote.metadata) {
|
||||||
Ok(local_file) => {
|
Ok(writer) => {
|
||||||
// Download file from remote
|
// Download file from remote
|
||||||
match self.client.open(remote.path.as_path()) {
|
match self.client.open(remote.path.as_path()) {
|
||||||
Ok(rhnd) => self.filetransfer_recv_one_with_stream(
|
Ok(rhnd) => self.filetransfer_recv_one_with_stream(
|
||||||
local, remote, file_name, rhnd, local_file,
|
host_bridge,
|
||||||
|
remote,
|
||||||
|
file_name,
|
||||||
|
rhnd,
|
||||||
|
writer,
|
||||||
),
|
),
|
||||||
Err(err) if err.kind == RemoteErrorType::UnsupportedFeature => {
|
Err(err) if err.kind == RemoteErrorType::UnsupportedFeature => {
|
||||||
self.filetransfer_recv_one_wno_stream(local, remote, file_name)
|
self.filetransfer_recv_one_wno_stream(host_bridge, remote, file_name)
|
||||||
}
|
}
|
||||||
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
||||||
}
|
}
|
||||||
@@ -909,16 +982,16 @@ impl FileTransferActivity {
|
|||||||
/// Receive an `File` from remote using stream
|
/// Receive an `File` from remote using stream
|
||||||
fn filetransfer_recv_one_with_stream(
|
fn filetransfer_recv_one_with_stream(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: &Path,
|
host_bridge: &Path,
|
||||||
remote: &File,
|
remote: &File,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
mut reader: ReadStream,
|
mut reader: ReadStream,
|
||||||
mut writer: StdFile,
|
mut writer: Box<dyn Write + Send>,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
let mut total_bytes_written: usize = 0;
|
let mut total_bytes_written: usize = 0;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
self.transfer.partial.init(remote.metadata.size as usize);
|
self.transfer.partial.init(remote.metadata.size as usize);
|
||||||
// Write local file
|
// Write host_bridge file
|
||||||
let mut last_progress_val: f64 = 0.0;
|
let mut last_progress_val: f64 = 0.0;
|
||||||
let mut last_input_event_fetch: Option<Instant> = None;
|
let mut last_input_event_fetch: Option<Instant> = None;
|
||||||
// While the entire file hasn't been completely read,
|
// While the entire file hasn't been completely read,
|
||||||
@@ -951,7 +1024,7 @@ impl FileTransferActivity {
|
|||||||
match writer.write(&buffer[delta..bytes_read]) {
|
match writer.write(&buffer[delta..bytes_read]) {
|
||||||
Ok(bytes) => delta += bytes,
|
Ok(bytes) => delta += bytes,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(TransferErrorReason::LocalIoError(err));
|
return Err(TransferErrorReason::HostIoError(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -984,14 +1057,20 @@ impl FileTransferActivity {
|
|||||||
if self.transfer.aborted() {
|
if self.transfer.aborted() {
|
||||||
return Err(TransferErrorReason::Abrupted);
|
return Err(TransferErrorReason::Abrupted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finalize write
|
||||||
|
self.host_bridge
|
||||||
|
.finalize_write(writer)
|
||||||
|
.map_err(TransferErrorReason::HostError)?;
|
||||||
|
|
||||||
// Apply file mode to file
|
// Apply file mode to file
|
||||||
if let Err(err) = self.host.setstat(local, remote.metadata()) {
|
if let Err(err) = self.host_bridge.setstat(host_bridge, remote.metadata()) {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
"Could not set stat to file {:?} to \"{}\": {}",
|
"Could not set stat to file {:?} to \"{}\": {}",
|
||||||
remote.metadata(),
|
remote.metadata(),
|
||||||
local.display(),
|
host_bridge.display(),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1002,25 +1081,26 @@ impl FileTransferActivity {
|
|||||||
format!(
|
format!(
|
||||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||||
remote.path.display(),
|
remote.path.display(),
|
||||||
local.display(),
|
host_bridge.display(),
|
||||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive an `File` from remote without using stream
|
/// Receive an `File` from remote without using stream
|
||||||
fn filetransfer_recv_one_wno_stream(
|
fn filetransfer_recv_one_wno_stream(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: &Path,
|
host_bridge: &Path,
|
||||||
remote: &File,
|
remote: &File,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
// Open local file
|
// Open host_bridge file
|
||||||
let reader = self
|
let reader = self
|
||||||
.host
|
.host_bridge
|
||||||
.open_file_write(local)
|
.create_file(host_bridge, &remote.metadata)
|
||||||
.map_err(TransferErrorReason::HostError)
|
.map_err(TransferErrorReason::HostError)
|
||||||
.map(Box::new)?;
|
.map(Box::new)?;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
@@ -1043,13 +1123,13 @@ impl FileTransferActivity {
|
|||||||
self.update_progress_bar(format!("Downloading \"{file_name}\""));
|
self.update_progress_bar(format!("Downloading \"{file_name}\""));
|
||||||
self.view();
|
self.view();
|
||||||
// Apply file mode to file
|
// Apply file mode to file
|
||||||
if let Err(err) = self.host.setstat(local, remote.metadata()) {
|
if let Err(err) = self.host_bridge.setstat(host_bridge, remote.metadata()) {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!(
|
format!(
|
||||||
"Could not set stat to file {:?} to \"{}\": {}",
|
"Could not set stat to file {:?} to \"{}\": {}",
|
||||||
remote.metadata(),
|
remote.metadata(),
|
||||||
local.display(),
|
host_bridge.display(),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1060,7 +1140,7 @@ impl FileTransferActivity {
|
|||||||
format!(
|
format!(
|
||||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||||
remote.path.display(),
|
remote.path.display(),
|
||||||
local.display(),
|
host_bridge.display(),
|
||||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||||
),
|
),
|
||||||
@@ -1068,20 +1148,47 @@ impl FileTransferActivity {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change directory for local
|
/// Change directory for host_bridge
|
||||||
pub(super) fn local_changedir(&mut self, path: &Path, push: bool) {
|
pub(super) fn host_bridge_changedir(&mut self, path: &Path, push: bool) {
|
||||||
// Get current directory
|
// Get current directory
|
||||||
let prev_dir: PathBuf = self.local().wrkdir.clone();
|
let prev_dir: PathBuf = self.host_bridge().wrkdir.clone();
|
||||||
// Change directory
|
// Change directory
|
||||||
match self.host.change_wrkdir(path) {
|
match self.host_bridge.change_wrkdir(path) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
format!("Changed directory on local: {}", path.display()),
|
format!("Changed directory on host_bridge: {}", path.display()),
|
||||||
);
|
);
|
||||||
// Push prev_dir to stack
|
// Push prev_dir to stack
|
||||||
if push {
|
if push {
|
||||||
self.local_mut().pushd(prev_dir.as_path())
|
self.host_bridge_mut().pushd(prev_dir.as_path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Report err
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Could not change working directory: {err}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn local_changedir(&mut self, path: &Path, push: bool) {
|
||||||
|
// Get current directory
|
||||||
|
let prev_dir: PathBuf = self.host_bridge().wrkdir.clone();
|
||||||
|
// Change directory
|
||||||
|
match self.host_bridge.change_wrkdir(path) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
format!("Changed directory on host bridge: {}", path.display()),
|
||||||
|
);
|
||||||
|
// Update files
|
||||||
|
self.reload_host_bridge_dir();
|
||||||
|
// Push prev_dir to stack
|
||||||
|
if push {
|
||||||
|
self.host_bridge_mut().pushd(prev_dir.as_path())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1152,14 +1259,14 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
// -- transfer sizes
|
// -- transfer sizes
|
||||||
|
|
||||||
/// Get total size of transfer for localhost
|
/// Get total size of transfer for host_bridgehost
|
||||||
fn get_total_transfer_size_local(&mut self, entry: &File) -> usize {
|
fn get_total_transfer_size_host(&mut self, entry: &File) -> usize {
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
// List dir
|
// List dir
|
||||||
match self.host.list_dir(entry.path()) {
|
match self.host_bridge.list_dir(entry.path()) {
|
||||||
Ok(files) => files
|
Ok(files) => files
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| self.get_total_transfer_size_local(x))
|
.map(|x| self.get_total_transfer_size_host(x))
|
||||||
.sum(),
|
.sum(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log(
|
self.log(
|
||||||
@@ -1206,23 +1313,23 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
// file changed
|
// file changed
|
||||||
|
|
||||||
/// Check whether provided file has changed on local disk, compared to remote file
|
/// Check whether provided file has changed on host_bridge disk, compared to remote file
|
||||||
fn has_local_file_changed(&self, local: &Path, remote: &File) -> bool {
|
fn has_host_bridge_file_changed(&mut self, host_bridge: &Path, remote: &File) -> bool {
|
||||||
// check if files are equal (in case, don't transfer)
|
// check if files are equal (in case, don't transfer)
|
||||||
if let Ok(local_file) = self.host.stat(local) {
|
if let Ok(host_bridge_file) = self.host_bridge.stat(host_bridge) {
|
||||||
local_file.metadata().modified != remote.metadata().modified
|
host_bridge_file.metadata().modified != remote.metadata().modified
|
||||||
|| local_file.metadata().size != remote.metadata().size
|
|| host_bridge_file.metadata().size != remote.metadata().size
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether remote file has changed compared to local file
|
/// Checks whether remote file has changed compared to host_bridge file
|
||||||
fn has_remote_file_changed(&mut self, remote: &Path, local_metadata: &Metadata) -> bool {
|
fn has_remote_file_changed(&mut self, remote: &Path, host_bridge_metadata: &Metadata) -> bool {
|
||||||
// check if files are equal (in case, don't transfer)
|
// check if files are equal (in case, don't transfer)
|
||||||
if let Ok(remote_file) = self.client.stat(remote) {
|
if let Ok(remote_file) = self.client.stat(remote) {
|
||||||
local_metadata.modified != remote_file.metadata().modified
|
host_bridge_metadata.modified != remote_file.metadata().modified
|
||||||
|| local_metadata.size != remote_file.metadata().size
|
|| host_bridge_metadata.size != remote_file.metadata().size
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -1230,11 +1337,11 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
// -- file exist
|
// -- file exist
|
||||||
|
|
||||||
pub(crate) fn local_file_exists(&mut self, p: &Path) -> bool {
|
pub(crate) fn host_bridge_file_exists(&mut self, p: &Path) -> bool {
|
||||||
self.host.file_exists(p)
|
self.host_bridge.exists(p).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remote_file_exists(&mut self, p: &Path) -> bool {
|
pub(crate) fn remote_file_exists(&mut self, p: &Path) -> bool {
|
||||||
self.client.stat(p).is_ok()
|
self.client.exists(p).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,14 +40,12 @@ impl FileTransferActivity {
|
|||||||
self.umount_chmod();
|
self.umount_chmod();
|
||||||
self.mount_blocking_wait("Applying new file mode…");
|
self.mount_blocking_wait("Applying new file mode…");
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
#[cfg(unix)]
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge
|
||||||
FileExplorerTab::Local => self.action_local_chmod(mode),
|
if self.host_bridge.is_localhost() && cfg!(windows) => {}
|
||||||
#[cfg(unix)]
|
FileExplorerTab::HostBridge => self.action_local_chmod(mode),
|
||||||
FileExplorerTab::FindLocal => self.action_find_local_chmod(mode),
|
FileExplorerTab::FindHostBridge => self.action_find_local_chmod(mode),
|
||||||
FileExplorerTab::Remote => self.action_remote_chmod(mode),
|
FileExplorerTab::Remote => self.action_remote_chmod(mode),
|
||||||
FileExplorerTab::FindRemote => self.action_find_remote_chmod(mode),
|
FileExplorerTab::FindRemote => self.action_find_remote_chmod(mode),
|
||||||
#[cfg(windows)]
|
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => {}
|
|
||||||
}
|
}
|
||||||
self.umount_wait();
|
self.umount_wait();
|
||||||
self.update_browser_file_list();
|
self.update_browser_file_list();
|
||||||
@@ -56,7 +54,7 @@ impl FileTransferActivity {
|
|||||||
self.umount_copy();
|
self.umount_copy();
|
||||||
self.mount_blocking_wait("Copying file(s)…");
|
self.mount_blocking_wait("Copying file(s)…");
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_copy(dest),
|
FileExplorerTab::HostBridge => self.action_local_copy(dest),
|
||||||
FileExplorerTab::Remote => self.action_remote_copy(dest),
|
FileExplorerTab::Remote => self.action_remote_copy(dest),
|
||||||
_ => panic!("Found tab doesn't support COPY"),
|
_ => panic!("Found tab doesn't support COPY"),
|
||||||
}
|
}
|
||||||
@@ -68,7 +66,7 @@ impl FileTransferActivity {
|
|||||||
self.umount_symlink();
|
self.umount_symlink();
|
||||||
self.mount_blocking_wait("Creating symlink…");
|
self.mount_blocking_wait("Creating symlink…");
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_symlink(name),
|
FileExplorerTab::HostBridge => self.action_local_symlink(name),
|
||||||
FileExplorerTab::Remote => self.action_remote_symlink(name),
|
FileExplorerTab::Remote => self.action_remote_symlink(name),
|
||||||
_ => panic!("Found tab doesn't support SYMLINK"),
|
_ => panic!("Found tab doesn't support SYMLINK"),
|
||||||
}
|
}
|
||||||
@@ -80,9 +78,9 @@ impl FileTransferActivity {
|
|||||||
self.umount_radio_delete();
|
self.umount_radio_delete();
|
||||||
self.mount_blocking_wait("Removing file(s)…");
|
self.mount_blocking_wait("Removing file(s)…");
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_delete(),
|
FileExplorerTab::HostBridge => self.action_local_delete(),
|
||||||
FileExplorerTab::Remote => self.action_remote_delete(),
|
FileExplorerTab::Remote => self.action_remote_delete(),
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
// Get entry
|
// Get entry
|
||||||
self.action_find_delete();
|
self.action_find_delete();
|
||||||
// Delete entries
|
// Delete entries
|
||||||
@@ -108,20 +106,20 @@ impl FileTransferActivity {
|
|||||||
self.umount_wait();
|
self.umount_wait();
|
||||||
// Reload files
|
// Reload files
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.update_local_filelist(),
|
FileExplorerTab::HostBridge => self.update_host_bridge_filelist(),
|
||||||
FileExplorerTab::Remote => self.update_remote_filelist(),
|
FileExplorerTab::Remote => self.update_remote_filelist(),
|
||||||
FileExplorerTab::FindLocal => self.update_local_filelist(),
|
FileExplorerTab::FindHostBridge => self.update_host_bridge_filelist(),
|
||||||
FileExplorerTab::FindRemote => self.update_remote_filelist(),
|
FileExplorerTab::FindRemote => self.update_remote_filelist(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Local => {
|
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::HostBridge => {
|
||||||
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
||||||
self.action_submit_local(entry);
|
self.action_submit_local(entry);
|
||||||
// Update file list if sync
|
// Update file list if sync
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
self.update_local_filelist();
|
self.update_host_bridge_filelist();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Remote => {
|
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Remote => {
|
||||||
@@ -129,7 +127,7 @@ impl FileTransferActivity {
|
|||||||
self.action_submit_remote(entry);
|
self.action_submit_remote(entry);
|
||||||
// Update file list if sync
|
// Update file list if sync
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.update_local_filelist();
|
self.update_host_bridge_filelist();
|
||||||
}
|
}
|
||||||
self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
@@ -150,7 +148,7 @@ impl FileTransferActivity {
|
|||||||
self.umount_exec();
|
self.umount_exec();
|
||||||
self.mount_blocking_wait(format!("Executing '{cmd}'…").as_str());
|
self.mount_blocking_wait(format!("Executing '{cmd}'…").as_str());
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_exec(cmd),
|
FileExplorerTab::HostBridge => self.action_local_exec(cmd),
|
||||||
FileExplorerTab::Remote => self.action_remote_exec(cmd),
|
FileExplorerTab::Remote => self.action_remote_exec(cmd),
|
||||||
_ => panic!("Found tab doesn't support EXEC"),
|
_ => panic!("Found tab doesn't support EXEC"),
|
||||||
}
|
}
|
||||||
@@ -160,7 +158,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
TransferMsg::GoTo(dir) => {
|
TransferMsg::GoTo(dir) => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_change_local_dir(dir),
|
FileExplorerTab::HostBridge => self.action_change_local_dir(dir),
|
||||||
FileExplorerTab::Remote => self.action_change_remote_dir(dir),
|
FileExplorerTab::Remote => self.action_change_remote_dir(dir),
|
||||||
_ => panic!("Found tab doesn't support GOTO"),
|
_ => panic!("Found tab doesn't support GOTO"),
|
||||||
}
|
}
|
||||||
@@ -175,18 +173,18 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
TransferMsg::GoToParentDirectory => {
|
TransferMsg::GoToParentDirectory => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => {
|
FileExplorerTab::HostBridge => {
|
||||||
self.action_go_to_local_upper_dir();
|
self.action_go_to_local_upper_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_local_filelist()
|
self.update_host_bridge_filelist()
|
||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
self.action_go_to_remote_upper_dir();
|
self.action_go_to_remote_upper_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.update_local_filelist();
|
self.update_host_bridge_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
@@ -196,18 +194,18 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
TransferMsg::GoToPreviousDirectory => {
|
TransferMsg::GoToPreviousDirectory => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => {
|
FileExplorerTab::HostBridge => {
|
||||||
self.action_go_to_previous_local_dir();
|
self.action_go_to_previous_local_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_local_filelist()
|
self.update_host_bridge_filelist()
|
||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
self.action_go_to_previous_remote_dir();
|
self.action_go_to_previous_remote_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
self.update_local_filelist();
|
self.update_host_bridge_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
@@ -220,7 +218,7 @@ impl FileTransferActivity {
|
|||||||
self.mount_walkdir_wait();
|
self.mount_walkdir_wait();
|
||||||
// Find
|
// Find
|
||||||
let res: Result<Vec<File>, WalkdirError> = match self.browser.tab() {
|
let res: Result<Vec<File>, WalkdirError> = match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_walkdir_local(),
|
FileExplorerTab::HostBridge => self.action_walkdir_local(),
|
||||||
FileExplorerTab::Remote => self.action_walkdir_remote(),
|
FileExplorerTab::Remote => self.action_walkdir_remote(),
|
||||||
_ => panic!("Trying to search for files, while already in a find result"),
|
_ => panic!("Trying to search for files, while already in a find result"),
|
||||||
};
|
};
|
||||||
@@ -242,13 +240,13 @@ impl FileTransferActivity {
|
|||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
// Get wrkdir
|
// Get wrkdir
|
||||||
let wrkdir = match self.browser.tab() {
|
let wrkdir = match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.local().wrkdir.clone(),
|
FileExplorerTab::HostBridge => self.host_bridge().wrkdir.clone(),
|
||||||
_ => self.remote().wrkdir.clone(),
|
_ => self.remote().wrkdir.clone(),
|
||||||
};
|
};
|
||||||
// Create explorer and load files
|
// Create explorer and load files
|
||||||
self.browser.set_found(
|
self.browser.set_found(
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => FoundExplorerTab::Local,
|
FileExplorerTab::HostBridge => FoundExplorerTab::Local,
|
||||||
_ => FoundExplorerTab::Remote,
|
_ => FoundExplorerTab::Remote,
|
||||||
},
|
},
|
||||||
files,
|
files,
|
||||||
@@ -261,16 +259,16 @@ impl FileTransferActivity {
|
|||||||
self.update_find_list();
|
self.update_find_list();
|
||||||
// Initialize tab
|
// Initialize tab
|
||||||
self.browser.change_tab(match self.browser.tab() {
|
self.browser.change_tab(match self.browser.tab() {
|
||||||
FileExplorerTab::Local => FileExplorerTab::FindLocal,
|
FileExplorerTab::HostBridge => FileExplorerTab::FindHostBridge,
|
||||||
FileExplorerTab::Remote => FileExplorerTab::FindRemote,
|
FileExplorerTab::Remote => FileExplorerTab::FindRemote,
|
||||||
_ => FileExplorerTab::FindLocal,
|
_ => FileExplorerTab::FindHostBridge,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransferMsg::Mkdir(dir) => {
|
TransferMsg::Mkdir(dir) => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_mkdir(dir),
|
FileExplorerTab::HostBridge => self.action_local_mkdir(dir),
|
||||||
FileExplorerTab::Remote => self.action_remote_mkdir(dir),
|
FileExplorerTab::Remote => self.action_remote_mkdir(dir),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -280,7 +278,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
TransferMsg::NewFile(name) => {
|
TransferMsg::NewFile(name) => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_newfile(name),
|
FileExplorerTab::HostBridge => self.action_local_newfile(name),
|
||||||
FileExplorerTab::Remote => self.action_remote_newfile(name),
|
FileExplorerTab::Remote => self.action_remote_newfile(name),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -289,15 +287,17 @@ impl FileTransferActivity {
|
|||||||
self.update_browser_file_list()
|
self.update_browser_file_list()
|
||||||
}
|
}
|
||||||
TransferMsg::OpenFile => match self.browser.tab() {
|
TransferMsg::OpenFile => match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_open_local(),
|
FileExplorerTab::HostBridge => self.action_open_local(),
|
||||||
FileExplorerTab::Remote => self.action_open_remote(),
|
FileExplorerTab::Remote => self.action_open_remote(),
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => self.action_find_open(),
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
|
self.action_find_open()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
TransferMsg::OpenFileWith(prog) => {
|
TransferMsg::OpenFileWith(prog) => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_open_with(&prog),
|
FileExplorerTab::HostBridge => self.action_local_open_with(&prog),
|
||||||
FileExplorerTab::Remote => self.action_remote_open_with(&prog),
|
FileExplorerTab::Remote => self.action_remote_open_with(&prog),
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
self.action_find_open_with(&prog)
|
self.action_find_open_with(&prog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,7 +305,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
TransferMsg::OpenTextFile => {
|
TransferMsg::OpenTextFile => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_edit_local_file(),
|
FileExplorerTab::HostBridge => self.action_edit_local_file(),
|
||||||
FileExplorerTab::Remote => self.action_edit_remote_file(),
|
FileExplorerTab::Remote => self.action_edit_remote_file(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,7 @@ impl FileTransferActivity {
|
|||||||
self.umount_rename();
|
self.umount_rename();
|
||||||
self.mount_blocking_wait("Moving file(s)…");
|
self.mount_blocking_wait("Moving file(s)…");
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_rename(dest),
|
FileExplorerTab::HostBridge => self.action_local_rename(dest),
|
||||||
FileExplorerTab::Remote => self.action_remote_rename(dest),
|
FileExplorerTab::Remote => self.action_remote_rename(dest),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -336,9 +336,9 @@ impl FileTransferActivity {
|
|||||||
TransferMsg::SaveFileAs(dest) => {
|
TransferMsg::SaveFileAs(dest) => {
|
||||||
self.umount_saveas();
|
self.umount_saveas();
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_saveas(dest),
|
FileExplorerTab::HostBridge => self.action_local_saveas(dest),
|
||||||
FileExplorerTab::Remote => self.action_remote_saveas(dest),
|
FileExplorerTab::Remote => self.action_remote_saveas(dest),
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
// Get entry
|
// Get entry
|
||||||
self.action_find_transfer(TransferOpts::default().save_as(Some(dest)));
|
self.action_find_transfer(TransferOpts::default().save_as(Some(dest)));
|
||||||
}
|
}
|
||||||
@@ -352,9 +352,9 @@ impl FileTransferActivity {
|
|||||||
TransferMsg::ToggleWatchFor(index) => self.action_toggle_watch_for(index),
|
TransferMsg::ToggleWatchFor(index) => self.action_toggle_watch_for(index),
|
||||||
TransferMsg::TransferFile => {
|
TransferMsg::TransferFile => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.action_local_send(),
|
FileExplorerTab::HostBridge => self.action_local_send(),
|
||||||
FileExplorerTab::Remote => self.action_remote_recv(),
|
FileExplorerTab::Remote => self.action_remote_recv(),
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
self.action_find_transfer(TransferOpts::default())
|
self.action_find_transfer(TransferOpts::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,8 +371,8 @@ impl FileTransferActivity {
|
|||||||
UiMsg::CloseChmodPopup => self.umount_chmod(),
|
UiMsg::CloseChmodPopup => self.umount_chmod(),
|
||||||
UiMsg::ChangeFileSorting(sorting) => {
|
UiMsg::ChangeFileSorting(sorting) => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => {
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => {
|
||||||
self.local_mut().sort_by(sorting);
|
self.host_bridge_mut().sort_by(sorting);
|
||||||
self.refresh_local_status_bar();
|
self.refresh_local_status_bar();
|
||||||
}
|
}
|
||||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => {
|
FileExplorerTab::Remote | FileExplorerTab::FindRemote => {
|
||||||
@@ -384,22 +384,28 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
UiMsg::ChangeTransferWindow => {
|
UiMsg::ChangeTransferWindow => {
|
||||||
let new_tab = match self.browser.tab() {
|
let new_tab = match self.browser.tab() {
|
||||||
FileExplorerTab::Local if self.browser.found().is_some() => {
|
FileExplorerTab::HostBridge if self.browser.found().is_some() => {
|
||||||
FileExplorerTab::FindRemote
|
FileExplorerTab::FindRemote
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => FileExplorerTab::Remote,
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
FileExplorerTab::Remote if self.browser.found().is_some() => {
|
FileExplorerTab::Remote
|
||||||
FileExplorerTab::FindLocal
|
}
|
||||||
|
FileExplorerTab::Remote if self.browser.found().is_some() => {
|
||||||
|
FileExplorerTab::FindHostBridge
|
||||||
|
}
|
||||||
|
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||||
|
FileExplorerTab::HostBridge
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => FileExplorerTab::Local,
|
|
||||||
};
|
};
|
||||||
// Set focus
|
// Set focus
|
||||||
match new_tab {
|
match new_tab {
|
||||||
FileExplorerTab::Local => assert!(self.app.active(&Id::ExplorerLocal).is_ok()),
|
FileExplorerTab::HostBridge => {
|
||||||
|
assert!(self.app.active(&Id::ExplorerHostBridge).is_ok())
|
||||||
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
assert!(self.app.active(&Id::ExplorerRemote).is_ok())
|
assert!(self.app.active(&Id::ExplorerRemote).is_ok())
|
||||||
}
|
}
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||||
assert!(self.app.active(&Id::ExplorerFind).is_ok())
|
assert!(self.app.active(&Id::ExplorerFind).is_ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,13 +447,13 @@ impl FileTransferActivity {
|
|||||||
let files = self.filter(&filter);
|
let files = self.filter(&filter);
|
||||||
// Get wrkdir
|
// Get wrkdir
|
||||||
let wrkdir = match self.browser.tab() {
|
let wrkdir = match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.local().wrkdir.clone(),
|
FileExplorerTab::HostBridge => self.host_bridge().wrkdir.clone(),
|
||||||
_ => self.remote().wrkdir.clone(),
|
_ => self.remote().wrkdir.clone(),
|
||||||
};
|
};
|
||||||
// Create explorer and load files
|
// Create explorer and load files
|
||||||
self.browser.set_found(
|
self.browser.set_found(
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local => FoundExplorerTab::Local,
|
FileExplorerTab::HostBridge => FoundExplorerTab::Local,
|
||||||
_ => FoundExplorerTab::Remote,
|
_ => FoundExplorerTab::Remote,
|
||||||
},
|
},
|
||||||
files,
|
files,
|
||||||
@@ -458,9 +464,9 @@ impl FileTransferActivity {
|
|||||||
self.update_find_list();
|
self.update_find_list();
|
||||||
// Initialize tab
|
// Initialize tab
|
||||||
self.browser.change_tab(match self.browser.tab() {
|
self.browser.change_tab(match self.browser.tab() {
|
||||||
FileExplorerTab::Local => FileExplorerTab::FindLocal,
|
FileExplorerTab::HostBridge => FileExplorerTab::FindHostBridge,
|
||||||
FileExplorerTab::Remote => FileExplorerTab::FindRemote,
|
FileExplorerTab::Remote => FileExplorerTab::FindRemote,
|
||||||
_ => FileExplorerTab::FindLocal,
|
_ => FileExplorerTab::FindHostBridge,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
UiMsg::FuzzySearch(needle) => {
|
UiMsg::FuzzySearch(needle) => {
|
||||||
@@ -471,7 +477,7 @@ impl FileTransferActivity {
|
|||||||
assert!(self.app.active(&Id::Log).is_ok());
|
assert!(self.app.active(&Id::Log).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::LogBackTabbed => {
|
UiMsg::LogBackTabbed => {
|
||||||
assert!(self.app.active(&Id::ExplorerLocal).is_ok());
|
assert!(self.app.active(&Id::ExplorerHostBridge).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::Quit => {
|
UiMsg::Quit => {
|
||||||
self.disconnect_and_quit();
|
self.disconnect_and_quit();
|
||||||
@@ -488,14 +494,16 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
UiMsg::ShowChmodPopup => {
|
UiMsg::ShowChmodPopup => {
|
||||||
let selected_file = match self.browser.tab() {
|
let selected_file = match self.browser.tab() {
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
FileExplorerTab::Local => self.get_local_selected_entries(),
|
FileExplorerTab::HostBridge => self.get_local_selected_entries(),
|
||||||
#[cfg(unix)]
|
#[cfg(posix)]
|
||||||
FileExplorerTab::FindLocal => self.get_found_selected_entries(),
|
FileExplorerTab::FindHostBridge => self.get_found_selected_entries(),
|
||||||
FileExplorerTab::Remote => self.get_remote_selected_entries(),
|
FileExplorerTab::Remote => self.get_remote_selected_entries(),
|
||||||
FileExplorerTab::FindRemote => self.get_found_selected_entries(),
|
FileExplorerTab::FindRemote => self.get_found_selected_entries(),
|
||||||
#[cfg(windows)]
|
#[cfg(win)]
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => SelectedFile::None,
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => {
|
||||||
|
SelectedFile::None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if let Some(mode) = selected_file.unix_pex() {
|
if let Some(mode) = selected_file.unix_pex() {
|
||||||
self.mount_chmod(
|
self.mount_chmod(
|
||||||
@@ -516,7 +524,7 @@ impl FileTransferActivity {
|
|||||||
UiMsg::ShowDeletePopup => self.mount_radio_delete(),
|
UiMsg::ShowDeletePopup => self.mount_radio_delete(),
|
||||||
UiMsg::ShowDisconnectPopup => self.mount_disconnect(),
|
UiMsg::ShowDisconnectPopup => self.mount_disconnect(),
|
||||||
UiMsg::ShowExecPopup => self.mount_exec(),
|
UiMsg::ShowExecPopup => self.mount_exec(),
|
||||||
UiMsg::ShowFileInfoPopup if self.browser.tab() == FileExplorerTab::Local => {
|
UiMsg::ShowFileInfoPopup if self.browser.tab() == FileExplorerTab::HostBridge => {
|
||||||
if let SelectedFile::One(file) = self.get_local_selected_entries() {
|
if let SelectedFile::One(file) = self.get_local_selected_entries() {
|
||||||
self.mount_file_info(&file);
|
self.mount_file_info(&file);
|
||||||
}
|
}
|
||||||
@@ -543,9 +551,9 @@ impl FileTransferActivity {
|
|||||||
UiMsg::ShowSaveAsPopup => self.mount_saveas(),
|
UiMsg::ShowSaveAsPopup => self.mount_saveas(),
|
||||||
UiMsg::ShowSymlinkPopup => {
|
UiMsg::ShowSymlinkPopup => {
|
||||||
if match self.browser.tab() {
|
if match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.is_local_selected_one(),
|
FileExplorerTab::HostBridge => self.is_local_selected_one(),
|
||||||
FileExplorerTab::Remote => self.is_remote_selected_one(),
|
FileExplorerTab::Remote => self.is_remote_selected_one(),
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => false,
|
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => false,
|
||||||
} {
|
} {
|
||||||
// Only if only one entry is selected
|
// Only if only one entry is selected
|
||||||
self.mount_symlink();
|
self.mount_symlink();
|
||||||
@@ -558,8 +566,8 @@ impl FileTransferActivity {
|
|||||||
UiMsg::ShowWatchedPathsList => self.action_show_watched_paths_list(),
|
UiMsg::ShowWatchedPathsList => self.action_show_watched_paths_list(),
|
||||||
UiMsg::ShowWatcherPopup => self.action_show_radio_watch(),
|
UiMsg::ShowWatcherPopup => self.action_show_radio_watch(),
|
||||||
UiMsg::ToggleHiddenFiles => match self.browser.tab() {
|
UiMsg::ToggleHiddenFiles => match self.browser.tab() {
|
||||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => {
|
||||||
self.browser.local_mut().toggle_hidden_files();
|
self.browser.host_bridge_mut().toggle_hidden_files();
|
||||||
self.refresh_local_status_bar();
|
self.refresh_local_status_bar();
|
||||||
self.update_browser_file_list();
|
self.update_browser_file_list();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
use remotefs::fs::{File, UnixPex};
|
use remotefs::fs::{File, UnixPex};
|
||||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||||
use tuirealm::props::{PropPayload, PropValue, TextSpan};
|
use tuirealm::props::{PropPayload, PropValue, TextSpan};
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||||
use tuirealm::tui::widgets::Clear;
|
use tuirealm::ratatui::widgets::Clear;
|
||||||
use tuirealm::{AttrValue, Attribute, Sub, SubClause, SubEventClause};
|
use tuirealm::{AttrValue, Attribute, Sub, SubClause, SubEventClause};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ impl FileTransferActivity {
|
|||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.mount(
|
.mount(
|
||||||
Id::ExplorerLocal,
|
Id::ExplorerHostBridge,
|
||||||
Box::new(components::ExplorerLocal::new(
|
Box::new(components::ExplorerLocal::new(
|
||||||
"",
|
"",
|
||||||
&[],
|
&[],
|
||||||
@@ -81,12 +81,12 @@ impl FileTransferActivity {
|
|||||||
self.refresh_local_status_bar();
|
self.refresh_local_status_bar();
|
||||||
self.refresh_remote_status_bar();
|
self.refresh_remote_status_bar();
|
||||||
// Update components
|
// Update components
|
||||||
self.update_local_filelist();
|
self.update_host_bridge_filelist();
|
||||||
// self.update_remote_filelist();
|
// self.update_remote_filelist();
|
||||||
// Global listener
|
// Global listener
|
||||||
self.mount_global_listener();
|
self.mount_global_listener();
|
||||||
// Give focus to local explorer
|
// Give focus to local explorer
|
||||||
assert!(self.app.active(&Id::ExplorerLocal).is_ok());
|
assert!(self.app.active(&Id::ExplorerHostBridge).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- view
|
// -- view
|
||||||
@@ -106,7 +106,7 @@ impl FileTransferActivity {
|
|||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.split(f.size());
|
.split(f.area());
|
||||||
// main chunks
|
// main chunks
|
||||||
let main_chunks = Layout::default()
|
let main_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
@@ -141,7 +141,7 @@ impl FileTransferActivity {
|
|||||||
if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Local)) {
|
if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Local)) {
|
||||||
self.app.view(&Id::ExplorerFind, f, tabs_chunks[0]);
|
self.app.view(&Id::ExplorerFind, f, tabs_chunks[0]);
|
||||||
} else {
|
} else {
|
||||||
self.app.view(&Id::ExplorerLocal, f, tabs_chunks[0]);
|
self.app.view(&Id::ExplorerHostBridge, f, tabs_chunks[0]);
|
||||||
}
|
}
|
||||||
// @! Remote explorer (Find or default)
|
// @! Remote explorer (Find or default)
|
||||||
if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Remote)) {
|
if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Remote)) {
|
||||||
@@ -152,80 +152,81 @@ impl FileTransferActivity {
|
|||||||
// Draw log box
|
// Draw log box
|
||||||
self.app.view(&Id::Log, f, bottom_chunks[1]);
|
self.app.view(&Id::Log, f, bottom_chunks[1]);
|
||||||
// Draw status bar
|
// Draw status bar
|
||||||
self.app.view(&Id::StatusBarLocal, f, status_bar_chunks[0]);
|
self.app
|
||||||
|
.view(&Id::StatusBarHostBridge, f, status_bar_chunks[0]);
|
||||||
self.app.view(&Id::StatusBarRemote, f, status_bar_chunks[1]);
|
self.app.view(&Id::StatusBarRemote, f, status_bar_chunks[1]);
|
||||||
// @! Draw popups
|
// @! Draw popups
|
||||||
if self.app.mounted(&Id::FatalPopup) {
|
if self.app.mounted(&Id::FatalPopup) {
|
||||||
let popup = Popup(
|
let popup = Popup(
|
||||||
Size::Percentage(50),
|
Size::Percentage(50),
|
||||||
self.calc_popup_height(Id::FatalPopup, f.size().width, f.size().height),
|
self.calc_popup_height(Id::FatalPopup, f.area().width, f.area().height),
|
||||||
)
|
)
|
||||||
.draw_in(f.size());
|
.draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::FatalPopup, f, popup);
|
self.app.view(&Id::FatalPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::CopyPopup) {
|
} else if self.app.mounted(&Id::CopyPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::CopyPopup, f, popup);
|
self.app.view(&Id::CopyPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::ChmodPopup) {
|
} else if self.app.mounted(&Id::ChmodPopup) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Unit(12)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Unit(12)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::ChmodPopup, f, popup);
|
self.app.view(&Id::ChmodPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::FilterPopup) {
|
} else if self.app.mounted(&Id::FilterPopup) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::FilterPopup, f, popup);
|
self.app.view(&Id::FilterPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::GotoPopup) {
|
} else if self.app.mounted(&Id::GotoPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::GotoPopup, f, popup);
|
self.app.view(&Id::GotoPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::MkdirPopup) {
|
} else if self.app.mounted(&Id::MkdirPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::MkdirPopup, f, popup);
|
self.app.view(&Id::MkdirPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::NewfilePopup) {
|
} else if self.app.mounted(&Id::NewfilePopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::NewfilePopup, f, popup);
|
self.app.view(&Id::NewfilePopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::OpenWithPopup) {
|
} else if self.app.mounted(&Id::OpenWithPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::OpenWithPopup, f, popup);
|
self.app.view(&Id::OpenWithPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::RenamePopup) {
|
} else if self.app.mounted(&Id::RenamePopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::RenamePopup, f, popup);
|
self.app.view(&Id::RenamePopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::SaveAsPopup) {
|
} else if self.app.mounted(&Id::SaveAsPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::SaveAsPopup, f, popup);
|
self.app.view(&Id::SaveAsPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::SymlinkPopup) {
|
} else if self.app.mounted(&Id::SymlinkPopup) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::SymlinkPopup, f, popup);
|
self.app.view(&Id::SymlinkPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::ExecPopup) {
|
} else if self.app.mounted(&Id::ExecPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::ExecPopup, f, popup);
|
self.app.view(&Id::ExecPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::FileInfoPopup) {
|
} else if self.app.mounted(&Id::FileInfoPopup) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::FileInfoPopup, f, popup);
|
self.app.view(&Id::FileInfoPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::ProgressBarPartial) {
|
} else if self.app.mounted(&Id::ProgressBarPartial) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Percentage(20)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Percentage(20)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
let popup_chunks = Layout::default()
|
let popup_chunks = Layout::default()
|
||||||
@@ -241,14 +242,14 @@ impl FileTransferActivity {
|
|||||||
self.app.view(&Id::ProgressBarFull, f, popup_chunks[0]);
|
self.app.view(&Id::ProgressBarFull, f, popup_chunks[0]);
|
||||||
self.app.view(&Id::ProgressBarPartial, f, popup_chunks[1]);
|
self.app.view(&Id::ProgressBarPartial, f, popup_chunks[1]);
|
||||||
} else if self.app.mounted(&Id::DeletePopup) {
|
} else if self.app.mounted(&Id::DeletePopup) {
|
||||||
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::DeletePopup, f, popup);
|
self.app.view(&Id::DeletePopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::ReplacePopup) {
|
} else if self.app.mounted(&Id::ReplacePopup) {
|
||||||
// NOTE: handle extended / normal modes
|
// NOTE: handle extended / normal modes
|
||||||
if self.is_radio_replace_extended() {
|
if self.is_radio_replace_extended() {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
let popup_chunks = Layout::default()
|
let popup_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
@@ -264,42 +265,42 @@ impl FileTransferActivity {
|
|||||||
.view(&Id::ReplacingFilesListPopup, f, popup_chunks[0]);
|
.view(&Id::ReplacingFilesListPopup, f, popup_chunks[0]);
|
||||||
self.app.view(&Id::ReplacePopup, f, popup_chunks[1]);
|
self.app.view(&Id::ReplacePopup, f, popup_chunks[1]);
|
||||||
} else {
|
} else {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::ReplacePopup, f, popup);
|
self.app.view(&Id::ReplacePopup, f, popup);
|
||||||
}
|
}
|
||||||
} else if self.app.mounted(&Id::DisconnectPopup) {
|
} else if self.app.mounted(&Id::DisconnectPopup) {
|
||||||
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::DisconnectPopup, f, popup);
|
self.app.view(&Id::DisconnectPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::QuitPopup) {
|
} else if self.app.mounted(&Id::QuitPopup) {
|
||||||
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::QuitPopup, f, popup);
|
self.app.view(&Id::QuitPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::WatchedPathsList) {
|
} else if self.app.mounted(&Id::WatchedPathsList) {
|
||||||
let popup = Popup(Size::Percentage(60), Size::Percentage(50)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(60), Size::Percentage(50)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::WatchedPathsList, f, popup);
|
self.app.view(&Id::WatchedPathsList, f, popup);
|
||||||
} else if self.app.mounted(&Id::WatcherPopup) {
|
} else if self.app.mounted(&Id::WatcherPopup) {
|
||||||
let popup = Popup(Size::Percentage(60), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(60), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::WatcherPopup, f, popup);
|
self.app.view(&Id::WatcherPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::SortingPopup) {
|
} else if self.app.mounted(&Id::SortingPopup) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::SortingPopup, f, popup);
|
self.app.view(&Id::SortingPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::ErrorPopup) {
|
} else if self.app.mounted(&Id::ErrorPopup) {
|
||||||
let popup = Popup(
|
let popup = Popup(
|
||||||
Size::Percentage(50),
|
Size::Percentage(50),
|
||||||
self.calc_popup_height(Id::ErrorPopup, f.size().width, f.size().height),
|
self.calc_popup_height(Id::ErrorPopup, f.area().width, f.area().height),
|
||||||
)
|
)
|
||||||
.draw_in(f.size());
|
.draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::ErrorPopup, f, popup);
|
self.app.view(&Id::ErrorPopup, f, popup);
|
||||||
@@ -312,17 +313,17 @@ impl FileTransferActivity {
|
|||||||
.unwrap_or(1) as u16;
|
.unwrap_or(1) as u16;
|
||||||
|
|
||||||
let popup =
|
let popup =
|
||||||
Popup(Size::Percentage(50), Size::Unit(2 + wait_popup_lines)).draw_in(f.size());
|
Popup(Size::Percentage(50), Size::Unit(2 + wait_popup_lines)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::WaitPopup, f, popup);
|
self.app.view(&Id::WaitPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::SyncBrowsingMkdirPopup) {
|
} else if self.app.mounted(&Id::SyncBrowsingMkdirPopup) {
|
||||||
let popup = Popup(Size::Percentage(60), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(60), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::SyncBrowsingMkdirPopup, f, popup);
|
self.app.view(&Id::SyncBrowsingMkdirPopup, f, popup);
|
||||||
} else if self.app.mounted(&Id::KeybindingsPopup) {
|
} else if self.app.mounted(&Id::KeybindingsPopup) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Percentage(80)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Percentage(80)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::KeybindingsPopup, f, popup);
|
self.app.view(&Id::KeybindingsPopup, f, popup);
|
||||||
@@ -555,7 +556,7 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn mount_find(&mut self, msg: impl ToString, fuzzy_search: bool) {
|
pub(super) fn mount_find(&mut self, msg: impl ToString, fuzzy_search: bool) {
|
||||||
// Get color
|
// Get color
|
||||||
let (bg, fg, hg) = match self.browser.tab() {
|
let (bg, fg, hg) = match self.browser.tab() {
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => (
|
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => (
|
||||||
self.theme().transfer_local_explorer_background,
|
self.theme().transfer_local_explorer_background,
|
||||||
self.theme().transfer_local_explorer_foreground,
|
self.theme().transfer_local_explorer_foreground,
|
||||||
self.theme().transfer_local_explorer_highlighted,
|
self.theme().transfer_local_explorer_highlighted,
|
||||||
@@ -763,7 +764,7 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn mount_file_sorting(&mut self) {
|
pub(super) fn mount_file_sorting(&mut self) {
|
||||||
let sorting_color = self.theme().transfer_status_sorting;
|
let sorting_color = self.theme().transfer_status_sorting;
|
||||||
let sorting: FileSorting = match self.browser.tab() {
|
let sorting: FileSorting = match self.browser.tab() {
|
||||||
FileExplorerTab::Local => self.local().get_file_sorting(),
|
FileExplorerTab::HostBridge => self.host_bridge().get_file_sorting(),
|
||||||
FileExplorerTab::Remote => self.remote().get_file_sorting(),
|
FileExplorerTab::Remote => self.remote().get_file_sorting(),
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
@@ -901,7 +902,7 @@ impl FileTransferActivity {
|
|||||||
assert!(self
|
assert!(self
|
||||||
.app
|
.app
|
||||||
.remount(
|
.remount(
|
||||||
Id::StatusBarLocal,
|
Id::StatusBarHostBridge,
|
||||||
Box::new(components::StatusBarLocal::new(
|
Box::new(components::StatusBarLocal::new(
|
||||||
&self.browser,
|
&self.browser,
|
||||||
sorting_color,
|
sorting_color,
|
||||||
@@ -1066,138 +1067,34 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
/// Returns a sub clause which requires that no popup is mounted in order to be satisfied
|
/// Returns a sub clause which requires that no popup is mounted in order to be satisfied
|
||||||
fn no_popup_mounted_clause() -> SubClause<Id> {
|
fn no_popup_mounted_clause() -> SubClause<Id> {
|
||||||
SubClause::And(
|
tuirealm::subclause_and_not!(
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::CopyPopup,
|
Id::CopyPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::DeletePopup,
|
Id::DeletePopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::DisconnectPopup,
|
Id::DisconnectPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ErrorPopup,
|
Id::ErrorPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ExecPopup,
|
Id::ExecPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::FatalPopup,
|
Id::FatalPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::FileInfoPopup,
|
Id::FileInfoPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::GotoPopup,
|
Id::GotoPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::KeybindingsPopup,
|
Id::KeybindingsPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::MkdirPopup,
|
Id::MkdirPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::NewfilePopup,
|
Id::NewfilePopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::OpenWithPopup,
|
Id::OpenWithPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ProgressBarFull,
|
Id::ProgressBarFull,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ProgressBarPartial,
|
Id::ProgressBarPartial,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ExplorerFind,
|
Id::ExplorerFind,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::QuitPopup,
|
Id::QuitPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::RenamePopup,
|
Id::RenamePopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ReplacePopup,
|
Id::ReplacePopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::SaveAsPopup,
|
Id::SaveAsPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::SortingPopup,
|
Id::SortingPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::SyncBrowsingMkdirPopup,
|
Id::SyncBrowsingMkdirPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::SymlinkPopup,
|
Id::SymlinkPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::WatcherPopup,
|
Id::WatcherPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::WatchedPathsList,
|
Id::WatchedPathsList,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::ChmodPopup,
|
Id::ChmodPopup,
|
||||||
)))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::WaitPopup,
|
Id::WaitPopup,
|
||||||
)))),
|
Id::FilterPopup
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
|
||||||
Id::FilterPopup,
|
|
||||||
)))),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ pub mod auth;
|
|||||||
pub mod filetransfer;
|
pub mod filetransfer;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|
||||||
|
const CROSSTERM_MAX_POLL: usize = 10;
|
||||||
|
|
||||||
// -- Exit reason
|
// -- Exit reason
|
||||||
|
|
||||||
pub enum ExitReason {
|
pub enum ExitReason {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Locals
|
// Locals
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use tuirealm::tui::style::Color;
|
use tuirealm::ratatui::style::Color;
|
||||||
use tuirealm::{State, StateValue};
|
use tuirealm::{State, StateValue};
|
||||||
|
|
||||||
use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout};
|
use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use tuirealm::listener::EventListenerCfg;
|
|||||||
use tuirealm::props::Color;
|
use tuirealm::props::Color;
|
||||||
use tuirealm::{Application, NoUserEvent, Update};
|
use tuirealm::{Application, NoUserEvent, Update};
|
||||||
|
|
||||||
use super::{Activity, Context, ExitReason};
|
use super::{Activity, Context, ExitReason, CROSSTERM_MAX_POLL};
|
||||||
use crate::config::themes::Theme;
|
use crate::config::themes::Theme;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use crate::system::theme_provider::ThemeProvider;
|
use crate::system::theme_provider::ThemeProvider;
|
||||||
@@ -262,7 +262,7 @@ impl SetupActivity {
|
|||||||
Self {
|
Self {
|
||||||
app: Application::init(
|
app: Application::init(
|
||||||
EventListenerCfg::default()
|
EventListenerCfg::default()
|
||||||
.default_input_listener(ticks)
|
.crossterm_input_listener(ticks, CROSSTERM_MAX_POLL)
|
||||||
.poll_timeout(ticks),
|
.poll_timeout(ticks),
|
||||||
),
|
),
|
||||||
exit_reason: None,
|
exit_reason: None,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub mod ssh_keys;
|
|||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
|
||||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||||
use tuirealm::tui::widgets::Clear;
|
use tuirealm::ratatui::widgets::Clear;
|
||||||
use tuirealm::{Frame, Sub, SubClause, SubEventClause};
|
use tuirealm::{Frame, Sub, SubClause, SubEventClause};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -112,23 +112,23 @@ impl SetupActivity {
|
|||||||
|
|
||||||
pub(super) fn view_popups(&mut self, f: &mut Frame) {
|
pub(super) fn view_popups(&mut self, f: &mut Frame) {
|
||||||
if self.app.mounted(&Id::Common(IdCommon::ErrorPopup)) {
|
if self.app.mounted(&Id::Common(IdCommon::ErrorPopup)) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::Common(IdCommon::ErrorPopup), f, popup);
|
self.app.view(&Id::Common(IdCommon::ErrorPopup), f, popup);
|
||||||
} else if self.app.mounted(&Id::Common(IdCommon::QuitPopup)) {
|
} else if self.app.mounted(&Id::Common(IdCommon::QuitPopup)) {
|
||||||
// make popup
|
// make popup
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
self.app.view(&Id::Common(IdCommon::QuitPopup), f, popup);
|
self.app.view(&Id::Common(IdCommon::QuitPopup), f, popup);
|
||||||
} else if self.app.mounted(&Id::Common(IdCommon::Keybindings)) {
|
} else if self.app.mounted(&Id::Common(IdCommon::Keybindings)) {
|
||||||
// make popup
|
// make popup
|
||||||
let popup = Popup(Size::Percentage(50), Size::Percentage(70)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Percentage(70)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
self.app.view(&Id::Common(IdCommon::Keybindings), f, popup);
|
self.app.view(&Id::Common(IdCommon::Keybindings), f, popup);
|
||||||
} else if self.app.mounted(&Id::Common(IdCommon::SavePopup)) {
|
} else if self.app.mounted(&Id::Common(IdCommon::SavePopup)) {
|
||||||
// make popup
|
// make popup
|
||||||
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
self.app.view(&Id::Common(IdCommon::SavePopup), f, popup);
|
self.app.view(&Id::Common(IdCommon::SavePopup), f, popup);
|
||||||
}
|
}
|
||||||
@@ -235,33 +235,13 @@ impl SetupActivity {
|
|||||||
|
|
||||||
/// Returns a sub clause which requires that no popup is mounted in order to be satisfied
|
/// Returns a sub clause which requires that no popup is mounted in order to be satisfied
|
||||||
fn no_popup_mounted_clause() -> SubClause<Id> {
|
fn no_popup_mounted_clause() -> SubClause<Id> {
|
||||||
SubClause::And(
|
tuirealm::subclause_and_not!(
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(Id::Common(
|
Id::Common(IdCommon::ErrorPopup),
|
||||||
IdCommon::ErrorPopup,
|
Id::Common(IdCommon::Keybindings),
|
||||||
))))),
|
Id::Common(IdCommon::QuitPopup),
|
||||||
Box::new(SubClause::And(
|
Id::Common(IdCommon::SavePopup),
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(Id::Common(
|
Id::Ssh(IdSsh::DelSshKeyPopup),
|
||||||
IdCommon::Keybindings,
|
Id::Ssh(IdSsh::SshHost)
|
||||||
))))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(Id::Common(
|
|
||||||
IdCommon::QuitPopup,
|
|
||||||
))))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(Id::Common(
|
|
||||||
IdCommon::SavePopup,
|
|
||||||
))))),
|
|
||||||
Box::new(SubClause::And(
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(Id::Ssh(
|
|
||||||
IdSsh::DelSshKeyPopup,
|
|
||||||
))))),
|
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(Id::Ssh(
|
|
||||||
IdSsh::SshHost,
|
|
||||||
))))),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// Ext
|
// Ext
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||||
use tuirealm::{State, StateValue};
|
use tuirealm::{State, StateValue};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -50,7 +50,7 @@ impl SetupActivity {
|
|||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.split(f.size());
|
.split(f.area());
|
||||||
// Render common widget
|
// Render common widget
|
||||||
self.app.view(&Id::Common(IdCommon::Header), f, chunks[0]);
|
self.app.view(&Id::Common(IdCommon::Header), f, chunks[0]);
|
||||||
self.app.view(&Id::Common(IdCommon::Footer), f, chunks[2]);
|
self.app.view(&Id::Common(IdCommon::Footer), f, chunks[2]);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
// Ext
|
// Ext
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||||
use tuirealm::tui::widgets::Clear;
|
use tuirealm::ratatui::widgets::Clear;
|
||||||
|
|
||||||
use super::{components, Context, Id, IdCommon, IdSsh, SetupActivity, ViewLayout};
|
use super::{components, Context, Id, IdCommon, IdSsh, SetupActivity, ViewLayout};
|
||||||
use crate::utils::ui::{Popup, Size};
|
use crate::utils::ui::{Popup, Size};
|
||||||
@@ -37,7 +37,7 @@ impl SetupActivity {
|
|||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.split(f.size());
|
.split(f.area());
|
||||||
// Render common widget
|
// Render common widget
|
||||||
self.app.view(&Id::Common(IdCommon::Header), f, chunks[0]);
|
self.app.view(&Id::Common(IdCommon::Header), f, chunks[0]);
|
||||||
self.app.view(&Id::Common(IdCommon::Footer), f, chunks[2]);
|
self.app.view(&Id::Common(IdCommon::Footer), f, chunks[2]);
|
||||||
@@ -45,11 +45,11 @@ impl SetupActivity {
|
|||||||
// Popups
|
// Popups
|
||||||
self.view_popups(f);
|
self.view_popups(f);
|
||||||
if self.app.mounted(&Id::Ssh(IdSsh::DelSshKeyPopup)) {
|
if self.app.mounted(&Id::Ssh(IdSsh::DelSshKeyPopup)) {
|
||||||
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
self.app.view(&Id::Ssh(IdSsh::DelSshKeyPopup), f, popup);
|
self.app.view(&Id::Ssh(IdSsh::DelSshKeyPopup), f, popup);
|
||||||
} else if self.app.mounted(&Id::Ssh(IdSsh::SshHost)) {
|
} else if self.app.mounted(&Id::Ssh(IdSsh::SshHost)) {
|
||||||
let popup = Popup(Size::Percentage(50), Size::Percentage(20)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(50), Size::Percentage(20)).draw_in(f.area());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
let popup_chunks = Layout::default()
|
let popup_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
// Ext
|
// Ext
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||||
|
|
||||||
use super::{components, Context, Id, IdCommon, IdTheme, SetupActivity, Theme, ViewLayout};
|
use super::{components, Context, Id, IdCommon, IdTheme, SetupActivity, Theme, ViewLayout};
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ impl SetupActivity {
|
|||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.split(f.size());
|
.split(f.area());
|
||||||
// Render common widget
|
// Render common widget
|
||||||
self.app.view(&Id::Common(IdCommon::Header), f, chunks[0]);
|
self.app.view(&Id::Common(IdCommon::Header), f, chunks[0]);
|
||||||
self.app.view(&Id::Common(IdCommon::Footer), f, chunks[2]);
|
self.app.view(&Id::Common(IdCommon::Footer), f, chunks[2]);
|
||||||
|
|||||||
@@ -3,21 +3,22 @@
|
|||||||
//! `Context` is the module which provides all the functionalities related to the UI data holder, called Context
|
//! `Context` is the module which provides all the functionalities related to the UI data holder, called Context
|
||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
use tuirealm::terminal::TerminalBridge;
|
use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalBridge};
|
||||||
|
|
||||||
use super::store::Store;
|
use super::store::Store;
|
||||||
use crate::filetransfer::FileTransferParams;
|
use crate::filetransfer::{FileTransferParams, HostBridgeParams};
|
||||||
use crate::system::bookmarks_client::BookmarksClient;
|
use crate::system::bookmarks_client::BookmarksClient;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use crate::system::theme_provider::ThemeProvider;
|
use crate::system::theme_provider::ThemeProvider;
|
||||||
|
|
||||||
/// Context holds data structures shared by the activities
|
/// Context holds data structures shared by the activities
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
ft_params: Option<FileTransferParams>,
|
host_bridge_params: Option<HostBridgeParams>,
|
||||||
|
remote_params: Option<FileTransferParams>,
|
||||||
bookmarks_client: Option<BookmarksClient>,
|
bookmarks_client: Option<BookmarksClient>,
|
||||||
config_client: ConfigClient,
|
config_client: ConfigClient,
|
||||||
pub(crate) store: Store,
|
pub(crate) store: Store,
|
||||||
pub(crate) terminal: TerminalBridge,
|
pub(crate) terminal: TerminalBridge<CrosstermTerminalAdapter>,
|
||||||
theme_provider: ThemeProvider,
|
theme_provider: ThemeProvider,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -30,27 +31,29 @@ impl Context {
|
|||||||
theme_provider: ThemeProvider,
|
theme_provider: ThemeProvider,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
) -> Context {
|
) -> Context {
|
||||||
let mut ctx = Context {
|
let mut terminal = TerminalBridge::init_crossterm().expect("Could not initialize terminal");
|
||||||
|
let _ = terminal.disable_mouse_capture();
|
||||||
|
|
||||||
|
Context {
|
||||||
bookmarks_client,
|
bookmarks_client,
|
||||||
config_client,
|
config_client,
|
||||||
ft_params: None,
|
host_bridge_params: None,
|
||||||
|
remote_params: None,
|
||||||
store: Store::init(),
|
store: Store::init(),
|
||||||
terminal: TerminalBridge::new().expect("Could not initialize terminal"),
|
terminal,
|
||||||
theme_provider,
|
theme_provider,
|
||||||
error,
|
error,
|
||||||
};
|
}
|
||||||
|
|
||||||
// Init terminal state
|
|
||||||
let _ = ctx.terminal.enable_raw_mode();
|
|
||||||
let _ = ctx.terminal.enter_alternate_screen();
|
|
||||||
|
|
||||||
ctx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- getters
|
// -- getters
|
||||||
|
|
||||||
pub fn ft_params(&self) -> Option<&FileTransferParams> {
|
pub fn remote_params(&self) -> Option<&FileTransferParams> {
|
||||||
self.ft_params.as_ref()
|
self.remote_params.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn host_bridge_params(&self) -> Option<&HostBridgeParams> {
|
||||||
|
self.host_bridge_params.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bookmarks_client(&self) -> Option<&BookmarksClient> {
|
pub fn bookmarks_client(&self) -> Option<&BookmarksClient> {
|
||||||
@@ -85,23 +88,22 @@ impl Context {
|
|||||||
&mut self.theme_provider
|
&mut self.theme_provider
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminal(&mut self) -> &mut TerminalBridge {
|
pub fn terminal(&mut self) -> &mut TerminalBridge<CrosstermTerminalAdapter> {
|
||||||
&mut self.terminal
|
&mut self.terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- setter
|
// -- setter
|
||||||
|
|
||||||
pub fn set_ftparams(&mut self, params: FileTransferParams) {
|
pub fn set_remote_params(&mut self, params: FileTransferParams) {
|
||||||
self.ft_params = Some(params);
|
self.remote_params = Some(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_host_bridge_params(&mut self, params: HostBridgeParams) {
|
||||||
|
self.host_bridge_params = Some(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- error
|
// -- error
|
||||||
|
|
||||||
/// Set context error
|
|
||||||
pub fn set_error(&mut self, err: String) {
|
|
||||||
self.error = Some(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get error message and remove it from the context
|
/// Get error message and remove it from the context
|
||||||
pub fn error(&mut self) -> Option<String> {
|
pub fn error(&mut self) -> Option<String> {
|
||||||
self.error.take()
|
self.error.take()
|
||||||
@@ -110,8 +112,8 @@ impl Context {
|
|||||||
|
|
||||||
impl Drop for Context {
|
impl Drop for Context {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Re-enable terminal stuff
|
if let Err(err) = self.terminal.restore() {
|
||||||
let _ = self.terminal.disable_raw_mode();
|
error!("Could not restore terminal: {err}");
|
||||||
let _ = self.terminal.leave_alternate_screen();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user