diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml index 1dd2a50..9faf518 100644 --- a/.github/workflows/build-artifacts.yml +++ b/.github/workflows/build-artifacts.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: env: - TERMSCP_VERSION: "0.18.0" + TERMSCP_VERSION: "0.19.0" jobs: build-binaries: @@ -14,13 +14,24 @@ jobs: platform: - release_for: MacOS-x86_64 os: macos-latest + platform: macos target: x86_64-apple-darwin - script: macos.sh - - release_for: MacOS-M1 + - release_for: MacOS-aarch64 os: macos-latest + platform: macos target: aarch64-apple-darwin - script: macos.sh + + - release_for: Linux-x86_64 + os: ubuntu-latest + platform: linux + target: x86_64-unknown-linux-gnu + debian_suffix: amd64 + + - release_for: Windows-x86_64 + os: windows-latest + platform: windows + target: x86_64-pc-windows-msvc runs-on: ${{ matrix.platform.os }} steps: @@ -29,18 +40,122 @@ jobs: with: toolchain: stable targets: ${{ matrix.platform.target }} - - name: Build release - run: cargo build --release --target ${{ matrix.platform.target }} - - name: Prepare artifact files + + - name: Install dependencies (Linux) + if: matrix.platform.platform == 'linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + make \ + libgit2-dev \ + build-essential \ + pkg-config \ + libbsd-dev \ + libcap-dev \ + libcups2-dev \ + libgnutls28-dev \ + libicu-dev \ + libjansson-dev \ + libkeyutils-dev \ + libldap2-dev \ + zlib1g-dev \ + libpam0g-dev \ + libacl1-dev \ + libarchive-dev \ + flex \ + bison \ + libntirpc-dev \ + libtracker-sparql-3.0-dev \ + libglib2.0-dev \ + libdbus-1-dev \ + libsasl2-dev \ + libunistring-dev \ + libdbus-1-dev \ + cpanminus; + sudo cpanm Parse::Yapp::Driver + + - name: Install dependencies (MacOS) + if: matrix.platform.platform == 'macos' + run: | + brew update + brew install \ + bison \ + cpanminus \ + cups \ + flex \ + gettext \ + gmp \ + gnutls \ + icu4c \ + jansson \ + libarchive \ + libbsd \ + libunistring \ + libgit2 \ + libtirpc \ + openldap \ + pkg-config \ + zlib + brew link --force bison + brew link --force cups + brew link --force flex + brew link --force gettext + brew link --force gmp + brew link --force gnutls + brew link --force icu4c + brew link --force jansson + brew link --force libarchive + brew link --force libbsd + brew link --force libgit2 + brew link --force libtirpc + brew link --force libunistring + brew link --force openldap + brew link --force zlib + cpanm Parse::Yapp::Driver + - name: Build release (MacOS Intel) + if: matrix.platform.target == 'x86_64-apple-darwin' + run: cargo build --release --no-default-features --features keyring --target ${{ matrix.platform.target }} + + - name: Build release (others) + if: matrix.platform.target != 'x86_64-apple-darwin' + run: cargo build --release --features smb-vendored --target ${{ matrix.platform.target }} + + - name: Build deb + if: matrix.platform.platform == 'linux' + run: | + cargo install cargo-deb + cargo deb --target ${{ matrix.platform.target }} --features smb-vendored + + - name: Prepare artifact files (Posix) + if: matrix.platform.platform != 'windows' run: | mkdir -p .artifact mv target/${{ matrix.platform.target }}/release/termscp .artifact/termscp tar -czf .artifact/termscp-v${{ env.TERMSCP_VERSION }}-${{ matrix.platform.target }}.tar.gz -C .artifact termscp ls -l .artifact/ - - name: "Upload artifact" + - name: Upload artifact (Posix) + if: matrix.platform.platform != 'windows' uses: actions/upload-artifact@v4 with: if-no-files-found: error retention-days: 1 name: termscp-${{ matrix.platform.target }} path: .artifact/termscp-v${{ env.TERMSCP_VERSION }}-${{ matrix.platform.target }}.tar.gz + + - name: Upload artifact (Windows) + if: matrix.platform.platform == 'windows' + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + retention-days: 1 + name: termscp-${{ matrix.platform.target }} + path: target/${{ matrix.platform.target }}/release/termscp.exe + + - name: Upload artifact (Deb) + if: matrix.platform.platform == 'linux' + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + retention-days: 1 + name: termscp-${{ matrix.platform.target }}-deb + path: target/debian/termscp_${{ env.TERMSCP_VERSION }}-1_${{ matrix.platform.debian_suffix }}.deb diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7ca11b4..3922f76 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -22,6 +22,13 @@ jobs: with: toolchain: stable components: rustfmt, clippy + - name: Install dependencies + run: | + brew update + brew install \ + pkg-config \ + samba + brew link --force samba - name: Build run: cargo build - name: Run tests diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1f4235c..cc45cc1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -15,7 +15,7 @@ env: jobs: build: - runs-on: windows-2019 + runs-on: windows-latest steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 74119a7..dda8da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [0.19.0](#0190) - [0.18.0](#0180) - [0.17.0](#0170) - [0.16.1](#0161) @@ -41,9 +42,25 @@ --- +## 0.19.0 + +Released on 11/11/2025 + +- [Issue 297](https://github.com/veeso/termscp/issues/297): Added `` keybinding to get the total size of selected paths. +- [Issue 331](https://github.com/veeso/termscp/issues/331): Added new `import-ssh-hosts` CLI subcommand to import all the hosts from the ssh config as bookmarks. +- [Issue 335](https://github.com/veeso/termscp/issues/335): Changed file overwrite behaviour + - Now the user can choose for each file whether to overwrite, skip or overwrite all/skip all. +- [Issue 354](https://github.com/veeso/termscp/issues/354): + - Removed error popup message if failed to check for updates. + - Prevent long timeouts when checking for updates if the network is down or the DNS is not working. +- [Issue 356](https://github.com/veeso/termscp/issues/356): Fixed SSH auth issue not trying with the password if any RSA key was found. +- [Issue 334](https://github.com/veeso/termscp/issues/334): SMB support for MacOS with vendored build of libsmbclient. +- [Issue 337](https://github.com/veeso/termscp/issues/337): Migrated to libssh.org on Linux and MacOS for better ssh agent support. +- [Issue 361](https://github.com/veeso/termscp/issues/361): Report a message while calculating total size of files to transfer. + ## 0.18.0 -Released on 10/06/2025 +Released on 11/11/2025 - 🐚 An **Embedded shell for termscp**: - [Issue 340](https://github.com/veeso/termscp/issues/340): Replaced the `Exec` popup with a **fully functional terminal emulator** embedded thanks to [A-Kenji's tui-term](https://github.com/a-kenji/tui-term). diff --git a/Cargo.lock b/Cargo.lock index aaa76c4..7038a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -43,12 +34,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -60,15 +45,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -119,15 +104,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.6.3" +version = "1.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a18fd934af6ae7ca52410d4548b98eb895aab0f1ea417d168d85db1434a141" +checksum = "8bc1b40fb26027769f16960d2f4a6bc20c4bb755d403e552c8c1a73af433c246" dependencies = [ "aws-credential-types", "aws-runtime", @@ -155,9 +140,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.3" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687bc16bc431a8533fe0097c7f0182874767f920989d7260950172ae8e3c4465" +checksum = "d025db5d9f52cbc413b167136afb3d8aeea708c0d8884783cf6253be5e22f6f2" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -167,9 +152,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" dependencies = [ "aws-lc-sys", "zeroize", @@ -177,22 +162,23 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", + "libloading", ] [[package]] name = "aws-runtime" -version = "1.5.7" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4063282c69991e57faab9e5cb21ae557e59f5b0fb285c196335243df8dc25c" +checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -215,9 +201,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.91.0" +version = "1.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c7d58f9c99e7d33e5a9b288ec84db24de046add7ba4c1e98baf6b3a5b37fde" +checksum = "2c230530df49ed3f2b7b4d9c8613b72a04cdac6452eede16d587fc62addfabac" dependencies = [ "aws-credential-types", "aws-runtime", @@ -249,9 +235,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.72.0" +version = "1.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13118ad30741222f67b1a18e5071385863914da05124652b38e172d6d3d9ce31" +checksum = "357a841807f6b52cb26123878b3326921e2a25faca412fabdd32bd35b7edd5d3" dependencies = [ "aws-credential-types", "aws-runtime", @@ -271,9 +257,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.73.0" +version = "1.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f879a8572b4683a8f84f781695bebf2f25cf11a81a2693c31fc0e0215c2c1726" +checksum = "9d1cc7fb324aa12eb4404210e6381195c5b5e9d52c2682384f295f38716dd3c7" dependencies = [ "aws-credential-types", "aws-runtime", @@ -293,9 +279,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.73.0" +version = "1.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e9c3c24e36183e2f698235ed38dcfbbdff1d09b9232dc866c4be3011e0b47e" +checksum = "e7d835f123f307cafffca7b9027c14979f1d403b417d8541d67cf252e8a21e35" dependencies = [ "aws-credential-types", "aws-runtime", @@ -316,9 +302,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3734aecf9ff79aa401a6ca099d076535ab465ff76b46440cf567c8e70b65dc13" +checksum = "084c34162187d39e3740cb635acd73c4e3a551a36146ad6fe8883c929c9f876c" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -355,9 +341,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.63.3" +version = "0.63.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f77a921dbd2c78ebe70726799787c1d110a2245dd65e39b20923dfdfb2deee" +checksum = "56d2df0314b8e307995a3b86d44565dfe9de41f876901a7d71886c756a25979f" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -375,9 +361,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.8" +version = "0.60.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c45d3dddac16c5c59d553ece225a88870cf81b7b813c9cc17b78cf4685eac7a" +checksum = "182b03393e8c677347fb5705a04a9392695d47d20ef0a2f8cfe28c8e6b9b9778" dependencies = [ "aws-smithy-types", "bytes", @@ -386,9 +372,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.1" +version = "0.62.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99335bec6cdc50a346fda1437f9fefe33abf8c99060739a546a16457f2862ca9" +checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", @@ -407,38 +393,39 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "073d330f94bdf1f47bb3e0f5d45dda1e372a54a553c39ab6e9646902c8c81594" +checksum = "734b4282fbb7372923ac339cc2222530f8180d9d4745e582de19a18cee409fd8" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "h2 0.3.26", - "h2 0.4.10", + "h2 0.3.27", + "h2 0.4.12", "http 0.2.12", "http 1.3.1", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.24.2", "hyper-rustls 0.27.7", "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.27", + "rustls 0.23.32", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", + "tokio-rustls 0.26.4", "tower 0.5.2", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.61.3" +version = "0.61.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +checksum = "eaa31b350998e703e9826b2104dd6f63be0508666e1aba88137af060e8944047" dependencies = [ "aws-smithy-types", ] @@ -464,9 +451,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.3" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14302f06d1d5b7d333fd819943075b13d27c7700b414f574c3c35859bfb55d5e" +checksum = "4fa63ad37685ceb7762fa4d73d06f1d5493feb88e3f27259b9ed277f4c01b185" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -488,9 +475,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e5d9e3a80a18afa109391fb5ad09c3daf887b516c6fd805a157c6ea7994a57" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -505,9 +492,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40076bd09fadbc12d5e026ae080d0930defa606856186e31d83ccc6a255eeaf3" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" dependencies = [ "base64-simd", "bytes", @@ -531,18 +518,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.9" +version = "0.60.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.7" +version = "1.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" +checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -552,21 +539,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -603,25 +575,22 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bindgen" -version = "0.69.5" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", + "itertools", "log", "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash 1.1.0", + "rustc-hash", "shlex", "syn", - "which", ] [[package]] @@ -632,9 +601,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block-buffer" @@ -665,9 +634,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -693,26 +662,26 @@ dependencies = [ [[package]] name = "bytesize" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" +checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" [[package]] name = "bytestring" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" dependencies = [ "bytes", ] [[package]] name = "camino" -version = "1.1.10" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -735,7 +704,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -746,9 +715,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] @@ -764,10 +733,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.26" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -784,9 +754,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -796,17 +766,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -951,23 +920,22 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc-fast" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fcb2be5386ffb77e30bf10820934cb89a628bcb976e7cc632dcd88c059ebea" +checksum = "6bf62af4cc77d8fe1c22dde4e721d87f2f54056139d8c412e1366b740305f56f" dependencies = [ - "cc", "crc", "digest", "libc", - "rand 0.9.1", + "rand 0.9.2", "regex", ] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1003,7 +971,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "crossterm_winapi", "mio", "parking_lot", @@ -1019,13 +987,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", - "rustix 1.0.7", + "rustix 1.1.2", "signal-hook", "signal-hook-mio", "winapi", @@ -1142,27 +1110,24 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "dbus" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9" dependencies = [ "libc", "libdbus-sys", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "dbus-secret-service" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42a16374481d92aed73ae45b1f120207d8e71d24fb89f357fadbd8f946fd84b" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" dependencies = [ "dbus", - "futures-util", - "num", - "once_cell", "openssl", - "rand 0.8.5", + "zeroize", ] [[package]] @@ -1193,18 +1158,18 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", @@ -1307,7 +1272,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1316,7 +1281,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "objc2", ] @@ -1370,9 +1335,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -1442,12 +1407,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1474,16 +1439,22 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "flate2" version = "1.1.2" @@ -1523,9 +1494,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1595,9 +1566,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -1680,23 +1651,17 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "git2" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "libgit2-sys", "log", @@ -1707,9 +1672,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" @@ -1724,9 +1689,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -1743,9 +1708,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1762,9 +1727,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1833,7 +1798,7 @@ checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -1914,14 +1879,14 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1930,19 +1895,21 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.10", + "futures-core", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1958,13 +1925,13 @@ dependencies = [ "futures-util", "headers", "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.27.7", "hyper-util", "pin-project-lite", "rustls-native-certs 0.7.3", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tower-service", ] @@ -1991,14 +1958,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "log", - "rustls 0.23.27", + "rustls 0.23.32", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tower-service", "webpki-roots", ] @@ -2009,7 +1976,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "pin-project-lite", "tokio", @@ -2031,9 +1998,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -2042,12 +2009,12 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -2055,9 +2022,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2171,9 +2138,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -2192,9 +2159,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -2225,7 +2192,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "inotify-sys", "libc", ] @@ -2251,9 +2218,9 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ "darling", "indoc", @@ -2297,15 +2264,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -2323,9 +2281,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -2333,9 +2291,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -2371,17 +2329,18 @@ dependencies = [ [[package]] name = "keyring" -version = "3.6.2" +version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1961983669d57bdfe6c0f3ef8e4c229b5ef751afcc7d87e4271d2f71f6ccfa8b" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ "byteorder", "dbus-secret-service", "log", "openssl", "security-framework 2.11.1", - "security-framework 3.2.0", - "windows-sys 0.59.0", + "security-framework 3.5.1", + "windows-sys 0.60.2", + "zeroize", ] [[package]] @@ -2430,7 +2389,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-http-proxy", "hyper-rustls 0.27.7", "hyper-timeout", @@ -2440,7 +2399,7 @@ dependencies = [ "kube-core", "pem", "rand 0.8.5", - "rustls 0.23.27", + "rustls 0.23.32", "rustls-pemfile 2.2.0", "secrecy", "serde", @@ -2472,9 +2431,9 @@ dependencies = [ [[package]] name = "lazy-regex" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -2483,9 +2442,9 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" dependencies = [ "proc-macro2", "quote", @@ -2499,23 +2458,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" -version = "0.2.172" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libdbus-sys" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f" dependencies = [ "cc", "pkg-config", @@ -2523,9 +2476,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.18.1+1.9.0" +version = "0.18.2+1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" +checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" dependencies = [ "cc", "libc", @@ -2542,20 +2495,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.48.5", ] [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "redox_syscall", ] +[[package]] +name = "libssh-rs" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3066e110d6bb95a5addbd9c2ee595efdaeecaddd4793f3803dfee2968a74c685" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libssh-rs-sys", + "openssl-sys", + "thiserror 1.0.69", +] + +[[package]] +name = "libssh-rs-sys" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d528ea9ac190fa364ff12193da82222dfc645e7ab28666ae91493bd288a1a0" +dependencies = [ + "cc", + "libz-sys", + "openssl-sys", + "pkg-config", +] + [[package]] name = "libssh2-sys" version = "0.3.1" @@ -2590,9 +2568,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -2602,9 +2580,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -2618,9 +2596,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" @@ -2639,9 +2617,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "mac-notification-sys" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b95dfb34071d1592b45622bf93e315e3a72d414b6782aca9a015c12bec367ef" +checksum = "119c8490084af61b44c9eda9d626475847a186737c0378c85e32d77c33a01cd4" dependencies = [ "cc", "objc2", @@ -2677,9 +2655,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -2749,12 +2727,11 @@ checksum = "995defdca0a589acfdd1bd2e8e3b896b4d4f7675a31fd14c32611440c7f608e6" [[package]] name = "notify" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.1", - "filetime", + "bitflags 2.9.4", "fsevent-sys", "inotify", "kqueue", @@ -2763,7 +2740,7 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2815,39 +2792,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -2863,28 +2807,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2911,9 +2833,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] @@ -2924,7 +2846,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "dispatch2", "objc2", ] @@ -2941,22 +2863,13 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "libc", "objc2", "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -2980,7 +2893,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "foreign-types", "libc", @@ -3008,9 +2921,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.0+3.5.0" +version = "300.5.3+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" dependencies = [ "cc", ] @@ -3095,12 +3008,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "path-slash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696" - [[package]] name = "path-slash" version = "0.2.1" @@ -3115,23 +3022,23 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pavao" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5b84c6ab7b745d233668e868f67dac1f40a8329c0f9d41ac50addf36adb883" +checksum = "f716d52d838ee3062b42302f328d6a3ecfb0006b651707886fa890e8951c7cf7" dependencies = [ "cfg_aliases", "lazy_static", "libc", "log", "pavao-sys", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "pavao-src" -version = "4.22.0" +version = "4.22.0-3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b20a487b78fd44d081eb16717d062f78cae52888a7affeed1e7da3be1b9247" +checksum = "18364b5a8ff85f60fcca22e0d21a60f50ab4b9152d51f7358f3b42bbace88aab" dependencies = [ "cc", "git2", @@ -3140,9 +3047,9 @@ dependencies = [ [[package]] name = "pavao-sys" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd4336fa254e9df9f752adc3e2b70f01b4eacfb32f34433bafda3a898056737" +checksum = "51792a86ac195a0e5b7a942f5b62e8f9494a260a5febe31673c5e7bb72ade55f" dependencies = [ "cc", "libc", @@ -3162,26 +3069,26 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.17", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" dependencies = [ "pest", "pest_generator", @@ -3189,9 +3096,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" dependencies = [ "pest", "pest_meta", @@ -3202,11 +3109,10 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -3277,9 +3183,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -3311,9 +3217,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -3321,9 +3227,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -3348,19 +3254,19 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", - "rustls 0.23.27", - "socket2", - "thiserror 2.0.12", + "rustc-hash", + "rustls 0.23.32", + "socket2 0.5.10", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -3368,20 +3274,20 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", - "rustc-hash 2.1.1", - "rustls 0.23.27", + "rustc-hash", + "rustls 0.23.32", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -3389,32 +3295,32 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -3429,9 +3335,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -3481,13 +3387,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cassowary", "compact_str", "crossterm 0.28.1", "indoc", "instability", - "itertools 0.13.0", + "itertools", "lru", "paste", "strum", @@ -3498,9 +3404,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -3508,9 +3414,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -3518,29 +3424,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -3550,9 +3456,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -3561,15 +3467,15 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "remotefs" @@ -3592,20 +3498,20 @@ dependencies = [ "aws-credential-types", "aws-sdk-s3", "log", - "path-slash 0.2.1", + "path-slash", "remotefs", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", ] [[package]] name = "remotefs-ftp" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5ed3e1a54684a0254980535eb4276dce92f16762f260f713ca9c3d574d9f0e" +checksum = "b1c84ed1367170d6f589f09998b648e7d9bc09d93091130293be8ff09b7a8329" dependencies = [ "log", - "path-slash 0.1.5", + "path-slash", "remotefs", "suppaftp", ] @@ -3622,7 +3528,7 @@ dependencies = [ "kube", "lazy-regex", "log", - "path-slash 0.2.1", + "path-slash", "remotefs", "tar", "tempfile", @@ -3647,14 +3553,15 @@ dependencies = [ [[package]] name = "remotefs-ssh" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0835109bdc3eeeb4b77f40b6146e0442155372cd9eb741a49e7479e97ea5c746" +checksum = "5ca8b65fbd60801ac03973a41196b030fb04d1ce1af33d3c58c5bad94d46eb27" dependencies = [ "chrono", "lazy-regex", + "libssh-rs", "log", - "path-slash 0.2.1", + "path-slash", "remotefs", "ssh2", "ssh2-config", @@ -3692,7 +3599,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", @@ -3723,20 +3630,20 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.20" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.4.10", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.27.7", "hyper-util", "js-sys", @@ -3744,14 +3651,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.27", + "rustls 0.23.32", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tower 0.5.2", "tower-http 0.6.6", "tower-service", @@ -3814,18 +3721,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -3847,24 +3742,24 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "linux-raw-sys 0.11.0", + "windows-sys 0.52.0", ] [[package]] @@ -3881,16 +3776,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.3", + "rustls-webpki 0.103.7", "subtle", "zeroize", ] @@ -3929,7 +3824,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.5.1", ] [[package]] @@ -3972,9 +3867,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "aws-lc-rs", "ring", @@ -3984,9 +3879,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustydav" @@ -4014,20 +3909,20 @@ dependencies = [ [[package]] name = "scc" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.1", ] [[package]] @@ -4048,9 +3943,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.8" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "sec1" @@ -4082,7 +3977,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -4091,11 +3986,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4104,9 +3999,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -4131,12 +4026,12 @@ checksum = "d832c086ece0dacc29fb2947bb4219b8f6e12fe9e40b7108f9e57c4224e47b5c" dependencies = [ "either", "flate2", - "hyper 1.6.0", + "hyper 1.7.0", "indicatif", "log", "quick-xml 0.37.5", "regex", - "reqwest 0.12.20", + "reqwest 0.12.23", "self-replace", "semver", "serde_json", @@ -4149,19 +4044,21 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -4176,10 +4073,19 @@ dependencies = [ ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -4188,23 +4094,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -4279,6 +4186,15 @@ dependencies = [ "digest", ] +[[package]] +name = "shellexpand" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +dependencies = [ + "dirs", +] + [[package]] name = "shlex" version = "1.3.0" @@ -4308,9 +4224,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -4354,12 +4270,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -4383,6 +4296,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spki" version = "0.6.0" @@ -4409,7 +4332,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f84d13b3b8a0d4e91a2629911e951db1bb8671512f5c09d7d4ba34500ba68c8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "libssh2-sys", "parking_lot", @@ -4417,17 +4340,17 @@ dependencies = [ [[package]] name = "ssh2-config" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c8904276fd411f602a6d7e447cccb6f669068badf71359bc4564a5144a866b" +checksum = "bc7aae258493fa8ea06796b133b9076f3002c46cd1a4085ddd6df7236fee7034" dependencies = [ "anyhow", - "bitflags 2.9.1", + "bitflags 2.9.4", "dirs", "git2", "glob", "log", - "thiserror 2.0.12", + "thiserror 2.0.17", "wildmatch", ] @@ -4479,23 +4402,23 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "suppaftp" -version = "6.3.0" +version = "7.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d869e942cc5f349ad91645925a9e6b570f62c4c170ad1c7b92b867bd16bd54" +checksum = "ba8928c89e226be233f0eb1594e9bd023f72a948dc06581c0d908387f57de1de" dependencies = [ "chrono", "futures-lite", "lazy-regex", "log", "native-tls", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "syn" -version = "2.0.102" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4580,22 +4503,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml 0.37.5", - "thiserror 2.0.12", - "windows 0.61.1", + "thiserror 2.0.17", + "windows 0.61.3", "windows-version", ] [[package]] name = "tempfile" -version = "3.20.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.52.0", ] [[package]] @@ -4609,10 +4532,10 @@ dependencies = [ [[package]] name = "termscp" -version = "0.18.0" +version = "0.19.0" dependencies = [ "argh", - "bitflags 2.9.1", + "bitflags 2.9.4", "bytesize", "cfg_aliases", "chrono", @@ -4631,7 +4554,7 @@ dependencies = [ "nucleo", "open", "pretty_assertions", - "rand 0.9.1", + "rand 0.9.2", "regex", "remotefs", "remotefs-aws-s3", @@ -4644,10 +4567,11 @@ dependencies = [ "self_update", "serde", "serial_test", + "shellexpand", "simplelog", "ssh2-config", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "toml", "tui-realm-stdlib", @@ -4683,11 +4607,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -4703,9 +4627,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -4723,9 +4647,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -4740,15 +4664,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -4766,9 +4690,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -4781,26 +4705,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -4829,11 +4752,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.27", + "rustls 0.23.32", "tokio", ] @@ -4851,9 +4774,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -4864,44 +4787,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", - "toml_write", + "toml_parser", + "toml_writer", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_datetime" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tower" @@ -4942,7 +4863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "base64 0.21.7", - "bitflags 2.9.1", + "bitflags 2.9.4", "bytes", "http 1.3.1", "http-body 1.0.1", @@ -4960,7 +4881,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "bytes", "futures-util", "http 1.3.1", @@ -4998,9 +4919,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -5045,15 +4966,15 @@ dependencies = [ [[package]] name = "tuirealm" -version = "3.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29607ee819f733f15c6bbad6aa760ed3d52b345588130003399bd792bfc6b0a8" +checksum = "7af0e5ae72e143be385952fe1bb76cc1fd5c517ba4f3a44f5f66e71e2e9d93e9" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "crossterm 0.29.0", "lazy-regex", "ratatui", - "thiserror 2.0.12", + "thiserror 2.0.17", "tuirealm_derive", ] @@ -5088,9 +5009,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -5100,9 +5021,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-linebreak" @@ -5122,7 +5043,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools 0.13.0", + "itertools", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -5153,13 +5074,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -5188,9 +5110,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "js-sys", "wasm-bindgen", @@ -5257,9 +5179,9 @@ dependencies = [ [[package]] name = "version-compare" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "version_check" @@ -5333,11 +5255,20 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", ] [[package]] @@ -5348,21 +5279,22 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -5374,9 +5306,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -5387,9 +5319,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5397,9 +5329,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -5410,18 +5342,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -5439,9 +5371,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -5460,20 +5392,20 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall", + "libredox", "wasite", "web-sys", ] [[package]] name = "wildmatch" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" +checksum = "2d654e41fe05169e03e27b97e0c23716535da037c1652a31fd99c6b2fad84059" [[package]] name = "winapi" @@ -5493,11 +5425,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -5518,14 +5450,14 @@ dependencies = [ [[package]] name = "windows" -version = "0.61.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core 0.61.2", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -5556,9 +5488,9 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link", + "windows-implement 0.60.1", + "windows-interface 0.59.2", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings", ] @@ -5570,7 +5502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -5587,9 +5519,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -5609,9 +5541,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", @@ -5620,9 +5552,15 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-numerics" @@ -5631,7 +5569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5649,7 +5587,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5658,7 +5596,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5688,6 +5626,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.4", +] + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5721,10 +5677,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ + "windows-link 0.2.0", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -5741,16 +5698,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] name = "windows-version" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -5893,12 +5850,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" -dependencies = [ - "memchr", -] +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] name = "winreg" @@ -5911,13 +5865,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -5927,12 +5878,12 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "xattr" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.0.7", + "rustix 1.1.2", ] [[package]] @@ -5973,18 +5924,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -6014,9 +5965,23 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" @@ -6031,9 +5996,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -6064,7 +6029,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "zopfli", ] @@ -6077,7 +6042,7 @@ checksum = "dba6063ff82cdbd9a765add16d369abe81e520f836054e997c2db217ceca40c0" dependencies = [ "base64 0.22.1", "ed25519-dalek", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e5fc639..c4f56d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ license = "MIT" name = "termscp" readme = "README.md" repository = "https://github.com/veeso/termscp" -version = "0.18.0" -rust-version = "1.85.1" +version = "0.19.0" +rust-version = "1.88.0" [package.metadata.rpm] package = "termscp" @@ -60,6 +60,7 @@ regex = "^1" remotefs = "^0.3" remotefs-aws-s3 = "0.4" remotefs-kube = "0.4" +remotefs-smb = { version = "^0.3", optional = true } remotefs-webdav = "^0.2" rpassword = "^7" self_update = { version = "^0.42", default-features = false, features = [ @@ -70,12 +71,13 @@ self_update = { version = "^0.42", default-features = false, features = [ "compression-zip-deflate", ] } serde = { version = "^1", features = ["derive"] } +shellexpand = "3" simplelog = "^0.12" -ssh2-config = "^0.5" +ssh2-config = "^0.6" tempfile = "3" thiserror = "2" -tokio = { version = "1.44", features = ["rt"] } -toml = "^0.8" +tokio = { version = "1", features = ["rt"] } +toml = "^0.9" tui-realm-stdlib = "3" tuirealm = "3" tui-term = "0.2" @@ -84,17 +86,20 @@ version-compare = "^0.2" whoami = "^1.6" wildmatch = "^2" -[target."cfg(not(target_os = \"macos\"))".dependencies] -remotefs-smb = { version = "^0.3", optional = true } - [target."cfg(target_family = \"unix\")".dependencies] -remotefs-ftp = { version = "^0.2", features = ["vendored", "native-tls"] } -remotefs-ssh = { version = "^0.6", features = ["ssh2-vendored"] } +remotefs-ftp = { version = "^0.3", features = [ + "native-tls-vendored", + "native-tls", +] } +remotefs-ssh = { version = "^0.7", default-features = false, features = [ + "find", + "libssh-vendored", +] } uzers = "0.12" [target."cfg(target_family = \"windows\")".dependencies] -remotefs-ftp = { version = "^0.2", features = ["native-tls"] } -remotefs-ssh = { version = "^0.6" } +remotefs-ftp = { version = "^0.3", features = ["native-tls"] } +remotefs-ssh = { version = "^0.7" } [dev-dependencies] pretty_assertions = "^1" diff --git a/README.md b/README.md index 3e95f58..16a7fae 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@

Developed by @veeso

-

Current version: 0.18.0 10/06/2025

+

Current version: 0.19.0 11/11/2025

Result<(), Box> { posix: { target_family = "unix" }, win: { target_family = "windows" }, // exclusive features - smb: { all(feature = "smb", not( macos )) }, - smb_unix: { all(unix, feature = "smb", not(macos)) }, + smb: { feature = "smb" }, + smb_unix: { all(unix, feature = "smb") }, smb_windows: { all(windows, feature = "smb") } } diff --git a/docs/de/README.md b/docs/de/README.md index 7a1471f..be113be 100644 --- a/docs/de/README.md +++ b/docs/de/README.md @@ -71,7 +71,7 @@

Entwickelt von @veeso

-

Aktuelle Version: 0.18.0 10/06/2025

+

Aktuelle Version: 0.19.0 11/11/2025

` aus Führen Sie termscp als `termscp update` aus +### Unterbefehle + +#### Ein Theme importieren + +Führen Sie termscp mit `termscp theme ` aus. + +#### Neueste Version installieren + +Führen Sie termscp mit `termscp update` aus. + +#### SSH-Hosts importieren + +Führen Sie termscp mit `termscp import-ssh-hosts [ssh-config-datei]` aus. + +Importieren Sie alle Hosts aus der angegebenen SSH-Konfigurationsdatei (wenn keine angegeben ist, wird `~/.ssh/config` verwendet) als Lesezeichen in termscp. Identitätsdateien werden ebenfalls als SSH-Schlüssel in termscp importiert. + --- ## S3-Verbindungsparameter @@ -296,6 +316,7 @@ Diese Panels sind im Wesentlichen 3 (ja, tatsächlich drei): | | Alle Dateien auswählen | | | | Alle Dateien abwählen | | | | Dateiübertragungsvorgang abbrechen | | +| `` | Gesamte Größe des ausgewählten Pfads abrufen | Size | | | Alle synchronisierten Pfade anzeigen | Track | ### Mit mehreren Dateien arbeiten 🄷 diff --git a/docs/es/README.md b/docs/es/README.md index e052306..8deca9d 100644 --- a/docs/es/README.md +++ b/docs/es/README.md @@ -71,7 +71,7 @@

Desarrollado por @veeso

-

Versión actual: 0.18.0 10/06/2025

+

Versión actual: 0.19.0 11/11/2025

` + +#### Instalar la versión mÔs reciente + +Ejecute termscp como `termscp update` + +#### Importar hosts SSH + +Ejecute termscp como `termscp import-ssh-hosts [archivo-config-ssh]` + +Importa todos los hosts del archivo de configuración SSH especificado (si no se proporciona, se usarÔ `~/.ssh/config`) como marcadores en termscp. Los archivos de identidad también se importarÔn como claves SSH en termscp. + --- ## S3 parÔmetros de conexión @@ -231,25 +251,25 @@ Para cambiar de panel, debe escribir `` para mover el panel del explorador | `` | Cambiar entre la pestaña de registro y el explorador | | | `` | Alternar archivos ocultos | All | | `` | Ordenar archivos por | Bubblesort? | -| `` | Copiar archivo / directorio | Copy | -| `` | Hacer directorio | Directory | -| `` | Eliminar archivo | Erase | +| `` | Copiar archivo / directorio | Copy | +| `` | Hacer directorio | Directory | +| `` | Eliminar archivo | Erase | | `` | Búsqueda de archivos | Find | | `` | Ir a la ruta proporcionada | Go to | -| `` | Mostrar ayuda | Help | +| `` | Mostrar ayuda | Help | | `` | Mostrar información sobre el archivo | Info | | `` | Crear un enlace simbólico que apunte a la entrada seleccionada actualmente | symlinK | | `` | Recargar contenido del directorio / Borrar selección | List | | `` | Seleccione un archivo | Mark | | `` | Crear un nuevo archivo con el nombre proporcionado | New | -| `` | Editar archivo | Open | +| `` | Editar archivo | Open | | `

` | Open log panel | Panel | -| `` | Salir de termscp | Quit | -| `` | Renombrar archivo | Rename | -| `` | Guardar archivo como... | Save | +| `` | Salir de termscp | Quit | +| `` | Renombrar archivo | Rename | +| `` | Guardar archivo como... | Save | | `` | Sincronizar los cambios en la ruta seleccionada con el control remoto | Track | | `` | Ir al directorio principal | Upper | -| `` | Abrir archivo con el programa predeterminado | View | +| `` | Abrir archivo con el programa predeterminado | View | | `` | Abrir archivo con el programa proporcionado | With | | `` | Ejecutar un comando | eXecute | | `` | Alternar navegación sincronizada | sYnc | @@ -258,9 +278,10 @@ Para cambiar de panel, debe escribir `` para mover el panel del explorador | `` | Seleccionar todos los archivos | | | `` | Deseleccionar todos los archivos | | | `` | Abortar el proceso de transferencia de archivos | | +| `` | Obtener el tamaño total de la ruta seleccionada | Size | | `` | Mostrar todas las rutas sincronizadas | Track | -### Trabajar con múltiples archivos 🄷 +### Trabajar con múltiples archivos 🄷 Puedes optar por trabajar con varios archivos, usando estos controles: diff --git a/docs/fr/README.md b/docs/fr/README.md index e3a216b..5692aa6 100644 --- a/docs/fr/README.md +++ b/docs/fr/README.md @@ -71,7 +71,7 @@

DƩveloppƩ par @veeso

-

Version actuelle: 0.18.0 10/06/2025

+

Version actuelle: 0.19.0 11/11/2025

[:port]/[/path/.../] ``` - #### Comment le mot de passe peut ĆŖtre fourni šŸ” Vous avez probablement remarquĆ© que, lorsque vous fournissez l'adresse comme argument, il n'y a aucun moyen de fournir le mot de passe. @@ -152,6 +155,22 @@ Le mot de passe peut ĆŖtre fourni de 3 maniĆØres lorsque l'argument d'adresse es - Avec `sshpass`: vous pouvez fournir un mot de passe via `sshpass`, par ex. `sshpass -f ~/.ssh/topsecret.key termscp cvisintin@192.168.1.31` - Il vous sera demandĆ© : si vous n'utilisez aucune des mĆ©thodes prĆ©cĆ©dentes, le mot de passe vous sera demandĆ©, comme c'est le cas avec les outils plus classiques tels que `scp`, `ssh`, etc. +### Sous-commandes + +#### Importer un thĆØme + +ExĆ©cutez termscp avec `termscp theme ` + +#### Installer la derniĆØre version + +ExĆ©cutez termscp avec `termscp update` + +#### Importer des hĆ“tes SSH + +ExĆ©cutez termscp avec `termscp import-ssh-hosts [fichier-config-ssh]` + +Importez tous les hĆ“tes du fichier de configuration SSH spĆ©cifiĆ© (si non fourni, `~/.ssh/config` sera utilisĆ©) comme favoris dans termscp. Les fichiers d'identitĆ© seront Ć©galement importĆ©s comme clĆ©s SSH dans termscp. + --- ## S3 paramĆØtres de connexion @@ -230,25 +249,25 @@ Pour changer de panneau, vous devez taper `` pour dĆ©placer le panneau de | `` | Basculer entre l'onglet journal et l'explorateur | | | `` | Basculer les fichiers cachĆ©s | All | | `` | Trier les fichiers par | Bubblesort? | -| `` | Copier le fichier/rĆ©pertoire | Copy | -| `` | CrĆ©er un dossier | Directory | -| `` | Supprimer le fichier (Identique Ć  `DEL`) | Erase | +| `` | Copier le fichier/rĆ©pertoire | Copy | +| `` | CrĆ©er un dossier | Directory | +| `` | Supprimer le fichier (Identique Ć  `DEL`) | Erase | | `` | Rechercher des fichiers | Find | | `` | Aller au chemin fourni | Go to | -| `` | Afficher l'aide | Help | +| `` | Afficher l'aide | Help | | `` | Afficher les informations sur le fichier ou le dossier sĆ©lectionnĆ© | Info | | `` | CrĆ©er un lien symbolique pointant vers l'entrĆ©e actuellement sĆ©lectionnĆ©e | symlinK | | `` | Recharger le contenu du rĆ©pertoire actuel / Effacer la sĆ©lection | List | | `` | SĆ©lectionner un fichier | Mark | | `` | CrĆ©er un nouveau fichier avec le nom fourni | New | -| `` | Modifier le fichier | Open | +| `` | Modifier le fichier | Open | | `

` | Ouvre le panel de journals | Panel | -| `` | Quitter termscp | Quit | -| `` | Renommer le fichier | Rename | -| `` | Enregistrer le fichier sous... | Save | +| `` | Quitter termscp | Quit | +| `` | Renommer le fichier | Rename | +| `` | Enregistrer le fichier sous... | Save | | `` | Synchroniser les modifications apportées au chemin sélectionné | Track | | `` | Aller dans le répertoire parent | Upper | -| `` | Ouvrir le fichier avec le programme défaut pour le type de fichier | View | +| `` | Ouvrir le fichier avec le programme défaut pour le type de fichier | View | | `` | Ouvrir le fichier avec le programme spécifié | With | | `` | Exécuter une commande | eXecute | | `` | Basculer la navigation synchronisée | sYnc | @@ -257,9 +276,10 @@ Pour changer de panneau, vous devez taper `` pour déplacer le panneau de | `` | Sélectionner tous les fichiers | | | `` | Desélectionner tous les fichiers | | | `` | Abandonner le processus de transfert de fichiers | | +| `` | Obtenir la taille totale du chemin sélectionné | Size | | `` | Afficher tous les chemins synchronisés | Track | -### Travailler sur plusieurs fichiers 🄷 +### Travailler sur plusieurs fichiers 🄷 Vous pouvez choisir de travailler sur plusieurs fichiers avec ces simples commandes : diff --git a/docs/it/README.md b/docs/it/README.md index 696bc5c..4653907 100644 --- a/docs/it/README.md +++ b/docs/it/README.md @@ -71,7 +71,7 @@

Sviluppato da @veeso

-

Versione corrente: 0.18.0 10/06/2025

+

Versione corrente: 0.19.0 11/11/2025

[:port]/[/path/.../] ``` - #### Come fornire la password šŸ” Quando si usa l'argomento indirizzo non ĆØ possibile fornire la password direttamente nell'argomento, esistono però altri metodi per farlo: @@ -149,6 +152,22 @@ Quando si usa l'argomento indirizzo non ĆØ possibile fornire la password diretta - Tramite `sshpass`: puoi fornire la password tramite l'applicazione GNU/Linux sshpass `sshpass -f ~/.ssh/topsecret.key termscp cvisintin@192.168.1.31` - Forniscila quando richiesta: se non la fornisci tramite nessun metodo precedente, alla connessione ti verrĆ  richiesto di fornirla in un prompt che la oscurerĆ  (come avviene con sudo tipo). +### Sottocomandi + +#### Importare un tema + +Esegui termscp come `termscp theme ` + +#### Installare l’ultima versione + +Esegui termscp come `termscp update` + +#### Importare host SSH + +Esegui termscp come `termscp import-ssh-hosts [file-config-ssh]` + +Importa tutti gli host dal file di configurazione SSH specificato (se non fornito, verrĆ  usato `~/.ssh/config`) come segnalibri in termscp. I file di identitĆ  verranno importati come chiavi SSH in termscp. + --- ## Parametri di connessione S3 @@ -226,25 +245,25 @@ Per cambiare pannello ti puoi muovere con le frecce, `` per andare sul pan | `` | Cambia tra explorer e pannello di log | | | `` | Mostra/nascondi file nascosti | All | | `` | Ordina file per | Bubblesort? | -| `` | Copia file/directory | Copy | -| `` | Crea directory | Directory | -| `` | Elimina file | Erase | +| `` | Copia file/directory | Copy | +| `` | Crea directory | Directory | +| `` | Elimina file | Erase | | `` | Cerca file (wild match supportato) | Find | | `` | Vai al percorso indicato | Go to | -| `` | Mostra help | Help | +| `` | Mostra help | Help | | `` | Mostra informazioni per il file selezionato | Info | | `` | Crea un link simbolico che punta al file selezionato | symlinK | | `` | Ricarica posizione corrente / pulisci selezione file | List | | `` | Seleziona file | Mark | | `` | Crea nuovo file con il nome fornito | New | -| `` | Modifica file; Vedi text editor | Open | +| `` | Modifica file; Vedi text editor | Open | | `

` | Apri pannello log | Panel | -| `` | Termina termscp | Quit | -| `` | Rinomina file | Rename | -| `` | Salva file con nome | Save | +| `` | Termina termscp | Quit | +| `` | Rinomina file | Rename | +| `` | Salva file con nome | Save | | `` | Sincronizza il percorso locale con l'host remoto | Track | | `` | Vai alla directory padre | Upper | -| `` | Apri il file con il programma definito dal sistema | View | +| `` | Apri il file con il programma definito dal sistema | View | | `` | Apri il file con il programma specificato | With | | `` | Esegui comando shell | eXecute | | `` | Abilita/disabilita Sync-Browsing | sYnc | @@ -253,6 +272,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `` per andare sul pan | `` | Seleziona tutti i file | | | `` | Deseleziona tutti i file | | | `` | Annulla trasferimento file | | +| `` | Ottieni la dimensione totale del percorso selezionato | Size | | `` | Visualizza tutti i percorsi sincronizzati | Track | ### Lavora con più file 🄷 diff --git a/docs/man.md b/docs/man.md index 15db697..9a29fc8 100644 --- a/docs/man.md +++ b/docs/man.md @@ -11,6 +11,7 @@ - [Subcommands](#subcommands) - [Import a theme](#import-a-theme) - [Install latest version](#install-latest-version) + - [Import ssh hosts](#import-ssh-hosts) - [S3 connection parameters](#s3-connection-parameters) - [S3 credentials 🦊](#s3-credentials-) - [File explorer šŸ“‚](#file-explorer-) @@ -166,6 +167,12 @@ Run termscp as `termscp theme ` Run termscp as `termscp update` +#### Import ssh hosts + +Run termscp as `termscp import-ssh-hosts [ssh-config-file]` + +Import all the hosts from the specified ssh config file (if not provided, `~/.ssh/config` will be used) as bookmarks in termscp. Identity files will be imported as ssh keys in termscp too. + --- ## S3 connection parameters @@ -271,6 +278,7 @@ In order to change panel you need to type `` to move the remote explorer p | `` | Select all files | | | `` | Deselect all files | | | `` | Abort file transfer process | | +| `` | Get total size of the selected path | Size | | `` | Show all synchronized paths | Track | ### Work on multiple files 🄷 diff --git a/docs/pt-BR/README.md b/docs/pt-BR/README.md index e2f2656..9703cfc 100644 --- a/docs/pt-BR/README.md +++ b/docs/pt-BR/README.md @@ -71,7 +71,7 @@

Desenvolvido por @veeso

-

Versão atual: 0.18.0 10/06/2025

+

Versão atual: 0.19.0 11/11/2025

` Execute o termscp como `termscp update` +#### Importar hosts SSH + +Execute o termscp como `termscp import-ssh-hosts [arquivo-config-ssh]` + +Importe todos os hosts do arquivo de configuração SSH especificado (se não for fornecido, `~/.ssh/config` serÔ usado) como favoritos no termscp. Os arquivos de identidade também serão importados como chaves SSH no termscp. + --- ## Parâmetros de Conexão do S3 @@ -271,6 +278,7 @@ Para trocar de painel, você precisa pressionar `` para mover para o paine | `` | Selecionar todos os arquivos | | | `` | Deselecionar todos os arquivos | | | `` | Abortir processo de transferência de arquivo | | +| `` | Obter o tamanho total do caminho selecionado | | Size | | `` | Mostrar todos os caminhos sincronizados | Track | ### Trabalhar com múltiplos arquivos 🄷 diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 6a02cef..4be3610 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -71,7 +71,7 @@

ē”± @veeso 开发

-

å½“å‰ē‰ˆęœ¬ļ¼š 0.18.0 10/06/2025

+

å½“å‰ē‰ˆęœ¬ļ¼š 0.19.0 11/11/2025

[:port]/[/path/.../] - é€ščæ‡ `sshpass`: ä½ åÆä»„é€ščæ‡ `sshpass` 传兄密码, ä¾‹å¦‚ļ¼š `sshpass -f ~/.ssh/topsecret.key termscp cvisintin@192.168.1.31` - ęē¤ŗč¾“å…„åÆ†ē ļ¼šå¦‚ęžœä½ äøä½æē”Øå‰é¢ēš„ä»»ä½•ę–¹ę³•ļ¼Œä½ ä¼šč¢«ęē¤ŗč¾“å…„åÆ†ē ļ¼Œå°±åƒ `scp`态`ssh` ē­‰ęÆ”č¾ƒē»å…øēš„å·„å…·äøŠäø€ę ·ć€‚ +### 子命令 + +#### åÆ¼å…„äø»é¢˜ + +仄 termscp theme ēš„ę–¹å¼čæč”Œ termscp怂 + +#### å®‰č£…ęœ€ę–°ē‰ˆęœ¬ + +仄 termscp update ēš„ę–¹å¼čæč”Œ termscp怂 + +#### 导兄 SSH 主机 + +仄 `termscp import-ssh-hosts [ssh-config-file]` ēš„ę–¹å¼čæč”Œ termscp怂 +ä»ŽęŒ‡å®šēš„ SSH é…ē½®ę–‡ä»¶äø­åÆ¼å…„ę‰€ęœ‰äø»ęœŗļ¼ˆå¦‚ęžœęœŖęä¾›ļ¼Œåˆ™ä½æē”Ø `~/.ssh/config`ļ¼‰ä½œäøŗ termscp äø­ēš„ä¹¦ē­¾ć€‚čŗ«ä»½ę–‡ä»¶ä¹Ÿä¼šä½œäøŗ SSH åÆ†é’„åÆ¼å…„åˆ° termscp 中。 + --- ## S3 čæžęŽ„å‚ę•° @@ -226,25 +245,25 @@ termscpäø­ēš„ę–‡ä»¶čµ„ęŗē®”ē†å™Øę˜ÆęŒ‡ä½ äøŽčæœēØ‹å»ŗē«‹čæžęŽ„åŽåÆä»„ēœ‹åˆ° | `` | åœØę—„åæ—é¢ęæå’Œē®”ē†å™Øé¢ęæä¹‹é—“åˆ‡ę¢ | | | `` | ę˜Æå¦ę˜¾ē¤ŗéšč—ę–‡ä»¶ | All | | `` | ꌉ..ęŽ’åŗ | Bubblesort? | -| `` | å¤åˆ¶ę–‡ä»¶ļ¼ˆå¤¹ļ¼‰ | Copy | -| `` | åˆ›å»ŗę–‡ä»¶å¤¹ | Directory | -| `` | åˆ é™¤ę–‡ä»¶ | Erase | +| `` | å¤åˆ¶ę–‡ä»¶ļ¼ˆå¤¹ļ¼‰ | Copy | +| `` | åˆ›å»ŗę–‡ä»¶å¤¹ | Directory | +| `` | åˆ é™¤ę–‡ä»¶ | Erase | | `` | ę–‡ä»¶ęœē“¢ (ę”ÆęŒé€šé…ē¬¦) | Find | | `` | č·³č½¬åˆ°ęŒ‡å®šč·Æå¾„ | Go to | -| `` | 显示帮助 | Help | +| `` | 显示帮助 | Help | | `` | ę˜¾ē¤ŗé€‰äø­ę–‡ä»¶ļ¼ˆå¤¹ļ¼‰äæ”ęÆ | Info | | `` | åˆ›å»ŗęŒ‡å‘å½“å‰é€‰å®šę”ē›®ēš„ē¬¦å·é“¾ęŽ„ | symlinK | | `` | åˆ·ę–°å½“å‰ē›®å½•åˆ—č”Ø / ęø…é™¤é€‰äø­ēŠ¶ę€ | List | | `` | 选中文件 | Mark | | `` | ä½æē”Øé”®å…„ēš„åē§°ę–°å»ŗę–‡ä»¶ | New | -| `` | ē¼–č¾‘ę–‡ä»¶ļ¼›å‚č€ƒę–‡ęœ¬ē¼–č¾‘å™Øę–‡ę”£ | Open | +| `` | ē¼–č¾‘ę–‡ä»¶ļ¼›å‚č€ƒę–‡ęœ¬ē¼–č¾‘å™Øę–‡ę”£ | Open | | `

` | ę‰“å¼€ę—„åæ—é¢ęæ | Panel | -| `` | 退出termscp | Quit | -| `` | é‡å‘½åę–‡ä»¶ | Rename | -| `` | å¦å­˜äøŗ... | Save | +| `` | 退出termscp | Quit | +| `` | é‡å‘½åę–‡ä»¶ | Rename | +| `` | å¦å­˜äøŗ... | Save | | `` | ę˜¾ē¤ŗę‰€ęœ‰åŒę­„č·Æå¾„ | Track | | `` | čæ›å…„äøŠå±‚ē›®å½• | Upper | -| `` | ä½æē”Øé»˜č®¤ę–¹å¼ę‰“å¼€ę–‡ä»¶ | View | +| `` | ä½æē”Øé»˜č®¤ę–¹å¼ę‰“å¼€ę–‡ä»¶ | View | | `` | ä½æē”ØęŒ‡å®šēØ‹åŗę‰“å¼€ę–‡ä»¶ | With | | `` | čæč”Œå‘½ä»¤ | eXecute | | `` | ę˜Æå¦å¼€åÆåŒę­„ęµč§ˆ | sYnc | @@ -253,6 +272,7 @@ termscpäø­ēš„ę–‡ä»¶čµ„ęŗē®”ē†å™Øę˜ÆęŒ‡ä½ äøŽčæœēØ‹å»ŗē«‹čæžęŽ„åŽåÆä»„ēœ‹åˆ° | `` | é€‰äø­ę‰€ęœ‰ę–‡ä»¶ | | | `` | å–ę¶ˆé€‰ę‹©ę‰€ęœ‰ę–‡ä»¶ | | | `` | ē»ˆę­¢ę–‡ä»¶ä¼ č¾“ | | +| `` | čŽ·å–ę‰€é€‰č·Æå¾„ēš„ę€»å¤§å° | Size | | `` | ę˜¾ē¤ŗę‰€ęœ‰åŒę­„č·Æå¾„ | Track | ### ę“ä½œå¤šäøŖę–‡ä»¶ 🄷 diff --git a/install.sh b/install.sh index 3cd58cf..127b2e3 100755 --- a/install.sh +++ b/install.sh @@ -8,7 +8,7 @@ # -f, -y, --force, --yes # Skip the confirmation prompt during installation -TERMSCP_VERSION="0.18.0" +TERMSCP_VERSION="0.19.0" GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}" DEB_URL_AMD64="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb" DEB_URL_AARCH64="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_arm64.deb" diff --git a/site/html/get-started.html b/site/html/get-started.html index 475acf0..6ab060b 100644 --- a/site/html/get-started.html +++ b/site/html/get-started.html @@ -35,7 +35,7 @@ Consider that Chocolatey moderation can take up to a few weeks since last release, so if the latest version is not available yet, you can install it downloading the ZIP file from - Github and then, from the ZIP directory, install it via

@@ -74,7 +74,7 @@ On Debian based distros, you can install termscp using the Deb package via:

-
wget -O termscp.deb https://github.com/veeso/termscp/releases/latest/download/termscp_0.18.0_amd64.deb
+          
wget -O termscp.deb https://github.com/veeso/termscp/releases/latest/download/termscp_0.19.0_amd64.deb
 sudo dpkg -i termscp.deb

diff --git a/site/html/home.html b/site/html/home.html index 90ec757..e01f928 100644 --- a/site/html/home.html +++ b/site/html/home.html @@ -12,7 +12,7 @@

- termscp 0.18.0 is NOW out! Download it from  + termscp 0.19.0 is NOW out! Download it from  here!

diff --git a/site/lang/en.json b/site/lang/en.json index d02566f..6661ec5 100644 --- a/site/lang/en.json +++ b/site/lang/en.json @@ -12,7 +12,7 @@ "intro": { "caption": "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "Get started →", - "versionAlert": "termscp 0.18.0 is NOW out! Download it from", + "versionAlert": "termscp 0.19.0 is NOW out! Download it from", "here": "here", "features": { "handy": { @@ -112,4 +112,4 @@ "then": "Once started, you will be prompted whether to install or not the update. Confirm the installation and ta-dah, the new version of termscp should now be available on your machine" } } -} \ No newline at end of file +} diff --git a/site/lang/es.json b/site/lang/es.json index 48154b3..ea6c605 100644 --- a/site/lang/es.json +++ b/site/lang/es.json @@ -12,7 +12,7 @@ "intro": { "caption": "Un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "Para iniciar →", - "versionAlert": "termscp 0.18.0 ya estĆ” disponible! DescĆ”rgalo desde", + "versionAlert": "termscp 0.19.0 ya estĆ” disponible! DescĆ”rgalo desde", "here": "aquƬ", "features": { "handy": { @@ -112,4 +112,4 @@ "then": "Una vez iniciado, se le preguntarĆ” si desea instalar o no la actualización. Confirme la instalación y ta-dah, la nueva versión de termscp ahora deberĆ­a estar disponible en su mĆ”quina" } } -} \ No newline at end of file +} diff --git a/site/lang/fr.json b/site/lang/fr.json index a6a9462..79e2dae 100644 --- a/site/lang/fr.json +++ b/site/lang/fr.json @@ -12,7 +12,7 @@ "intro": { "caption": "Un file transfer et navigateur de terminal riche en fonctionnalitĆ©s avec support pour SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "Pour commencer →", - "versionAlert": "termscp 0.18.0 est maintenant sorti! TĆ©lĆ©charge-le depuis", + "versionAlert": "termscp 0.19.0 est maintenant sorti! TĆ©lĆ©charge-le depuis", "here": "ici", "features": { "handy": { @@ -112,4 +112,4 @@ "then": "Une fois dĆ©marrĆ©, vous serez invitĆ© Ć  installer ou non la mise Ć  jour. Confirmez l'installation et ta-dah, la nouvelle version de termscp devrait maintenant ĆŖtre disponible sur votre machine" } } -} \ No newline at end of file +} diff --git a/site/lang/it.json b/site/lang/it.json index f444677..ec66c83 100644 --- a/site/lang/it.json +++ b/site/lang/it.json @@ -12,7 +12,7 @@ "intro": { "caption": "Un file transfer ed explorer ricco di funzionalitĆ  con supporto per SFTP/SCP/FTP/S3", "getStarted": "Installa termscp →", - "versionAlert": "termscp 0.18.0 ĆØ ORA disponbile! Scaricalo da", + "versionAlert": "termscp 0.19.0 ĆØ ORA disponbile! Scaricalo da", "here": "qui", "features": { "handy": { @@ -112,4 +112,4 @@ "then": "Una volta lanciato, se c'ĆØ un aggiornamento disponibile ti chiederĆ  se procedere. Conferma e a questo punto dovrebbe installarlo. Se tutto ĆØ andato a buon fine, riavviando termscp dovrebbe essere l'ultima versione." } } -} \ No newline at end of file +} diff --git a/site/lang/zh-CN.json b/site/lang/zh-CN.json index 15efff5..0d38421 100644 --- a/site/lang/zh-CN.json +++ b/site/lang/zh-CN.json @@ -12,7 +12,7 @@ "intro": { "caption": "åŠŸčƒ½äø°åÆŒēš„ē»ˆē«Æ UI ę–‡ä»¶ä¼ č¾“å’Œęµč§ˆå™Øļ¼Œę”ÆęŒ SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "开始 →", - "versionAlert": "termscp 0.18.0 ēŽ°å·²å‘åøƒļ¼ ä»Žäø‹č½½", + "versionAlert": "termscp 0.19.0 ēŽ°å·²å‘åøƒļ¼ ä»Žäø‹č½½", "here": "čæ™é‡Œ", "features": { "handy": { @@ -112,4 +112,4 @@ "then": "åÆåŠØåŽļ¼Œē³»ē»Ÿå°†ęē¤ŗę‚Øę˜Æå¦å®‰č£…ę›“ę–°ć€‚ ē”®č®¤å®‰č£…å’Œ ta-dahļ¼Œę–°ē‰ˆęœ¬ēš„termscp ēŽ°åœØåŗ”čÆ„åÆä»„åœØä½ ēš„ęœŗå™ØäøŠä½æē”Øäŗ†" } } -} \ No newline at end of file +} diff --git a/src/activity_manager.rs b/src/activity_manager.rs index faf617c..38ed111 100644 --- a/src/activity_manager.rs +++ b/src/activity_manager.rs @@ -448,35 +448,7 @@ impl ActivityManager { // -- misc fn init_bookmarks_client(keyring: bool) -> Result, String> { - // Get config dir - match environment::init_config_dir() { - Ok(path) => { - // If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system. - if let Some(config_dir_path) = path { - let bookmarks_file: PathBuf = - environment::get_bookmarks_paths(config_dir_path.as_path()); - // Initialize client - BookmarksClient::new( - bookmarks_file.as_path(), - config_dir_path.as_path(), - 16, - keyring, - ) - .map(Option::Some) - .map_err(|e| { - format!( - "Could not initialize bookmarks (at \"{}\", \"{}\"): {}", - bookmarks_file.display(), - config_dir_path.display(), - e - ) - }) - } else { - Ok(None) - } - } - Err(err) => Err(err), - } + crate::support::bookmarks_client(keyring) } /// Initialize configuration client diff --git a/src/cli.rs b/src/cli.rs index 840a18b..e497f89 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,6 +15,9 @@ use crate::system::logging::LogLevel; pub enum Task { Activity(NextActivity), + /// Import ssh hosts from the specified ssh config file, or from the default location + /// and save them as bookmarks. + ImportSshHosts(Option), ImportTheme(PathBuf), InstallUpdate, Version, @@ -72,7 +75,8 @@ pub struct Args { #[argh(subcommand)] pub enum ArgsSubcommands { Config(ConfigArgs), - LoadTheme(LoadThemeArgs), + ImportSshHosts(ImportSshHostsArgs), + ImportTheme(ImportThemeArgs), Update(UpdateArgs), } @@ -86,10 +90,20 @@ pub struct ConfigArgs {} #[argh(subcommand, name = "update")] pub struct UpdateArgs {} +#[derive(FromArgs)] +/// import ssh hosts from the specified ssh config file, or from the default location +/// and save them as bookmarks. +#[argh(subcommand, name = "import-ssh-hosts")] +pub struct ImportSshHostsArgs { + #[argh(positional)] + /// optional ssh config file; if not specified, the default location will be used + pub ssh_config: Option, +} + #[derive(FromArgs)] /// import the specified theme #[argh(subcommand, name = "theme")] -pub struct LoadThemeArgs { +pub struct ImportThemeArgs { #[argh(positional)] /// theme file pub theme: PathBuf, @@ -118,6 +132,14 @@ impl RunOpts { } } + pub fn import_ssh_hosts(ssh_config: Option, keyring: bool) -> Self { + Self { + task: Task::ImportSshHosts(ssh_config), + keyring, + ..Default::default() + } + } + pub fn import_theme(theme: PathBuf) -> Self { Self { task: Task::ImportTheme(theme), diff --git a/src/explorer/builder.rs b/src/explorer/builder.rs index d571d80..59a3e89 100644 --- a/src/explorer/builder.rs +++ b/src/explorer/builder.rs @@ -65,10 +65,10 @@ impl FileExplorerBuilder { /// Set formatter for FileExplorer pub fn with_formatter(&mut self, fmt_str: Option<&str>) -> &mut FileExplorerBuilder { - if let Some(e) = self.explorer.as_mut() { - if let Some(fmt_str) = fmt_str { - e.fmt = Formatter::new(fmt_str); - } + if let Some(e) = self.explorer.as_mut() + && let Some(fmt_str) = fmt_str + { + e.fmt = Formatter::new(fmt_str); } self } diff --git a/src/filetransfer/remotefs_builder.rs b/src/filetransfer/remotefs_builder.rs index 53cf17c..5c58fbc 100644 --- a/src/filetransfer/remotefs_builder.rs +++ b/src/filetransfer/remotefs_builder.rs @@ -13,6 +13,10 @@ use remotefs_kube::KubeMultiPodFs as KubeFs; use remotefs_smb::SmbOptions; #[cfg(smb)] use remotefs_smb::{SmbCredentials, SmbFs}; +#[cfg(windows)] +use remotefs_ssh::LibSsh2Session as SshSession; +#[cfg(unix)] +use remotefs_ssh::LibSshSession as SshSession; use remotefs_ssh::{ScpFs, SftpFs, SshAgentIdentity, SshConfigParseRule, SshOpts}; use remotefs_webdav::WebDAVFs; @@ -138,12 +142,18 @@ impl RemoteFsBuilder { } /// Build scp client - fn scp_client(params: GenericProtocolParams, config_client: &ConfigClient) -> ScpFs { + fn scp_client( + params: GenericProtocolParams, + config_client: &ConfigClient, + ) -> ScpFs { Self::build_ssh_opts(params, config_client).into() } /// Build sftp client - fn sftp_client(params: GenericProtocolParams, config_client: &ConfigClient) -> SftpFs { + fn sftp_client( + params: GenericProtocolParams, + config_client: &ConfigClient, + ) -> SftpFs { Self::build_ssh_opts(params, config_client).into() } @@ -235,10 +245,10 @@ impl RemoteFsBuilder { } // For SSH protocols, only set password if explicitly provided and non-empty. // This allows the SSH library to prioritize key-based and agent authentication. - if let Some(password) = params.password { - if !password.is_empty() { - opts = opts.password(password); - } + if let Some(password) = params.password + && !password.is_empty() + { + opts = opts.password(password); } if let Some(config_path) = config_client.get_ssh_config() { opts = opts.config_file( diff --git a/src/main.rs b/src/main.rs index 77c08f8..1bff2ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ extern crate log; extern crate magic_crypt; use std::env; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::Duration; use self::activity_manager::{ActivityManager, NextActivity}; @@ -72,7 +72,10 @@ fn main() -> MainResult<()> { fn parse_args(args: Args) -> Result { let run_opts = match args.nested { Some(ArgsSubcommands::Update(_)) => RunOpts::update(), - Some(ArgsSubcommands::LoadTheme(args)) => RunOpts::import_theme(args.theme), + Some(ArgsSubcommands::ImportSshHosts(subargs)) => { + RunOpts::import_ssh_hosts(subargs.ssh_config, !args.wno_keyring) + } + Some(ArgsSubcommands::ImportTheme(args)) => RunOpts::import_theme(args.theme), Some(ArgsSubcommands::Config(_)) => RunOpts::config(), None => { let mut run_opts: RunOpts = RunOpts::default(); @@ -111,10 +114,10 @@ fn parse_args(args: Args) -> Result { }; // Local directory - if let Some(localdir) = run_opts.remote.local_dir.as_deref() { - if let Err(err) = env::set_current_dir(localdir) { - return Err(format!("Bad working directory argument: {err}")); - } + if let Some(localdir) = run_opts.remote.local_dir.as_deref() + && let Err(err) = env::set_current_dir(localdir) + { + return Err(format!("Bad working directory argument: {err}")); } run_opts @@ -127,6 +130,7 @@ fn parse_args(args: Args) -> Result { /// Run task and return rc fn run(run_opts: RunOpts) -> MainResult<()> { match run_opts.task { + Task::ImportSshHosts(ssh_config) => run_import_ssh_hosts(ssh_config, run_opts.keyring), Task::ImportTheme(theme) => run_import_theme(&theme), Task::InstallUpdate => run_install_update(), Task::Activity(activity) => { @@ -145,6 +149,17 @@ fn print_version() -> MainResult<()> { Ok(()) } +fn run_import_ssh_hosts(ssh_config_path: Option, keyring: bool) -> MainResult<()> { + support::import_ssh_hosts(ssh_config_path, keyring) + .map(|_| { + println!("SSH hosts have been successfully imported!"); + }) + .map_err(|err| { + eprintln!("{err}"); + err.into() + }) +} + fn run_import_theme(theme: &Path) -> MainResult<()> { match support::import_theme(theme) { Ok(_) => { diff --git a/src/support.rs b/src/support.rs index 64a0561..7e083ef 100644 --- a/src/support.rs +++ b/src/support.rs @@ -2,11 +2,14 @@ //! //! this module exposes some extra run modes for termscp, meant to be used for "support", such as installing themes -// mod +mod import_ssh_hosts; + use std::fs; use std::path::{Path, PathBuf}; +pub use self::import_ssh_hosts::import_ssh_hosts; use crate::system::auto_update::{Update, UpdateStatus}; +use crate::system::bookmarks_client::BookmarksClient; use crate::system::config_client::ConfigClient; use crate::system::environment; use crate::system::notifications::Notification; @@ -83,3 +86,36 @@ fn get_config_client() -> Option { } } } + +/// Init [`BookmarksClient`]. +pub fn bookmarks_client(keyring: bool) -> Result, String> { + // Get config dir + match environment::init_config_dir() { + Ok(path) => { + // If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system. + if let Some(config_dir_path) = path { + let bookmarks_file: PathBuf = + environment::get_bookmarks_paths(config_dir_path.as_path()); + // Initialize client + BookmarksClient::new( + bookmarks_file.as_path(), + config_dir_path.as_path(), + 16, + keyring, + ) + .map(Option::Some) + .map_err(|e| { + format!( + "Could not initialize bookmarks (at \"{}\", \"{}\"): {}", + bookmarks_file.display(), + config_dir_path.display(), + e + ) + }) + } else { + Ok(None) + } + } + Err(err) => Err(err), + } +} diff --git a/src/support/import_ssh_hosts.rs b/src/support/import_ssh_hosts.rs new file mode 100644 index 0000000..9e8ae9b --- /dev/null +++ b/src/support/import_ssh_hosts.rs @@ -0,0 +1,326 @@ +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +use ssh2_config::{Host, HostClause, ParseRule, SshConfig}; + +use crate::filetransfer::params::GenericProtocolParams; +use crate::filetransfer::{FileTransferParams, FileTransferProtocol, ProtocolParams}; + +/// Parameters required to add an ssh key for a host. +struct SshKeyParams { + host: String, + ssh_key: String, + username: String, +} + +/// Import ssh hosts from the specified ssh config file, or from the default location +/// and save them as bookmarks. +pub fn import_ssh_hosts(ssh_config: Option, keyring: bool) -> Result<(), String> { + // get config client + let mut cfg_client = super::get_config_client() + .ok_or_else(|| String::from("Could not import ssh hosts: could not load configuration"))?; + + // resolve ssh_config + let ssh_config = ssh_config.or_else(|| cfg_client.get_ssh_config().map(PathBuf::from)); + + // load bookmarks client + let mut bookmarks_client = super::bookmarks_client(keyring)? + .ok_or_else(|| String::from("Could not import ssh hosts: could not load bookmarks"))?; + + // load ssh config + let ssh_config = match ssh_config { + Some(p) => { + debug!("Importing ssh hosts from file: {}", p.display()); + let mut reader = BufReader::new( + File::open(&p) + .map_err(|e| format!("Could not open ssh config file {}: {e}", p.display()))?, + ); + SshConfig::default().parse(&mut reader, ParseRule::ALLOW_UNKNOWN_FIELDS) + } + None => { + debug!("Importing ssh hosts from default location"); + SshConfig::parse_default_file(ParseRule::ALLOW_UNKNOWN_FIELDS) + } + } + .map_err(|e| format!("Could not parse ssh config file: {e}"))?; + + // iter hosts and add bookmarks + ssh_config + .get_hosts() + .iter() + .flat_map(host_to_params) + .for_each(|(name, params, identity_file_params)| { + debug!("Adding bookmark for host: {name} with params: {params:?}"); + bookmarks_client.add_bookmark(name, params, false); + + // add ssh key if any + if let Some(identity_file_params) = identity_file_params { + debug!( + "Host {host} has identity file, will add ssh key for it", + host = identity_file_params.host + ); + if let Err(err) = cfg_client.add_ssh_key( + &identity_file_params.host, + &identity_file_params.username, + &identity_file_params.ssh_key, + ) { + error!( + "Could not add ssh key for host {host}: {err}", + host = identity_file_params.host + ); + } + } + }); + + // save bookmarks + if let Err(err) = bookmarks_client.write_bookmarks() { + return Err(format!( + "Could not save imported ssh hosts as bookmarks: {err}" + )); + } + + println!("Imported ssh hosts"); + + Ok(()) +} + +/// Tries to derive [`FileTransferParams`] from the specified ssh host. +fn host_to_params( + host: &Host, +) -> impl Iterator)> { + host.pattern + .iter() + .filter_map(|pattern| host_pattern_to_params(host, pattern)) +} + +/// Tries to derive [`FileTransferParams`] from the specified ssh host and pattern. +/// +/// If `IdentityFile` is specified in the host parameters, it will be included in the returned tuple. +fn host_pattern_to_params( + host: &Host, + pattern: &HostClause, +) -> Option<(String, FileTransferParams, Option)> { + debug!("Processing host with pattern: {pattern:?}",); + if pattern.negated || pattern.pattern.contains('*') || pattern.pattern.contains('?') { + debug!("Skipping host with pattern: {pattern}",); + return None; + } + + let address = host + .params + .host_name + .as_deref() + .unwrap_or(pattern.pattern.as_str()) + .to_string(); + debug!("Resolved address for pattern {pattern}: {address}"); + let port = host.params.port.unwrap_or(22); + debug!("Resolved port for pattern {pattern}: {port}"); + let username = host.params.user.clone(); + debug!("Resolved username for pattern {pattern}: {username:?}"); + + let identity_file_params = resolve_identity_file_path(host, pattern, &address); + + Some(( + pattern.to_string(), + FileTransferParams::new( + FileTransferProtocol::Sftp, + ProtocolParams::Generic( + GenericProtocolParams::default() + .address(address) + .port(port) + .username(username), + ), + ), + identity_file_params, + )) +} + +fn resolve_identity_file_path( + host: &Host, + pattern: &HostClause, + resolved_address: &str, +) -> Option { + let (Some(username), Some(identity_file)) = ( + host.params.user.as_ref(), + host.params.identity_file.as_ref().and_then(|v| v.first()), + ) else { + debug!( + "No identity file specified for host {host}, skipping ssh key import", + host = pattern.pattern + ); + return None; + }; + + // expand tilde + let identity_filepath = shellexpand::tilde(&identity_file.display().to_string()).to_string(); + debug!("Resolved identity file for pattern {pattern}: {identity_filepath}",); + let Ok(mut ssh_file) = File::open(identity_file) else { + error!( + "Could not open identity file {identity_filepath} for host {host}", + host = pattern.pattern + ); + return None; + }; + let mut ssh_key = String::new(); + use std::io::Read as _; + if let Err(err) = ssh_file.read_to_string(&mut ssh_key) { + error!( + "Could not read identity file {identity_filepath} for host {host}: {err}", + host = pattern.pattern + ); + return None; + } + + Some(SshKeyParams { + host: resolved_address.to_string(), + username: username.clone(), + ssh_key, + }) +} + +#[cfg(test)] +mod tests { + + use pretty_assertions::assert_eq; + use tempfile::NamedTempFile; + + use super::*; + use crate::system::bookmarks_client::BookmarksClient; + + #[test] + fn test_should_import_ssh_hosts() { + let ssh_test_config = ssh_test_config(); + + // import ssh hosts + let result = import_ssh_hosts(Some(ssh_test_config.config.path().to_path_buf()), false); + assert!(result.is_ok()); + + // verify imported hosts + let config_client = super::super::get_config_client() + .ok_or_else(|| String::from("Could not import ssh hosts: could not load configuration")) + .expect("failed to load config client"); + + // load bookmarks client + let bookmarks_client = super::super::bookmarks_client(false) + .expect("failed to load bookmarks client") + .expect("bookmarks client is none"); + + // verify bookmarks + check_bookmark(&bookmarks_client, "test1", "test1.example.com", 2200, None); + check_bookmark( + &bookmarks_client, + "test2", + "test2.example.com", + 22, + Some("test2user"), + ); + check_bookmark( + &bookmarks_client, + "test3", + "test3.example.com", + 2222, + Some("test3user"), + ); + + // verify ssh keys + let (host, username, _key) = config_client + .get_ssh_key("test3user@test3.example.com") + .expect("ssh key is missing for test3user@test3.example.com"); + + assert_eq!(host, "test3.example.com"); + assert_eq!(username, "test3user"); + } + + fn check_bookmark( + bookmarks_client: &BookmarksClient, + name: &str, + expected_address: &str, + expected_port: u16, + expected_username: Option<&str>, + ) { + // verify bookmarks + let bookmark = bookmarks_client + .get_bookmark(name) + .expect("failed to get bookmark"); + let params1 = bookmark + .params + .generic_params() + .expect("should have generic params"); + assert_eq!(params1.address, expected_address); + assert_eq!(params1.port, expected_port); + assert_eq!(params1.username.as_deref(), expected_username); + assert!(params1.password.is_none()); + } + + struct SshTestConfig { + config: NamedTempFile, + #[allow(dead_code)] + identity_file: NamedTempFile, + } + + fn ssh_test_config() -> SshTestConfig { + use std::io::Write as _; + let mut identity_file = NamedTempFile::new().expect("failed to create tempfile"); + writeln!( + identity_file, + 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-----" + ) + .expect("failed to write identity file"); + + let mut file = NamedTempFile::new().expect("failed to create tempfile"); + + // let's declare a couple of hosts + writeln!( + file, + r#" +Host test1 + HostName test1.example.com + Port 2200 + +Host test2 + HostName test2.example.com + User test2user + +Host test3 + HostName test3.example.com + User test3user + Port 2222 + IdentityFile {identity_path} +"#, + identity_path = identity_file.path().display() + ) + .expect("failed to write ssh config"); + + SshTestConfig { + config: file, + identity_file, + } + } +} diff --git a/src/system/auto_update.rs b/src/system/auto_update.rs index f66e9ff..a0b618e 100644 --- a/src/system/auto_update.rs +++ b/src/system/auto_update.rs @@ -2,6 +2,8 @@ //! //! Automatic update module. This module is used to upgrade the current version of termscp to the latest available on Github +use std::net::ToSocketAddrs as _; + use self_update::backends::github::Update as GithubUpdater; pub use self_update::errors::Error as UpdateError; use self_update::update::Release as UpdRelease; @@ -67,6 +69,9 @@ impl Update { /// otherwise if no version is available, return None /// In case of error returns Error with the error description pub fn is_new_version_available() -> Result, UpdateError> { + // check if api.github.com is reachable before doing anything + Self::check_github_api_reachable()?; + info!("Checking whether a new version is available..."); GithubUpdater::configure() // Set default options @@ -83,6 +88,27 @@ impl Update { .map(Self::check_version) } + /// Check if api.github.com is reachable + /// This is useful to avoid long timeouts when the network is down + /// or the DNS is not working + fn check_github_api_reachable() -> Result<(), UpdateError> { + let Some(socket_addr) = ("api.github.com", 443) + .to_socket_addrs() + .ok() + .and_then(|mut i| i.next()) + else { + error!("Could not resolve api.github.com"); + return Err(UpdateError::Network( + "Could not resolve api.github.com".into(), + )); + }; + + // just try to open a connection to api.github.com with a timeout of 5 seconds with tcp + std::net::TcpStream::connect_timeout(&socket_addr, std::time::Duration::from_secs(5)) + .map(|_| ()) + .map_err(|e| UpdateError::Network(format!("Could not reach api.github.com: {e}"))) + } + /// In case received version is newer than current one, version as Some is returned; otherwise None fn check_version(r: Release) -> Option { debug!("got version from GitHub: {}", r.version); @@ -212,4 +238,9 @@ mod test { assert!(!Update::is_new_version_higher("0.9.9", "0.10.1")); assert!(!Update::is_new_version_higher("0.10.9", "0.11.0")); } + + #[test] + fn test_should_check_whether_github_api_is_reachable() { + assert!(Update::check_github_api_reachable().is_ok()); + } } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index e7b9aa1..90fe0aa 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -300,19 +300,18 @@ impl ConfigClient { /// Get ssh key from host. /// None is returned if key doesn't exist - /// `std::io::Error` is returned in case it was not possible to read the key file - pub fn get_ssh_key(&self, mkey: &str) -> std::io::Result> { + pub fn get_ssh_key(&self, mkey: &str) -> Option { if self.degraded { - return Ok(None); + return None; } // Check if Key exists match self.config.remote.ssh_keys.get(mkey) { - None => Ok(None), + None => None, Some(key_path) => { // Get host and username let (host, username): (String, String) = Self::get_ssh_tokens(mkey); // Return key - Ok(Some((host, username, PathBuf::from(key_path)))) + Some((host, username, PathBuf::from(key_path))) } } } @@ -451,7 +450,7 @@ mod tests { // I/O assert!(client.add_ssh_key("Omar", "omar", "omar").is_err()); assert!(client.del_ssh_key("omar", "omar").is_err()); - assert!(client.get_ssh_key("omar").ok().unwrap().is_none()); + assert!(client.get_ssh_key("omar").is_none()); assert!(client.write_config().is_err()); assert!(client.read_config().is_err()); } @@ -493,7 +492,7 @@ mod tests { let mut expected_key_path: PathBuf = key_path; expected_key_path.push("pi@192.168.1.31.key"); assert_eq!( - client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(), + client.get_ssh_key("pi@192.168.1.31").unwrap(), ( String::from("192.168.1.31"), String::from("pi"), @@ -684,7 +683,7 @@ mod tests { ); // Iterate keys for key in client.iter_ssh_keys() { - let host: SshHost = client.get_ssh_key(key).ok().unwrap().unwrap(); + let host: SshHost = client.get_ssh_key(key).unwrap(); assert_eq!(host.0, String::from("192.168.1.31")); assert_eq!(host.1, String::from("pi")); let mut expected_key_path: PathBuf = key_path.clone(); @@ -699,7 +698,7 @@ mod tests { assert_eq!(key, rsa_key); } // Unexisting key - assert!(client.get_ssh_key("test").ok().unwrap().is_none()); + assert!(client.get_ssh_key("test").is_none()); // Delete key assert!(client.del_ssh_key("192.168.1.31", "pi").is_ok()); } diff --git a/src/system/sshkey_storage.rs b/src/system/sshkey_storage.rs index 259357a..2101b73 100644 --- a/src/system/sshkey_storage.rs +++ b/src/system/sshkey_storage.rs @@ -103,17 +103,11 @@ impl From<&ConfigClient> for SshKeyStorage { // Iterate over keys in storage for key in cfg_client.iter_ssh_keys() { match cfg_client.get_ssh_key(key) { - Ok(host) => match host { - Some((addr, username, rsa_key_path)) => { - let key_name: String = Self::make_mapkey(&addr, &username); - hosts.insert(key_name, rsa_key_path); - } - None => continue, - }, - Err(err) => { - error!("Failed to get SSH key for {}: {}", key, err); - continue; + Some((addr, username, rsa_key_path)) => { + let key_name: String = Self::make_mapkey(&addr, &username); + hosts.insert(key_name, rsa_key_path); } + None => continue, } info!("Got SSH key for {}", key); } diff --git a/src/ui/activities/auth/bookmarks.rs b/src/ui/activities/auth/bookmarks.rs index 3e70e62..06f9e7f 100644 --- a/src/ui/activities/auth/bookmarks.rs +++ b/src/ui/activities/auth/bookmarks.rs @@ -30,13 +30,13 @@ impl AuthActivity { pub(super) fn load_bookmark(&mut self, form_tab: FormTab, idx: usize) { if let Some(bookmarks_cli) = self.bookmarks_client() { // Iterate over bookmarks - if let Some(key) = self.bookmarks_list.get(idx) { - if let Some(bookmark) = bookmarks_cli.get_bookmark(key) { - // Load parameters into components - match form_tab { - FormTab::Remote => self.load_remote_bookmark_into_gui(bookmark), - FormTab::HostBridge => self.load_host_bridge_bookmark_into_gui(bookmark), - } + if let Some(key) = self.bookmarks_list.get(idx) + && let Some(bookmark) = bookmarks_cli.get_bookmark(key) + { + // Load parameters into components + match form_tab { + FormTab::Remote => self.load_remote_bookmark_into_gui(bookmark), + FormTab::HostBridge => self.load_host_bridge_bookmark_into_gui(bookmark), } } } @@ -99,13 +99,13 @@ impl AuthActivity { pub(super) fn load_recent(&mut self, form_tab: FormTab, idx: usize) { if let Some(client) = self.bookmarks_client() { // Iterate over bookmarks - if let Some(key) = self.recents_list.get(idx) { - if let Some(bookmark) = client.get_recent(key) { - // Load parameters - match form_tab { - FormTab::Remote => self.load_remote_bookmark_into_gui(bookmark), - FormTab::HostBridge => self.load_host_bridge_bookmark_into_gui(bookmark), - } + if let Some(key) = self.recents_list.get(idx) + && let Some(bookmark) = client.get_recent(key) + { + // Load parameters + match form_tab { + FormTab::Remote => self.load_remote_bookmark_into_gui(bookmark), + FormTab::HostBridge => self.load_host_bridge_bookmark_into_gui(bookmark), } } } @@ -129,10 +129,10 @@ impl AuthActivity { /// Write bookmarks to file fn write_bookmarks(&mut self) { - if let Some(bookmarks_cli) = self.bookmarks_client() { - if let Err(err) = bookmarks_cli.write_bookmarks() { - self.mount_error(format!("Could not write bookmarks: {err}").as_str()); - } + if let Some(bookmarks_cli) = self.bookmarks_client() + && let Err(err) = bookmarks_cli.write_bookmarks() + { + self.mount_error(format!("Could not write bookmarks: {err}").as_str()); } } diff --git a/src/ui/activities/auth/misc.rs b/src/ui/activities/auth/misc.rs index 7de6194..e011d7e 100644 --- a/src/ui/activities/auth/misc.rs +++ b/src/ui/activities/auth/misc.rs @@ -223,10 +223,7 @@ impl AuthActivity { } Err(err) => { // Report error - error!("Failed to get latest version: {}", err); - self.mount_error( - format!("Could not check for new updates: {err}").as_str(), - ); + error!("Failed to get latest version: {err}",); } } } else { diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index 539cbf9..5e4b69e 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -126,13 +126,13 @@ impl AuthActivity { self.host_bridge_protocol = protocol; // Update port let port: u16 = self.get_input_port(FormTab::HostBridge); - if let HostBridgeProtocol::Remote(remote_protocol) = protocol { - if Self::is_port_standard(port) { - self.mount_port( - FormTab::HostBridge, - Self::get_default_port_for_protocol(remote_protocol), - ); - } + if let HostBridgeProtocol::Remote(remote_protocol) = protocol + && Self::is_port_standard(port) + { + self.mount_port( + FormTab::HostBridge, + Self::get_default_port_for_protocol(remote_protocol), + ); } } FormMsg::RemoteProtocolChanged(protocol) => { diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index 70e2ffd..0916724 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -687,30 +687,30 @@ impl AuthActivity { /// mount release notes text area pub(super) fn mount_release_notes(&mut self) { - if let Some(ctx) = self.context.as_ref() { - if let Some(release_notes) = ctx.store().get_string(super::STORE_KEY_RELEASE_NOTES) { - // make spans - let info_color = self.theme().misc_info_dialog; - assert!( - self.app - .remount( - Id::NewVersionChangelog, - Box::new(components::ReleaseNotes::new(release_notes, info_color)), - vec![] - ) - .is_ok() - ); - assert!( - self.app - .remount( - Id::InstallUpdatePopup, - Box::new(components::InstallUpdatePopup::new(info_color)), - vec![] - ) - .is_ok() - ); - assert!(self.app.active(&Id::InstallUpdatePopup).is_ok()); - } + if let Some(ctx) = self.context.as_ref() + && let Some(release_notes) = ctx.store().get_string(super::STORE_KEY_RELEASE_NOTES) + { + // make spans + let info_color = self.theme().misc_info_dialog; + assert!( + self.app + .remount( + Id::NewVersionChangelog, + Box::new(components::ReleaseNotes::new(release_notes, info_color)), + vec![] + ) + .is_ok() + ); + assert!( + self.app + .remount( + Id::InstallUpdatePopup, + Box::new(components::InstallUpdatePopup::new(info_color)), + vec![] + ) + .is_ok() + ); + assert!(self.app.active(&Id::InstallUpdatePopup).is_ok()); } } diff --git a/src/ui/activities/filetransfer/actions/file_size.rs b/src/ui/activities/filetransfer/actions/file_size.rs new file mode 100644 index 0000000..fb0251e --- /dev/null +++ b/src/ui/activities/filetransfer/actions/file_size.rs @@ -0,0 +1,94 @@ +use remotefs::File; + +use super::{FileTransferActivity, LogLevel}; +use crate::ui::activities::filetransfer::lib::browser::FileExplorerTab; + +#[derive(Debug, Copy, Clone)] +enum Host { + HostBridge, + Remote, +} + +impl FileTransferActivity { + pub(crate) fn action_get_file_size(&mut self) { + // Get selected file + self.mount_blocking_wait("Getting total path size..."); + + let total_size = match self.browser.tab() { + FileExplorerTab::HostBridge => { + let files = self.get_local_selected_entries().get_files(); + self.get_files_size(files, Host::HostBridge) + } + FileExplorerTab::Remote => { + let files = self.get_remote_selected_entries().get_files(); + self.get_files_size(files, Host::Remote) + } + FileExplorerTab::FindHostBridge => { + let files = self.get_found_selected_entries().get_files(); + self.get_files_size(files, Host::HostBridge) + } + FileExplorerTab::FindRemote => { + let files = self.get_found_selected_entries().get_files(); + self.get_files_size(files, Host::Remote) + } + }; + + self.umount_wait(); + self.mount_info(format!( + "Total file size: {size}", + size = bytesize::ByteSize::b(total_size) + )); + } + + fn get_files_size(&mut self, files: Vec, host: Host) -> u64 { + files.into_iter().map(|f| self.get_file_size(f, host)).sum() + } + + fn get_file_size(&mut self, file: File, host: Host) -> u64 { + if let Some(symlink) = &file.metadata().symlink { + // stat + let stat_res = match host { + Host::HostBridge => self.host_bridge.stat(symlink).map_err(|e| e.to_string()), + Host::Remote => self.client.stat(symlink).map_err(|e| e.to_string()), + }; + match stat_res { + Ok(stat) => stat.metadata().size, + Err(err_msg) => { + self.log( + LogLevel::Error, + format!( + "Failed to stat symlink target {path}: {err_msg}", + path = symlink.display(), + ), + ); + 0 + } + } + } else if file.is_dir() { + // list and sum + let list_res = match host { + Host::HostBridge => self + .host_bridge + .list_dir(&file.path) + .map_err(|e| e.to_string()), + Host::Remote => self.client.list_dir(&file.path).map_err(|e| e.to_string()), + }; + + match list_res { + Ok(list) => list.into_iter().map(|f| self.get_file_size(f, host)).sum(), + Err(err_msg) => { + self.log( + LogLevel::Error, + format!( + "Failed to list directory {path}: {err_msg}", + path = file.path.display(), + ), + ); + 0 + } + } + } else { + file.metadata().size + } + } +} diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index a8f3c9f..9ccff01 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -99,24 +99,16 @@ impl FileTransferActivity { // Iter files match self.browser.tab() { FileExplorerTab::FindHostBridge | FileExplorerTab::HostBridge => { - if self.config().get_prompt_on_file_replace() { - // Check which file would be replaced - let existing_files: Vec<&File> = entries - .iter() - .filter(|(x, dest_path)| { - self.remote_file_exists( - Self::file_to_check_many(x, dest_path.as_path()).as_path(), - ) - }) - .map(|(x, _)| x) - .collect(); - // Check whether to replace files - if !existing_files.is_empty() - && !self.should_replace_files(existing_files) - { - return; - } - } + let super::save::TransferFilesWithOverwritesResult::FilesToTransfer( + entries, + ) = self.get_files_to_transfer_with_overwrites( + entries, + super::save::CheckFileExists::Remote, + ) + else { + debug!("User cancelled file transfer due to overwrites"); + return; + }; if let Err(err) = self.filetransfer_send( TransferPayload::TransferQueue(entries), dest_path.as_path(), @@ -131,24 +123,16 @@ impl FileTransferActivity { } } FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - if self.config().get_prompt_on_file_replace() { - // Check which file would be replaced - let existing_files: Vec<&File> = entries - .iter() - .filter(|(x, dest_path)| { - self.host_bridge_file_exists( - Self::file_to_check_many(x, dest_path.as_path()).as_path(), - ) - }) - .map(|(x, _)| x) - .collect(); - // Check whether to replace files - if !existing_files.is_empty() - && !self.should_replace_files(existing_files) - { - return; - } - } + let super::save::TransferFilesWithOverwritesResult::FilesToTransfer( + entries, + ) = self.get_files_to_transfer_with_overwrites( + entries, + super::save::CheckFileExists::HostBridge, + ) + else { + debug!("User cancelled file transfer due to overwrites"); + return; + }; if let Err(err) = self.filetransfer_recv( TransferPayload::TransferQueue(entries), dest_path.as_path(), diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index ee89bc6..600a0b0 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -23,6 +23,7 @@ pub(crate) mod copy; pub(crate) mod delete; pub(crate) mod edit; pub(crate) mod exec; +pub(crate) mod file_size; pub(crate) mod filter; pub(crate) mod find; pub(crate) mod mark; diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index fe2cf65..d6c477e 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -10,6 +10,37 @@ use super::{ TransferPayload, }; +enum GetFileToReplaceResult { + Replace(Vec<(File, PathBuf)>), + Cancel, +} + +/// Result of getting files to transfer with overwrites. +/// +/// - FilesToTransfer: files to transfer. +/// - Cancel: user cancelled the operation. +pub(crate) enum TransferFilesWithOverwritesResult { + FilesToTransfer(Vec<(File, PathBuf)>), + Cancel, +} + +/// Decides whether to check file existence on host bridge or remote side. +pub(crate) enum CheckFileExists { + HostBridge, + Remote, +} + +/// Options for all files replacement. +/// +/// - ReplaceAll: user wants to replace all files. +/// - SkipAll: user wants to skip all files. +/// - Unset: no option set yet. +enum AllOpts { + ReplaceAll, + SkipAll, + Unset, +} + impl FileTransferActivity { pub(crate) fn action_local_saveas(&mut self, input: String) { self.local_send_file(TransferOpts::default().save_as(Some(input))); @@ -60,22 +91,12 @@ impl FileTransferActivity { dest_path.push(save_as); } // Iter files - if self.config().get_prompt_on_file_replace() { - // Check which file would be replaced - let existing_files: Vec<&File> = entries - .iter() - .filter(|(x, dest_path)| { - self.remote_file_exists( - Self::file_to_check_many(x, dest_path.as_path()).as_path(), - ) - }) - .map(|(x, _)| x) - .collect(); - // Check whether to replace files - if !existing_files.is_empty() && !self.should_replace_files(existing_files) { - return; - } - } + let TransferFilesWithOverwritesResult::FilesToTransfer(entries) = + self.get_files_to_transfer_with_overwrites(entries, CheckFileExists::Remote) + else { + debug!("User cancelled file transfer due to overwrites"); + return; + }; if let Err(err) = self.filetransfer_send( TransferPayload::TransferQueue(entries), dest_path.as_path(), @@ -128,23 +149,13 @@ impl FileTransferActivity { if let Some(save_as) = opts.save_as { dest_path.push(save_as); } - // Iter files - if self.config().get_prompt_on_file_replace() { - // Check which file would be replaced - let existing_files: Vec<&File> = entries - .iter() - .filter(|(x, dest_path)| { - self.host_bridge_file_exists( - Self::file_to_check_many(x, dest_path.as_path()).as_path(), - ) - }) - .map(|(x, _)| x) - .collect(); - // Check whether to replace files - if !existing_files.is_empty() && !self.should_replace_files(existing_files) { - return; - } - } + let TransferFilesWithOverwritesResult::FilesToTransfer(entries) = self + .get_files_to_transfer_with_overwrites(entries, CheckFileExists::HostBridge) + else { + debug!("User cancelled file transfer due to overwrites"); + return; + }; + if let Err(err) = self.filetransfer_recv( TransferPayload::TransferQueue(entries), dest_path.as_path(), @@ -172,11 +183,17 @@ impl FileTransferActivity { self.mount_radio_replace(&file_name); // Wait for answer trace!("Asking user whether he wants to replace file {}", file_name); - if self.wait_for_pending_msg(&[ - Msg::PendingAction(PendingActionMsg::CloseReplacePopups), - Msg::PendingAction(PendingActionMsg::TransferPendingFile), - ]) == Msg::PendingAction(PendingActionMsg::TransferPendingFile) - { + if matches!( + self.wait_for_pending_msg(&[ + Msg::PendingAction(PendingActionMsg::ReplaceCancel), + Msg::PendingAction(PendingActionMsg::ReplaceOverwrite), + Msg::PendingAction(PendingActionMsg::ReplaceSkip), + Msg::PendingAction(PendingActionMsg::ReplaceSkipAll), + Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll), + ]), + Msg::PendingAction(PendingActionMsg::ReplaceOverwrite) + | Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll) + ) { trace!("User wants to replace file"); self.umount_radio_replace(); true @@ -187,28 +204,76 @@ impl FileTransferActivity { } } - /// Set pending transfer for many files into storage and mount radio - pub(crate) fn should_replace_files(&mut self, files: Vec<&File>) -> bool { - let file_names: Vec = files.iter().map(|x| x.name()).collect(); - self.mount_radio_replace_many(file_names.as_slice()); - // Wait for answer - trace!( - "Asking user whether he wants to replace files {:?}", - file_names - ); - if self.wait_for_pending_msg(&[ - Msg::PendingAction(PendingActionMsg::CloseReplacePopups), - Msg::PendingAction(PendingActionMsg::TransferPendingFile), - ]) == Msg::PendingAction(PendingActionMsg::TransferPendingFile) - { - trace!("User wants to replace files"); + /// Get files to replace + fn get_files_to_replace(&mut self, files: Vec<(File, PathBuf)>) -> GetFileToReplaceResult { + // keep only files the user want to replace + let mut files_to_replace = vec![]; + let mut all_opts = AllOpts::Unset; + for (file, p) in files { + // Check for all opts + match all_opts { + AllOpts::ReplaceAll => { + trace!( + "User wants to replace all files, including file {}", + file.name() + ); + files_to_replace.push((file, p)); + continue; + } + AllOpts::SkipAll => { + trace!( + "User wants to skip all files, including file {}", + file.name() + ); + continue; + } + AllOpts::Unset => {} + } + + let file_name = file.name(); + self.mount_radio_replace(&file_name); + + // Wait for answer + match self.wait_for_pending_msg(&[ + Msg::PendingAction(PendingActionMsg::ReplaceCancel), + Msg::PendingAction(PendingActionMsg::ReplaceOverwrite), + Msg::PendingAction(PendingActionMsg::ReplaceSkip), + Msg::PendingAction(PendingActionMsg::ReplaceSkipAll), + Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll), + ]) { + Msg::PendingAction(PendingActionMsg::ReplaceCancel) => { + trace!("The user cancelled the replace operation"); + self.umount_radio_replace(); + return GetFileToReplaceResult::Cancel; + } + Msg::PendingAction(PendingActionMsg::ReplaceOverwrite) => { + trace!("User wants to replace file {}", file_name); + files_to_replace.push((file, p)); + } + Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll) => { + trace!( + "User wants to replace all files from now on, including file {}", + file_name + ); + files_to_replace.push((file, p)); + all_opts = AllOpts::ReplaceAll; + } + Msg::PendingAction(PendingActionMsg::ReplaceSkip) => { + trace!("The user skipped file {}", file_name); + } + Msg::PendingAction(PendingActionMsg::ReplaceSkipAll) => { + trace!( + "The user skipped all files from now on, including file {}", + file_name + ); + all_opts = AllOpts::SkipAll; + } + _ => {} + } self.umount_radio_replace(); - true - } else { - trace!("The user doesn't want replace file"); - self.umount_radio_replace(); - false } + + GetFileToReplaceResult::Replace(files_to_replace) } /// Get file to check for path @@ -224,4 +289,40 @@ impl FileTransferActivity { p.push(e.name()); p } + + /// Get the files to transfer with overwrites. + /// + /// Existing and unexisting files are splitted, and only existing files are prompted for replacement. + pub(crate) fn get_files_to_transfer_with_overwrites( + &mut self, + files: Vec<(File, PathBuf)>, + file_exists: CheckFileExists, + ) -> TransferFilesWithOverwritesResult { + if !self.config().get_prompt_on_file_replace() { + return TransferFilesWithOverwritesResult::FilesToTransfer(files); + } + + // unzip between existing and non-existing files + let (existing_files, new_files): (Vec<_>, Vec<_>) = + files.into_iter().partition(|(x, dest_path)| { + let p = Self::file_to_check_many(x, dest_path); + match file_exists { + CheckFileExists::Remote => self.remote_file_exists(p.as_path()), + CheckFileExists::HostBridge => self.host_bridge_file_exists(p.as_path()), + } + }); + + // filter only files to replace + let existing_files = match self.get_files_to_replace(existing_files) { + GetFileToReplaceResult::Replace(files) => files, + GetFileToReplaceResult::Cancel => { + return TransferFilesWithOverwritesResult::Cancel; + } + }; + + // merge back + TransferFilesWithOverwritesResult::FilesToTransfer( + existing_files.into_iter().chain(new_files).collect(), + ) + } } diff --git a/src/ui/activities/filetransfer/components/mod.rs b/src/ui/activities/filetransfer/components/mod.rs index d47a477..4f20a1e 100644 --- a/src/ui/activities/filetransfer/components/mod.rs +++ b/src/ui/activities/filetransfer/components/mod.rs @@ -21,9 +21,8 @@ pub use popups::{ ATTR_FILES, ChmodPopup, CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, FatalPopup, FileInfoPopup, FilterPopup, GotoPopup, KeybindingsPopup, MkdirPopup, NewfilePopup, OpenWithPopup, ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup, - ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote, - SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WalkdirWaitPopup, WatchedPathsList, - WatcherPopup, + SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote, SymlinkPopup, + SyncBrowsingMkdirPopup, WaitPopup, WalkdirWaitPopup, WatchedPathsList, WatcherPopup, }; pub use transfer::{ExplorerFind, ExplorerFuzzy, ExplorerLocal, ExplorerRemote}; diff --git a/src/ui/activities/filetransfer/components/popups.rs b/src/ui/activities/filetransfer/components/popups.rs index 3b6b656..2c5469a 100644 --- a/src/ui/activities/filetransfer/components/popups.rs +++ b/src/ui/activities/filetransfer/components/popups.rs @@ -644,6 +644,11 @@ impl KeybindingsPopup { .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Interrupt file transfer")) .add_row() + .add_col(TextSpan::new("").bold().fg(key_color)) + .add_col(TextSpan::from( + " Get total path size of selected files", + )) + .add_row() .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Show watched paths")) .build(), @@ -1196,7 +1201,7 @@ impl ReplacePopup { .modifiers(BorderType::Rounded), ) .foreground(color) - .choices(["Yes", "No"]) + .choices(["Replace", "Skip", "Replace All", "Skip All", "Cancel"]) .title(text, Alignment::Center), } } @@ -1205,9 +1210,6 @@ impl ReplacePopup { impl Component for ReplacePopup { fn on(&mut self, ev: Event) -> Option { match ev { - Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => { - Some(Msg::Ui(UiMsg::ReplacePopupTabbed)) - } Event::Keyboard(KeyEvent { code: Key::Left, .. }) => { @@ -1221,102 +1223,36 @@ impl Component for ReplacePopup { Some(Msg::None) } Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { - Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)) + Some(Msg::PendingAction(PendingActionMsg::ReplaceCancel)) } Event::Keyboard(KeyEvent { code: Key::Char('y'), modifiers: KeyModifiers::NONE, - }) => Some(Msg::PendingAction(PendingActionMsg::TransferPendingFile)), + }) => Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwrite)), Event::Keyboard(KeyEvent { code: Key::Char('n'), modifiers: KeyModifiers::NONE, - }) => Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)), + }) => Some(Msg::PendingAction(PendingActionMsg::ReplaceSkip)), Event::Keyboard(KeyEvent { code: Key::Enter, .. - }) => { - if matches!( - self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) - ) { - Some(Msg::PendingAction(PendingActionMsg::TransferPendingFile)) - } else { - Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)) + }) => match self.perform(Cmd::Submit) { + CmdResult::Submit(State::One(StateValue::Usize(0))) => { + Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwrite)) } - } - _ => None, - } - } -} - -#[derive(MockComponent)] -pub struct ReplacingFilesListPopup { - component: List, -} - -impl ReplacingFilesListPopup { - pub fn new(files: &[String], color: Color) -> Self { - Self { - component: List::default() - .borders( - Borders::default() - .color(color) - .modifiers(BorderType::Rounded), - ) - .scroll(true) - .step(4) - .highlighted_color(color) - .highlighted_str("āž¤ ") - .title( - "The following files are going to be replaced", - Alignment::Center, - ) - .rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()), - } - } -} - -impl Component for ReplacingFilesListPopup { - fn on(&mut self, ev: Event) -> Option { - match ev { - Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { - Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)) - } - Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => { - Some(Msg::Ui(UiMsg::ReplacePopupTabbed)) - } - Event::Keyboard(KeyEvent { - code: Key::Down, .. - }) => { - self.perform(Cmd::Move(Direction::Down)); - Some(Msg::None) - } - Event::Keyboard(KeyEvent { code: Key::Up, .. }) => { - self.perform(Cmd::Move(Direction::Up)); - Some(Msg::None) - } - Event::Keyboard(KeyEvent { - code: Key::PageDown, - .. - }) => { - self.perform(Cmd::Scroll(Direction::Down)); - Some(Msg::None) - } - Event::Keyboard(KeyEvent { - code: Key::PageUp, .. - }) => { - self.perform(Cmd::Scroll(Direction::Up)); - Some(Msg::None) - } - Event::Keyboard(KeyEvent { - code: Key::Home, .. - }) => { - self.perform(Cmd::GoTo(Position::Begin)); - Some(Msg::None) - } - Event::Keyboard(KeyEvent { code: Key::End, .. }) => { - self.perform(Cmd::GoTo(Position::End)); - Some(Msg::None) - } + CmdResult::Submit(State::One(StateValue::Usize(1))) => { + Some(Msg::PendingAction(PendingActionMsg::ReplaceSkip)) + } + CmdResult::Submit(State::One(StateValue::Usize(2))) => { + Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll)) + } + CmdResult::Submit(State::One(StateValue::Usize(3))) => { + Some(Msg::PendingAction(PendingActionMsg::ReplaceSkipAll)) + } + CmdResult::Submit(State::One(StateValue::Usize(4))) => { + Some(Msg::PendingAction(PendingActionMsg::ReplaceCancel)) + } + _ => Some(Msg::None), + }, _ => None, } } diff --git a/src/ui/activities/filetransfer/components/popups/goto.rs b/src/ui/activities/filetransfer/components/popups/goto.rs index d7447d1..4797ed5 100644 --- a/src/ui/activities/filetransfer/components/popups/goto.rs +++ b/src/ui/activities/filetransfer/components/popups/goto.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use tui_realm_stdlib::Input; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; @@ -158,7 +158,7 @@ impl OwnStates { .unwrap_or_else(|| PathBuf::from("/")); // if path is `.`, then return None - if parent == PathBuf::from(".") { + if parent == Path::new(".") { return Suggestion::None; } diff --git a/src/ui/activities/filetransfer/components/transfer/mod.rs b/src/ui/activities/filetransfer/components/transfer/mod.rs index 9ef347a..29eb61a 100644 --- a/src/ui/activities/filetransfer/components/transfer/mod.rs +++ b/src/ui/activities/filetransfer/components/transfer/mod.rs @@ -192,6 +192,10 @@ impl ExplorerFuzzy { code: Key::Char('i'), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowFileInfoPopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('s'), + modifiers: KeyModifiers::CONTROL, + }) => Some(Msg::Transfer(TransferMsg::GetFileSize)), Event::Keyboard(KeyEvent { code: Key::Char('s') | Key::Function(2), modifiers: KeyModifiers::NONE, @@ -338,6 +342,10 @@ impl Component for ExplorerFind { code: Key::Char('i'), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowFileInfoPopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('s'), + modifiers: KeyModifiers::CONTROL, + }) => Some(Msg::Transfer(TransferMsg::GetFileSize)), Event::Keyboard(KeyEvent { code: Key::Char('s') | Key::Function(2), modifiers: KeyModifiers::NONE, @@ -528,6 +536,10 @@ impl Component for ExplorerLocal { code: Key::Char('r') | Key::Function(6), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowRenamePopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('s'), + modifiers: KeyModifiers::CONTROL, + }) => Some(Msg::Transfer(TransferMsg::GetFileSize)), Event::Keyboard(KeyEvent { code: Key::Char('s') | Key::Function(2), modifiers: KeyModifiers::NONE, @@ -742,6 +754,10 @@ impl Component for ExplorerRemote { code: Key::Char('r') | Key::Function(6), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowRenamePopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('s'), + modifiers: KeyModifiers::CONTROL, + }) => Some(Msg::Transfer(TransferMsg::GetFileSize)), Event::Keyboard(KeyEvent { code: Key::Char('s') | Key::Function(2), modifiers: KeyModifiers::NONE, diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 3d515ed..d0b43e7 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -72,7 +72,6 @@ enum Id { QuitPopup, RenamePopup, ReplacePopup, - ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarHostBridge, @@ -98,10 +97,14 @@ enum Msg { #[derive(Debug, PartialEq)] enum PendingActionMsg { - CloseReplacePopups, CloseSyncBrowsingMkdirPopup, MakePendingDirectory, - TransferPendingFile, + /// Replace file popup + ReplaceCancel, + ReplaceOverwrite, + ReplaceOverwriteAll, + ReplaceSkip, + ReplaceSkipAll, } #[derive(Debug, PartialEq)] @@ -114,6 +117,7 @@ enum TransferMsg { DeleteFile, EnterDirectory, ExecuteCmd(String), + GetFileSize, GoTo(String), GoToParentDirectory, GoToPreviousDirectory, @@ -171,8 +175,8 @@ enum UiMsg { MarkAll, /// Clear all marks MarkClear, + Quit, - ReplacePopupTabbed, ShowChmodPopup, ShowCopyPopup, ShowDeletePopup, @@ -506,10 +510,10 @@ impl Activity for FileTransferActivity { /// This function must be called once before terminating the activity. fn on_destroy(&mut self) -> Option { // Destroy cache - if let Some(cache) = self.cache.take() { - if let Err(err) = cache.close() { - error!("Failed to delete cache: {}", err); - } + if let Some(cache) = self.cache.take() + && let Err(err) = cache.close() + { + error!("Failed to delete cache: {}", err); } // Disable raw mode if let Err(err) = self.context_mut().terminal().disable_raw_mode() { diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index f2e34e0..d58ac8d 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -1260,7 +1260,10 @@ impl FileTransferActivity { /// Get total size of transfer for host_bridgehost fn get_total_transfer_size_host(&mut self, entry: &File) -> usize { - if entry.is_dir() { + // mount message to tell we are calculating size + self.mount_blocking_wait("Calculating transfer size…"); + + let sz = if entry.is_dir() { // List dir match self.host_bridge.list_dir(entry.path()) { Ok(files) => files @@ -1281,12 +1284,18 @@ impl FileTransferActivity { } } else { entry.metadata.size as usize - } + }; + self.umount_wait(); + + sz } /// Get total size of transfer for remote host fn get_total_transfer_size_remote(&mut self, entry: &File) -> usize { - if entry.is_dir() { + // mount message to tell we are calculating size + self.mount_blocking_wait("Calculating transfer size…"); + + let sz = if entry.is_dir() { // List directory match self.client.list_dir(entry.path()) { Ok(files) => files @@ -1307,7 +1316,11 @@ impl FileTransferActivity { } } else { entry.metadata.size as usize - } + }; + + self.umount_wait(); + + sz } // file changed diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 418e780..bce542c 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -5,7 +5,6 @@ // locals // externals use remotefs::fs::File; -use tuirealm::props::{AttrValue, Attribute}; use tuirealm::{State, StateValue, Update}; use super::actions::SelectedFile; @@ -153,6 +152,9 @@ impl FileTransferActivity { _ => panic!("Found tab doesn't support EXEC"), }; } + TransferMsg::GetFileSize => { + self.action_get_file_size(); + } TransferMsg::GoTo(dir) => { match self.browser.tab() { FileExplorerTab::HostBridge => self.action_change_local_dir(dir), @@ -504,15 +506,6 @@ impl FileTransferActivity { self.disconnect_and_quit(); self.umount_quit(); } - UiMsg::ReplacePopupTabbed => { - if let Ok(Some(AttrValue::Flag(true))) = - self.app.query(&Id::ReplacePopup, Attribute::Focus) - { - assert!(self.app.active(&Id::ReplacingFilesListPopup).is_ok()); - } else { - assert!(self.app.active(&Id::ReplacePopup).is_ok()); - } - } UiMsg::ShowChmodPopup => { let selected_file = match self.browser.tab() { #[cfg(posix)] diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index 721ee00..0842285 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -269,29 +269,10 @@ impl FileTransferActivity { // make popup self.app.view(&Id::DeletePopup, f, popup); } else if self.app.mounted(&Id::ReplacePopup) { - // NOTE: handle extended / normal modes - if self.is_radio_replace_extended() { - let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.area()); - f.render_widget(Clear, popup); - let popup_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(85), // List - Constraint::Percentage(15), // Radio - ] - .as_ref(), - ) - .split(popup); - self.app - .view(&Id::ReplacingFilesListPopup, f, popup_chunks[0]); - self.app.view(&Id::ReplacePopup, f, popup_chunks[1]); - } else { - let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area()); - f.render_widget(Clear, popup); - // make popup - self.app.view(&Id::ReplacePopup, f, popup); - } + let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area()); + f.render_widget(Clear, popup); + // make popup + self.app.view(&Id::ReplacePopup, f, popup); } else if self.app.mounted(&Id::DisconnectPopup) { let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area()); f.render_widget(Clear, popup); @@ -944,37 +925,8 @@ impl FileTransferActivity { assert!(self.app.active(&Id::ReplacePopup).is_ok()); } - pub(super) fn mount_radio_replace_many(&mut self, files: &[String]) { - let warn_color = self.theme().misc_warn_dialog; - assert!( - self.app - .remount( - Id::ReplacingFilesListPopup, - Box::new(components::ReplacingFilesListPopup::new(files, warn_color)), - vec![], - ) - .is_ok() - ); - assert!( - self.app - .remount( - Id::ReplacePopup, - Box::new(components::ReplacePopup::new(None, warn_color)), - vec![], - ) - .is_ok() - ); - assert!(self.app.active(&Id::ReplacePopup).is_ok()); - } - - /// Returns whether radio replace is in "extended" mode (for many files) - pub(super) fn is_radio_replace_extended(&self) -> bool { - self.app.mounted(&Id::ReplacingFilesListPopup) - } - pub(super) fn umount_radio_replace(&mut self) { let _ = self.app.umount(&Id::ReplacePopup); - let _ = self.app.umount(&Id::ReplacingFilesListPopup); // NOTE: replace anyway } pub(super) fn mount_file_info(&mut self, file: &File) { diff --git a/src/ui/activities/setup/actions.rs b/src/ui/activities/setup/actions.rs index 115ead6..dd27e6a 100644 --- a/src/ui/activities/setup/actions.rs +++ b/src/ui/activities/setup/actions.rs @@ -99,27 +99,14 @@ impl SetupActivity { Ok(State::One(StateValue::Usize(idx))) => Some(idx), _ => None, }; - if let Some(idx) = idx { - let key: Option = self.config().iter_ssh_keys().nth(idx).cloned(); - if let Some(key) = key { - match self.config().get_ssh_key(&key) { - Ok(opt) => { - if let Some((host, username, _)) = opt { - if let Err(err) = self.delete_ssh_key(host.as_str(), username.as_str()) - { - // Report error - self.mount_error(err.as_str()); - } - } - } - Err(err) => { - // Report error - self.mount_error( - format!("Could not get ssh key \"{key}\": {err}").as_str(), - ); - } - } - } + // get ssh key and delete it + if let Some(Err(err)) = idx + .and_then(|i| self.config().iter_ssh_keys().nth(i).cloned()) + .and_then(|key| self.config().get_ssh_key(&key)) + .map(|(host, username, _)| self.delete_ssh_key(host.as_str(), username.as_str())) + { + // Report error + self.mount_error(err.as_str()); } } diff --git a/src/ui/activities/setup/config.rs b/src/ui/activities/setup/config.rs index ae49445..d26e086 100644 --- a/src/ui/activities/setup/config.rs +++ b/src/ui/activities/setup/config.rs @@ -77,16 +77,11 @@ impl SetupActivity { Some(key) => { // Get key path match ctx.config().get_ssh_key(key) { - Ok(ssh_key) => match ssh_key { - None => Ok(()), - Some((_, _, key_path)) => { - match edit::edit_file(key_path.as_path()) { - Ok(_) => Ok(()), - Err(err) => Err(format!("Could not edit ssh key: {err}")), - } - } + None => Ok(()), + Some((_, _, key_path)) => match edit::edit_file(key_path.as_path()) { + Ok(_) => Ok(()), + Err(err) => Err(format!("Could not edit ssh key: {err}")), }, - Err(err) => Err(format!("Could not read ssh key: {err}")), } } None => Ok(()), diff --git a/src/ui/activities/setup/view/ssh_keys.rs b/src/ui/activities/setup/view/ssh_keys.rs index 7dba30f..56933e1 100644 --- a/src/ui/activities/setup/view/ssh_keys.rs +++ b/src/ui/activities/setup/view/ssh_keys.rs @@ -126,7 +126,7 @@ impl SetupActivity { .config() .iter_ssh_keys() .map(|x| { - let (addr, username, _) = self.config().get_ssh_key(x).ok().unwrap().unwrap(); + let (addr, username, _) = self.config().get_ssh_key(x).unwrap(); format!("{username} at {addr}") }) .collect();