Merge branch '0.6.0' into keyring-rs-linux
This commit is contained in:
commit
2d8ce475f8
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -32,3 +32,9 @@ Please select relevant options.
|
|||
- [ ] I have introduced no new *C-bindings*
|
||||
- [ ] The changes I've made are Windows, MacOS, UNIX, Linux compatible (or I've handled them using `cfg target_os`)
|
||||
- [ ] I increased or maintained the code coverage for the project, compared to the previous commit
|
||||
|
||||
## Acceptance tests
|
||||
|
||||
wait for a *project maintainer* to fulfill this section...
|
||||
|
||||
- [ ] regression test: ...
|
||||
|
|
17
.github/workflows/coverage.yml
vendored
17
.github/workflows/coverage.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: coverage
|
||||
name: Coverage
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
@ -6,22 +6,23 @@ env:
|
|||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Generate coverage
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup rust toolchain
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup containers
|
||||
run: docker-compose -f "tests/docker-compose.yml" up -d --build
|
||||
- name: Setup nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: Run tests
|
||||
- name: Run tests (nightly)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --no-default-features --features githubActions --features with-containers --no-fail-fast
|
||||
args: --no-default-features --features github-actions --features with-containers --no-fail-fast
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests"
|
||||
|
|
22
.github/workflows/freebsd.yml
vendored
Normal file
22
.github/workflows/freebsd.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: FreeBSD
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: FreeBSD build
|
||||
id: test
|
||||
uses: vmactions/freebsd-vm@v0.1.4
|
||||
with:
|
||||
usesh: true
|
||||
prepare: pkg install -y curl wget libssh gcc vim
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh && \
|
||||
chmod +x /tmp/rustup.sh && \
|
||||
/tmp/rustup.sh -y
|
||||
. $HOME/.cargo/env
|
||||
cargo build --no-default-features
|
||||
cargo test --no-default-features --verbose --lib --features github-actions -- --test-threads 1
|
7
.github/workflows/linux.yml
vendored
7
.github/workflows/linux.yml
vendored
|
@ -11,15 +11,18 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup containers
|
||||
run: docker-compose -f "tests/docker-compose.yml" up -d --build
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --no-default-features --features githubActions --features with-containers --no-fail-fast
|
||||
args: --no-default-features --features github-actions --features with-containers --no-fail-fast
|
||||
- name: Format
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Clippy
|
||||
|
|
4
.github/workflows/macos.yml
vendored
4
.github/workflows/macos.yml
vendored
|
@ -12,8 +12,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
run: cargo build
|
||||
- name: Run tests
|
||||
run: cargo test --verbose --features githubActions -- --test-threads 1
|
||||
run: cargo test --verbose --lib --features github-actions -- --test-threads 1
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
|
|
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
|
@ -12,8 +12,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
run: cargo build
|
||||
- name: Run tests
|
||||
run: cargo test --verbose --features githubActions -- --test-threads 1
|
||||
run: cargo test --verbose --lib --features github-actions -- --test-threads 1
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -2,6 +2,7 @@
|
|||
|
||||
- [Changelog](#changelog)
|
||||
- [0.6.0](#060)
|
||||
- [0.5.1](#051)
|
||||
- [0.5.0](#050)
|
||||
- [0.4.2](#042)
|
||||
- [0.4.1](#041)
|
||||
|
@ -40,6 +41,35 @@ Released on FIXME: ??
|
|||
- Updated `textwrap` to `0.14.0`
|
||||
- Updated `tui-realm` to `0.4.1`
|
||||
|
||||
## 0.5.1
|
||||
|
||||
Released on 21/06/2021
|
||||
|
||||
- Enhancements:
|
||||
- **CI now uses containers to test file transfers (SSH/FTP)**
|
||||
- Improved coverage
|
||||
- Found many bugs which has now been fixed
|
||||
- Build in CI won't fail due to test servers not responding
|
||||
- We're now able to test all the functionalities of the file transfers
|
||||
- **Status bar improvements**
|
||||
- "Show hidden files" in status bar
|
||||
- Status bar has now been splitted into two, one for each explorer tab
|
||||
- **Error message if terminal window is too small**
|
||||
- If the terminal window has less than 24 lines, then an error message is displayed in the auth activity
|
||||
- Changed auth layout to absolute sizes
|
||||
- Bugfix:
|
||||
- Fixed UI not showing connection errors
|
||||
- Fixed termscp on Windows dying whenever opening a file with text editor
|
||||
- Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2)
|
||||
- Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP
|
||||
- Fixed [Issue 43](https://github.com/veeso/termscp/issues/43): Could not remove non-empty directories in FTP
|
||||
- Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens
|
||||
- Fixed [Issue 38](https://github.com/veeso/termscp/issues/38): Transfer size was wrong when transferring "selected" files (with mark)
|
||||
- Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files
|
||||
- Dependencies:
|
||||
- Updated `textwrap` to `0.14.0`
|
||||
- Updated `tui-realm` to `0.4.2`
|
||||
|
||||
## 0.5.0
|
||||
|
||||
Released on 23/05/2021
|
||||
|
|
|
@ -115,7 +115,9 @@ Let's make it simple and clear:
|
|||
6. Report changes to the PR you opened, writing a report of what you changed and what you have introduced.
|
||||
7. Update the `CHANGELOG.md` file with details of changes to the application. In changelog report changes under a chapter called `PR{PULL_REQUEST_NUMBER}` (e.g. PR12).
|
||||
8. Assign a maintainer to the reviewers.
|
||||
9. Request maintainers to merge your changes.
|
||||
9. Wait for a maintainer to fullfil the acceptance tests
|
||||
10. Wait for a maintainer to complete the acceptance tests
|
||||
11. Request maintainers to merge your changes.
|
||||
|
||||
### Software guidelines
|
||||
|
||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1451,9 +1451,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tuirealm"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bee2a1c050878fac02ba3a6c2e93aa92a1de56849d5deec00d4ab4bc7928c0a"
|
||||
checksum = "9897335542e4a4a87ad391419c35e54b4088661e671ba53e578fbbb1154740c2"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"textwrap",
|
||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -34,6 +34,7 @@ content_inspector = "0.2.4"
|
|||
crossterm = "0.19.0"
|
||||
dirs = "3.0.1"
|
||||
edit = "0.1.3"
|
||||
ftp4 = { version = "4.0.2", features = [ "secure" ] }
|
||||
getopts = "0.2.21"
|
||||
hostname = "0.3.1"
|
||||
keyring = { version = "0.10.1", optional = true }
|
||||
|
@ -44,39 +45,29 @@ open = "1.7.0"
|
|||
rand = "0.8.3"
|
||||
regex = "1.5.4"
|
||||
rpassword = "5.0.1"
|
||||
serde = { version = "^1.0.0", features = [ "derive" ] }
|
||||
simplelog = "0.10.0"
|
||||
ssh2 = "0.9.0"
|
||||
tempfile = "3.1.0"
|
||||
textwrap = "0.14.0"
|
||||
thiserror = "^1.0.0"
|
||||
toml = "0.5.8"
|
||||
tuirealm = { version = "0.4.1", features = [ "with-components" ] }
|
||||
tuirealm = { version = "0.4.2", features = [ "with-components" ] }
|
||||
ureq = { version = "2.1.0", features = [ "json" ] }
|
||||
whoami = "1.1.1"
|
||||
wildmatch = "2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.7.2"
|
||||
|
||||
[dependencies.ftp4]
|
||||
features = ["secure"]
|
||||
version = "^4.0.2"
|
||||
|
||||
[dependencies.serde]
|
||||
features = ["derive"]
|
||||
version = "^1.0.0"
|
||||
|
||||
[dependencies.ureq]
|
||||
features = ["json"]
|
||||
version = "2.1.0"
|
||||
|
||||
[features]
|
||||
default = [ "with-keyring" ]
|
||||
githubActions = []
|
||||
github-actions = []
|
||||
with-containers = []
|
||||
with-keyring = [ "keyring" ]
|
||||
|
||||
[target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))"]
|
||||
[target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))".dependencies]
|
||||
[target."cfg(target_family = \"unix\")"]
|
||||
[target."cfg(target_family = \"unix\")".dependencies]
|
||||
users = "0.11.0"
|
||||
|
||||
[target."cfg(target_os = \"windows\")"]
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
</p>
|
||||
|
||||
<p align="center">Developed by <a href="https://veeso.github.io/">@veeso</a></p>
|
||||
<p align="center">Current version: 0.6.0 FIXME: (23/05/2021)</p>
|
||||
<p align="center">Current version: 0.6.0 FIXME: (21/06/2021)</p>
|
||||
|
||||
[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.6.0-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp)
|
||||
[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.5.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp)
|
||||
|
||||
[![Build](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![Build](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Build](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp)
|
||||
[![Linux](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![MacOs](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Windows](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![FreeBSD](https://github.com/veeso/termscp/workflows/FreeBSD/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp)
|
||||
|
||||
---
|
||||
|
||||
|
@ -59,7 +59,7 @@ Termscp is a feature rich terminal file transfer and explorer, with support for
|
|||
If you're considering to install termscp I want to thank you 💜 ! I hope you will enjoy termscp!
|
||||
If you want to contribute to this project, don't forget to check out our contribute guide. [Read More](CONTRIBUTING.md)
|
||||
|
||||
If you are a Linux or a MacOS user this simple shell script will install termscp on your system with a single command:
|
||||
If you are a Linux, a FreeBSD or a MacOS user this simple shell script will install termscp on your system with a single command:
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf "https://raw.githubusercontent.com/veeso/termscp/main/install.sh" | sh
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
ignore:
|
||||
- src/main.rs
|
||||
- src/lib.rs
|
||||
- src/activity_manager.rs
|
||||
- src/ui/activities/
|
||||
- src/ui/context.rs
|
||||
- src/ui/input.rs
|
2
dist/build/x86_64_archlinux/Dockerfile
vendored
2
dist/build/x86_64_archlinux/Dockerfile
vendored
|
@ -1,4 +1,4 @@
|
|||
FROM archlinux:base-20210120.0.13969 as builder
|
||||
FROM archlinux:latest as builder
|
||||
|
||||
WORKDIR /usr/src/
|
||||
# Install dependencies
|
||||
|
|
2
dist/pkgs/arch/PKGBUILD
vendored
2
dist/pkgs/arch/PKGBUILD
vendored
|
@ -9,7 +9,7 @@ arch=("x86_64")
|
|||
provides=("termscp")
|
||||
options=("strip")
|
||||
source=("https://github.com/veeso/termscp/releases/download/v$pkgver/termscp-$pkgver-x86_64.tar.gz")
|
||||
sha256sums=("279b4cab7da68c6db0efc054ddf72e36de85910110721b66d5cdc55833c99ccf")
|
||||
sha256sums=("f66a1d1602dc8ea336ba4a42bfbe818edc9c20722e1761b471b76109c272094c")
|
||||
|
||||
package() {
|
||||
install -Dm755 termscp -t "$pkgdir/usr/bin/"
|
||||
|
|
17
dist/pkgs/freebsd/manifest
vendored
Executable file
17
dist/pkgs/freebsd/manifest
vendored
Executable file
|
@ -0,0 +1,17 @@
|
|||
name: "termscp"
|
||||
version: 0.5.1
|
||||
origin: veeso/termscp
|
||||
comment: "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP"
|
||||
desc: <<EOD
|
||||
A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP
|
||||
EOD
|
||||
arch: "amd64"
|
||||
www: "https://veeso.github.io/termscp/"
|
||||
maintainer: "christian.visintin1997@gmail.com"
|
||||
prefix: "/usr/local/bin"
|
||||
deps: {
|
||||
libssh: {origin: security/libssh, version: 0.9.5}
|
||||
}
|
||||
files: {
|
||||
/usr/local/bin/termscp: "87543d13b11b6e601ba8cdde9d704c80dc3515f1681fbf71fd0b31d7206efc09"
|
||||
}
|
|
@ -3,15 +3,25 @@
|
|||
Document audience: developers
|
||||
|
||||
- [Developer Manual](#developer-manual)
|
||||
- [How to test](#how-to-test)
|
||||
- [How termscp works](#how-termscp-works)
|
||||
- [Activities](#activities)
|
||||
- [The Context](#the-context)
|
||||
- [Tests fails due to receivers](#tests-fails-due-to-receivers)
|
||||
- [Implementing File Transfers](#implementing-file-transfers)
|
||||
|
||||
Welcome to the developer manual for termscp. This chapter DOESN'T contain the documentation for termscp modules, which can instead be found on Rust Docs at <https://docs.rs/termscp>
|
||||
This chapter describes how termscp works and the guide lines to implement stuff such as file transfers and add features to the user interface.
|
||||
|
||||
## How to test
|
||||
|
||||
First an introduction to tests.
|
||||
|
||||
Usually it's enough to run `cargo test`, but please note that whenever you're working on file transfer you'll need one more step.
|
||||
In order to run tests with file transfers, you need to start the file transfer server containers, which can be started via `docker`.
|
||||
|
||||
To run all tests with file transfers just run: `./tests/test.sh`
|
||||
|
||||
---
|
||||
|
||||
## How termscp works
|
||||
|
||||
termscp is basically made up of 4 components:
|
||||
|
@ -61,146 +71,3 @@ The context basically holds the following data:
|
|||
- The **Terminal**: the terminal is used to view the tui on the terminal
|
||||
|
||||
---
|
||||
|
||||
## Tests fails due to receivers
|
||||
|
||||
Yes. This happens quite often and is related to the fact that I'm using public SSH/SFTP/FTP server to test file receivers and sometimes this server go down for even a day or more. If your tests don't pass due to this, don't worry, submit the pull request and I'll take care of testing them by myself.
|
||||
|
||||
---
|
||||
|
||||
## Implementing File Transfers
|
||||
|
||||
This chapter describes how to implement a file transfer in termscp. A file transfer is a module which implements the `FileTransfer` trait. The file transfer provides different modules to interact with a remote server, which in addition to the most obvious methods, used to download and upload files, provides also methods to list files, delete files, create directories etc.
|
||||
|
||||
In the following steps I will describe how to implement a new file transfer, in this case I will be implementing the SCP file transfer (which I'm actually implementing the moment I'm writing this lines).
|
||||
|
||||
1. Add the Scp protocol to the `FileTransferProtocol` enum.
|
||||
|
||||
Move to `src/filetransfer/mod.rs` and add `Scp` to the `FileTransferProtocol` enum
|
||||
|
||||
```rs
|
||||
/// ## FileTransferProtocol
|
||||
///
|
||||
/// This enum defines the different transfer protocol available in termscp
|
||||
#[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone)]
|
||||
pub enum FileTransferProtocol {
|
||||
Sftp,
|
||||
Ftp(bool), // Bool is for secure (true => ftps)
|
||||
Scp, // <-- here
|
||||
}
|
||||
```
|
||||
|
||||
In this case Scp is a "plain" enum type. If you need particular options, follow the implementation of `Ftp` which uses a boolean flag for indicating if using FTPS or FTP.
|
||||
|
||||
2. Implement the FileTransfer struct
|
||||
|
||||
Create a file at `src/filetransfer/mytransfer.rs`
|
||||
|
||||
Declare your file transfer struct
|
||||
|
||||
```rs
|
||||
/// ## ScpFileTransfer
|
||||
///
|
||||
/// SFTP file transfer structure
|
||||
pub struct ScpFileTransfer {
|
||||
session: Option<Session>,
|
||||
sftp: Option<Sftp>,
|
||||
wrkdir: PathBuf,
|
||||
}
|
||||
```
|
||||
|
||||
3. Implement the `FileTransfer` trait for it
|
||||
|
||||
You'll have to implement the following methods for your file transfer:
|
||||
|
||||
- connect: connect to remote server
|
||||
- disconnect: disconnect from remote server
|
||||
- is_connected: returns whether the file transfer is connected to remote
|
||||
- pwd: get working directory
|
||||
- change_dir: change working directory.
|
||||
- list_dir: get files and directories at a certain path
|
||||
- mkdir: make a new directory. Return an error in case the directory already exists
|
||||
- remove: remove a file or a directory. In case the protocol doesn't support recursive removing of directories you MUST implement this through a recursive algorithm
|
||||
- rename: rename a file or a directory
|
||||
- stat: returns detail for a certain path
|
||||
- send_file: opens a stream to a remote path for write purposes (write a remote file)
|
||||
- recv_file: opens a stream to a remote path for read purposes (write a local file)
|
||||
- on_sent: finalize a stream when writing a remote file. In case it's not necessary just return `Ok(())`
|
||||
- on_recv: fianlize a stream when reading a remote file. In case it's not necessary just return `Ok(())`
|
||||
|
||||
In case the protocol you're working on doesn't support any of this features, just return `Err(FileTransferError::new(FileTransferErrorType::UnsupportedFeature))`
|
||||
|
||||
4. Add your transfer to filetransfers:
|
||||
|
||||
Move to `src/filetransfer/mod.rs` and declare your file transfer:
|
||||
|
||||
```rs
|
||||
// Transfers
|
||||
pub mod ftp_transfer;
|
||||
pub mod scp_transfer; // <-- here
|
||||
pub mod sftp_transfer;
|
||||
```
|
||||
|
||||
5. Handle FileTransfer in `FileTransferActivity::new`
|
||||
|
||||
Move to `src/ui/activities/filetransfer_activity/mod.rs` and add the new protocol to the client match
|
||||
|
||||
```rs
|
||||
client: match protocol {
|
||||
FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new()),
|
||||
FileTransferProtocol::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)),
|
||||
FileTransferProtocol::Scp => Box::new(ScpFileTransfer::new()), // <--- here
|
||||
},
|
||||
```
|
||||
|
||||
6. Handle right/left input events in `AuthActivity`:
|
||||
|
||||
Move to `src/ui/activities/auth_activity.rs` and handle the new protocol in `handle_input_event_mode_text` for `KeyCode::Left` and `KeyCode::Right`.
|
||||
Consider that the order they "rotate" must match the way they will be drawned in the interface.
|
||||
For newer protocols, please put them always at the end of the list. In this list I won't, because Scp is more important than Ftp imo.
|
||||
|
||||
```rs
|
||||
KeyCode::Left => {
|
||||
// If current field is Protocol handle event... (move element left)
|
||||
if self.selected_field == InputField::Protocol {
|
||||
self.protocol = match self.protocol {
|
||||
FileTransferProtocol::Sftp => FileTransferProtocol::Ftp(true), // End of list (wrap)
|
||||
FileTransferProtocol::Scp => FileTransferProtocol::Sftp,
|
||||
FileTransferProtocol::Ftp(ftps) => match ftps {
|
||||
false => FileTransferProtocol::Scp,
|
||||
true => FileTransferProtocol::Ftp(false),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
KeyCode::Right => {
|
||||
// If current field is Protocol handle event... ( move element right )
|
||||
if self.selected_field == InputField::Protocol {
|
||||
self.protocol = match self.protocol {
|
||||
FileTransferProtocol::Sftp => FileTransferProtocol::Scp,
|
||||
FileTransferProtocol::Scp => FileTransferProtocol::Ftp(false),
|
||||
FileTransferProtocol::Ftp(ftps) => match ftps {
|
||||
false => FileTransferProtocol::Ftp(true),
|
||||
true => FileTransferProtocol::Sftp, // End of list (wrap)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
7. Add your new file transfer to the protocol input field
|
||||
|
||||
Move to `AuthActivity::draw_protocol_select` method.
|
||||
Here add your new protocol to the `Spans` vector and to the match case, which chooses which element to highlight.
|
||||
|
||||
```rs
|
||||
let protocols: Vec<Spans> = vec![Spans::from("SFTP"), Spans::from("SCP"), Spans::from("FTP"), Spans::from("FTPS")];
|
||||
let index: usize = match self.protocol {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(ftps) => match ftps {
|
||||
false => 2,
|
||||
true => 3,
|
||||
}
|
||||
};
|
||||
```
|
||||
|
|
17
install.sh
17
install.sh
|
@ -11,6 +11,7 @@
|
|||
TERMSCP_VERSION="0.6.0"
|
||||
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
||||
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
||||
FREEBSD_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}.txz"
|
||||
RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm"
|
||||
|
||||
set -eu
|
||||
|
@ -170,7 +171,21 @@ confirm() {
|
|||
# Installers
|
||||
|
||||
install_on_bsd() {
|
||||
try_with_cargo "we currently don't distribute any pre-built package for BSD"
|
||||
info "Installing termscp via FreeBSD pkg"
|
||||
archive=$(get_tmpfile "txz")
|
||||
download "${archive}" "${FREEBSD_URL}"
|
||||
info "Downloaded FreeBSD package to ${archive}"
|
||||
if test_writeable "/usr/local/bin"; then
|
||||
sudo=""
|
||||
msg="Installing termscp, please wait…"
|
||||
else
|
||||
warn "Root permissions are required to install termscp…"
|
||||
elevate_priv
|
||||
sudo="sudo"
|
||||
msg="Installing termscp as root, please wait…"
|
||||
fi
|
||||
info "$msg"
|
||||
$sudo pkg install -y "${archive}"
|
||||
}
|
||||
|
||||
install_on_linux() {
|
||||
|
|
|
@ -183,6 +183,12 @@ mod tests {
|
|||
file_fmt: Some(String::from("{NAME}")),
|
||||
remote_file_fmt: Some(String::from("{USER}")),
|
||||
};
|
||||
assert_eq!(ui.default_protocol, String::from("SFTP"));
|
||||
assert_eq!(ui.text_editor, PathBuf::from("nano"));
|
||||
assert_eq!(ui.show_hidden_files, true);
|
||||
assert_eq!(ui.check_for_updates, Some(true));
|
||||
assert_eq!(ui.group_dirs, Some(String::from("first")));
|
||||
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
|
||||
let cfg: UserConfig = UserConfig {
|
||||
user_interface: ui,
|
||||
remote: remote,
|
||||
|
@ -219,7 +225,7 @@ mod tests {
|
|||
PathBuf::from(cfg.user_interface.text_editor.file_name().unwrap()), // NOTE: since edit 0.1.3 real path is used
|
||||
PathBuf::from("vim.EXE")
|
||||
);
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
PathBuf::from(cfg.user_interface.text_editor.file_name().unwrap()), // NOTE: since edit 0.1.3 real path is used
|
||||
PathBuf::from("vim")
|
||||
|
|
|
@ -208,6 +208,25 @@ mod tests {
|
|||
assert!(serializer.deserialize(Box::new(toml_file)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_fail_write() {
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
let writer: Box<dyn Write> = Box::new(std::fs::File::open(toml_file.path()).unwrap());
|
||||
// Try to write unexisting file
|
||||
let serializer: ConfigSerializer = ConfigSerializer {};
|
||||
let cfg: UserConfig = UserConfig::default();
|
||||
assert!(serializer.serialize(writer, &cfg).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_fail_read() {
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
let reader: Box<dyn Read> = Box::new(std::fs::File::open(toml_file.path()).unwrap());
|
||||
// Try to write unexisting file
|
||||
let serializer: ConfigSerializer = ConfigSerializer {};
|
||||
assert!(serializer.deserialize(reader).is_err());
|
||||
}
|
||||
|
||||
fn create_good_toml() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
|
|
|
@ -73,7 +73,7 @@ impl FtpFileTransfer {
|
|||
PathBuf::from(path_slash::PathExt::to_slash_lossy(p).as_str())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn resolve(p: &Path) -> PathBuf {
|
||||
p.to_path_buf()
|
||||
}
|
||||
|
@ -491,7 +491,10 @@ impl FileTransfer for FtpFileTransfer {
|
|||
info!("Disconnecting from FTP server...");
|
||||
match &mut self.stream {
|
||||
Some(stream) => match stream.quit() {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => {
|
||||
self.stream = None;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::ConnectionError,
|
||||
err.to_string(),
|
||||
|
@ -629,23 +632,36 @@ impl FileTransfer for FtpFileTransfer {
|
|||
));
|
||||
}
|
||||
info!("Removing entry {}", fsentry.get_abs_path().display());
|
||||
let wrkdir: PathBuf = self.pwd()?;
|
||||
match fsentry {
|
||||
// Match fs entry...
|
||||
FsEntry::File(file) => {
|
||||
debug!("entry is a file; removing file");
|
||||
// Go to parent directory
|
||||
if let Some(parent_dir) = file.abs_path.parent() {
|
||||
debug!("Changing wrkdir to {}", parent_dir.display());
|
||||
self.change_dir(parent_dir)?;
|
||||
}
|
||||
debug!("entry is a file; removing file {}", file.abs_path.display());
|
||||
// Remove file directly
|
||||
match self.stream.as_mut().unwrap().rm(file.name.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::PexError,
|
||||
err.to_string(),
|
||||
)),
|
||||
let result = self
|
||||
.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.rm(file.name.as_ref())
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
FileTransferError::new_ex(FileTransferErrorType::PexError, e.to_string())
|
||||
});
|
||||
// Go to source directory
|
||||
match self.change_dir(wrkdir.as_path()) {
|
||||
Err(err) => Err(err),
|
||||
Ok(_) => result,
|
||||
}
|
||||
}
|
||||
FsEntry::Directory(dir) => {
|
||||
// Get directory files
|
||||
debug!("Entry is a directory; iterating directory entries");
|
||||
match self.list_dir(dir.abs_path.as_path()) {
|
||||
let result = match self.list_dir(dir.abs_path.as_path()) {
|
||||
Ok(files) => {
|
||||
// Remove recursively files
|
||||
debug!("Removing {} entries from directory...", files.len());
|
||||
|
@ -658,9 +674,21 @@ impl FileTransfer for FtpFileTransfer {
|
|||
}
|
||||
}
|
||||
// Once all files in directory have been deleted, remove directory
|
||||
debug!("Finally removing directory {}", dir.name);
|
||||
debug!("Finally removing directory {}...", dir.name);
|
||||
// Enter parent directory
|
||||
if let Some(parent_dir) = dir.abs_path.parent() {
|
||||
debug!(
|
||||
"Changing wrkdir to {} to delete directory {}",
|
||||
parent_dir.display(),
|
||||
dir.name
|
||||
);
|
||||
self.change_dir(parent_dir)?;
|
||||
}
|
||||
match self.stream.as_mut().unwrap().rmdir(dir.name.as_str()) {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => {
|
||||
debug!("Removed {}", dir.abs_path.display());
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::PexError,
|
||||
err.to_string(),
|
||||
|
@ -671,6 +699,11 @@ impl FileTransfer for FtpFileTransfer {
|
|||
FileTransferErrorType::DirStatFailed,
|
||||
err.to_string(),
|
||||
)),
|
||||
};
|
||||
// Restore directory
|
||||
match self.change_dir(wrkdir.as_path()) {
|
||||
Err(err) => Err(err),
|
||||
Ok(_) => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -693,17 +726,8 @@ impl FileTransfer for FtpFileTransfer {
|
|||
FsEntry::Directory(dir) => dir.name.clone(),
|
||||
FsEntry::File(file) => file.name.clone(),
|
||||
};
|
||||
let dst_name: PathBuf = match dst.file_name() {
|
||||
Some(p) => PathBuf::from(p),
|
||||
None => {
|
||||
return Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::FileCreateDenied,
|
||||
String::from("Invalid destination name"),
|
||||
))
|
||||
}
|
||||
};
|
||||
// Only names are supported
|
||||
match stream.rename(src_name.as_str(), &dst_name.as_path().to_string_lossy()) {
|
||||
match stream.rename(src_name.as_str(), &dst.as_path().to_string_lossy()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::FileCreateDenied,
|
||||
|
@ -838,9 +862,14 @@ impl FileTransfer for FtpFileTransfer {
|
|||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::utils::file::open_file;
|
||||
use crate::utils::fmt::fmt_time;
|
||||
#[cfg(feature = "with-containers")]
|
||||
use crate::utils::test_helpers::write_file;
|
||||
use crate::utils::test_helpers::{create_sample_file_entry, make_fsentry};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
|
@ -854,120 +883,281 @@ mod tests {
|
|||
assert!(ftp.stream.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_ftp_server() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Sample file
|
||||
let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry();
|
||||
// Connect
|
||||
#[cfg(not(feature = "github-actions"))]
|
||||
let hostname: String = String::from("127.0.0.1");
|
||||
#[cfg(feature = "github-actions")]
|
||||
let hostname: String = String::from("127.0.0.1");
|
||||
assert!(ftp
|
||||
.connect(
|
||||
hostname,
|
||||
10021,
|
||||
Some(String::from("test")),
|
||||
Some(String::from("test")),
|
||||
)
|
||||
.is_ok());
|
||||
assert_eq!(ftp.is_connected(), true);
|
||||
// Get pwd
|
||||
assert_eq!(ftp.pwd().unwrap(), PathBuf::from("/"));
|
||||
// List dir (dir is empty)
|
||||
assert_eq!(ftp.list_dir(&Path::new("/")).unwrap().len(), 0);
|
||||
// Make directory
|
||||
assert!(ftp.mkdir(PathBuf::from("/home").as_path()).is_ok());
|
||||
// Make directory (err)
|
||||
assert!(ftp.mkdir(PathBuf::from("/root/pommlar").as_path()).is_err());
|
||||
// Change directory
|
||||
assert!(ftp.change_dir(PathBuf::from("/home").as_path()).is_ok());
|
||||
// Change directory (err)
|
||||
assert!(ftp
|
||||
.change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path())
|
||||
.is_err());
|
||||
// Copy (not supported)
|
||||
assert!(ftp
|
||||
.copy(&FsEntry::File(entry.clone()), PathBuf::from("/").as_path())
|
||||
.is_err());
|
||||
// Exec (not supported)
|
||||
assert!(ftp.exec("echo 1;").is_err());
|
||||
// Upload 2 files
|
||||
let mut writable = ftp
|
||||
.send_file(&entry, PathBuf::from("omar.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(ftp.on_sent(writable).is_ok());
|
||||
let mut writable = ftp
|
||||
.send_file(&entry, PathBuf::from("README.md").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(ftp.on_sent(writable).is_ok());
|
||||
// Upload file (err)
|
||||
assert!(ftp
|
||||
.send_file(&entry, PathBuf::from("/ommlar/omarone").as_path())
|
||||
.is_err());
|
||||
// List dir
|
||||
let list: Vec<FsEntry> = ftp.list_dir(PathBuf::from("/home").as_path()).ok().unwrap();
|
||||
assert_eq!(list.len(), 2);
|
||||
// Find
|
||||
assert!(ftp.change_dir(PathBuf::from("/").as_path()).is_ok());
|
||||
assert_eq!(ftp.find("*.txt").ok().unwrap().len(), 1);
|
||||
assert_eq!(ftp.find("*.md").ok().unwrap().len(), 1);
|
||||
assert_eq!(ftp.find("*.jpeg").ok().unwrap().len(), 0);
|
||||
assert!(ftp.change_dir(PathBuf::from("/home").as_path()).is_ok());
|
||||
// Rename
|
||||
assert!(ftp.mkdir(PathBuf::from("/uploads").as_path()).is_ok());
|
||||
assert!(ftp
|
||||
.rename(
|
||||
list.get(0).unwrap(),
|
||||
PathBuf::from("/uploads/README.txt").as_path()
|
||||
)
|
||||
.is_ok());
|
||||
// Rename (err)
|
||||
assert!(ftp
|
||||
.rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path())
|
||||
.is_err());
|
||||
let dummy: FsEntry = FsEntry::File(FsFile {
|
||||
name: String::from("cucumber.txt"),
|
||||
abs_path: PathBuf::from("/cucumber.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
assert!(ftp
|
||||
.rename(&dummy, PathBuf::from("/a/b/c").as_path())
|
||||
.is_err());
|
||||
// Remove
|
||||
assert!(ftp.remove(list.get(1).unwrap()).is_ok());
|
||||
assert!(ftp.remove(list.get(1).unwrap()).is_err());
|
||||
// Receive file
|
||||
let mut writable = ftp
|
||||
.send_file(&entry, PathBuf::from("/uploads/README.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(ftp.on_sent(writable).is_ok());
|
||||
let file: FsFile = ftp
|
||||
.list_dir(PathBuf::from("/uploads").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone()
|
||||
.unwrap_file();
|
||||
let mut readable = ftp.recv_file(&file).ok().unwrap();
|
||||
let mut data: Vec<u8> = vec![0; 1024];
|
||||
assert!(readable.read(&mut data).is_ok());
|
||||
assert!(ftp.on_recv(readable).is_ok());
|
||||
// Receive file (err)
|
||||
assert!(ftp.recv_file(&entry).is_err());
|
||||
// Cleanup
|
||||
assert!(ftp.change_dir(PathBuf::from("/").as_path()).is_ok());
|
||||
assert!(ftp
|
||||
.remove(&make_fsentry(PathBuf::from("/home"), true))
|
||||
.is_ok());
|
||||
assert!(ftp
|
||||
.remove(&make_fsentry(PathBuf::from("/uploads"), true))
|
||||
.is_ok());
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
assert_eq!(ftp.is_connected(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_ftp_server_bad_auth() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(
|
||||
String::from("127.0.0.1"),
|
||||
10021,
|
||||
Some(String::from("omar")),
|
||||
Some(String::from("ommlar")),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_ftp_no_credentials() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
assert!(ftp
|
||||
.connect(String::from("127.0.0.1"), 10021, None, None)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_server_bad_server() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(
|
||||
String::from("mybadserver.veryverybad.awful"),
|
||||
21,
|
||||
Some(String::from("omar")),
|
||||
Some(String::from("ommlar")),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_parse_list_line_unix() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Simple file
|
||||
let fs_entry: FsEntry = ftp
|
||||
let file: FsFile = ftp
|
||||
.parse_list_line(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"-rw-rw-r-- 1 root dialout 8192 Nov 5 2018 omar.txt",
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::File(file) = fs_entry {
|
||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||
assert_eq!(file.name, String::from("omar.txt"));
|
||||
assert_eq!(file.size, 8192);
|
||||
assert!(file.symlink.is_none());
|
||||
assert_eq!(file.user, None);
|
||||
assert_eq!(file.group, None);
|
||||
assert_eq!(file.unix_pex.unwrap(), (6, 6, 4));
|
||||
assert_eq!(
|
||||
file.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
file.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
file.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
} else {
|
||||
panic!("Expected file, got directory");
|
||||
}
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||
assert_eq!(file.name, String::from("omar.txt"));
|
||||
assert_eq!(file.size, 8192);
|
||||
assert!(file.symlink.is_none());
|
||||
assert_eq!(file.user, None);
|
||||
assert_eq!(file.group, None);
|
||||
assert_eq!(file.unix_pex.unwrap(), (6, 6, 4));
|
||||
assert_eq!(
|
||||
file.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
file.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
file.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
// Simple file with number as gid, uid
|
||||
let fs_entry: FsEntry = ftp
|
||||
let file: FsFile = ftp
|
||||
.parse_list_line(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"-rwxr-xr-x 1 0 9 4096 Nov 5 16:32 omar.txt",
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::File(file) = fs_entry {
|
||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||
assert_eq!(file.name, String::from("omar.txt"));
|
||||
assert_eq!(file.size, 4096);
|
||||
assert!(file.symlink.is_none());
|
||||
assert_eq!(file.user, Some(0));
|
||||
assert_eq!(file.group, Some(9));
|
||||
assert_eq!(file.unix_pex.unwrap(), (7, 5, 5));
|
||||
assert_eq!(
|
||||
fmt_time(file.last_access_time, "%m %d %M").as_str(),
|
||||
"11 05 32"
|
||||
);
|
||||
assert_eq!(
|
||||
fmt_time(file.last_change_time, "%m %d %M").as_str(),
|
||||
"11 05 32"
|
||||
);
|
||||
assert_eq!(
|
||||
fmt_time(file.creation_time, "%m %d %M").as_str(),
|
||||
"11 05 32"
|
||||
);
|
||||
} else {
|
||||
panic!("Expected file, got directory");
|
||||
}
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||
assert_eq!(file.name, String::from("omar.txt"));
|
||||
assert_eq!(file.size, 4096);
|
||||
assert!(file.symlink.is_none());
|
||||
assert_eq!(file.user, Some(0));
|
||||
assert_eq!(file.group, Some(9));
|
||||
assert_eq!(file.unix_pex.unwrap(), (7, 5, 5));
|
||||
assert_eq!(
|
||||
fmt_time(file.last_access_time, "%m %d %M").as_str(),
|
||||
"11 05 32"
|
||||
);
|
||||
assert_eq!(
|
||||
fmt_time(file.last_change_time, "%m %d %M").as_str(),
|
||||
"11 05 32"
|
||||
);
|
||||
assert_eq!(
|
||||
fmt_time(file.creation_time, "%m %d %M").as_str(),
|
||||
"11 05 32"
|
||||
);
|
||||
// Directory
|
||||
let fs_entry: FsEntry = ftp
|
||||
let dir: FsDirectory = ftp
|
||||
.parse_list_line(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"drwxrwxr-x 1 0 9 4096 Nov 5 2018 docs",
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::Directory(dir) = fs_entry {
|
||||
assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs"));
|
||||
assert_eq!(dir.name, String::from("docs"));
|
||||
assert!(dir.symlink.is_none());
|
||||
assert_eq!(dir.user, Some(0));
|
||||
assert_eq!(dir.group, Some(9));
|
||||
assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5));
|
||||
assert_eq!(
|
||||
dir.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(dir.readonly, false);
|
||||
} else {
|
||||
panic!("Expected directory, got directory");
|
||||
}
|
||||
.unwrap()
|
||||
.unwrap_dir();
|
||||
assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs"));
|
||||
assert_eq!(dir.name, String::from("docs"));
|
||||
assert!(dir.symlink.is_none());
|
||||
assert_eq!(dir.user, Some(0));
|
||||
assert_eq!(dir.group, Some(9));
|
||||
assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5));
|
||||
assert_eq!(
|
||||
dir.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1541376000)
|
||||
);
|
||||
assert_eq!(dir.readonly, false);
|
||||
// Error
|
||||
assert!(ftp
|
||||
.parse_list_line(
|
||||
|
@ -981,186 +1171,85 @@ mod tests {
|
|||
fn test_filetransfer_ftp_parse_list_line_dos() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Simple file
|
||||
let fs_entry: FsEntry = ftp
|
||||
let file: FsFile = ftp
|
||||
.parse_list_line(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"04-08-14 03:09PM 8192 omar.txt",
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::File(file) = fs_entry {
|
||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||
assert_eq!(file.name, String::from("omar.txt"));
|
||||
assert_eq!(file.size, 8192);
|
||||
assert!(file.symlink.is_none());
|
||||
assert_eq!(file.user, None);
|
||||
assert_eq!(file.group, None);
|
||||
assert_eq!(file.unix_pex, None);
|
||||
assert_eq!(
|
||||
file.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
file.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
file.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
} else {
|
||||
panic!("Expected file, got directory");
|
||||
}
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||
assert_eq!(file.name, String::from("omar.txt"));
|
||||
assert_eq!(file.size, 8192);
|
||||
assert!(file.symlink.is_none());
|
||||
assert_eq!(file.user, None);
|
||||
assert_eq!(file.group, None);
|
||||
assert_eq!(file.unix_pex, None);
|
||||
assert_eq!(
|
||||
file.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
file.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
file.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
// Directory
|
||||
let fs_entry: FsEntry = ftp
|
||||
let dir: FsDirectory = ftp
|
||||
.parse_list_line(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"04-08-14 03:09PM <DIR> docs",
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::Directory(dir) = fs_entry {
|
||||
assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs"));
|
||||
assert_eq!(dir.name, String::from("docs"));
|
||||
assert!(dir.symlink.is_none());
|
||||
assert_eq!(dir.user, None);
|
||||
assert_eq!(dir.group, None);
|
||||
assert_eq!(dir.unix_pex, None);
|
||||
assert_eq!(
|
||||
dir.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(dir.readonly, false);
|
||||
} else {
|
||||
panic!("Expected directory, got directory");
|
||||
}
|
||||
.unwrap()
|
||||
.unwrap_dir();
|
||||
assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs"));
|
||||
assert_eq!(dir.name, String::from("docs"));
|
||||
assert!(dir.symlink.is_none());
|
||||
assert_eq!(dir.user, None);
|
||||
assert_eq!(dir.group, None);
|
||||
assert_eq!(dir.unix_pex, None);
|
||||
assert_eq!(
|
||||
dir.last_access_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.last_change_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(
|
||||
dir.creation_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Duration::from_secs(1407164940)
|
||||
);
|
||||
assert_eq!(dir.readonly, false);
|
||||
// Error
|
||||
assert!(ftp
|
||||
.parse_list_line(PathBuf::from("/").as_path(), "04-08-14 omar.txt")
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_connect_unsecure_anonymous() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(String::from("speedtest.tele2.net"), 21, None, None)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_connect_unsecure_username() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
21,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_connect_secure() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(true);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
21,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_change_dir() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(String::from("speedtest.tele2.net"), 21, None, None)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Cwd
|
||||
assert!(ftp.change_dir(PathBuf::from("upload/").as_path()).is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/upload"));
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_copy() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(String::from("speedtest.tele2.net"), 21, None, None)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Copy
|
||||
let file: FsFile = FsFile {
|
||||
name: String::from("readme.txt"),
|
||||
abs_path: PathBuf::from("/readme.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
};
|
||||
assert!(ftp
|
||||
.copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt"))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_list_dir_dos_syntax() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
|
@ -1184,94 +1273,16 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn test_filetransfer_ftp_list_dir_unix_syntax() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(String::from("speedtest.tele2.net"), 21, None, None)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// List dir
|
||||
let files: Vec<FsEntry> = ftp.list_dir(PathBuf::from("/").as_path()).ok().unwrap();
|
||||
// There should be at least 1 file
|
||||
assert!(files.len() > 0);
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
/* NOTE: they don't work
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_recv() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp.connect(String::from("test.rebex.net"), 21, Some(String::from("demo")), Some(String::from("password"))).is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Recv 100KB
|
||||
assert!(ftp.recv_file(PathBuf::from("readme.txt").as_path()).is_ok());
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_send() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp.connect(String::from("speedtest.tele2.net"), 21, None, None).is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Cwd
|
||||
assert!(ftp.change_dir(PathBuf::from("upload/").as_path()).is_ok());
|
||||
// Pwd
|
||||
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/upload"));
|
||||
// Send a sample file 100KB
|
||||
assert!(ftp.send_file(PathBuf::from("test.txt").as_path()).is_ok());
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}*/
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_exec() {
|
||||
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(ftp
|
||||
.connect(String::from("speedtest.tele2.net"), 21, None, None)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert!(ftp.exec("echo 1;").is_err());
|
||||
// Disconnect
|
||||
assert!(ftp.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_find() {
|
||||
let mut client: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
// Connect
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
21,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Pwd
|
||||
assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Search for file (let's search for pop3-*.png); there should be 2
|
||||
let search_res: Vec<FsEntry> = client.find("pop3-*.png").ok().unwrap();
|
||||
assert_eq!(search_res.len(), 2);
|
||||
// verify names
|
||||
assert_eq!(search_res[0].get_name(), "pop3-browser.png");
|
||||
assert_eq!(search_res[1].get_name(), "pop3-console-client.png");
|
||||
// Search directory
|
||||
let search_res: Vec<FsEntry> = client.find("pub").ok().unwrap();
|
||||
assert_eq!(search_res.len(), 1);
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
// Verify err
|
||||
assert!(client.find("pippo").is_err());
|
||||
fn test_filetransfer_ftp_get_name_and_link() {
|
||||
let client: FtpFileTransfer = FtpFileTransfer::new(false);
|
||||
assert_eq!(
|
||||
client.get_name_and_link("Cargo.toml"),
|
||||
(String::from("Cargo.toml"), None)
|
||||
);
|
||||
assert_eq!(
|
||||
client.get_name_and_link("Cargo -> Cargo.toml"),
|
||||
(String::from("Cargo"), Some(PathBuf::from("Cargo.toml")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1295,9 +1306,25 @@ mod tests {
|
|||
assert!(ftp.disconnect().is_err());
|
||||
assert!(ftp.list_dir(Path::new("/tmp")).is_err());
|
||||
assert!(ftp.mkdir(Path::new("/tmp")).is_err());
|
||||
assert!(ftp
|
||||
.remove(&make_fsentry(PathBuf::from("/nowhere"), false))
|
||||
.is_err());
|
||||
assert!(ftp
|
||||
.rename(
|
||||
&make_fsentry(PathBuf::from("/nowhere"), false),
|
||||
PathBuf::from("/culonia").as_path()
|
||||
)
|
||||
.is_err());
|
||||
assert!(ftp.pwd().is_err());
|
||||
assert!(ftp.stat(Path::new("/tmp")).is_err());
|
||||
assert!(ftp.recv_file(&file).is_err());
|
||||
assert!(ftp.send_file(&file, Path::new("/tmp/omar.txt")).is_err());
|
||||
let (_, temp): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry();
|
||||
let readable: Box<dyn Read> = Box::new(std::fs::File::open(temp.path()).unwrap());
|
||||
assert!(ftp.on_recv(readable).is_err());
|
||||
let (_, temp): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry();
|
||||
let writable: Box<dyn Write> =
|
||||
Box::new(open_file(temp.path(), true, true, true).ok().unwrap());
|
||||
assert!(ftp.on_sent(writable).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -284,10 +284,7 @@ pub trait FileTransfer {
|
|||
if filter.matches(dir.name.as_str()) {
|
||||
drained.push(FsEntry::Directory(dir.clone()));
|
||||
}
|
||||
match self.iter_search(dir.abs_path.as_path(), filter) {
|
||||
Ok(mut filtered) => drained.append(&mut filtered),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
drained.append(&mut self.iter_search(dir.abs_path.as_path(), filter)?);
|
||||
}
|
||||
FsEntry::File(file) => {
|
||||
if filter.matches(file.name.as_str()) {
|
||||
|
|
|
@ -77,7 +77,7 @@ impl ScpFileTransfer {
|
|||
PathBuf::from(path_slash::PathExt::to_slash_lossy(p).as_str())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn resolve(p: &Path) -> PathBuf {
|
||||
p.to_path_buf()
|
||||
}
|
||||
|
@ -445,10 +445,9 @@ impl FileTransfer for ScpFileTransfer {
|
|||
self.session = Some(session);
|
||||
// Get working directory
|
||||
debug!("Getting working directory...");
|
||||
match self.perform_shell_cmd("pwd") {
|
||||
Ok(output) => self.wrkdir = PathBuf::from(output.as_str().trim()),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
self.wrkdir = self
|
||||
.perform_shell_cmd("pwd")
|
||||
.map(|x| PathBuf::from(x.as_str().trim()))?;
|
||||
info!(
|
||||
"Connection established; working directory: {}",
|
||||
self.wrkdir.display()
|
||||
|
@ -486,7 +485,7 @@ impl FileTransfer for ScpFileTransfer {
|
|||
///
|
||||
/// Indicates whether the client is connected to remote
|
||||
fn is_connected(&self) -> bool {
|
||||
self.session.as_ref().is_some()
|
||||
self.session.is_some()
|
||||
}
|
||||
|
||||
/// ### pwd
|
||||
|
@ -853,7 +852,15 @@ impl FileTransfer for ScpFileTransfer {
|
|||
) -> Result<Box<dyn Write>, FileTransferError> {
|
||||
match self.session.as_ref() {
|
||||
Some(session) => {
|
||||
let file_name: PathBuf = Self::resolve(file_name);
|
||||
let file_name: PathBuf = match file_name.is_absolute() {
|
||||
true => PathBuf::from(file_name),
|
||||
false => {
|
||||
let mut p: PathBuf = self.wrkdir.clone();
|
||||
p.push(file_name);
|
||||
Self::resolve(p.as_path())
|
||||
}
|
||||
};
|
||||
let file_name: PathBuf = Self::resolve(file_name.as_path());
|
||||
info!(
|
||||
"Sending file {} to {}",
|
||||
local.abs_path.display(),
|
||||
|
@ -963,8 +970,12 @@ impl FileTransfer for ScpFileTransfer {
|
|||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::utils::test_helpers::make_fsentry;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[cfg(feature = "with-containers")]
|
||||
use crate::utils::test_helpers::{create_sample_file_entry, write_file, write_ssh_key};
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_new() {
|
||||
let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
|
@ -973,31 +984,210 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_connect() {
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_scp_server() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert_eq!(client.is_connected(), false);
|
||||
// Sample file
|
||||
let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry();
|
||||
// Connect
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
String::from("127.0.0.1"),
|
||||
10222,
|
||||
Some(String::from("sftp")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/config"));
|
||||
assert_eq!(client.is_connected(), true);
|
||||
// Pwd
|
||||
assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap());
|
||||
// Stat
|
||||
let stat: FsFile = client
|
||||
.stat(PathBuf::from("sshd.pid").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(stat.abs_path, PathBuf::from("/config/sshd.pid"));
|
||||
let stat: FsDirectory = client
|
||||
.stat(PathBuf::from("/config/").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.unwrap_dir();
|
||||
assert_eq!(stat.abs_path, PathBuf::from("/config/"));
|
||||
// Stat (err)
|
||||
assert!(client
|
||||
.stat(PathBuf::from("/config/5t0ca220.log").as_path())
|
||||
.is_err());
|
||||
// List dir (dir has 4 (one is hidden :D) entries)
|
||||
assert!(client.list_dir(&Path::new("/config")).unwrap().len() >= 4);
|
||||
// Make directory
|
||||
assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok());
|
||||
// Make directory (err)
|
||||
assert!(client
|
||||
.mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path())
|
||||
.is_err());
|
||||
// Change directory
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/tmp/omar").as_path())
|
||||
.is_ok());
|
||||
// Change directory (err)
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path())
|
||||
.is_err());
|
||||
// Copy file
|
||||
assert!(client
|
||||
.copy(
|
||||
&make_fsentry(PathBuf::from("/config/sshd.pid"), false),
|
||||
PathBuf::from("/tmp/sshd.pid").as_path()
|
||||
)
|
||||
.is_ok());
|
||||
// Copy dir
|
||||
assert!(client
|
||||
.copy(
|
||||
&make_fsentry(PathBuf::from("/tmp/omar"), true),
|
||||
PathBuf::from("/tmp/ommlar").as_path()
|
||||
)
|
||||
.is_ok());
|
||||
// Copy (err)
|
||||
assert!(client
|
||||
.copy(
|
||||
&make_fsentry(PathBuf::from("/tmp/zattera"), false),
|
||||
PathBuf::from("/").as_path()
|
||||
)
|
||||
.is_err());
|
||||
// Exec
|
||||
assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n");
|
||||
// Change dir to ommlar
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/tmp/ommlar/").as_path())
|
||||
.is_ok());
|
||||
// Upload 2 files
|
||||
let mut writable = client
|
||||
.send_file(&entry, PathBuf::from("omar.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(client.on_sent(writable).is_ok());
|
||||
let mut writable = client
|
||||
.send_file(&entry, PathBuf::from("README.md").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(client.on_sent(writable).is_ok());
|
||||
// Upload file (err)
|
||||
assert!(client
|
||||
.send_file(&entry, PathBuf::from("/ommlar/omarone").as_path())
|
||||
.is_err());
|
||||
// List dir
|
||||
let list: Vec<FsEntry> = client
|
||||
.list_dir(PathBuf::from("/tmp/ommlar").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
assert_eq!(list.len(), 2);
|
||||
// Find
|
||||
assert_eq!(client.find("*.txt").ok().unwrap().len(), 1);
|
||||
assert_eq!(client.find("*.md").ok().unwrap().len(), 1);
|
||||
assert_eq!(client.find("*.jpeg").ok().unwrap().len(), 0);
|
||||
// Rename
|
||||
assert!(client
|
||||
.mkdir(PathBuf::from("/tmp/uploads").as_path())
|
||||
.is_ok());
|
||||
assert!(client
|
||||
.rename(
|
||||
list.get(0).unwrap(),
|
||||
PathBuf::from("/tmp/uploads/README.txt").as_path()
|
||||
)
|
||||
.is_ok());
|
||||
// Rename (err)
|
||||
assert!(client
|
||||
.rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path())
|
||||
.is_err());
|
||||
let dummy: FsEntry = FsEntry::File(FsFile {
|
||||
name: String::from("cucumber.txt"),
|
||||
abs_path: PathBuf::from("/cucumber.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
assert!(client
|
||||
.rename(&dummy, PathBuf::from("/a/b/c").as_path())
|
||||
.is_err());
|
||||
// Remove
|
||||
assert!(client.remove(list.get(1).unwrap()).is_ok());
|
||||
// Receive file
|
||||
let mut writable = client
|
||||
.send_file(&entry, PathBuf::from("/tmp/uploads/README.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(client.on_sent(writable).is_ok());
|
||||
let file: FsFile = client
|
||||
.list_dir(PathBuf::from("/tmp/uploads").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone()
|
||||
.unwrap_file();
|
||||
let mut readable = client.recv_file(&file).ok().unwrap();
|
||||
let mut data: Vec<u8> = vec![0; 1024];
|
||||
assert!(readable.read(&mut data).is_ok());
|
||||
assert!(client.on_recv(readable).is_ok());
|
||||
// Receive file (err)
|
||||
assert!(client.recv_file(&entry).is_err());
|
||||
// Cleanup
|
||||
assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok());
|
||||
assert!(client
|
||||
.remove(&make_fsentry(PathBuf::from("/tmp/ommlar"), true))
|
||||
.is_ok());
|
||||
assert!(client
|
||||
.remove(&make_fsentry(PathBuf::from("/tmp/omar"), true))
|
||||
.is_ok());
|
||||
assert!(client
|
||||
.remove(&make_fsentry(PathBuf::from("/tmp/uploads"), true))
|
||||
.is_ok());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
assert_eq!(client.is_connected(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_scp_ssh_storage() {
|
||||
let mut storage: SshKeyStorage = SshKeyStorage::empty();
|
||||
let key_file: tempfile::NamedTempFile = write_ssh_key();
|
||||
storage.add_key("127.0.0.1", "sftp", key_file.path().to_path_buf());
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(storage);
|
||||
// Connect
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("127.0.0.1"),
|
||||
10222,
|
||||
Some(String::from("sftp")),
|
||||
None,
|
||||
)
|
||||
.is_ok());
|
||||
assert_eq!(client.is_connected(), true);
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_bad_auth() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
String::from("127.0.0.1"),
|
||||
10222,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("badpassword"))
|
||||
)
|
||||
|
@ -1005,10 +1195,11 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_scp_no_credentials() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(String::from("test.rebex.net"), 22, None, None)
|
||||
.connect(String::from("127.0.0.1"), 10222, None, None)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
|
@ -1024,245 +1215,92 @@ mod tests {
|
|||
)
|
||||
.is_err());
|
||||
}
|
||||
#[test]
|
||||
fn test_filetransfer_scp_pwd() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Pwd
|
||||
assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/"));
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
fn test_filetransfer_scp_cwd() {
|
||||
fn test_filetransfer_scp_parse_ls() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
// File
|
||||
let entry: FsFile = client
|
||||
.parse_ls_output(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"-rw-r--r-- 1 root root 2056 giu 13 21:11 Cargo.toml",
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Cwd (relative)
|
||||
assert!(client.change_dir(PathBuf::from("pub/").as_path()).is_ok());
|
||||
// Cwd (absolute)
|
||||
assert!(client.change_dir(PathBuf::from("/pub").as_path()).is_ok());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_cwd_error() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Cwd (abs)
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/omar/gabber").as_path())
|
||||
.is_err());
|
||||
// Cwd (rel)
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("gomar/pett").as_path())
|
||||
.is_err());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_ls() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// List dir
|
||||
let pwd: PathBuf = client.pwd().ok().unwrap();
|
||||
let files: Vec<FsEntry> = client.list_dir(pwd.as_path()).ok().unwrap();
|
||||
assert_eq!(files.len(), 3); // There are 3 files
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_stat() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
let file: FsEntry = client
|
||||
.stat(PathBuf::from("readme.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::File(file) = file {
|
||||
assert_eq!(file.abs_path, PathBuf::from("/readme.txt"));
|
||||
} else {
|
||||
panic!("Expected readme.txt to be a file");
|
||||
}
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(entry.name.as_str(), "Cargo.toml");
|
||||
assert_eq!(entry.abs_path, PathBuf::from("/tmp/Cargo.toml"));
|
||||
assert_eq!(entry.unix_pex.unwrap(), (6, 4, 4));
|
||||
assert_eq!(entry.size, 2056);
|
||||
assert_eq!(entry.readonly, false);
|
||||
assert_eq!(entry.ftype.unwrap().as_str(), "toml");
|
||||
assert!(entry.symlink.is_none());
|
||||
// File (year)
|
||||
let entry: FsFile = client
|
||||
.parse_ls_output(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"-rw-rw-rw- 1 root root 3368 nov 7 2020 CODE_OF_CONDUCT.md",
|
||||
)
|
||||
.ok()
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(entry.name.as_str(), "CODE_OF_CONDUCT.md");
|
||||
assert_eq!(entry.abs_path, PathBuf::from("/tmp/CODE_OF_CONDUCT.md"));
|
||||
assert_eq!(entry.unix_pex.unwrap(), (6, 6, 6));
|
||||
assert_eq!(entry.size, 3368);
|
||||
assert_eq!(entry.readonly, false);
|
||||
assert_eq!(entry.ftype.unwrap().as_str(), "md");
|
||||
assert!(entry.symlink.is_none());
|
||||
// Directory
|
||||
let entry: FsDirectory = client
|
||||
.parse_ls_output(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"drwxr-xr-x 1 root root 512 giu 13 21:11 docs",
|
||||
)
|
||||
.ok()
|
||||
.unwrap()
|
||||
.unwrap_dir();
|
||||
assert_eq!(entry.name.as_str(), "docs");
|
||||
assert_eq!(entry.abs_path, PathBuf::from("/tmp/docs"));
|
||||
assert_eq!(entry.unix_pex.unwrap(), (7, 5, 5));
|
||||
assert_eq!(entry.readonly, false);
|
||||
assert!(entry.symlink.is_none());
|
||||
// Short metadata
|
||||
assert!(client
|
||||
.parse_ls_output(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"drwxr-xr-x 1 root root 512 giu 13 21:11",
|
||||
)
|
||||
.is_err());
|
||||
// Special file
|
||||
assert!(client
|
||||
.parse_ls_output(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"crwxr-xr-x 1 root root 512 giu 13 21:11 ttyS1",
|
||||
)
|
||||
.is_err());
|
||||
// Bad pex
|
||||
assert!(client
|
||||
.parse_ls_output(
|
||||
PathBuf::from("/tmp").as_path(),
|
||||
"-rwxr-xr 1 root root 512 giu 13 21:11 ttyS1",
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_exec() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Exec
|
||||
assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n");
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
fn test_filetransfer_scp_get_name_and_link() {
|
||||
let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert_eq!(
|
||||
client.get_name_and_link("Cargo.toml"),
|
||||
(String::from("Cargo.toml"), None)
|
||||
);
|
||||
assert_eq!(
|
||||
client.get_name_and_link("Cargo -> Cargo.toml"),
|
||||
(String::from("Cargo"), Some(PathBuf::from("Cargo.toml")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
//#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
fn test_filetransfer_scp_find() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Search for file (let's search for pop3-*.png); there should be 2
|
||||
let search_res: Vec<FsEntry> = client.find("pop3-*.png").ok().unwrap();
|
||||
assert_eq!(search_res.len(), 2);
|
||||
// verify names
|
||||
assert_eq!(search_res[0].get_name(), "pop3-browser.png");
|
||||
assert_eq!(search_res[1].get_name(), "pop3-console-client.png");
|
||||
// Search directory
|
||||
let search_res: Vec<FsEntry> = client.find("pub").ok().unwrap();
|
||||
assert_eq!(search_res.len(), 1);
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
// Verify err
|
||||
assert!(client.find("pippo").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_recv() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
let file: FsFile = FsFile {
|
||||
name: String::from("readme.txt"),
|
||||
abs_path: PathBuf::from("/readme.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
};
|
||||
// Receive file
|
||||
assert!(client.recv_file(&file).is_ok());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_filetransfer_scp_recv_failed_nosuchfile() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Receive file
|
||||
let file: FsFile = FsFile {
|
||||
name: String::from("omar.txt"),
|
||||
abs_path: PathBuf::from("/omar.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
};
|
||||
assert!(client.recv_file(&file).is_err());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
// NOTE: other functions doesn't work with this test scp server
|
||||
|
||||
/* NOTE: the server doesn't allow you to create directories
|
||||
#[test]
|
||||
fn test_filetransfer_scp_mkdir() {
|
||||
let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok());
|
||||
let dir: String = String::from("foo");
|
||||
// Mkdir
|
||||
assert!(client.mkdir(dir).is_ok());
|
||||
// cwd
|
||||
assert!(client.change_dir(PathBuf::from("foo/").as_path()).is_ok());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/foo"));
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_scp_uninitialized() {
|
||||
let file: FsFile = FsFile {
|
||||
|
@ -1282,9 +1320,19 @@ mod tests {
|
|||
let mut scp: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(scp.change_dir(Path::new("/tmp")).is_err());
|
||||
assert!(scp.disconnect().is_err());
|
||||
assert!(scp.exec("echo 5").is_err());
|
||||
assert!(scp.list_dir(Path::new("/tmp")).is_err());
|
||||
assert!(scp.mkdir(Path::new("/tmp")).is_err());
|
||||
assert!(scp.pwd().is_err());
|
||||
assert!(scp
|
||||
.remove(&make_fsentry(PathBuf::from("/nowhere"), false))
|
||||
.is_err());
|
||||
assert!(scp
|
||||
.rename(
|
||||
&make_fsentry(PathBuf::from("/nowhere"), false),
|
||||
PathBuf::from("/culonia").as_path()
|
||||
)
|
||||
.is_err());
|
||||
assert!(scp.stat(Path::new("/tmp")).is_err());
|
||||
assert!(scp.recv_file(&file).is_err());
|
||||
assert!(scp.send_file(&file, Path::new("/tmp/omar.txt")).is_err());
|
||||
|
|
|
@ -466,10 +466,7 @@ impl FileTransfer for SftpFileTransfer {
|
|||
match self.sftp.as_ref() {
|
||||
Some(_) => {
|
||||
// Change working directory
|
||||
self.wrkdir = match self.get_remote_path(dir) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
self.wrkdir = self.get_remote_path(dir)?;
|
||||
info!("Changed working directory to {}", self.wrkdir.display());
|
||||
Ok(self.wrkdir.clone())
|
||||
}
|
||||
|
@ -532,10 +529,7 @@ impl FileTransfer for SftpFileTransfer {
|
|||
match self.sftp.as_ref() {
|
||||
Some(sftp) => {
|
||||
// Get path
|
||||
let dir: PathBuf = match self.get_remote_path(path) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let dir: PathBuf = self.get_remote_path(path)?;
|
||||
info!("Getting file entries in {}", path.display());
|
||||
// Get files
|
||||
match sftp.readdir(dir.as_path()) {
|
||||
|
@ -609,10 +603,7 @@ impl FileTransfer for SftpFileTransfer {
|
|||
// Remove recursively
|
||||
debug!("{} is a directory; removing all directory entries", d.name);
|
||||
// Get directory files
|
||||
let directory_content: Vec<FsEntry> = match self.list_dir(d.abs_path.as_path()) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let directory_content: Vec<FsEntry> = self.list_dir(d.abs_path.as_path())?;
|
||||
for entry in directory_content.iter() {
|
||||
if let Err(err) = self.remove(&entry) {
|
||||
return Err(err);
|
||||
|
@ -666,10 +657,7 @@ impl FileTransfer for SftpFileTransfer {
|
|||
match self.sftp.as_ref() {
|
||||
Some(sftp) => {
|
||||
// Get path
|
||||
let dir: PathBuf = match self.get_remote_path(path) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let dir: PathBuf = self.get_remote_path(path)?;
|
||||
info!("Stat file {}", dir.display());
|
||||
// Get file
|
||||
match sftp.stat(dir.as_path()) {
|
||||
|
@ -758,10 +746,7 @@ impl FileTransfer for SftpFileTransfer {
|
|||
)),
|
||||
Some(sftp) => {
|
||||
// Get remote file name
|
||||
let remote_path: PathBuf = match self.get_remote_path(file.abs_path.as_path()) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let remote_path: PathBuf = self.get_remote_path(file.abs_path.as_path())?;
|
||||
info!("Receiving file {}", remote_path.display());
|
||||
// Open remote file
|
||||
match sftp.open(remote_path.as_path()) {
|
||||
|
@ -800,7 +785,9 @@ impl FileTransfer for SftpFileTransfer {
|
|||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::utils::test_helpers::make_fsentry;
|
||||
#[cfg(feature = "with-containers")]
|
||||
use crate::utils::test_helpers::{create_sample_file_entry, write_file, write_ssh_key};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
|
@ -813,34 +800,188 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_connect() {
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_sftp_server() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert_eq!(client.is_connected(), false);
|
||||
// Sample file
|
||||
let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry();
|
||||
// Connect
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
String::from("127.0.0.1"),
|
||||
10022,
|
||||
Some(String::from("sftp")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/config"));
|
||||
assert_eq!(client.is_connected(), true);
|
||||
// Pwd
|
||||
assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap());
|
||||
// Stat
|
||||
let stat: FsFile = client
|
||||
.stat(PathBuf::from("/config/sshd.pid").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.unwrap_file();
|
||||
assert_eq!(stat.name.as_str(), "sshd.pid");
|
||||
let stat: FsDirectory = client
|
||||
.stat(PathBuf::from("/config").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.unwrap_dir();
|
||||
assert_eq!(stat.name.as_str(), "config");
|
||||
// Stat (err)
|
||||
assert!(client
|
||||
.stat(PathBuf::from("/config/5t0ca220.log").as_path())
|
||||
.is_err());
|
||||
// List dir (dir has 4 (one is hidden :D) entries)
|
||||
assert!(client.list_dir(&Path::new("/config")).unwrap().len() >= 4);
|
||||
// Make directory
|
||||
assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok());
|
||||
// Make directory (err)
|
||||
assert!(client
|
||||
.mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path())
|
||||
.is_err());
|
||||
// Change directory
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/tmp/omar").as_path())
|
||||
.is_ok());
|
||||
// Change directory (err)
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path())
|
||||
.is_err());
|
||||
// Copy (not supported)
|
||||
assert!(client
|
||||
.copy(&FsEntry::File(entry.clone()), PathBuf::from("/").as_path())
|
||||
.is_err());
|
||||
// Exec
|
||||
assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n");
|
||||
// Upload 2 files
|
||||
let mut writable = client
|
||||
.send_file(&entry, PathBuf::from("omar.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(client.on_sent(writable).is_ok());
|
||||
let mut writable = client
|
||||
.send_file(&entry, PathBuf::from("README.md").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(client.on_sent(writable).is_ok());
|
||||
// Upload file (err)
|
||||
assert!(client
|
||||
.send_file(&entry, PathBuf::from("/ommlar/omarone").as_path())
|
||||
.is_err());
|
||||
// List dir
|
||||
let list: Vec<FsEntry> = client
|
||||
.list_dir(PathBuf::from("/tmp/omar").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
assert_eq!(list.len(), 2);
|
||||
// Find
|
||||
assert_eq!(client.find("*.txt").ok().unwrap().len(), 1);
|
||||
assert_eq!(client.find("*.md").ok().unwrap().len(), 1);
|
||||
assert_eq!(client.find("*.jpeg").ok().unwrap().len(), 0);
|
||||
// Rename
|
||||
assert!(client
|
||||
.mkdir(PathBuf::from("/tmp/uploads").as_path())
|
||||
.is_ok());
|
||||
assert!(client
|
||||
.rename(
|
||||
list.get(0).unwrap(),
|
||||
PathBuf::from("/tmp/uploads/README.txt").as_path()
|
||||
)
|
||||
.is_ok());
|
||||
// Rename (err)
|
||||
assert!(client
|
||||
.rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path())
|
||||
.is_err());
|
||||
let dummy: FsEntry = FsEntry::File(FsFile {
|
||||
name: String::from("cucumber.txt"),
|
||||
abs_path: PathBuf::from("/cucumber.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
assert!(client
|
||||
.rename(&dummy, PathBuf::from("/a/b/c").as_path())
|
||||
.is_err());
|
||||
// Remove
|
||||
assert!(client.remove(list.get(1).unwrap()).is_ok());
|
||||
assert!(client.remove(list.get(1).unwrap()).is_err());
|
||||
// Receive file
|
||||
let mut writable = client
|
||||
.send_file(&entry, PathBuf::from("/tmp/uploads/README.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
write_file(&file, &mut writable);
|
||||
assert!(client.on_sent(writable).is_ok());
|
||||
let file: FsFile = client
|
||||
.list_dir(PathBuf::from("/tmp/uploads").as_path())
|
||||
.ok()
|
||||
.unwrap()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone()
|
||||
.unwrap_file();
|
||||
let mut readable = client.recv_file(&file).ok().unwrap();
|
||||
let mut data: Vec<u8> = vec![0; 1024];
|
||||
assert!(readable.read(&mut data).is_ok());
|
||||
assert!(client.on_recv(readable).is_ok());
|
||||
// Receive file (err)
|
||||
assert!(client.recv_file(&entry).is_err());
|
||||
// Cleanup
|
||||
assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok());
|
||||
assert!(client
|
||||
.remove(&make_fsentry(PathBuf::from("/tmp/omar"), true))
|
||||
.is_ok());
|
||||
assert!(client
|
||||
.remove(&make_fsentry(PathBuf::from("/tmp/uploads"), true))
|
||||
.is_ok());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
assert_eq!(client.is_connected(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_sftp_ssh_storage() {
|
||||
let mut storage: SshKeyStorage = SshKeyStorage::empty();
|
||||
let key_file: tempfile::NamedTempFile = write_ssh_key();
|
||||
storage.add_key("127.0.0.1", "sftp", key_file.path().to_path_buf());
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(storage);
|
||||
// Connect
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("127.0.0.1"),
|
||||
10022,
|
||||
Some(String::from("sftp")),
|
||||
None,
|
||||
)
|
||||
.is_ok());
|
||||
assert_eq!(client.is_connected(), true);
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_bad_auth() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
String::from("127.0.0.1"),
|
||||
10022,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("badpassword"))
|
||||
)
|
||||
|
@ -848,13 +989,52 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_sftp_no_credentials() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(String::from("test.rebex.net"), 22, None, None)
|
||||
.connect(String::from("127.0.0.1"), 10022, None, None)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_filetransfer_sftp_get_remote_path() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
// Connect
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("127.0.0.1"),
|
||||
10022,
|
||||
Some(String::from("sftp")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// get realpath
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/config").as_path())
|
||||
.is_ok());
|
||||
assert_eq!(
|
||||
client
|
||||
.get_remote_path(PathBuf::from("sshd.pid").as_path())
|
||||
.ok()
|
||||
.unwrap(),
|
||||
PathBuf::from("/config/sshd.pid")
|
||||
);
|
||||
// No such file
|
||||
assert!(client
|
||||
.get_remote_path(PathBuf::from("omarone.txt").as_path())
|
||||
.is_err());
|
||||
// Ok abs path
|
||||
assert_eq!(
|
||||
client
|
||||
.get_remote_path(PathBuf::from("/config/sshd.pid").as_path())
|
||||
.ok()
|
||||
.unwrap(),
|
||||
PathBuf::from("/config/sshd.pid")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_bad_server() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
|
@ -868,302 +1048,6 @@ mod tests {
|
|||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_pwd() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
// Pwd
|
||||
assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_cwd() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
// Pwd
|
||||
assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap());
|
||||
// Cwd (relative)
|
||||
assert!(client.change_dir(PathBuf::from("pub/").as_path()).is_ok());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/pub"));
|
||||
// Cwd (absolute)
|
||||
assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_copy() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
// Copy
|
||||
let file: FsFile = FsFile {
|
||||
name: String::from("readme.txt"),
|
||||
abs_path: PathBuf::from("/readme.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
};
|
||||
assert!(client
|
||||
.copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt"))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_cwd_error() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Cwd (abs)
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("/omar/gabber").as_path())
|
||||
.is_err());
|
||||
// Cwd (rel)
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("gomar/pett").as_path())
|
||||
.is_err());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
assert!(client
|
||||
.change_dir(PathBuf::from("gomar/pett").as_path())
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_ls() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
// List dir
|
||||
let pwd: PathBuf = client.pwd().ok().unwrap();
|
||||
let files: Vec<FsEntry> = client.list_dir(pwd.as_path()).ok().unwrap();
|
||||
assert_eq!(files.len(), 3); // There are 3 files
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
// Verify err
|
||||
assert!(client.list_dir(pwd.as_path()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_stat() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
let file: FsEntry = client
|
||||
.stat(PathBuf::from("readme.txt").as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
if let FsEntry::File(file) = file {
|
||||
assert_eq!(file.abs_path, PathBuf::from("/readme.txt"));
|
||||
} else {
|
||||
panic!("Expected readme.txt to be a file");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_exec() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Exec
|
||||
assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n");
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
// Verify err
|
||||
assert!(client.exec("echo 1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_find() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and scp
|
||||
assert!(client.session.is_some());
|
||||
// Search for file (let's search for pop3-*.png); there should be 2
|
||||
let search_res: Vec<FsEntry> = client.find("pop3-*.png").ok().unwrap();
|
||||
assert_eq!(search_res.len(), 2);
|
||||
// verify names
|
||||
assert_eq!(search_res[0].get_name(), "pop3-browser.png");
|
||||
assert_eq!(search_res[1].get_name(), "pop3-console-client.png");
|
||||
// Search directory
|
||||
let search_res: Vec<FsEntry> = client.find("pub").ok().unwrap();
|
||||
assert_eq!(search_res.len(), 1);
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
// Verify err
|
||||
assert!(client.find("pippo").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_recv() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
let file: FsFile = FsFile {
|
||||
name: String::from("readme.txt"),
|
||||
abs_path: PathBuf::from("/readme.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
};
|
||||
// Receive file
|
||||
assert!(client.recv_file(&file).is_ok());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_recv_failed_nosuchfile() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client
|
||||
.connect(
|
||||
String::from("test.rebex.net"),
|
||||
22,
|
||||
Some(String::from("demo")),
|
||||
Some(String::from("password"))
|
||||
)
|
||||
.is_ok());
|
||||
// Check session and sftp
|
||||
assert!(client.session.is_some());
|
||||
assert!(client.sftp.is_some());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/"));
|
||||
// Receive file
|
||||
let file: FsFile = FsFile {
|
||||
name: String::from("omar.txt"),
|
||||
abs_path: PathBuf::from("/omar.txt"),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 0,
|
||||
ftype: Some(String::from("txt")), // File type
|
||||
readonly: true,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
};
|
||||
assert!(client.recv_file(&file).is_err());
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
|
||||
// NOTE: other functions doesn't work with this test SFTP server
|
||||
|
||||
/* NOTE: the server doesn't allow you to create directories
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_mkdir() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok());
|
||||
let dir: String = String::from("foo");
|
||||
// Mkdir
|
||||
assert!(client.mkdir(dir).is_ok());
|
||||
// cwd
|
||||
assert!(client.change_dir(PathBuf::from("foo/").as_path()).is_ok());
|
||||
assert_eq!(client.wrkdir, PathBuf::from("/foo"));
|
||||
// Disconnect
|
||||
assert!(client.disconnect().is_ok());
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_filetransfer_sftp_uninitialized() {
|
||||
let file: FsFile = FsFile {
|
||||
|
@ -1182,10 +1066,26 @@ mod tests {
|
|||
};
|
||||
let mut sftp: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
|
||||
assert!(sftp.change_dir(Path::new("/tmp")).is_err());
|
||||
assert!(sftp
|
||||
.copy(
|
||||
&make_fsentry(PathBuf::from("/nowhere"), false),
|
||||
PathBuf::from("/culonia").as_path()
|
||||
)
|
||||
.is_err());
|
||||
assert!(sftp.exec("echo 5").is_err());
|
||||
assert!(sftp.disconnect().is_err());
|
||||
assert!(sftp.list_dir(Path::new("/tmp")).is_err());
|
||||
assert!(sftp.mkdir(Path::new("/tmp")).is_err());
|
||||
assert!(sftp.pwd().is_err());
|
||||
assert!(sftp
|
||||
.remove(&make_fsentry(PathBuf::from("/nowhere"), false))
|
||||
.is_err());
|
||||
assert!(sftp
|
||||
.rename(
|
||||
&make_fsentry(PathBuf::from("/nowhere"), false),
|
||||
PathBuf::from("/culonia").as_path()
|
||||
)
|
||||
.is_err());
|
||||
assert!(sftp.stat(Path::new("/tmp")).is_err());
|
||||
assert!(sftp.recv_file(&file).is_err());
|
||||
assert!(sftp.send_file(&file, Path::new("/tmp/omar.txt")).is_err());
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
// Deps
|
||||
extern crate bytesize;
|
||||
extern crate regex;
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
extern crate users;
|
||||
// Locals
|
||||
use super::FsEntry;
|
||||
|
@ -36,7 +36,7 @@ use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time};
|
|||
// Ext
|
||||
use bytesize::ByteSize;
|
||||
use regex::Regex;
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
use users::{get_group_by_gid, get_user_by_uid};
|
||||
// Types
|
||||
// FmtCallback: Formatter, fsentry: &FsEntry, cur_str, prefix, length, extra
|
||||
|
@ -251,7 +251,7 @@ impl Formatter {
|
|||
_fmt_extra: Option<&String>,
|
||||
) -> String {
|
||||
// Get username
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
let group: String = match fsentry.get_group() {
|
||||
Some(gid) => match get_group_by_gid(gid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
|
@ -431,7 +431,7 @@ impl Formatter {
|
|||
_fmt_extra: Option<&String>,
|
||||
) -> String {
|
||||
// Get username
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
let username: String = match fsentry.get_user() {
|
||||
Some(uid) => match get_user_by_uid(uid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
|
@ -605,7 +605,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
|
@ -636,7 +636,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
|
@ -667,7 +667,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: None, // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
|
@ -698,7 +698,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: None, // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
|
@ -734,7 +734,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((7, 5, 5)), // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
|
@ -763,7 +763,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: None, // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
formatter.fmt(&entry),
|
||||
format!(
|
||||
|
|
|
@ -306,6 +306,13 @@ impl FileExplorer {
|
|||
pub fn toggle_hidden_files(&mut self) {
|
||||
self.opts.toggle(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||
}
|
||||
|
||||
/// ### hidden_files_visible
|
||||
///
|
||||
/// Returns whether hidden files are visible
|
||||
pub fn hidden_files_visible(&self) -> bool {
|
||||
self.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES)
|
||||
}
|
||||
}
|
||||
|
||||
// Traits
|
||||
|
@ -411,6 +418,7 @@ mod tests {
|
|||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
// Don't show hidden files
|
||||
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||
assert_eq!(explorer.hidden_files_visible(), false);
|
||||
// Create files
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
|
@ -434,6 +442,7 @@ mod tests {
|
|||
assert_eq!(explorer.iter_files().count(), 4);
|
||||
// Toggle hidden
|
||||
explorer.toggle_hidden_files();
|
||||
assert_eq!(explorer.hidden_files_visible(), true);
|
||||
assert_eq!(explorer.iter_files().count(), 6); // All files are returned now
|
||||
}
|
||||
|
||||
|
@ -586,7 +595,7 @@ mod tests {
|
|||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
explorer.fmt_file(&entry),
|
||||
format!(
|
||||
|
|
|
@ -226,6 +226,27 @@ impl FsEntry {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// ### unwrap_file
|
||||
///
|
||||
/// Unwrap FsEntry as FsFile
|
||||
pub fn unwrap_file(self) -> FsFile {
|
||||
match self {
|
||||
FsEntry::File(file) => file,
|
||||
_ => panic!("unwrap_file: not a file"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// ### unwrap_dir
|
||||
///
|
||||
/// Unwrap FsEntry as FsDirectory
|
||||
pub fn unwrap_dir(self) -> FsDirectory {
|
||||
match self {
|
||||
FsEntry::Directory(dir) => dir,
|
||||
_ => panic!("unwrap_dir: not a directory"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -262,6 +283,7 @@ mod tests {
|
|||
assert_eq!(entry.is_dir(), true);
|
||||
assert_eq!(entry.is_file(), false);
|
||||
assert_eq!(entry.get_unix_pex(), Some((7, 5, 5)));
|
||||
assert_eq!(entry.unwrap_dir().abs_path, PathBuf::from("/foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -294,6 +316,47 @@ mod tests {
|
|||
assert_eq!(entry.is_symlink(), false);
|
||||
assert_eq!(entry.is_dir(), false);
|
||||
assert_eq!(entry.is_file(), true);
|
||||
assert_eq!(entry.unwrap_file().abs_path, PathBuf::from("/bar.txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_fs_fsentry_file_unwrap_bad() {
|
||||
let t_now: SystemTime = SystemTime::now();
|
||||
let entry: FsEntry = FsEntry::File(FsFile {
|
||||
name: String::from("bar.txt"),
|
||||
abs_path: PathBuf::from("/bar.txt"),
|
||||
last_change_time: t_now,
|
||||
last_access_time: t_now,
|
||||
creation_time: t_now,
|
||||
size: 8192,
|
||||
readonly: false,
|
||||
ftype: Some(String::from("txt")),
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
});
|
||||
entry.unwrap_dir();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_fs_fsentry_dir_unwrap_bad() {
|
||||
let t_now: SystemTime = SystemTime::now();
|
||||
let entry: FsEntry = FsEntry::Directory(FsDirectory {
|
||||
name: String::from("foo"),
|
||||
abs_path: PathBuf::from("/foo"),
|
||||
last_change_time: t_now,
|
||||
last_access_time: t_now,
|
||||
creation_time: t_now,
|
||||
readonly: false,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((7, 5, 5)), // UNIX only
|
||||
});
|
||||
entry.unwrap_file();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
150
src/host/mod.rs
150
src/host/mod.rs
|
@ -34,9 +34,9 @@ use std::time::SystemTime;
|
|||
use thiserror::Error;
|
||||
use wildmatch::WildMatch;
|
||||
// Metadata ext
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::fs::set_permissions;
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
|
||||
// Locals
|
||||
|
@ -439,7 +439,7 @@ impl Localhost {
|
|||
/// ### stat
|
||||
///
|
||||
/// Stat file and create a FsEntry
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> {
|
||||
info!("Stating file {}", path.display());
|
||||
let path: PathBuf = self.to_abs_path(path);
|
||||
|
@ -605,7 +605,7 @@ impl Localhost {
|
|||
/// ### chmod
|
||||
///
|
||||
/// Change file mode to file, according to UNIX permissions
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn chmod(&self, path: &Path, pex: (u8, u8, u8)) -> Result<(), HostError> {
|
||||
let path: PathBuf = self.to_abs_path(path);
|
||||
// Get metadta
|
||||
|
@ -773,10 +773,7 @@ impl Localhost {
|
|||
if filter.matches(dir.name.as_str()) {
|
||||
drained.push(FsEntry::Directory(dir.clone()));
|
||||
}
|
||||
match self.iter_search(dir.abs_path.as_path(), filter) {
|
||||
Ok(mut filtered) => drained.append(&mut filtered),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
drained.append(&mut self.iter_search(dir.abs_path.as_path(), filter)?);
|
||||
}
|
||||
FsEntry::File(file) => {
|
||||
if filter.matches(file.name.as_str()) {
|
||||
|
@ -793,7 +790,7 @@ impl Localhost {
|
|||
/// ### u32_to_mode
|
||||
///
|
||||
/// Return string with format xxxxxx to tuple of permissions (user, group, others)
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn u32_to_mode(&self, mode: u32) -> (u8, u8, u8) {
|
||||
let user: u8 = ((mode >> 6) & 0x7) as u8;
|
||||
let group: u8 = ((mode >> 3) & 0x7) as u8;
|
||||
|
@ -804,7 +801,7 @@ impl Localhost {
|
|||
/// mode_to_u32
|
||||
///
|
||||
/// Convert owner,group,others to u32
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn mode_to_u32(&self, mode: (u8, u8, u8)) -> u32 {
|
||||
((mode.0 as u32) << 6) + ((mode.1 as u32) << 3) + mode.2 as u32
|
||||
}
|
||||
|
@ -829,12 +826,17 @@ impl Localhost {
|
|||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::utils::test_helpers::{create_sample_file, make_fsentry};
|
||||
use crate::utils::test_helpers::{make_dir_at, make_file_at};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::fs::File;
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::io::Write;
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::os::unix::fs::{symlink, PermissionsExt};
|
||||
|
||||
#[test]
|
||||
|
@ -846,7 +848,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_new() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
assert_eq!(host.wrkdir, PathBuf::from("/dev"));
|
||||
|
@ -882,14 +884,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_pwd() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
assert_eq!(host.pwd(), PathBuf::from("/dev"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_list_files() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Scan dir
|
||||
|
@ -902,7 +904,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_change_dir() {
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
let new_dir: PathBuf = PathBuf::from("/dev");
|
||||
|
@ -918,7 +920,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[should_panic]
|
||||
fn test_host_localhost_change_dir_failed() {
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
|
@ -927,7 +929,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_open_read() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Create temp file
|
||||
|
@ -936,7 +938,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[should_panic]
|
||||
fn test_host_localhost_open_read_err_no_such_file() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
|
@ -946,7 +948,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
fn test_host_localhost_open_read_err_not_accessible() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
let file: tempfile::NamedTempFile = create_sample_file();
|
||||
|
@ -957,7 +959,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_open_write() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
// Create temp file
|
||||
|
@ -966,7 +968,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
fn test_host_localhost_open_write_err() {
|
||||
let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
||||
let file: tempfile::NamedTempFile = create_sample_file();
|
||||
|
@ -975,7 +977,8 @@ mod tests {
|
|||
//fs::set_permissions(file.path(), perms)?;
|
||||
assert!(host.open_file_write(file.path()).is_err());
|
||||
}
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[test]
|
||||
fn test_host_localhost_symlinks() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
@ -1023,7 +1026,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_mkdir() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
|
@ -1038,10 +1041,17 @@ mod tests {
|
|||
assert!(host
|
||||
.mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true)
|
||||
.is_ok());
|
||||
// Fail
|
||||
assert!(host
|
||||
.mkdir_ex(
|
||||
PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(),
|
||||
true
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_remove() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
// Create sample file
|
||||
|
@ -1060,10 +1070,17 @@ mod tests {
|
|||
let files: Vec<FsEntry> = host.list_dir();
|
||||
assert_eq!(files.len(), 1); // There should be 1 file now
|
||||
assert!(host.remove(files.get(0).unwrap()).is_ok());
|
||||
// Remove unexisting directory
|
||||
assert!(host
|
||||
.remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true))
|
||||
.is_err());
|
||||
assert!(host
|
||||
.remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_host_localhost_rename() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
// Create sample file
|
||||
|
@ -1073,7 +1090,7 @@ mod tests {
|
|||
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
let files: Vec<FsEntry> = host.list_dir();
|
||||
assert_eq!(files.len(), 1); // There should be 1 file now
|
||||
assert_eq!(get_filename(files.get(0).unwrap()), String::from("foo.txt"));
|
||||
assert_eq!(files.get(0).unwrap().get_name(), "foo.txt");
|
||||
// Rename file
|
||||
let dst_path: PathBuf =
|
||||
PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str());
|
||||
|
@ -1083,7 +1100,7 @@ mod tests {
|
|||
// There should be still 1 file now, but named bar.txt
|
||||
let files: Vec<FsEntry> = host.list_dir();
|
||||
assert_eq!(files.len(), 1); // There should be 0 files now
|
||||
assert_eq!(get_filename(files.get(0).unwrap()), String::from("bar.txt"));
|
||||
assert_eq!(files.get(0).unwrap().get_name(), "bar.txt");
|
||||
// Fail
|
||||
let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu");
|
||||
assert!(host
|
||||
|
@ -1091,7 +1108,7 @@ mod tests {
|
|||
.is_err());
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[test]
|
||||
fn test_host_chmod() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
@ -1110,7 +1127,7 @@ mod tests {
|
|||
.is_err());
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[test]
|
||||
fn test_host_copy_file_absolute() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
@ -1131,9 +1148,16 @@ mod tests {
|
|||
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
|
||||
// Verify host has two files
|
||||
assert_eq!(host.files.len(), 2);
|
||||
// Fail copy
|
||||
assert!(host
|
||||
.copy(
|
||||
&make_fsentry(PathBuf::from("/a/a7/a/a7a"), false),
|
||||
PathBuf::from("571k422i").as_path()
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[test]
|
||||
fn test_host_copy_file_relative() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
@ -1155,7 +1179,7 @@ mod tests {
|
|||
assert_eq!(host.files.len(), 2);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[test]
|
||||
fn test_host_copy_directory_absolute() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
@ -1186,7 +1210,7 @@ mod tests {
|
|||
assert!(host.stat(test_file_path.as_path()).is_ok());
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
#[test]
|
||||
fn test_host_copy_directory_relative() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
|
@ -1221,7 +1245,7 @@ mod tests {
|
|||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
||||
// Execute
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\n");
|
||||
#[cfg(target_os = "windows")]
|
||||
assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\r\n");
|
||||
|
@ -1232,16 +1256,16 @@ mod tests {
|
|||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
let dir_path: &Path = tmpdir.path();
|
||||
// Make files
|
||||
assert!(make_sample_file(dir_path, "pippo.txt").is_ok());
|
||||
assert!(make_sample_file(dir_path, "foo.jpg").is_ok());
|
||||
assert!(make_file_at(dir_path, "pippo.txt").is_ok());
|
||||
assert!(make_file_at(dir_path, "foo.jpg").is_ok());
|
||||
// Make nested struct
|
||||
assert!(make_dir(dir_path, "examples").is_ok());
|
||||
assert!(make_dir_at(dir_path, "examples").is_ok());
|
||||
let mut subdir: PathBuf = PathBuf::from(dir_path);
|
||||
subdir.push("examples/");
|
||||
assert!(make_sample_file(subdir.as_path(), "omar.txt").is_ok());
|
||||
assert!(make_sample_file(subdir.as_path(), "errors.txt").is_ok());
|
||||
assert!(make_sample_file(subdir.as_path(), "screenshot.png").is_ok());
|
||||
assert!(make_sample_file(subdir.as_path(), "examples.csv").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "omar.txt").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "errors.txt").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "screenshot.png").is_ok());
|
||||
assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok());
|
||||
let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap();
|
||||
// Find txt files
|
||||
let mut result: Vec<FsEntry> = host.find("*.txt").ok().unwrap();
|
||||
|
@ -1300,50 +1324,4 @@ mod tests {
|
|||
String::from("File already exists")
|
||||
);
|
||||
}
|
||||
|
||||
/// ### make_sample_file
|
||||
///
|
||||
/// Make a file with `name` in the current directory
|
||||
fn make_sample_file(dir: &Path, filename: &str) -> std::io::Result<()> {
|
||||
let mut p: PathBuf = PathBuf::from(dir);
|
||||
p.push(filename);
|
||||
let mut file: File = File::create(p.as_path())?;
|
||||
write!(
|
||||
file,
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n"
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### make_dir
|
||||
///
|
||||
/// Make a directory in `dir`
|
||||
fn make_dir(dir: &Path, dirname: &str) -> std::io::Result<()> {
|
||||
let mut p: PathBuf = PathBuf::from(dir);
|
||||
p.push(dirname);
|
||||
std::fs::create_dir(p.as_path())
|
||||
}
|
||||
|
||||
/// ### create_sample_file
|
||||
///
|
||||
/// Create a sample file
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
fn create_sample_file() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
write!(
|
||||
tmpfile,
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n"
|
||||
)
|
||||
.unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
fn get_filename(entry: &FsEntry) -> String {
|
||||
match entry {
|
||||
FsEntry::Directory(d) => d.name.clone(),
|
||||
FsEntry::File(f) => f.name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -427,11 +427,12 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_new() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let client: BookmarksClient =
|
||||
|
@ -445,7 +446,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "netbsd"
|
||||
))]
|
||||
fn test_system_bookmarks_new_err() {
|
||||
assert!(BookmarksClient::new(
|
||||
Path::new("/tmp/oifoif/omar"),
|
||||
|
@ -454,7 +460,7 @@ mod tests {
|
|||
)
|
||||
.is_err());
|
||||
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
assert!(
|
||||
BookmarksClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar"), 16).is_err()
|
||||
|
@ -464,7 +470,7 @@ mod tests {
|
|||
#[test]
|
||||
|
||||
fn test_system_bookmarks_new_from_existing() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -510,7 +516,7 @@ mod tests {
|
|||
#[test]
|
||||
|
||||
fn test_system_bookmarks_manipulate_bookmarks() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -556,7 +562,7 @@ mod tests {
|
|||
#[should_panic]
|
||||
|
||||
fn test_system_bookmarks_bad_bookmark_name() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -575,7 +581,7 @@ mod tests {
|
|||
#[test]
|
||||
|
||||
fn test_system_bookmarks_manipulate_recents() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -610,7 +616,7 @@ mod tests {
|
|||
#[test]
|
||||
|
||||
fn test_system_bookmarks_dup_recent() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -635,7 +641,7 @@ mod tests {
|
|||
#[test]
|
||||
|
||||
fn test_system_bookmarks_recents_more_than_limit() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -681,9 +687,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
|
||||
fn test_system_bookmarks_add_bookmark_empty() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
// Initialize a new bookmarks client
|
||||
let mut client: BookmarksClient =
|
||||
|
@ -702,19 +707,10 @@ mod tests {
|
|||
/// ### get_paths
|
||||
///
|
||||
/// Get paths for configuration and key for bookmarks
|
||||
|
||||
fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
|
||||
let k: PathBuf = PathBuf::from(dir);
|
||||
let mut c: PathBuf = k.clone();
|
||||
c.push("bookmarks.toml");
|
||||
(c, k)
|
||||
}
|
||||
|
||||
/// ### create_tmp_dir
|
||||
///
|
||||
/// Create temporary directory
|
||||
|
||||
fn create_tmp_dir() -> tempfile::TempDir {
|
||||
tempfile::TempDir::new().ok().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -408,10 +408,11 @@ mod tests {
|
|||
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::io::Read;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_system_config_new() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, ssh_keys_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), ssh_keys_path.as_path())
|
||||
.ok()
|
||||
|
@ -437,14 +438,14 @@ mod tests {
|
|||
ConfigClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar"),)
|
||||
.is_err()
|
||||
);
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
assert!(ConfigClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_config_from_existing() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -477,7 +478,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_text_editor() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -488,7 +489,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_default_protocol() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -502,7 +503,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_show_hidden_files() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -513,7 +514,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_check_for_updates() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -527,7 +528,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_group_dirs() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -540,7 +541,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_local_file_fmt() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -555,7 +556,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_remote_file_fmt() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -573,7 +574,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_config_ssh_keys() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -637,13 +638,6 @@ mod tests {
|
|||
(c, k)
|
||||
}
|
||||
|
||||
/// ### create_tmp_dir
|
||||
///
|
||||
/// Create temporary directory
|
||||
fn create_tmp_dir() -> tempfile::TempDir {
|
||||
tempfile::TempDir::new().ok().unwrap()
|
||||
}
|
||||
|
||||
fn get_sample_rsa_key() -> String {
|
||||
format!(
|
||||
"-----BEGIN OPENSSH PRIVATE KEY-----\n{}\n-----END OPENSSH PRIVATE KEY-----",
|
||||
|
|
|
@ -87,6 +87,16 @@ impl SshKeyStorage {
|
|||
fn make_mapkey(host: &str, username: &str) -> String {
|
||||
format!("{}@{}", username, host)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// ### add_key
|
||||
///
|
||||
/// Add a key to storage
|
||||
/// NOTE: available only for tests
|
||||
pub fn add_key(&mut self, host: &str, username: &str, p: PathBuf) {
|
||||
let key: String = Self::make_mapkey(host, username);
|
||||
self.hosts.insert(key, p);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -100,7 +110,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_system_sshkey_storage_new() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
|
@ -128,6 +138,16 @@ mod tests {
|
|||
assert_eq!(storage.hosts.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_sshkey_storage_add() {
|
||||
let mut storage: SshKeyStorage = SshKeyStorage::empty();
|
||||
storage.add_key("deskichup", "veeso", PathBuf::from("/tmp/omar"));
|
||||
assert_eq!(
|
||||
*storage.resolve("deskichup", "veeso").unwrap(),
|
||||
PathBuf::from("/tmp/omar")
|
||||
);
|
||||
}
|
||||
|
||||
/// ### get_paths
|
||||
///
|
||||
/// Get paths for configuration and keys directory
|
||||
|
@ -138,11 +158,4 @@ mod tests {
|
|||
c.push("config.toml");
|
||||
(c, k)
|
||||
}
|
||||
|
||||
/// ### create_tmp_dir
|
||||
///
|
||||
/// Create temporary directory
|
||||
fn create_tmp_dir() -> tempfile::TempDir {
|
||||
tempfile::TempDir::new().ok().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,4 +68,16 @@ impl AuthActivity {
|
|||
pub(super) fn is_port_standard(port: u16) -> bool {
|
||||
port < 1024
|
||||
}
|
||||
|
||||
/// ### check_minimum_window_size
|
||||
///
|
||||
/// Check minimum window size window
|
||||
pub(super) fn check_minimum_window_size(&mut self, height: u16) {
|
||||
if height < 25 {
|
||||
// Mount window error
|
||||
self.mount_size_err();
|
||||
} else {
|
||||
self.umount_size_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ use crate::ui::context::FileTransferParams;
|
|||
use crate::utils::git;
|
||||
|
||||
// Includes
|
||||
use crossterm::event::Event;
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tuirealm::{Update, View};
|
||||
|
||||
|
@ -53,6 +54,7 @@ const COMPONENT_TEXT_NEW_VERSION: &str = "TEXT_NEW_VERSION";
|
|||
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
|
||||
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
|
||||
const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR";
|
||||
const COMPONENT_TEXT_SIZE_ERR: &str = "TEXT_SIZE_ERR";
|
||||
const COMPONENT_INPUT_ADDR: &str = "INPUT_ADDRESS";
|
||||
const COMPONENT_INPUT_PORT: &str = "INPUT_PORT";
|
||||
const COMPONENT_INPUT_USERNAME: &str = "INPUT_USERNAME";
|
||||
|
@ -199,6 +201,10 @@ impl Activity for AuthActivity {
|
|||
if let Ok(Some(event)) = self.context.as_ref().unwrap().input_hnd.read_event() {
|
||||
// Set redraw to true
|
||||
self.redraw = true;
|
||||
// Handle on resize
|
||||
if let Event::Resize(_, h) = event {
|
||||
self.check_minimum_window_size(h);
|
||||
}
|
||||
// Handle event on view and update
|
||||
let msg = self.view.on(event);
|
||||
self.update(msg);
|
||||
|
|
|
@ -32,7 +32,7 @@ use super::{
|
|||
COMPONENT_INPUT_PORT, COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
|
||||
COMPONENT_RADIO_BOOKMARK_DEL_RECENT, COMPONENT_RADIO_BOOKMARK_SAVE_PWD,
|
||||
COMPONENT_RADIO_PROTOCOL, COMPONENT_RADIO_QUIT, COMPONENT_RECENTS_LIST, COMPONENT_TEXT_ERROR,
|
||||
COMPONENT_TEXT_HELP,
|
||||
COMPONENT_TEXT_HELP, COMPONENT_TEXT_SIZE_ERR,
|
||||
};
|
||||
use crate::ui::keymap::*;
|
||||
use tuirealm::components::InputPropsBuilder;
|
||||
|
@ -306,6 +306,8 @@ impl Update for AuthActivity {
|
|||
self.umount_quit();
|
||||
None
|
||||
}
|
||||
// -- text size error; block everything
|
||||
(COMPONENT_TEXT_SIZE_ERR, _) => None,
|
||||
// On submit on any unhandled (connect)
|
||||
(_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => {
|
||||
// Match <ENTER> key for all other components
|
||||
|
|
|
@ -37,8 +37,8 @@ use tuirealm::components::{
|
|||
input::{Input, InputPropsBuilder},
|
||||
label::{Label, LabelPropsBuilder},
|
||||
radio::{Radio, RadioPropsBuilder},
|
||||
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
|
||||
span::{Span, SpanPropsBuilder},
|
||||
table::{Table, TablePropsBuilder},
|
||||
};
|
||||
use tuirealm::tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
|
@ -234,14 +234,17 @@ impl AuthActivity {
|
|||
pub(super) fn view(&mut self) {
|
||||
let mut ctx: Context = self.context.take().unwrap();
|
||||
let _ = ctx.terminal.draw(|f| {
|
||||
// Check window size
|
||||
let height: u16 = f.size().height;
|
||||
self.check_minimum_window_size(height);
|
||||
// Prepare chunks
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(70), // Auth Form
|
||||
Constraint::Percentage(30), // Bookmarks
|
||||
Constraint::Length(21), // Auth Form
|
||||
Constraint::Min(3), // Bookmarks
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
|
@ -303,6 +306,14 @@ impl AuthActivity {
|
|||
self.view.render(super::COMPONENT_TEXT_ERROR, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_TEXT_SIZE_ERR) {
|
||||
if props.visible {
|
||||
let popup = draw_area_in(f.size(), 80, 20);
|
||||
f.render_widget(Clear, popup);
|
||||
// make popup
|
||||
self.view.render(super::COMPONENT_TEXT_SIZE_ERR, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_QUIT) {
|
||||
if props.visible {
|
||||
// make popup
|
||||
|
@ -478,6 +489,38 @@ impl AuthActivity {
|
|||
self.view.umount(super::COMPONENT_TEXT_ERROR);
|
||||
}
|
||||
|
||||
/// ### mount_size_err
|
||||
///
|
||||
/// Mount size error
|
||||
pub(super) fn mount_size_err(&mut self) {
|
||||
// Mount
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_SIZE_ERR,
|
||||
Box::new(MsgBox::new(
|
||||
MsgBoxPropsBuilder::default()
|
||||
.with_foreground(Color::Red)
|
||||
.with_borders(Borders::ALL, BorderType::Thick, Color::Red)
|
||||
.bold()
|
||||
.with_texts(
|
||||
None,
|
||||
vec![TextSpan::from(
|
||||
"termscp requires at least 24 lines of height to run",
|
||||
)],
|
||||
)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Give focus to error
|
||||
self.view.active(super::COMPONENT_TEXT_SIZE_ERR);
|
||||
}
|
||||
|
||||
/// ### umount_size_err
|
||||
///
|
||||
/// Umount error size error
|
||||
pub(super) fn umount_size_err(&mut self) {
|
||||
self.view.umount(super::COMPONENT_TEXT_SIZE_ERR);
|
||||
}
|
||||
|
||||
/// ### mount_quit
|
||||
///
|
||||
/// Mount quit popup
|
||||
|
@ -622,9 +665,12 @@ impl AuthActivity {
|
|||
pub(super) fn mount_help(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_HELP,
|
||||
Box::new(Table::new(
|
||||
TablePropsBuilder::default()
|
||||
Box::new(Scrolltable::new(
|
||||
ScrollTablePropsBuilder::default()
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
||||
.with_highlighted_str(Some("?"))
|
||||
.with_max_scroll_step(8)
|
||||
.bold()
|
||||
.with_table(
|
||||
Some(String::from("Help")),
|
||||
TableBuilder::default()
|
||||
|
|
|
@ -27,8 +27,9 @@
|
|||
*/
|
||||
extern crate tempfile;
|
||||
// locals
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
|
||||
use crate::filetransfer::FileTransferErrorType;
|
||||
use crate::fs::FsFile;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
impl FileTransferActivity {
|
||||
|
@ -66,7 +67,7 @@ impl FileTransferActivity {
|
|||
match self.get_remote_selected_entries() {
|
||||
SelectedEntry::One(entry) => {
|
||||
let dest_path: PathBuf = PathBuf::from(input);
|
||||
self.remote_copy_file(&entry, dest_path.as_path());
|
||||
self.remote_copy_file(entry, dest_path.as_path());
|
||||
// Reload entries
|
||||
self.reload_remote_dir();
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ impl FileTransferActivity {
|
|||
// Try to copy each file to Input/{FILE_NAME}
|
||||
let base_path: PathBuf = PathBuf::from(input);
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
for entry in entries.into_iter() {
|
||||
let mut dest_path: PathBuf = base_path.clone();
|
||||
dest_path.push(entry.get_name());
|
||||
self.remote_copy_file(entry, dest_path.as_path());
|
||||
|
@ -110,8 +111,8 @@ impl FileTransferActivity {
|
|||
}
|
||||
}
|
||||
|
||||
fn remote_copy_file(&mut self, entry: &FsEntry, dest: &Path) {
|
||||
match self.client.as_mut().copy(entry, dest) {
|
||||
fn remote_copy_file(&mut self, entry: FsEntry, dest: &Path) {
|
||||
match self.client.as_mut().copy(&entry, dest) {
|
||||
Ok(_) => {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
|
@ -139,4 +140,123 @@ impl FileTransferActivity {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// ### tricky_copy
|
||||
///
|
||||
/// Tricky copy will be used whenever copy command is not available on remote host
|
||||
fn tricky_copy(&mut self, entry: FsEntry, dest: &Path) {
|
||||
// match entry
|
||||
match entry {
|
||||
FsEntry::File(entry) => {
|
||||
// Create tempfile
|
||||
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not create temporary file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Download file
|
||||
let name = entry.name.clone();
|
||||
let entry_path = entry.abs_path.clone();
|
||||
if let Err(err) =
|
||||
self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name))
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not download to temporary file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) {
|
||||
Ok(e) => e.unwrap_file(),
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copy failed: could not stat \"{}\": {}",
|
||||
tmpfile.path().display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Upload file to destination
|
||||
let wrkdir = self.remote().wrkdir.clone();
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::File(tmpfile_entry),
|
||||
wrkdir.as_path(),
|
||||
Some(String::from(dest.to_string_lossy())),
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copy failed: could not write file {}: {}",
|
||||
entry_path.display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
FsEntry::Directory(_) => {
|
||||
let tempdir: tempfile::TempDir = match tempfile::TempDir::new() {
|
||||
Ok(d) => d,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not create temporary directory: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Get path of dest
|
||||
let mut tempdir_path: PathBuf = tempdir.path().to_path_buf();
|
||||
tempdir_path.push(entry.get_name());
|
||||
// Download file
|
||||
if let Err(err) =
|
||||
self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None)
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: failed to download file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Stat dir
|
||||
let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copy failed: could not stat \"{}\": {}",
|
||||
tempdir.path().display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Upload to destination
|
||||
let wrkdir: PathBuf = self.remote().wrkdir.clone();
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Any(tempdir_entry),
|
||||
wrkdir.as_path(),
|
||||
Some(String::from(dest.to_string_lossy())),
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: failed to send file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,14 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
// locals
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
|
||||
use crate::fs::FsFile;
|
||||
// ext
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_edit_local_file(&mut self) {
|
||||
|
@ -60,15 +67,15 @@ impl FileTransferActivity {
|
|||
SelectedEntry::None => vec![],
|
||||
};
|
||||
// Edit all entries
|
||||
for entry in entries.iter() {
|
||||
for entry in entries.into_iter() {
|
||||
// Check if file
|
||||
if let FsEntry::File(file) = entry {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!("Opening file \"{}\"...", entry.get_abs_path().display()),
|
||||
format!("Opening file \"{}\"...", file.abs_path.display()),
|
||||
);
|
||||
// Edit file
|
||||
if let Err(err) = self.edit_remote_file(&file) {
|
||||
if let Err(err) = self.edit_remote_file(file) {
|
||||
self.log_and_alert(LogLevel::Error, err);
|
||||
}
|
||||
}
|
||||
|
@ -76,4 +83,150 @@ impl FileTransferActivity {
|
|||
// Reload entries
|
||||
self.reload_remote_dir();
|
||||
}
|
||||
|
||||
/// ### edit_local_file
|
||||
///
|
||||
/// Edit a file on localhost
|
||||
fn edit_local_file(&mut self, path: &Path) -> Result<(), String> {
|
||||
// Read first 2048 bytes or less from file to check if it is textual
|
||||
match OpenOptions::new().read(true).open(path) {
|
||||
Ok(mut f) => {
|
||||
// Read
|
||||
let mut buff: [u8; 2048] = [0; 2048];
|
||||
match f.read(&mut buff) {
|
||||
Ok(size) => {
|
||||
if content_inspector::inspect(&buff[0..size]).is_binary() {
|
||||
return Err("Could not open file in editor: file is binary".to_string());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("Could not read file: {}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("Could not read file: {}", err));
|
||||
}
|
||||
}
|
||||
// Put input mode back to normal
|
||||
if let Err(err) = disable_raw_mode() {
|
||||
error!("Failed to disable raw mode: {}", err);
|
||||
}
|
||||
// Leave alternate mode
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
ctx.leave_alternate_screen();
|
||||
}
|
||||
// Open editor
|
||||
match edit::edit_file(path) {
|
||||
Ok(_) => self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Changes performed through editor saved to \"{}\"!",
|
||||
path.display()
|
||||
),
|
||||
),
|
||||
Err(err) => return Err(format!("Could not open editor: {}", err)),
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
// Clear screen
|
||||
ctx.clear_screen();
|
||||
// Enter alternate mode
|
||||
ctx.enter_alternate_screen();
|
||||
}
|
||||
// Re-enable raw mode
|
||||
let _ = enable_raw_mode();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### edit_remote_file
|
||||
///
|
||||
/// Edit file on remote host
|
||||
fn edit_remote_file(&mut self, file: FsFile) -> Result<(), String> {
|
||||
// Create temp file
|
||||
let tmpfile: PathBuf = match self.download_file_as_temp(&file) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
// Download file
|
||||
let file_name = file.name.clone();
|
||||
let file_path = file.abs_path.clone();
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::File(file),
|
||||
tmpfile.as_path(),
|
||||
Some(file_name.clone()),
|
||||
) {
|
||||
return Err(format!("Could not open file {}: {}", file_name, err));
|
||||
}
|
||||
// Get current file modification time
|
||||
let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e.get_last_change_time(),
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Could not stat \"{}\": {}",
|
||||
tmpfile.as_path().display(),
|
||||
err
|
||||
))
|
||||
}
|
||||
};
|
||||
// Edit file
|
||||
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
|
||||
return Err(err);
|
||||
}
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Could not stat \"{}\": {}",
|
||||
tmpfile.as_path().display(),
|
||||
err
|
||||
))
|
||||
}
|
||||
};
|
||||
// Check if file has changed
|
||||
match prev_mtime != tmpfile_entry.get_last_change_time() {
|
||||
true => {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"File \"{}\" has changed; writing changes to remote",
|
||||
file_path.display()
|
||||
),
|
||||
);
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e.unwrap_file(),
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Could not stat \"{}\": {}",
|
||||
tmpfile.as_path().display(),
|
||||
err
|
||||
))
|
||||
}
|
||||
};
|
||||
// Send file
|
||||
let wrkdir = self.remote().wrkdir.clone();
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::File(tmpfile_entry),
|
||||
wrkdir.as_path(),
|
||||
Some(file_name),
|
||||
) {
|
||||
return Err(format!(
|
||||
"Could not write file {}: {}",
|
||||
file_path.display(),
|
||||
err
|
||||
));
|
||||
}
|
||||
}
|
||||
false => {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!("File \"{}\" hasn't changed", file_path.display()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
*/
|
||||
// locals
|
||||
use super::super::browser::FileExplorerTab;
|
||||
use super::{FileTransferActivity, FsEntry, SelectedEntry};
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -77,10 +77,30 @@ impl FileTransferActivity {
|
|||
match self.get_found_selected_entries() {
|
||||
SelectedEntry::One(entry) => match self.browser.tab() {
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
||||
self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as);
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not upload file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||
self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as);
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not download file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
SelectedEntry::Many(entries) => {
|
||||
|
@ -90,21 +110,34 @@ impl FileTransferActivity {
|
|||
dest_path.push(save_as);
|
||||
}
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
||||
self.filetransfer_send(
|
||||
&entry.get_realfile(),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
);
|
||||
let entries = entries.iter().map(|x| x.get_realfile()).collect();
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Many(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not upload file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||
self.filetransfer_recv(
|
||||
&entry.get_realfile(),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
}
|
||||
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Many(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not download file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel};
|
||||
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel, TransferPayload};
|
||||
use tuirealm::{Payload, Value};
|
||||
|
||||
// actions
|
||||
|
@ -82,8 +82,7 @@ impl FileTransferActivity {
|
|||
let files: Vec<&FsEntry> = files
|
||||
.iter()
|
||||
.map(|x| self.local().get(*x)) // Usize to Option<FsEntry>
|
||||
.filter(|x| x.is_some()) // Get only some values
|
||||
.map(|x| x.unwrap()) // Option to FsEntry
|
||||
.flatten()
|
||||
.collect();
|
||||
SelectedEntry::from(files)
|
||||
}
|
||||
|
@ -101,8 +100,7 @@ impl FileTransferActivity {
|
|||
let files: Vec<&FsEntry> = files
|
||||
.iter()
|
||||
.map(|x| self.remote().get(*x)) // Usize to Option<FsEntry>
|
||||
.filter(|x| x.is_some()) // Get only some values
|
||||
.map(|x| x.unwrap()) // Option to FsEntry
|
||||
.flatten()
|
||||
.collect();
|
||||
SelectedEntry::from(files)
|
||||
}
|
||||
|
@ -122,8 +120,7 @@ impl FileTransferActivity {
|
|||
let files: Vec<&FsEntry> = files
|
||||
.iter()
|
||||
.map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option<FsEntry>
|
||||
.filter(|x| x.is_some()) // Get only some values
|
||||
.map(|x| x.unwrap()) // Option to FsEntry
|
||||
.flatten()
|
||||
.collect();
|
||||
SelectedEntry::from(files)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
// deps
|
||||
extern crate open;
|
||||
// locals
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
|
||||
// ext
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
@ -90,12 +90,25 @@ impl FileTransferActivity {
|
|||
}
|
||||
Some(p) => p.path().to_path_buf(),
|
||||
};
|
||||
self.filetransfer_recv(&entry, cache.as_path(), Some(tmpfile.clone()));
|
||||
// Make file and open if file exists
|
||||
let mut tmp: PathBuf = cache;
|
||||
tmp.push(tmpfile.as_str());
|
||||
if tmp.exists() {
|
||||
self.open_path_with(tmp.as_path(), open_with);
|
||||
match self.filetransfer_recv(
|
||||
TransferPayload::Any(entry),
|
||||
cache.as_path(),
|
||||
Some(tmpfile.clone()),
|
||||
) {
|
||||
Ok(_) => {
|
||||
// Make file and open if file exists
|
||||
let mut tmp: PathBuf = cache;
|
||||
tmp.push(tmpfile.as_str());
|
||||
if tmp.exists() {
|
||||
self.open_path_with(tmp.as_path(), open_with);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.log(
|
||||
LogLevel::Error,
|
||||
format!("Failed to download remote entry: {}", err),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
// locals
|
||||
use super::{FileTransferActivity, SelectedEntry};
|
||||
use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload};
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl FileTransferActivity {
|
||||
|
@ -50,7 +50,19 @@ impl FileTransferActivity {
|
|||
let wrkdir: PathBuf = self.remote().wrkdir.clone();
|
||||
match self.get_local_selected_entries() {
|
||||
SelectedEntry::One(entry) => {
|
||||
self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as);
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not upload file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectedEntry::Many(entries) => {
|
||||
// In case of selection: save multiple files in wrkdir/input
|
||||
|
@ -59,8 +71,19 @@ impl FileTransferActivity {
|
|||
dest_path.push(save_as);
|
||||
}
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
self.filetransfer_send(&entry.get_realfile(), dest_path.as_path(), None);
|
||||
let entries = entries.iter().map(|x| x.get_realfile()).collect();
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Many(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not upload file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectedEntry::None => {}
|
||||
|
@ -71,7 +94,19 @@ impl FileTransferActivity {
|
|||
let wrkdir: PathBuf = self.local().wrkdir.clone();
|
||||
match self.get_remote_selected_entries() {
|
||||
SelectedEntry::One(entry) => {
|
||||
self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as);
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Any(entry.get_realfile()),
|
||||
wrkdir.as_path(),
|
||||
save_as,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not download file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectedEntry::Many(entries) => {
|
||||
// In case of selection: save multiple files in wrkdir/input
|
||||
|
@ -80,8 +115,19 @@ impl FileTransferActivity {
|
|||
dest_path.push(save_as);
|
||||
}
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None);
|
||||
let entries = entries.iter().map(|x| x.get_realfile()).collect();
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Many(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not download file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectedEntry::None => {}
|
||||
|
|
|
@ -49,9 +49,10 @@ use crate::fs::explorer::FileExplorer;
|
|||
use crate::fs::FsEntry;
|
||||
use crate::host::Localhost;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
pub(crate) use lib::browser;
|
||||
pub(self) use lib::browser;
|
||||
use lib::browser::Browser;
|
||||
use lib::transfer::TransferStates;
|
||||
pub(self) use session::TransferPayload;
|
||||
|
||||
// Includes
|
||||
use chrono::{DateTime, Local};
|
||||
|
@ -89,7 +90,8 @@ const COMPONENT_RADIO_DELETE: &str = "RADIO_DELETE";
|
|||
const COMPONENT_RADIO_DISCONNECT: &str = "RADIO_DISCONNECT";
|
||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||
const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING";
|
||||
const COMPONENT_SPAN_STATUS_BAR: &str = "STATUS_BAR";
|
||||
const COMPONENT_SPAN_STATUS_BAR_LOCAL: &str = "STATUS_BAR_LOCAL";
|
||||
const COMPONENT_SPAN_STATUS_BAR_REMOTE: &str = "STATUS_BAR_REMOTE";
|
||||
const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO";
|
||||
|
||||
/// ## LogLevel
|
||||
|
|
|
@ -40,11 +40,9 @@ use crate::utils::fmt::fmt_millis;
|
|||
|
||||
// Ext
|
||||
use bytesize::ByteSize;
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Instant, SystemTime};
|
||||
use std::time::Instant;
|
||||
use thiserror::Error;
|
||||
|
||||
/// ## TransferErrorReason
|
||||
|
@ -66,6 +64,19 @@ enum TransferErrorReason {
|
|||
FileTransferError(FileTransferError),
|
||||
}
|
||||
|
||||
/// ## TransferPayload
|
||||
///
|
||||
/// Represents the entity to send or receive during a transfer.
|
||||
/// - File: describes an individual `FsFile` to send
|
||||
/// - Any: Can be any kind of `FsEntry`, but just one
|
||||
/// - Many: a list of `FsEntry`
|
||||
#[derive(Debug)]
|
||||
pub(super) enum TransferPayload {
|
||||
File(FsFile),
|
||||
Any(FsEntry),
|
||||
Many(Vec<FsEntry>),
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// ### connect
|
||||
///
|
||||
|
@ -106,6 +117,7 @@ impl FileTransferActivity {
|
|||
}
|
||||
Err(err) => {
|
||||
// Set popup fatal error
|
||||
self.umount_wait();
|
||||
self.mount_fatal(&err.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -196,11 +208,66 @@ impl FileTransferActivity {
|
|||
/// If dst_name is Some, entry will be saved with a different name.
|
||||
/// If entry is a directory, this applies to directory only
|
||||
pub(super) fn filetransfer_send(
|
||||
&mut self,
|
||||
payload: TransferPayload,
|
||||
curr_remote_path: &Path,
|
||||
dst_name: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
// Use different method based on payload
|
||||
match payload {
|
||||
TransferPayload::Any(entry) => {
|
||||
self.filetransfer_send_any(&entry, curr_remote_path, dst_name)
|
||||
}
|
||||
TransferPayload::File(file) => {
|
||||
self.filetransfer_send_file(&file, curr_remote_path, dst_name)
|
||||
}
|
||||
TransferPayload::Many(entries) => {
|
||||
self.filetransfer_send_many(entries, curr_remote_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### filetransfer_send_file
|
||||
///
|
||||
/// Send one file to remote at specified path.
|
||||
fn filetransfer_send_file(
|
||||
&mut self,
|
||||
file: &FsFile,
|
||||
curr_remote_path: &Path,
|
||||
dst_name: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total size of transfer
|
||||
let total_transfer_size: usize = file.size;
|
||||
self.transfer.full.init(total_transfer_size);
|
||||
// Mount progress bar
|
||||
self.mount_progress_bar(format!("Uploading {}...", file.abs_path.display()));
|
||||
// Get remote path
|
||||
let file_name: String = file.name.clone();
|
||||
let mut remote_path: PathBuf = PathBuf::from(curr_remote_path);
|
||||
let remote_file_name: PathBuf = match dst_name {
|
||||
Some(s) => PathBuf::from(s.as_str()),
|
||||
None => PathBuf::from(file_name.as_str()),
|
||||
};
|
||||
remote_path.push(remote_file_name);
|
||||
// Send
|
||||
let result = self.filetransfer_send_one(file, remote_path.as_path(), file_name);
|
||||
// Umount progress bar
|
||||
self.umount_progress_bar();
|
||||
// Return result
|
||||
result.map_err(|x| x.to_string())
|
||||
}
|
||||
|
||||
/// ### filetransfer_send_any
|
||||
///
|
||||
/// Send a `TransferPayload` of type `Any`
|
||||
fn filetransfer_send_any(
|
||||
&mut self,
|
||||
entry: &FsEntry,
|
||||
curr_remote_path: &Path,
|
||||
dst_name: Option<String>,
|
||||
) {
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total size of transfer
|
||||
|
@ -212,6 +279,34 @@ impl FileTransferActivity {
|
|||
self.filetransfer_send_recurse(entry, curr_remote_path, dst_name);
|
||||
// Umount progress bar
|
||||
self.umount_progress_bar();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### filetransfer_send_many
|
||||
///
|
||||
/// Send many entries to remote
|
||||
fn filetransfer_send_many(
|
||||
&mut self,
|
||||
entries: Vec<FsEntry>,
|
||||
curr_remote_path: &Path,
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total size of transfer
|
||||
let total_transfer_size: usize = entries
|
||||
.iter()
|
||||
.map(|x| self.get_total_transfer_size_local(x))
|
||||
.sum();
|
||||
self.transfer.full.init(total_transfer_size);
|
||||
// Mount progress bar
|
||||
self.mount_progress_bar(format!("Uploading {} entries...", entries.len()));
|
||||
// Send recurse
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|x| self.filetransfer_send_recurse(x, curr_remote_path, None));
|
||||
// Umount progress bar
|
||||
self.umount_progress_bar();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filetransfer_send_recurse(
|
||||
|
@ -235,8 +330,7 @@ impl FileTransferActivity {
|
|||
// Match entry
|
||||
match entry {
|
||||
FsEntry::File(file) => {
|
||||
if let Err(err) =
|
||||
self.filetransfer_send_file(file, remote_path.as_path(), file_name)
|
||||
if let Err(err) = self.filetransfer_send_one(file, remote_path.as_path(), file_name)
|
||||
{
|
||||
// Log error
|
||||
self.log_and_alert(
|
||||
|
@ -339,7 +433,7 @@ impl FileTransferActivity {
|
|||
/// ### filetransfer_send_file
|
||||
///
|
||||
/// Send local file and write it to remote path
|
||||
fn filetransfer_send_file(
|
||||
fn filetransfer_send_one(
|
||||
&mut self,
|
||||
local: &FsFile,
|
||||
remote: &Path,
|
||||
|
@ -448,11 +542,29 @@ impl FileTransferActivity {
|
|||
/// If dst_name is Some, entry will be saved with a different name.
|
||||
/// If entry is a directory, this applies to directory only
|
||||
pub(super) fn filetransfer_recv(
|
||||
&mut self,
|
||||
payload: TransferPayload,
|
||||
local_path: &Path,
|
||||
dst_name: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
match payload {
|
||||
TransferPayload::Any(entry) => self.filetransfer_recv_any(&entry, local_path, dst_name),
|
||||
TransferPayload::File(file) => self.filetransfer_recv_file(&file, local_path),
|
||||
TransferPayload::Many(entries) => self.filetransfer_recv_many(entries, local_path),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### filetransfer_recv_any
|
||||
///
|
||||
/// Recv fs entry from remote.
|
||||
/// If dst_name is Some, entry will be saved with a different name.
|
||||
/// If entry is a directory, this applies to directory only
|
||||
fn filetransfer_recv_any(
|
||||
&mut self,
|
||||
entry: &FsEntry,
|
||||
local_path: &Path,
|
||||
dst_name: Option<String>,
|
||||
) {
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total transfer size
|
||||
|
@ -464,6 +576,53 @@ impl FileTransferActivity {
|
|||
self.filetransfer_recv_recurse(entry, local_path, dst_name);
|
||||
// Umount progress bar
|
||||
self.umount_progress_bar();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### filetransfer_recv_file
|
||||
///
|
||||
/// Receive a single file from remote.
|
||||
fn filetransfer_recv_file(&mut self, entry: &FsFile, local_path: &Path) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total transfer size
|
||||
let total_transfer_size: usize = entry.size;
|
||||
self.transfer.full.init(total_transfer_size);
|
||||
// Mount progress bar
|
||||
self.mount_progress_bar(format!("Downloading {}...", entry.abs_path.display()));
|
||||
// Receive
|
||||
let result = self.filetransfer_recv_one(local_path, entry, entry.name.clone());
|
||||
// Umount progress bar
|
||||
self.umount_progress_bar();
|
||||
// Return result
|
||||
result.map_err(|x| x.to_string())
|
||||
}
|
||||
|
||||
/// ### filetransfer_send_many
|
||||
///
|
||||
/// Send many entries to remote
|
||||
fn filetransfer_recv_many(
|
||||
&mut self,
|
||||
entries: Vec<FsEntry>,
|
||||
curr_remote_path: &Path,
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total size of transfer
|
||||
let total_transfer_size: usize = entries
|
||||
.iter()
|
||||
.map(|x| self.get_total_transfer_size_remote(x))
|
||||
.sum();
|
||||
self.transfer.full.init(total_transfer_size);
|
||||
// Mount progress bar
|
||||
self.mount_progress_bar(format!("Downloading {} entries...", entries.len()));
|
||||
// Send recurse
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|x| self.filetransfer_recv_recurse(x, curr_remote_path, None));
|
||||
// Umount progress bar
|
||||
self.umount_progress_bar();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filetransfer_recv_recurse(
|
||||
|
@ -489,7 +648,7 @@ impl FileTransferActivity {
|
|||
local_file_path.push(local_file_name.as_str());
|
||||
// Download file
|
||||
if let Err(err) =
|
||||
self.filetransfer_recv_file(local_file_path.as_path(), file, file_name)
|
||||
self.filetransfer_recv_one(local_file_path.as_path(), file, file_name)
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
|
@ -537,7 +696,11 @@ impl FileTransferActivity {
|
|||
match self.host.mkdir_ex(local_dir_path.as_path(), true) {
|
||||
Ok(_) => {
|
||||
// Apply file mode to directory
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_family = "unix",
|
||||
target_os = "macos",
|
||||
target_os = "linux"
|
||||
))]
|
||||
if let Some(pex) = dir.unix_pex {
|
||||
if let Err(err) = self.host.chmod(local_dir_path.as_path(), pex) {
|
||||
self.log(
|
||||
|
@ -613,10 +776,10 @@ impl FileTransferActivity {
|
|||
}
|
||||
}
|
||||
|
||||
/// ### filetransfer_recv_file
|
||||
/// ### filetransfer_recv_one
|
||||
///
|
||||
/// Receive file from remote and write it to local path
|
||||
fn filetransfer_recv_file(
|
||||
fn filetransfer_recv_one(
|
||||
&mut self,
|
||||
local: &Path,
|
||||
remote: &FsFile,
|
||||
|
@ -694,7 +857,11 @@ impl FileTransferActivity {
|
|||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
// Apply file mode to file
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_family = "unix",
|
||||
target_os = "macos",
|
||||
target_os = "linux"
|
||||
))]
|
||||
if let Some(pex) = remote.unix_pex {
|
||||
if let Err(err) = self.host.chmod(local, pex) {
|
||||
self.log(
|
||||
|
@ -785,251 +952,6 @@ impl FileTransferActivity {
|
|||
}
|
||||
}
|
||||
|
||||
/// ### edit_local_file
|
||||
///
|
||||
/// Edit a file on localhost
|
||||
pub(super) fn edit_local_file(&mut self, path: &Path) -> Result<(), String> {
|
||||
// Read first 2048 bytes or less from file to check if it is textual
|
||||
match OpenOptions::new().read(true).open(path) {
|
||||
Ok(mut f) => {
|
||||
// Read
|
||||
let mut buff: [u8; 2048] = [0; 2048];
|
||||
match f.read(&mut buff) {
|
||||
Ok(size) => {
|
||||
if content_inspector::inspect(&buff[0..size]).is_binary() {
|
||||
return Err("Could not open file in editor: file is binary".to_string());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("Could not read file: {}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("Could not read file: {}", err));
|
||||
}
|
||||
}
|
||||
debug!("Ok, file {} is textual; opening file...", path.display());
|
||||
// Put input mode back to normal
|
||||
if let Err(err) = disable_raw_mode() {
|
||||
error!("Failed to disable raw mode: {}", err);
|
||||
}
|
||||
// Leave alternate mode
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
ctx.leave_alternate_screen();
|
||||
}
|
||||
// Open editor
|
||||
match edit::edit_file(path) {
|
||||
Ok(_) => self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Changes performed through editor saved to \"{}\"!",
|
||||
path.display()
|
||||
),
|
||||
),
|
||||
Err(err) => return Err(format!("Could not open editor: {}", err)),
|
||||
}
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
// Clear screen
|
||||
ctx.clear_screen();
|
||||
// Enter alternate mode
|
||||
ctx.enter_alternate_screen();
|
||||
}
|
||||
// Re-enable raw mode
|
||||
let _ = enable_raw_mode();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### edit_remote_file
|
||||
///
|
||||
/// Edit file on remote host
|
||||
pub(super) fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> {
|
||||
// Create temp file
|
||||
let tmpfile: PathBuf = match self.download_file_as_temp(file) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
// Get current file modification time
|
||||
let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e.get_last_change_time(),
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Could not stat \"{}\": {}",
|
||||
tmpfile.as_path().display(),
|
||||
err
|
||||
))
|
||||
}
|
||||
};
|
||||
// Edit file
|
||||
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
|
||||
return Err(err);
|
||||
}
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Could not stat \"{}\": {}",
|
||||
tmpfile.as_path().display(),
|
||||
err
|
||||
))
|
||||
}
|
||||
};
|
||||
// Check if file has changed
|
||||
match prev_mtime != tmpfile_entry.get_last_change_time() {
|
||||
true => {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"File \"{}\" has changed; writing changes to remote",
|
||||
file.abs_path.display()
|
||||
),
|
||||
);
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Could not stat \"{}\": {}",
|
||||
tmpfile.as_path().display(),
|
||||
err
|
||||
))
|
||||
}
|
||||
};
|
||||
// Write file
|
||||
let tmpfile_entry: &FsFile = match &tmpfile_entry {
|
||||
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
|
||||
FsEntry::File(f) => f,
|
||||
};
|
||||
// Send file
|
||||
if let Err(err) = self.filetransfer_send_file(
|
||||
tmpfile_entry,
|
||||
file.abs_path.as_path(),
|
||||
file.name.clone(),
|
||||
) {
|
||||
return Err(format!(
|
||||
"Could not write file {}: {}",
|
||||
file.abs_path.display(),
|
||||
err
|
||||
));
|
||||
}
|
||||
}
|
||||
false => {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!("File \"{}\" hasn't changed", file.abs_path.display()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### tricky_copy
|
||||
///
|
||||
/// Tricky copy will be used whenever copy command is not available on remote host
|
||||
pub(super) fn tricky_copy(&mut self, entry: &FsEntry, dest: &Path) {
|
||||
// match entry
|
||||
match entry {
|
||||
FsEntry::File(entry) => {
|
||||
// Create tempfile
|
||||
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not create temporary file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Download file
|
||||
if let Err(err) =
|
||||
self.filetransfer_recv_file(tmpfile.path(), entry, entry.name.clone())
|
||||
{
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not download to temporary file: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copy failed: could not stat \"{}\": {}",
|
||||
tmpfile.path().display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let tmpfile_entry = match &tmpfile_entry {
|
||||
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
|
||||
FsEntry::File(f) => f,
|
||||
};
|
||||
// Upload file to destination
|
||||
if let Err(err) = self.filetransfer_send_file(
|
||||
tmpfile_entry,
|
||||
dest,
|
||||
String::from(dest.to_string_lossy()),
|
||||
) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copy failed: could not write file {}: {}",
|
||||
entry.abs_path.display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
FsEntry::Directory(_) => {
|
||||
let tempdir: tempfile::TempDir = match tempfile::TempDir::new() {
|
||||
Ok(d) => d,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not create temporary directory: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Download file
|
||||
self.filetransfer_recv(entry, tempdir.path(), None);
|
||||
// Get path of dest
|
||||
let mut tempdir_path: PathBuf = tempdir.path().to_path_buf();
|
||||
tempdir_path.push(entry.get_name());
|
||||
// Stat dir
|
||||
let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copy failed: could not stat \"{}\": {}",
|
||||
tempdir.path().display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Upload to destination
|
||||
let wrkdir: PathBuf = self.remote().wrkdir.clone();
|
||||
self.filetransfer_send(
|
||||
&tempdir_entry,
|
||||
wrkdir.as_path(),
|
||||
Some(String::from(dest.to_string_lossy())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### download_file_as_temp
|
||||
///
|
||||
/// Download provided file as a temporary file
|
||||
|
@ -1047,7 +969,11 @@ impl FileTransferActivity {
|
|||
}
|
||||
};
|
||||
// Download file
|
||||
match self.filetransfer_recv_file(tmpfile.as_path(), file, file.name.clone()) {
|
||||
match self.filetransfer_recv(
|
||||
TransferPayload::File(file.clone()),
|
||||
tmpfile.as_path(),
|
||||
Some(file.name.clone()),
|
||||
) {
|
||||
Err(err) => Err(format!(
|
||||
"Could not download {} to temporary file: {}",
|
||||
file.abs_path.display(),
|
||||
|
|
|
@ -107,6 +107,8 @@ impl Update for FileTransferActivity {
|
|||
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => {
|
||||
// Toggle hidden files
|
||||
self.local_mut().toggle_hidden_files();
|
||||
// Update status bar
|
||||
self.refresh_local_status_bar();
|
||||
// Reload file list component
|
||||
self.update_local_filelist()
|
||||
}
|
||||
|
@ -179,6 +181,8 @@ impl Update for FileTransferActivity {
|
|||
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => {
|
||||
// Toggle hidden files
|
||||
self.remote_mut().toggle_hidden_files();
|
||||
// Update status bar
|
||||
self.refresh_remote_status_bar();
|
||||
// Reload file list component
|
||||
self.update_remote_filelist()
|
||||
}
|
||||
|
@ -295,7 +299,7 @@ impl Update for FileTransferActivity {
|
|||
// Toggle browser sync
|
||||
self.browser.toggle_sync_browsing();
|
||||
// Update status bar
|
||||
self.refresh_status_bar();
|
||||
self.refresh_remote_status_bar();
|
||||
None
|
||||
}
|
||||
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC)
|
||||
|
@ -652,7 +656,11 @@ impl Update for FileTransferActivity {
|
|||
_ => panic!("Found result doesn't support SORTING"),
|
||||
}
|
||||
// Update status bar
|
||||
self.refresh_status_bar();
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.refresh_local_status_bar(),
|
||||
FileExplorerTab::Remote => self.refresh_remote_status_bar(),
|
||||
_ => panic!("Found result doesn't support SORTING"),
|
||||
};
|
||||
// Reload files
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.update_local_filelist(),
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
// Deps
|
||||
extern crate bytesize;
|
||||
extern crate hostname;
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
extern crate users;
|
||||
// locals
|
||||
use super::{browser::FileExplorerTab, Context, FileTransferActivity};
|
||||
|
@ -49,6 +49,7 @@ use tuirealm::components::{
|
|||
input::{Input, InputPropsBuilder},
|
||||
progress_bar::{ProgressBar, ProgressBarPropsBuilder},
|
||||
radio::{Radio, RadioPropsBuilder},
|
||||
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
|
||||
span::{Span, SpanPropsBuilder},
|
||||
table::{Table, TablePropsBuilder},
|
||||
};
|
||||
|
@ -58,7 +59,7 @@ use tuirealm::tui::{
|
|||
style::Color,
|
||||
widgets::{BorderType, Borders, Clear},
|
||||
};
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
use users::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
impl FileTransferActivity {
|
||||
|
@ -99,13 +100,18 @@ impl FileTransferActivity {
|
|||
.build(),
|
||||
)),
|
||||
);
|
||||
// Mount status bar
|
||||
// Mount status bars
|
||||
self.view.mount(
|
||||
super::COMPONENT_SPAN_STATUS_BAR,
|
||||
super::COMPONENT_SPAN_STATUS_BAR_LOCAL,
|
||||
Box::new(Span::new(SpanPropsBuilder::default().build())),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_SPAN_STATUS_BAR_REMOTE,
|
||||
Box::new(Span::new(SpanPropsBuilder::default().build())),
|
||||
);
|
||||
// Load process bar
|
||||
self.refresh_status_bar();
|
||||
self.refresh_local_status_bar();
|
||||
self.refresh_remote_status_bar();
|
||||
// Update components
|
||||
let _ = self.update_local_filelist();
|
||||
let _ = self.update_remote_filelist();
|
||||
|
@ -144,6 +150,12 @@ impl FileTransferActivity {
|
|||
.constraints([Constraint::Length(1), Constraint::Length(10)].as_ref())
|
||||
.direction(Direction::Vertical)
|
||||
.split(chunks[1]);
|
||||
// Create status bar chunks
|
||||
let status_bar_chunks = Layout::default()
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.direction(Direction::Horizontal)
|
||||
.horizontal_margin(1)
|
||||
.split(bottom_chunks[0]);
|
||||
// If width is unset in the storage, set width
|
||||
if !store.isset(super::STORAGE_EXPLORER_WIDTH) {
|
||||
store.set_unsigned(super::STORAGE_EXPLORER_WIDTH, tabs_chunks[0].width as usize);
|
||||
|
@ -169,11 +181,20 @@ impl FileTransferActivity {
|
|||
.view
|
||||
.render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]),
|
||||
}
|
||||
// Draw log box and status bar
|
||||
// Draw log box
|
||||
self.view
|
||||
.render(super::COMPONENT_LOG_BOX, f, bottom_chunks[1]);
|
||||
self.view
|
||||
.render(super::COMPONENT_SPAN_STATUS_BAR, f, bottom_chunks[0]);
|
||||
// Draw status bar
|
||||
self.view.render(
|
||||
super::COMPONENT_SPAN_STATUS_BAR_LOCAL,
|
||||
f,
|
||||
status_bar_chunks[0],
|
||||
);
|
||||
self.view.render(
|
||||
super::COMPONENT_SPAN_STATUS_BAR_REMOTE,
|
||||
f,
|
||||
status_bar_chunks[1],
|
||||
);
|
||||
// @! Draw popups
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_COPY) {
|
||||
if props.visible {
|
||||
|
@ -817,7 +838,7 @@ impl FileTransferActivity {
|
|||
.build(),
|
||||
);
|
||||
// User
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
let username: String = match file.get_user() {
|
||||
Some(uid) => match get_user_by_uid(uid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
|
@ -828,7 +849,7 @@ impl FileTransferActivity {
|
|||
#[cfg(target_os = "windows")]
|
||||
let username: String = format!("{}", file.get_user().unwrap_or(0));
|
||||
// Group
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
let group: String = match file.get_group() {
|
||||
Some(gid) => match get_group_by_gid(gid) {
|
||||
Some(group) => group.name().to_string_lossy().to_string(),
|
||||
|
@ -864,9 +885,54 @@ impl FileTransferActivity {
|
|||
self.view.umount(super::COMPONENT_LIST_FILEINFO);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_status_bar(&mut self) {
|
||||
let bar_spans: Vec<TextSpan> = vec![
|
||||
TextSpanBuilder::new("Synchronized Browsing: ")
|
||||
pub(super) fn refresh_local_status_bar(&mut self) {
|
||||
let local_bar_spans: Vec<TextSpan> = vec![
|
||||
TextSpanBuilder::new("File sorting: ")
|
||||
.with_foreground(Color::LightYellow)
|
||||
.build(),
|
||||
TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting()))
|
||||
.with_foreground(Color::LightYellow)
|
||||
.reversed()
|
||||
.build(),
|
||||
TextSpanBuilder::new(" Hidden files: ")
|
||||
.with_foreground(Color::LightBlue)
|
||||
.build(),
|
||||
TextSpanBuilder::new(Self::get_hidden_files_str(
|
||||
self.local().hidden_files_visible(),
|
||||
))
|
||||
.with_foreground(Color::LightBlue)
|
||||
.reversed()
|
||||
.build(),
|
||||
];
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_LOCAL) {
|
||||
self.view.update(
|
||||
super::COMPONENT_SPAN_STATUS_BAR_LOCAL,
|
||||
SpanPropsBuilder::from(props)
|
||||
.with_spans(local_bar_spans)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn refresh_remote_status_bar(&mut self) {
|
||||
let remote_bar_spans: Vec<TextSpan> = vec![
|
||||
TextSpanBuilder::new("File sorting: ")
|
||||
.with_foreground(Color::LightYellow)
|
||||
.build(),
|
||||
TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting()))
|
||||
.with_foreground(Color::LightYellow)
|
||||
.reversed()
|
||||
.build(),
|
||||
TextSpanBuilder::new(" Hidden files: ")
|
||||
.with_foreground(Color::LightBlue)
|
||||
.build(),
|
||||
TextSpanBuilder::new(Self::get_hidden_files_str(
|
||||
self.remote().hidden_files_visible(),
|
||||
))
|
||||
.with_foreground(Color::LightBlue)
|
||||
.reversed()
|
||||
.build(),
|
||||
TextSpanBuilder::new(" Sync Browsing: ")
|
||||
.with_foreground(Color::LightGreen)
|
||||
.build(),
|
||||
TextSpanBuilder::new(match self.browser.sync_browsing {
|
||||
|
@ -876,25 +942,13 @@ impl FileTransferActivity {
|
|||
.with_foreground(Color::LightGreen)
|
||||
.reversed()
|
||||
.build(),
|
||||
TextSpanBuilder::new(" Localhost file sorting: ")
|
||||
.with_foreground(Color::LightYellow)
|
||||
.build(),
|
||||
TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting()))
|
||||
.with_foreground(Color::LightYellow)
|
||||
.reversed()
|
||||
.build(),
|
||||
TextSpanBuilder::new(" Remote host file sorting: ")
|
||||
.with_foreground(Color::LightBlue)
|
||||
.build(),
|
||||
TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting()))
|
||||
.with_foreground(Color::LightBlue)
|
||||
.reversed()
|
||||
.build(),
|
||||
];
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR) {
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_REMOTE) {
|
||||
self.view.update(
|
||||
super::COMPONENT_SPAN_STATUS_BAR,
|
||||
SpanPropsBuilder::from(props).with_spans(bar_spans).build(),
|
||||
super::COMPONENT_SPAN_STATUS_BAR_REMOTE,
|
||||
SpanPropsBuilder::from(props)
|
||||
.with_spans(remote_bar_spans)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -905,9 +959,12 @@ impl FileTransferActivity {
|
|||
pub(super) fn mount_help(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_HELP,
|
||||
Box::new(Table::new(
|
||||
TablePropsBuilder::default()
|
||||
Box::new(Scrolltable::new(
|
||||
ScrollTablePropsBuilder::default()
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
||||
.with_highlighted_str(Some("?"))
|
||||
.with_max_scroll_step(8)
|
||||
.bold()
|
||||
.with_table(
|
||||
Some(String::from("Help")),
|
||||
TableBuilder::default()
|
||||
|
@ -1171,4 +1228,11 @@ impl FileTransferActivity {
|
|||
FileSorting::BySize => "By size",
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hidden_files_str(show: bool) -> &'static str {
|
||||
match show {
|
||||
true => "Show",
|
||||
false => "Hide",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ impl SetupActivity {
|
|||
error!("Failed to disable raw mode: {}", err);
|
||||
}
|
||||
// Leave alternate mode
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
ctx.leave_alternate_screen();
|
||||
}
|
||||
|
@ -149,6 +150,7 @@ impl SetupActivity {
|
|||
}
|
||||
}
|
||||
// Restore terminal
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
// Clear screen
|
||||
ctx.clear_screen();
|
||||
|
|
|
@ -92,6 +92,7 @@ impl SetupActivity {
|
|||
error!("Failed to disable raw mode: {}", err);
|
||||
}
|
||||
// Leave alternate mode
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
ctx.leave_alternate_screen();
|
||||
// Get result
|
||||
let result: Result<(), String> = match ctx.config_client.as_ref() {
|
||||
|
@ -121,6 +122,7 @@ impl SetupActivity {
|
|||
// Clear screen
|
||||
ctx.clear_screen();
|
||||
// Enter alternate mode
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
ctx.enter_alternate_screen();
|
||||
// Re-enable raw mode
|
||||
if let Err(err) = enable_raw_mode() {
|
||||
|
|
|
@ -40,8 +40,8 @@ use std::path::PathBuf;
|
|||
use tuirealm::components::{
|
||||
input::{Input, InputPropsBuilder},
|
||||
radio::{Radio, RadioPropsBuilder},
|
||||
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
|
||||
span::{Span, SpanPropsBuilder},
|
||||
table::{Table, TablePropsBuilder},
|
||||
};
|
||||
use tuirealm::tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
|
@ -557,9 +557,12 @@ impl SetupActivity {
|
|||
pub(super) fn mount_help(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_HELP,
|
||||
Box::new(Table::new(
|
||||
TablePropsBuilder::default()
|
||||
Box::new(Scrolltable::new(
|
||||
ScrollTablePropsBuilder::default()
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
||||
.with_highlighted_str(Some("?"))
|
||||
.with_max_scroll_step(8)
|
||||
.bold()
|
||||
.with_table(
|
||||
Some(String::from("Help")),
|
||||
TableBuilder::default()
|
||||
|
|
|
@ -103,6 +103,7 @@ impl Context {
|
|||
/// ### enter_alternate_screen
|
||||
///
|
||||
/// Enter alternate screen (gui window)
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn enter_alternate_screen(&mut self) {
|
||||
match execute!(
|
||||
self.terminal.backend_mut(),
|
||||
|
@ -177,7 +178,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "githubActions"))]
|
||||
#[cfg(not(feature = "github-actions"))]
|
||||
fn test_ui_context() {
|
||||
// Prepare stuff
|
||||
let mut ctx: Context = Context::new(None, Some(String::from("alles kaput")));
|
||||
|
@ -190,9 +191,12 @@ mod tests {
|
|||
assert!(ctx.get_error().is_some());
|
||||
assert!(ctx.get_error().is_none());
|
||||
// Try other methods
|
||||
ctx.enter_alternate_screen();
|
||||
ctx.clear_screen();
|
||||
ctx.leave_alternate_screen();
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
ctx.enter_alternate_screen();
|
||||
ctx.clear_screen();
|
||||
ctx.leave_alternate_screen();
|
||||
}
|
||||
drop(ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,7 +216,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "linux", target_os = "macos"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_utils_fmt_path_elide() {
|
||||
let p: &Path = &Path::new("/develop/pippo");
|
||||
// Under max size
|
||||
|
|
|
@ -80,7 +80,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(all(target_os = "macos", feature = "githubActions")))]
|
||||
#[cfg(not(all(target_os = "macos", feature = "github-actions")))]
|
||||
fn test_utils_git_check_for_updates() {
|
||||
assert!(check_for_updates("100.0.0").ok().unwrap().is_none());
|
||||
assert!(check_for_updates("0.0.1").ok().unwrap().is_some());
|
||||
|
|
|
@ -33,3 +33,7 @@ pub mod git;
|
|||
pub mod parser;
|
||||
pub mod random;
|
||||
pub mod ui;
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
pub mod test_helpers;
|
||||
|
|
|
@ -186,13 +186,10 @@ pub fn parse_lstime(tm: &str, fmt_year: &str, fmt_hours: &str) -> Result<SystemT
|
|||
let this_year: i32 = Utc::now().year();
|
||||
let date_time_str: String = format!("{} {}", tm, this_year);
|
||||
// Now parse
|
||||
match NaiveDateTime::parse_from_str(
|
||||
NaiveDateTime::parse_from_str(
|
||||
date_time_str.as_ref(),
|
||||
format!("{} %Y", fmt_hours).as_ref(),
|
||||
) {
|
||||
Ok(dt) => dt,
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
)?
|
||||
}
|
||||
};
|
||||
// Convert datetime to system time
|
||||
|
|
248
src/utils/test_helpers.rs
Normal file
248
src/utils/test_helpers.rs
Normal file
|
@ -0,0 +1,248 @@
|
|||
//! ## TestHelpers
|
||||
//!
|
||||
//! contains helper functions for tests
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use crate::fs::{FsDirectory, FsEntry, FsFile};
|
||||
// ext
|
||||
use std::fs::File;
|
||||
#[cfg(feature = "with-containers")]
|
||||
use std::fs::OpenOptions;
|
||||
#[cfg(feature = "with-containers")]
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
pub fn create_sample_file_entry() -> (FsFile, NamedTempFile) {
|
||||
// Write
|
||||
let tmpfile = create_sample_file();
|
||||
(
|
||||
FsFile {
|
||||
name: tmpfile
|
||||
.path()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
abs_path: tmpfile.path().to_path_buf(),
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 127,
|
||||
ftype: None, // File type
|
||||
readonly: false,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
},
|
||||
tmpfile,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_sample_file() -> NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
writeln!(
|
||||
tmpfile,
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus."
|
||||
)
|
||||
.unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
/// ### make_file_at
|
||||
///
|
||||
/// Make a file with `name` at specified path
|
||||
pub fn make_file_at(dir: &Path, filename: &str) -> std::io::Result<()> {
|
||||
let mut p: PathBuf = PathBuf::from(dir);
|
||||
p.push(filename);
|
||||
let mut file: File = File::create(p.as_path())?;
|
||||
writeln!(
|
||||
file,
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus."
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### make_dir_at
|
||||
///
|
||||
/// Make a directory in `dir`
|
||||
pub fn make_dir_at(dir: &Path, dirname: &str) -> std::io::Result<()> {
|
||||
let mut p: PathBuf = PathBuf::from(dir);
|
||||
p.push(dirname);
|
||||
std::fs::create_dir(p.as_path())
|
||||
}
|
||||
|
||||
#[cfg(feature = "with-containers")]
|
||||
pub fn write_file(file: &NamedTempFile, writable: &mut Box<dyn Write>) {
|
||||
let mut fhnd = OpenOptions::new()
|
||||
.create(false)
|
||||
.read(true)
|
||||
.write(false)
|
||||
.open(file.path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
// Read file
|
||||
let mut buffer: [u8; 65536] = [0; 65536];
|
||||
assert!(fhnd.read(&mut buffer).is_ok());
|
||||
// Write file
|
||||
assert!(writable.write(&buffer).is_ok());
|
||||
}
|
||||
|
||||
#[cfg(feature = "with-containers")]
|
||||
pub fn write_ssh_key() -> NamedTempFile {
|
||||
let mut tmpfile: NamedTempFile = NamedTempFile::new().unwrap();
|
||||
writeln!(
|
||||
tmpfile,
|
||||
r"-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAQEAxKyYUMRCNPlb4ZV1VMofrzApu2l3wgP4Ot9wBvHsw/+RMpcHIbQK
|
||||
9iQqAVp8Z+M1fJyPXTKjoJtIzuCLF6Sjo0KI7/tFTh+yPnA5QYNLZOIRZb8skumL4gwHww
|
||||
5Z942FDPuUDQ30C2mZR9lr3Cd5pA8S1ZSPTAV9QQHkpgoS8cAL8QC6dp3CJjUC8wzvXh3I
|
||||
oN3bTKxCpM10KMEVuWO3lM4Nvr71auB9gzo1sFJ3bwebCZIRH01FROyA/GXRiaOtJFG/9N
|
||||
nWWI/iG5AJzArKpLZNHIP+FxV/NoRH0WBXm9Wq5MrBYrD1NQzm+kInpS/2sXk3m1aZWqLm
|
||||
HF2NKRXSbQAAA8iI+KSniPikpwAAAAdzc2gtcnNhAAABAQDErJhQxEI0+VvhlXVUyh+vMC
|
||||
m7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VO
|
||||
H7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAe
|
||||
SmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndv
|
||||
B5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkys
|
||||
FisPU1DOb6QielL/axeTebVplaouYcXY0pFdJtAAAAAwEAAQAAAP8u3PFuTVV5SfGazwIm
|
||||
MgNaux82iOsAT/HWFWecQAkqqrruUw5f+YajH/riV61NE9aq2qNOkcJrgpTWtqpt980GGd
|
||||
SHWlgpRWQzfIooEiDk6Pk8RVFZsEykkDlJQSIu2onZjhi5A5ojHgZoGGabDsztSqoyOjPq
|
||||
6WPvGYRiDAR3leBMyp1WufBCJqAsC4L8CjPJSmnZhc5a0zXkC9Syz74Fa08tdM7bGhtvP1
|
||||
GmzuYxkgxHH2IFeoumUSBHRiTZayGuRUDel6jgEiUMxenaDKXe7FpYzMm9tQZA10Mm4LhK
|
||||
5rP9nd2/KRTFRnfZMnKvtIRC9vtlSLBe14qw+4ZCl60AAACAf1kghlO3+HIWplOmk/lCL0
|
||||
w75Zz+RdvueL9UuoyNN1QrUEY420LsixgWSeRPby+Rb/hW+XSAZJQHowQ8acFJhU85So7f
|
||||
4O4wcDuE4f6hpsW9tTfkCEUdLCQJ7EKLCrod6jIV7hvI6rvXiVucRpeAzdOaq4uzj2cwDd
|
||||
tOdYVsnmQAAACBAOVxBsvO/Sr3rZUbNtA6KewZh/09HNGoKNaCeiD7vaSn2UJbbPRByF/o
|
||||
Oo5zv8ee8r3882NnmG808XfSn7pPZAzbbTmOaJt0fmyZhivCghSNzV6njW3o0PdnC0fGZQ
|
||||
ruVXgkd7RJFbsIiD4dDcF4VCjwWHfTK21EOgJUA5pN6TNvAAAAgQDbcJWRx8Uyhkj2+srb
|
||||
3n2Rt6CR7kEl9cw17ItFjMn+pO81/5U2aGw0iLlX7E06TAMQC+dyW/WaxQRey8RRdtbJ1e
|
||||
TNKCN34QCWkyuYRHGhcNc0quEDayPw5QWGXlP4BzjfRUcPxY9cCXLe5wDLYsX33HwOAc59
|
||||
RorU9FCmS/654wAAABFyb290QDhjNTBmZDRjMzQ1YQECAw==
|
||||
-----END OPENSSH PRIVATE KEY-----"
|
||||
)
|
||||
.unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
/// ### make_fsentry
|
||||
///
|
||||
/// Create a FsEntry at specified path
|
||||
pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry {
|
||||
match is_dir {
|
||||
true => FsEntry::Directory(FsDirectory {
|
||||
name: path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
abs_path: path,
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
readonly: false,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
}),
|
||||
false => FsEntry::File(FsFile {
|
||||
name: path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
abs_path: path,
|
||||
last_change_time: SystemTime::UNIX_EPOCH,
|
||||
last_access_time: SystemTime::UNIX_EPOCH,
|
||||
creation_time: SystemTime::UNIX_EPOCH,
|
||||
size: 127,
|
||||
ftype: None, // File type
|
||||
readonly: false,
|
||||
symlink: None, // UNIX only
|
||||
user: Some(0), // UNIX only
|
||||
group: Some(0), // UNIX only
|
||||
unix_pex: Some((6, 4, 4)), // UNIX only
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_utils_test_helpers_sample_file() {
|
||||
let (file, _) = create_sample_file_entry();
|
||||
assert_eq!(file.readonly, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_utils_test_helpers_write_file() {
|
||||
let (_, temp) = create_sample_file_entry();
|
||||
let tempdest = NamedTempFile::new().unwrap();
|
||||
let mut dest: Box<dyn Write> = Box::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.read(false)
|
||||
.write(true)
|
||||
.open(tempdest.path())
|
||||
.ok()
|
||||
.unwrap(),
|
||||
);
|
||||
write_file(&temp, &mut dest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "with-containers")]
|
||||
fn test_utils_test_helpers_write_ssh_key() {
|
||||
let _ = write_ssh_key();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utils_test_helpers_make_fsentry() {
|
||||
assert_eq!(
|
||||
make_fsentry(PathBuf::from("/tmp/omar.txt"), false)
|
||||
.unwrap_file()
|
||||
.name
|
||||
.as_str(),
|
||||
"omar.txt"
|
||||
);
|
||||
assert_eq!(
|
||||
make_fsentry(PathBuf::from("/tmp/cards"), true)
|
||||
.unwrap_dir()
|
||||
.name
|
||||
.as_str(),
|
||||
"cards"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utils_test_helpers_make_samples() {
|
||||
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
||||
assert!(make_file_at(tmpdir.path(), "omaroni.txt").is_ok());
|
||||
assert!(make_file_at(PathBuf::from("/aaaaa/bbbbb/cccc").as_path(), "readme.txt").is_err());
|
||||
assert!(make_dir_at(tmpdir.path(), "docs").is_ok());
|
||||
assert!(make_dir_at(PathBuf::from("/aaaaa/bbbbb/cccc").as_path(), "docs").is_err());
|
||||
}
|
||||
}
|
35
tests/docker-compose.yml
Normal file
35
tests/docker-compose.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
version: "3"
|
||||
services:
|
||||
openssh-server:
|
||||
image: ghcr.io/linuxserver/openssh-server
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/London
|
||||
- SUDO_ACCESS=false
|
||||
- PUBLIC_KEY=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a
|
||||
- PASSWORD_ACCESS=true
|
||||
- USER_PASSWORD=password
|
||||
- USER_NAME=sftp
|
||||
ports:
|
||||
- "10022:2222"
|
||||
openssh-server-scp:
|
||||
image: ghcr.io/linuxserver/openssh-server
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/London
|
||||
- SUDO_ACCESS=false
|
||||
- PASSWORD_ACCESS=true
|
||||
- PUBLIC_KEY=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a
|
||||
- USER_PASSWORD=password
|
||||
- USER_NAME=sftp
|
||||
ports:
|
||||
- "10222:2222"
|
||||
ftp-server:
|
||||
image: afharo/pure-ftp
|
||||
ports:
|
||||
- "10021:21"
|
||||
- "30000-30009:30000-30009"
|
||||
environment:
|
||||
- PUBLICHOST=localhost
|
29
tests/test.sh
Executable file
29
tests/test.sh
Executable file
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
if [ ! -f docker-compose.yml ]; then
|
||||
set -e
|
||||
cd tests/
|
||||
set +e
|
||||
fi
|
||||
|
||||
echo "Prepare volume..."
|
||||
rm -rf /tmp/termscp-test-ftp
|
||||
mkdir -p /tmp/termscp-test-ftp
|
||||
echo "Building docker image..."
|
||||
docker compose build
|
||||
set -e
|
||||
docker compose up -d
|
||||
set +e
|
||||
|
||||
# Go back to src root
|
||||
cd ..
|
||||
# Run tests
|
||||
echo "Running tests"
|
||||
cargo test --features with-containers -- --test-threads 1
|
||||
TEST_RESULT=$?
|
||||
# Stop container
|
||||
cd tests/
|
||||
echo "Stopping container..."
|
||||
docker compose stop
|
||||
|
||||
exit $TEST_RESULT
|
Loading…
Reference in a new issue